Merge commit '5f0892dec43a4c1fdbb3d3853bac0f2a0fee1bbd' into optolink

This commit is contained in:
j0ta29 2023-06-10 20:45:22 +00:00
commit 44fa332931
55 changed files with 1018 additions and 663 deletions

View file

@ -29,6 +29,8 @@ RUN \
git=1:2.30.2-1+deb11u2 \ git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \ curl=7.74.0-1.3+deb11u7 \
openssh-client=1:8.4p1-5+deb11u1 \ openssh-client=1:8.4p1-5+deb11u1 \
libcairo2=1.16.0-5 \
python3-cffi=1.14.5-1 \
&& rm -rf \ && rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \

View file

@ -206,7 +206,8 @@ message DeviceInfoResponse {
uint32 webserver_port = 10; uint32 webserver_port = 10;
uint32 bluetooth_proxy_version = 11; uint32 legacy_bluetooth_proxy_version = 11;
uint32 bluetooth_proxy_feature_flags = 15;
string manufacturer = 12; string manufacturer = 12;
@ -1130,6 +1131,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
option (id) = 66; option (id) = 66;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BLUETOOTH_PROXY"; option (ifdef) = "USE_BLUETOOTH_PROXY";
uint32 flags = 1;
} }
message BluetoothServiceData { message BluetoothServiceData {
@ -1154,6 +1157,23 @@ message BluetoothLEAdvertisementResponse {
uint32 address_type = 7; uint32 address_type = 7;
} }
message BluetoothLERawAdvertisement {
uint64 address = 1;
sint32 rssi = 2;
uint32 address_type = 3;
bytes data = 4;
}
message BluetoothLERawAdvertisementsResponse {
option (id) = 93;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
option (no_delay) = true;
repeated BluetoothLERawAdvertisement advertisements = 1;
}
enum BluetoothDeviceRequestType { enum BluetoothDeviceRequestType {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0; BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1; BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;

View file

@ -51,6 +51,14 @@ void APIConnection::start() {
helper_->set_log_info(client_info_); helper_->set_log_info(client_info_);
} }
APIConnection::~APIConnection() {
#ifdef USE_BLUETOOTH_PROXY
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
}
#endif
}
void APIConnection::loop() { void APIConnection::loop() {
if (this->remove_) if (this->remove_)
return; return;
@ -845,9 +853,13 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
}
void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
}
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) { bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
if (!this->bluetooth_le_advertisement_subscription_)
return false;
if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) { if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
BluetoothLEAdvertisementResponse resp = msg; BluetoothLEAdvertisementResponse resp = msg;
for (auto &service : resp.service_data) { for (auto &service : resp.service_data) {
@ -943,7 +955,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 = 8; resp.api_version_minor = 9;
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();
@ -995,9 +1007,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.webserver_port = USE_WEBSERVER_PORT; resp.webserver_port = USE_WEBSERVER_PORT;
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
? bluetooth_proxy::ACTIVE_CONNECTIONS_VERSION resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
: bluetooth_proxy::PASSIVE_ONLY_VERSION;
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version(); resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version();

View file

@ -16,7 +16,7 @@ namespace api {
class APIConnection : public APIServerConnection { class APIConnection : public APIServerConnection {
public: public:
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent); APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
virtual ~APIConnection() = default; virtual ~APIConnection();
void start(); void start();
void loop(); void loop();
@ -98,12 +98,8 @@ class APIConnection : public APIServerConnection {
this->send_homeassistant_service_response(call); this->send_homeassistant_service_response(call);
} }
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override { void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
this->bluetooth_le_advertisement_subscription_ = true; void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
}
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override {
this->bluetooth_le_advertisement_subscription_ = false;
}
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg); bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override; void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
@ -211,9 +207,6 @@ class APIConnection : public APIServerConnection {
uint32_t last_traffic_; uint32_t last_traffic_;
bool sent_ping_{false}; bool sent_ping_{false};
bool service_call_subscription_{false}; bool service_call_subscription_{false};
#ifdef USE_BLUETOOTH_PROXY
bool bluetooth_le_advertisement_subscription_{false};
#endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool voice_assistant_subscription_{false}; bool voice_assistant_subscription_{false};
#endif #endif

View file

@ -617,7 +617,11 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true; return true;
} }
case 11: { case 11: {
this->bluetooth_proxy_version = value.as_uint32(); this->legacy_bluetooth_proxy_version = value.as_uint32();
return true;
}
case 15: {
this->bluetooth_proxy_feature_flags = value.as_uint32();
return true; return true;
} }
case 14: { case 14: {
@ -681,7 +685,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(8, this->project_name); buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version); buffer.encode_string(9, this->project_version);
buffer.encode_uint32(10, this->webserver_port); buffer.encode_uint32(10, this->webserver_port);
buffer.encode_uint32(11, this->bluetooth_proxy_version); buffer.encode_uint32(11, this->legacy_bluetooth_proxy_version);
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->voice_assistant_version);
@ -731,8 +736,13 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" bluetooth_proxy_version: "); out.append(" legacy_bluetooth_proxy_version: ");
sprintf(buffer, "%u", this->bluetooth_proxy_version); sprintf(buffer, "%u", this->legacy_bluetooth_proxy_version);
out.append(buffer);
out.append("\n");
out.append(" bluetooth_proxy_feature_flags: ");
sprintf(buffer, "%u", this->bluetooth_proxy_feature_flags);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -5041,10 +5051,28 @@ void MediaPlayerCommandRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->flags = value.as_uint32();
return true;
}
default:
return false;
}
}
void SubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->flags);
}
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const {
out.append("SubscribeBluetoothLEAdvertisementsRequest {}"); __attribute__((unused)) char buffer[64];
out.append("SubscribeBluetoothLEAdvertisementsRequest {\n");
out.append(" flags: ");
sprintf(buffer, "%u", this->flags);
out.append(buffer);
out.append("\n");
out.append("}");
} }
#endif #endif
bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) { bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -5197,6 +5225,92 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool BluetoothLERawAdvertisement::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->rssi = value.as_sint32();
return true;
}
case 3: {
this->address_type = value.as_uint32();
return true;
}
default:
return false;
}
}
bool BluetoothLERawAdvertisement::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 4: {
this->data = value.as_string();
return true;
}
default:
return false;
}
}
void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_sint32(2, this->rssi);
buffer.encode_uint32(3, this->address_type);
buffer.encode_string(4, this->data);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothLERawAdvertisement {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" rssi: ");
sprintf(buffer, "%d", this->rssi);
out.append(buffer);
out.append("\n");
out.append(" address_type: ");
sprintf(buffer, "%u", this->address_type);
out.append(buffer);
out.append("\n");
out.append(" data: ");
out.append("'").append(this->data).append("'");
out.append("\n");
out.append("}");
}
#endif
bool BluetoothLERawAdvertisementsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->advertisements.push_back(value.as_message<BluetoothLERawAdvertisement>());
return true;
}
default:
return false;
}
}
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->advertisements) {
buffer.encode_message<BluetoothLERawAdvertisement>(1, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothLERawAdvertisementsResponse {\n");
for (const auto &it : this->advertisements) {
out.append(" advertisements: ");
it.dump_to(out);
out.append("\n");
}
out.append("}");
}
#endif
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {

View file

@ -287,7 +287,8 @@ class DeviceInfoResponse : public ProtoMessage {
std::string project_name{}; std::string project_name{};
std::string project_version{}; std::string project_version{};
uint32_t webserver_port{0}; uint32_t webserver_port{0};
uint32_t bluetooth_proxy_version{0}; uint32_t legacy_bluetooth_proxy_version{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 voice_assistant_version{0};
@ -1247,12 +1248,14 @@ class MediaPlayerCommandRequest : public ProtoMessage {
}; };
class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
public: public:
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;
#endif #endif
protected: protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class BluetoothServiceData : public ProtoMessage { class BluetoothServiceData : public ProtoMessage {
public: public:
@ -1286,6 +1289,32 @@ class BluetoothLEAdvertisementResponse : 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 BluetoothLERawAdvertisement : public ProtoMessage {
public:
uint64_t address{0};
int32_t rssi{0};
uint32_t address_type{0};
std::string data{};
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 BluetoothLERawAdvertisementsResponse : public ProtoMessage {
public:
std::vector<BluetoothLERawAdvertisement> advertisements{};
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;
};
class BluetoothDeviceRequest : public ProtoMessage { class BluetoothDeviceRequest : public ProtoMessage {
public: public:
uint64_t address{0}; uint64_t address{0};

View file

@ -339,6 +339,15 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu
} }
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_le_raw_advertisements_response(
const BluetoothLERawAdvertisementsResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_le_raw_advertisements_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothLERawAdvertisementsResponse>(msg, 93);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
#endif #endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) { bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {

View file

@ -161,6 +161,9 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg); bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
#endif #endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_le_raw_advertisements_response(const BluetoothLERawAdvertisementsResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){}; virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
#endif #endif

View file

@ -291,112 +291,7 @@ void APIServer::send_homeassistant_service_call(const HomeassistantServiceRespon
client->send_homeassistant_service_call(call); client->send_homeassistant_service_call(call);
} }
} }
#ifdef USE_BLUETOOTH_PROXY
void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_le_advertisement(call);
}
}
void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
BluetoothDeviceConnectionResponse call;
call.address = address;
call.connected = connected;
call.mtu = mtu;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_device_connection_response(call);
}
}
void APIServer::send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error) {
BluetoothDevicePairingResponse call;
call.address = address;
call.paired = paired;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_device_pairing_response(call);
}
}
void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error) {
BluetoothDeviceUnpairingResponse call;
call.address = address;
call.success = success;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_device_unpairing_response(call);
}
}
void APIServer::send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error) {
BluetoothDeviceClearCacheResponse call;
call.address = address;
call.success = success;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_device_clear_cache_response(call);
}
}
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
BluetoothConnectionsFreeResponse call;
call.free = free;
call.limit = limit;
for (auto &client : this->clients_) {
client->send_bluetooth_connections_free_response(call);
}
}
void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_read_response(call);
}
}
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_write_response(call);
}
}
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_notify_data_response(call);
}
}
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_notify_response(call);
}
}
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_get_services_response(call);
}
}
void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
BluetoothGATTGetServicesDoneResponse call;
call.address = address;
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_get_services_done_response(call);
}
}
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
BluetoothGATTErrorResponse call;
call.address = address;
call.handle = handle;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_error_response(call);
}
}
#endif
APIServer::APIServer() { global_api_server = this; } APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) { std::function<void(std::string)> f) {

View file

@ -75,21 +75,6 @@ class APIServer : public Component, public Controller {
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
#ifdef USE_BLUETOOTH_PROXY
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
void send_bluetooth_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
void send_bluetooth_gatt_services_done(uint64_t address);
void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
#endif
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void request_time(); void request_time();

View file

@ -442,7 +442,7 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
void BedJetHub::send_local_time() { void BedJetHub::send_local_time() {
if (this->time_id_.has_value()) { if (this->time_id_.has_value()) {
auto *time_id = *this->time_id_; auto *time_id = *this->time_id_;
time::ESPTime now = time_id->now(); ESPTime now = time_id->now();
if (now.is_valid()) { if (now.is_valid()) {
this->set_clock(now.hour, now.minute); this->set_clock(now.hour, now.minute);
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute); ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);

View file

@ -13,6 +13,7 @@
#ifdef USE_TIME #ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
#include "esphome/core/time.h"
#endif #endif
#include <esp_gattc_api.h> #include <esp_gattc_api.h>

View file

@ -1,6 +1,6 @@
#include "bluetooth_connection.h" #include "bluetooth_connection.h"
#include "esphome/components/api/api_server.h" #include "esphome/components/api/api_pb2.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@ -20,24 +20,21 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
switch (event) { switch (event) {
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {
api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->disconnect.reason); this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
this->set_address(0); this->set_address(0);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), this->proxy_->send_connections_free();
this->proxy_->get_bluetooth_connections_limit());
break; break;
} }
case ESP_GATTC_OPEN_EVT: { case ESP_GATTC_OPEN_EVT: {
if (param->open.conn_id != this->conn_id_) if (param->open.conn_id != this->conn_id_)
break; break;
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->open.status); this->proxy_->send_device_connection(this->address_, false, 0, param->open.status);
this->set_address(0); this->set_address(0);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), this->proxy_->send_connections_free();
this->proxy_->get_bluetooth_connections_limit());
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); this->proxy_->send_device_connection(this->address_, true, this->mtu_);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), this->proxy_->send_connections_free();
this->proxy_->get_bluetooth_connections_limit());
} }
this->seen_mtu_or_services_ = false; this->seen_mtu_or_services_ = false;
break; break;
@ -52,9 +49,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
this->seen_mtu_or_services_ = true; this->seen_mtu_or_services_ = true;
break; break;
} }
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); this->proxy_->send_device_connection(this->address_, true, this->mtu_);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), this->proxy_->send_connections_free();
this->proxy_->get_bluetooth_connections_limit());
break; break;
} }
case ESP_GATTC_SEARCH_CMPL_EVT: { case ESP_GATTC_SEARCH_CMPL_EVT: {
@ -67,9 +63,8 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
this->seen_mtu_or_services_ = true; this->seen_mtu_or_services_ = true;
break; break;
} }
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_); this->proxy_->send_device_connection(this->address_, true, this->mtu_);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(), this->proxy_->send_connections_free();
this->proxy_->get_bluetooth_connections_limit());
break; break;
} }
case ESP_GATTC_READ_DESCR_EVT: case ESP_GATTC_READ_DESCR_EVT:
@ -79,7 +74,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
if (param->read.status != ESP_GATT_OK) { if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_, ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->read.handle, param->read.status); this->address_str_.c_str(), param->read.handle, param->read.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status); this->proxy_->send_gatt_error(this->address_, param->read.handle, param->read.status);
break; break;
} }
api::BluetoothGATTReadResponse resp; api::BluetoothGATTReadResponse resp;
@ -89,7 +84,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
for (uint16_t i = 0; i < param->read.value_len; i++) { for (uint16_t i = 0; i < param->read.value_len; i++) {
resp.data.push_back(param->read.value[i]); resp.data.push_back(param->read.value[i]);
} }
api::global_api_server->send_bluetooth_gatt_read_response(resp); this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
break; break;
} }
case ESP_GATTC_WRITE_CHAR_EVT: case ESP_GATTC_WRITE_CHAR_EVT:
@ -99,13 +94,13 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
if (param->write.status != ESP_GATT_OK) { if (param->write.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_, ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->write.handle, param->write.status); this->address_str_.c_str(), param->write.handle, param->write.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status); this->proxy_->send_gatt_error(this->address_, param->write.handle, param->write.status);
break; break;
} }
api::BluetoothGATTWriteResponse resp; api::BluetoothGATTWriteResponse resp;
resp.address = this->address_; resp.address = this->address_;
resp.handle = param->write.handle; resp.handle = param->write.handle;
api::global_api_server->send_bluetooth_gatt_write_response(resp); this->proxy_->get_api_connection()->send_bluetooth_gatt_write_response(resp);
break; break;
} }
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: { case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
@ -113,28 +108,26 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d", ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle, this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
param->unreg_for_notify.status); param->unreg_for_notify.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle, this->proxy_->send_gatt_error(this->address_, param->unreg_for_notify.handle, param->unreg_for_notify.status);
param->unreg_for_notify.status);
break; break;
} }
api::BluetoothGATTNotifyResponse resp; api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_; resp.address = this->address_;
resp.handle = param->unreg_for_notify.handle; resp.handle = param->unreg_for_notify.handle;
api::global_api_server->send_bluetooth_gatt_notify_response(resp); this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
break; break;
} }
case ESP_GATTC_REG_FOR_NOTIFY_EVT: { case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
if (param->reg_for_notify.status != ESP_GATT_OK) { if (param->reg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_, ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status); this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle, this->proxy_->send_gatt_error(this->address_, param->reg_for_notify.handle, param->reg_for_notify.status);
param->reg_for_notify.status);
break; break;
} }
api::BluetoothGATTNotifyResponse resp; api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_; resp.address = this->address_;
resp.handle = param->reg_for_notify.handle; resp.handle = param->reg_for_notify.handle;
api::global_api_server->send_bluetooth_gatt_notify_response(resp); this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
break; break;
} }
case ESP_GATTC_NOTIFY_EVT: { case ESP_GATTC_NOTIFY_EVT: {
@ -149,7 +142,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
for (uint16_t i = 0; i < param->notify.value_len; i++) { for (uint16_t i = 0; i < param->notify.value_len; i++) {
resp.data.push_back(param->notify.value[i]); resp.data.push_back(param->notify.value[i]);
} }
api::global_api_server->send_bluetooth_gatt_notify_data_response(resp); this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
break; break;
} }
default: default:
@ -166,10 +159,9 @@ void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0)
break; break;
if (param->ble_security.auth_cmpl.success) { if (param->ble_security.auth_cmpl.success) {
api::global_api_server->send_bluetooth_device_pairing(this->address_, true); this->proxy_->send_device_pairing(this->address_, true);
} else { } else {
api::global_api_server->send_bluetooth_device_pairing(this->address_, false, this->proxy_->send_device_pairing(this->address_, false, param->ble_security.auth_cmpl.fail_reason);
param->ble_security.auth_cmpl.fail_reason);
} }
break; break;
default: default:

View file

@ -1,11 +1,10 @@
#include "bluetooth_proxy.h" #include "bluetooth_proxy.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/macros.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "esphome/components/api/api_server.h"
namespace esphome { namespace esphome {
namespace bluetooth_proxy { namespace bluetooth_proxy {
@ -27,15 +26,39 @@ std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; } BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (!api::global_api_server->is_connected()) if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
return false; return false;
ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(), ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
device.get_rssi()); device.get_rssi());
this->send_api_packet_(device); this->send_api_packet_(device);
return true; return true;
} }
bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
return false;
api::BluetoothLERawAdvertisementsResponse resp;
for (size_t i = 0; i < count; i++) {
auto &result = advertisements[i];
api::BluetoothLERawAdvertisement adv;
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
adv.rssi = result.rssi;
adv.address_type = result.ble_addr_type;
uint8_t length = result.adv_data_len + result.scan_rsp_len;
adv.data.reserve(length);
for (uint16_t i = 0; i < length; i++) {
adv.data.push_back(result.ble_adv[i]);
}
resp.advertisements.push_back(std::move(adv));
}
ESP_LOGV(TAG, "Proxying %d packets", count);
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
return true;
}
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
api::BluetoothLEAdvertisementResponse resp; api::BluetoothLEAdvertisementResponse resp;
resp.address = device.address_uint64(); resp.address = device.address_uint64();
@ -58,7 +81,7 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
manufacturer_data.data.assign(data.data.begin(), data.data.end()); manufacturer_data.data.assign(data.data.begin(), data.data.end());
resp.manufacturer_data.push_back(std::move(manufacturer_data)); resp.manufacturer_data.push_back(std::move(manufacturer_data));
} }
api::global_api_server->send_bluetooth_le_advertisement(resp); this->api_connection_->send_bluetooth_le_advertisement(resp);
} }
void BluetoothProxy::dump_config() { void BluetoothProxy::dump_config() {
@ -81,7 +104,7 @@ int BluetoothProxy::get_bluetooth_connections_free() {
} }
void BluetoothProxy::loop() { void BluetoothProxy::loop() {
if (!api::global_api_server->is_connected()) { if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->get_address() != 0) { if (connection->get_address() != 0) {
connection->disconnect(); connection->disconnect();
@ -92,7 +115,7 @@ void BluetoothProxy::loop() {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->send_service_ == connection->service_count_) { if (connection->send_service_ == connection->service_count_) {
connection->send_service_ = DONE_SENDING_SERVICES; connection->send_service_ = DONE_SENDING_SERVICES;
api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address()); this->send_gatt_services_done(connection->get_address());
if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE || if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) { connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
connection->release_services(); connection->release_services();
@ -170,7 +193,7 @@ void BluetoothProxy::loop() {
service_resp.characteristics.push_back(std::move(characteristic_resp)); service_resp.characteristics.push_back(std::move(characteristic_resp));
} }
resp.services.push_back(std::move(service_resp)); resp.services.push_back(std::move(service_resp));
api::global_api_server->send_bluetooth_gatt_services(resp); this->api_connection_->send_bluetooth_gatt_get_services_response(resp);
} }
} }
} }
@ -208,16 +231,15 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
auto *connection = this->get_connection_(msg.address, true); auto *connection = this->get_connection_(msg.address, true);
if (connection == nullptr) { if (connection == nullptr) {
ESP_LOGW(TAG, "No free connections available"); ESP_LOGW(TAG, "No free connections available");
api::global_api_server->send_bluetooth_device_connection(msg.address, false); this->send_device_connection(msg.address, false);
return; return;
} }
if (connection->state() == espbt::ClientState::CONNECTED || if (connection->state() == espbt::ClientState::CONNECTED ||
connection->state() == espbt::ClientState::ESTABLISHED) { connection->state() == espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(), ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
connection->address_str().c_str()); connection->address_str().c_str());
api::global_api_server->send_bluetooth_device_connection(msg.address, true); this->send_device_connection(msg.address, true);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), this->send_connections_free();
this->get_bluetooth_connections_limit());
return; return;
} else if (connection->state() == espbt::ClientState::SEARCHING) { } else if (connection->state() == espbt::ClientState::SEARCHING) {
ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device", ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
@ -263,25 +285,22 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
} else { } else {
connection->set_state(espbt::ClientState::SEARCHING); connection->set_state(espbt::ClientState::SEARCHING);
} }
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), this->send_connections_free();
this->get_bluetooth_connections_limit());
break; break;
} }
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: { case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
auto *connection = this->get_connection_(msg.address, false); auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) { if (connection == nullptr) {
api::global_api_server->send_bluetooth_device_connection(msg.address, false); this->send_device_connection(msg.address, false);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), this->send_connections_free();
this->get_bluetooth_connections_limit());
return; return;
} }
if (connection->state() != espbt::ClientState::IDLE) { if (connection->state() != espbt::ClientState::IDLE) {
connection->disconnect(); connection->disconnect();
} else { } else {
connection->set_address(0); connection->set_address(0);
api::global_api_server->send_bluetooth_device_connection(msg.address, false); this->send_device_connection(msg.address, false);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(), this->send_connections_free();
this->get_bluetooth_connections_limit());
} }
break; break;
} }
@ -291,10 +310,10 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
if (!connection->is_paired()) { if (!connection->is_paired()) {
auto err = connection->pair(); auto err = connection->pair();
if (err != ESP_OK) { if (err != ESP_OK) {
api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err); this->send_device_pairing(msg.address, false, err);
} }
} else { } else {
api::global_api_server->send_bluetooth_device_pairing(msg.address, true); this->send_device_pairing(msg.address, true);
} }
} }
break; break;
@ -303,14 +322,20 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
esp_bd_addr_t address; esp_bd_addr_t address;
uint64_to_bd_addr(msg.address, address); uint64_to_bd_addr(msg.address, address);
esp_err_t ret = esp_ble_remove_bond_device(address); esp_err_t ret = esp_ble_remove_bond_device(address);
api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret); this->send_device_pairing(msg.address, ret == ESP_OK, ret);
break; break;
} }
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: { case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE: {
esp_bd_addr_t address; esp_bd_addr_t address;
uint64_to_bd_addr(msg.address, address); uint64_to_bd_addr(msg.address, address);
esp_err_t ret = esp_ble_gattc_cache_clean(address); esp_err_t ret = esp_ble_gattc_cache_clean(address);
api::global_api_server->send_bluetooth_device_clear_cache(msg.address, ret == ESP_OK, ret); api::BluetoothDeviceClearCacheResponse call;
call.address = msg.address;
call.success = ret == ESP_OK;
call.error = ret;
this->api_connection_->send_bluetooth_device_clear_cache_response(call);
break; break;
} }
} }
@ -320,13 +345,13 @@ void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &ms
auto *connection = this->get_connection_(msg.address, false); auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) { if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected"); ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return; return;
} }
auto err = connection->read_characteristic(msg.handle); auto err = connection->read_characteristic(msg.handle);
if (err != ESP_OK) { if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }
} }
@ -334,13 +359,13 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
auto *connection = this->get_connection_(msg.address, false); auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) { if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected"); ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return; return;
} }
auto err = connection->write_characteristic(msg.handle, msg.data, msg.response); auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
if (err != ESP_OK) { if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }
} }
@ -348,13 +373,13 @@ void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTRead
auto *connection = this->get_connection_(msg.address, false); auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) { if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected"); ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return; return;
} }
auto err = connection->read_descriptor(msg.handle); auto err = connection->read_descriptor(msg.handle);
if (err != ESP_OK) { if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }
} }
@ -362,13 +387,13 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
auto *connection = this->get_connection_(msg.address, false); auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) { if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected"); ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return; return;
} }
auto err = connection->write_descriptor(msg.handle, msg.data, true); auto err = connection->write_descriptor(msg.handle, msg.data, true);
if (err != ESP_OK) { if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }
} }
@ -376,12 +401,12 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
auto *connection = this->get_connection_(msg.address, false); auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr || !connection->connected()) { if (connection == nullptr || !connection->connected()) {
ESP_LOGW(TAG, "Cannot get GATT services, not connected"); ESP_LOGW(TAG, "Cannot get GATT services, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED); this->send_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
return; return;
} }
if (!connection->service_count_) { if (!connection->service_count_) {
ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str()); ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
api::global_api_server->send_bluetooth_gatt_services_done(msg.address); this->send_gatt_services_done(msg.address);
return; return;
} }
if (connection->send_service_ == if (connection->send_service_ ==
@ -393,16 +418,89 @@ void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest
auto *connection = this->get_connection_(msg.address, false); auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) { if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected"); ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED); this->send_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return; return;
} }
auto err = connection->notify_characteristic(msg.handle, msg.enable); auto err = connection->notify_characteristic(msg.handle, msg.enable);
if (err != ESP_OK) { if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err); this->send_gatt_error(msg.address, msg.handle, err);
} }
} }
void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags) {
if (this->api_connection_ != nullptr) {
ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
return;
}
this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
}
void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) {
if (this->api_connection_ != api_connection) {
ESP_LOGV(TAG, "API connection is not subscribed");
return;
}
this->api_connection_ = nullptr;
this->raw_advertisements_ = false;
}
void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
if (this->api_connection_ == nullptr)
return;
api::BluetoothDeviceConnectionResponse call;
call.address = address;
call.connected = connected;
call.mtu = mtu;
call.error = error;
this->api_connection_->send_bluetooth_device_connection_response(call);
}
void BluetoothProxy::send_connections_free() {
if (this->api_connection_ == nullptr)
return;
api::BluetoothConnectionsFreeResponse call;
call.free = this->get_bluetooth_connections_free();
call.limit = this->get_bluetooth_connections_limit();
this->api_connection_->send_bluetooth_connections_free_response(call);
}
void BluetoothProxy::send_gatt_services_done(uint64_t address) {
if (this->api_connection_ == nullptr)
return;
api::BluetoothGATTGetServicesDoneResponse call;
call.address = address;
this->api_connection_->send_bluetooth_gatt_get_services_done_response(call);
}
void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
if (this->api_connection_ == nullptr)
return;
api::BluetoothGATTErrorResponse call;
call.address = address;
call.handle = handle;
call.error = error;
this->api_connection_->send_bluetooth_gatt_error_response(call);
}
void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
api::BluetoothDevicePairingResponse call;
call.address = address;
call.paired = paired;
call.error = error;
this->api_connection_->send_bluetooth_device_pairing_response(call);
}
void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
api::BluetoothDeviceUnpairingResponse call;
call.address = address;
call.success = success;
call.error = error;
this->api_connection_->send_bluetooth_device_unpairing_response(call);
}
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace bluetooth_proxy } // namespace bluetooth_proxy

View file

@ -5,6 +5,7 @@
#include <map> #include <map>
#include <vector> #include <vector>
#include "esphome/components/api/api_connection.h"
#include "esphome/components/api/api_pb2.h" #include "esphome/components/api/api_pb2.h"
#include "esphome/components/esp32_ble_client/ble_client_base.h" #include "esphome/components/esp32_ble_client/ble_client_base.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
@ -21,10 +22,33 @@ static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
using namespace esp32_ble_client; using namespace esp32_ble_client;
// Legacy versions:
// Version 1: Initial version without active connections
// Version 2: Support for active connections
// Version 3: New connection API
// Version 4: Pairing support
// Version 5: Cache clear support
static const uint32_t LEGACY_ACTIVE_CONNECTIONS_VERSION = 5;
static const uint32_t LEGACY_PASSIVE_ONLY_VERSION = 1;
enum BluetoothProxyFeature : uint32_t {
FEATURE_PASSIVE_SCAN = 1 << 0,
FEATURE_ACTIVE_CONNECTIONS = 1 << 1,
FEATURE_REMOTE_CACHING = 1 << 2,
FEATURE_PAIRING = 1 << 3,
FEATURE_CACHE_CLEARING = 1 << 4,
FEATURE_RAW_ADVERTISEMENTS = 1 << 5,
};
enum BluetoothProxySubscriptionFlag : uint32_t {
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
};
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component { class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
public: public:
BluetoothProxy(); BluetoothProxy();
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override;
void dump_config() override; void dump_config() override;
void loop() override; void loop() override;
@ -44,6 +68,18 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
int get_bluetooth_connections_free(); int get_bluetooth_connections_free();
int get_bluetooth_connections_limit() { return this->connections_.size(); } int get_bluetooth_connections_limit() { return this->connections_.size(); }
void subscribe_api_connection(api::APIConnection *api_connection, uint32_t flags);
void unsubscribe_api_connection(api::APIConnection *api_connection);
api::APIConnection *get_api_connection() { return this->api_connection_; }
void send_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
void send_connections_free();
void send_gatt_services_done(uint64_t address);
void send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
void send_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK);
void send_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK);
void send_device_clear_cache(uint64_t address, bool success, esp_err_t error = ESP_OK);
static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) { static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) {
bd_addr[0] = (address >> 40) & 0xff; bd_addr[0] = (address >> 40) & 0xff;
bd_addr[1] = (address >> 32) & 0xff; bd_addr[1] = (address >> 32) & 0xff;
@ -56,6 +92,27 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
void set_active(bool active) { this->active_ = active; } void set_active(bool active) { this->active_ = active; }
bool has_active() { return this->active_; } bool has_active() { return this->active_; }
uint32_t get_legacy_version() const {
if (this->active_) {
return LEGACY_ACTIVE_CONNECTIONS_VERSION;
}
return LEGACY_PASSIVE_ONLY_VERSION;
}
uint32_t get_feature_flags() const {
uint32_t flags = 0;
flags |= BluetoothProxyFeature::FEATURE_PASSIVE_SCAN;
flags |= BluetoothProxyFeature::FEATURE_RAW_ADVERTISEMENTS;
if (this->active_) {
flags |= BluetoothProxyFeature::FEATURE_ACTIVE_CONNECTIONS;
flags |= BluetoothProxyFeature::FEATURE_REMOTE_CACHING;
flags |= BluetoothProxyFeature::FEATURE_PAIRING;
flags |= BluetoothProxyFeature::FEATURE_CACHE_CLEARING;
}
return flags;
}
protected: protected:
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device); void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
@ -64,18 +121,12 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
bool active_; bool active_;
std::vector<BluetoothConnection *> connections_{}; std::vector<BluetoothConnection *> connections_{};
api::APIConnection *api_connection_{nullptr};
bool raw_advertisements_{false};
}; };
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Version 1: Initial version without active connections
// Version 2: Support for active connections
// Version 3: New connection API
// Version 4: Pairing support
// Version 5: Cache clear support
static const uint32_t ACTIVE_CONNECTIONS_VERSION = 5;
static const uint32_t PASSIVE_ONLY_VERSION = 1;
} // namespace bluetooth_proxy } // namespace bluetooth_proxy
} // namespace esphome } // namespace esphome

View file

@ -1,9 +1,9 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_sleep.h> #include <esp_sleep.h>
@ -11,6 +11,7 @@
#ifdef USE_TIME #ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
#include "esphome/core/time.h"
#endif #endif
namespace esphome { namespace esphome {
@ -170,7 +171,7 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
if (after_time) if (after_time)
timestamp += 60 * 60 * 24; timestamp += 60 * 60 * 24;
int32_t offset = time::ESPTime::timezone_offset(); int32_t offset = ESPTime::timezone_offset();
timestamp -= offset; // Change timestamp to utc timestamp -= offset; // Change timestamp to utc
const uint32_t ms_left = (timestamp - timestamp_now) * 1000; const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
this->deep_sleep_->set_sleep_duration(ms_left); this->deep_sleep_->set_sleep_duration(ms_left);

View file

@ -3,9 +3,9 @@
#include <utility> #include <utility>
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace display { namespace display {
@ -490,24 +490,21 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to); this->trigger(from, to);
} }
#ifdef USE_TIME void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time) {
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
this->print(x, y, font, color, align, buffer); this->print(x, y, font, color, align, buffer);
} }
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) { void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
} }
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) { void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, align, format, time); this->strftime(x, y, font, COLOR_ON, align, format, time);
} }
void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time::ESPTime time) { void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
} }
#endif
void DisplayBuffer::start_clipping(Rect rect) { void DisplayBuffer::start_clipping(Rect rect) {
if (!this->clipping_rectangle_.empty()) { if (!this->clipping_rectangle_.empty()) {

View file

@ -1,15 +1,12 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/automation.h"
#include "display_color_utils.h"
#include <cstdarg> #include <cstdarg>
#include <vector> #include <vector>
#include "display_color_utils.h"
#ifdef USE_TIME #include "esphome/core/automation.h"
#include "esphome/components/time/real_time_clock.h" #include "esphome/core/component.h"
#endif #include "esphome/core/defines.h"
#include "esphome/core/time.h"
#ifdef USE_GRAPH #ifdef USE_GRAPH
#include "esphome/components/graph/graph.h" #include "esphome/components/graph/graph.h"
@ -263,7 +260,6 @@ class DisplayBuffer {
*/ */
void printf(int x, int y, Font *font, const char *format, ...) __attribute__((format(printf, 5, 6))); void printf(int x, int y, Font *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
#ifdef USE_TIME
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
* *
* @param x The x coordinate of the text alignment anchor point. * @param x The x coordinate of the text alignment anchor point.
@ -274,7 +270,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 7, 0))); __attribute__((format(strftime, 7, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
@ -286,7 +282,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0))); __attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
@ -298,7 +294,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time)
__attribute__((format(strftime, 6, 0))); __attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
@ -309,9 +305,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
__attribute__((format(strftime, 5, 0)));
#endif
/** Draw the `image` with the top-left corner at [x,y] to the screen. /** Draw the `image` with the top-left corner at [x,y] to the screen.
* *

View file

@ -37,7 +37,7 @@ void DS1307Component::read_time() {
ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
return; return;
} }
time::ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10), ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
.minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10), .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
.hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10), .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
.day_of_week = uint8_t(ds1307_.reg.weekday), .day_of_week = uint8_t(ds1307_.reg.weekday),

View file

@ -244,6 +244,17 @@ void ESP32BLE::dump_config() {
} }
} }
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
uint64_t u = 0;
u |= uint64_t(address[0] & 0xFF) << 40;
u |= uint64_t(address[1] & 0xFF) << 32;
u |= uint64_t(address[2] & 0xFF) << 24;
u |= uint64_t(address[3] & 0xFF) << 16;
u |= uint64_t(address[4] & 0xFF) << 8;
u |= uint64_t(address[5] & 0xFF) << 0;
return u;
}
ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) ESP32BLE *global_ble = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esp32_ble } // namespace esp32_ble

View file

@ -18,6 +18,8 @@
namespace esphome { namespace esphome {
namespace esp32_ble { namespace esp32_ble {
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
// NOLINTNEXTLINE(modernize-use-using) // NOLINTNEXTLINE(modernize-use-using)
typedef struct { typedef struct {
void *peer_device; void *peer_device;

View file

@ -167,7 +167,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation( cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger),
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address),
} }
), ),
cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation( cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation(
@ -223,7 +223,10 @@ async def to_code(config):
for conf in config.get(CONF_ON_BLE_ADVERTISE, []): for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
if CONF_MAC_ADDRESS in conf: if CONF_MAC_ADDRESS in conf:
cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex)) addr_list = []
for it in conf[CONF_MAC_ADDRESS]:
addr_list.append(it.as_hex)
cg.add(trigger.set_addresses(addr_list))
await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf) await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf)
for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []): for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View file

@ -10,18 +10,22 @@ namespace esp32_ble_tracker {
class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener { class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
public: public:
explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
void set_address(uint64_t address) { this->address_ = address; } void set_addresses(const std::vector<uint64_t> &addresses) { this->address_vec_ = addresses; }
bool parse_device(const ESPBTDevice &device) override { bool parse_device(const ESPBTDevice &device) override {
if (this->address_ && device.address_uint64() != this->address_) { uint64_t u64_addr = device.address_uint64();
if (!address_vec_.empty()) {
if (std::find(address_vec_.begin(), address_vec_.end(), u64_addr) == address_vec_.end()) {
return false; return false;
} }
}
this->trigger(device); this->trigger(device);
return true; return true;
} }
protected: protected:
uint64_t address_ = 0; std::vector<uint64_t> address_vec_;
}; };
class BLEServiceDataAdvertiseTrigger : public Trigger<const adv_data_t &>, public ESPBTDeviceListener { class BLEServiceDataAdvertiseTrigger : public Trigger<const adv_data_t &>, public ESPBTDeviceListener {

View file

@ -34,17 +34,6 @@ static const char *const TAG = "esp32_ble_tracker";
ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
uint64_t u = 0;
u |= uint64_t(address[0] & 0xFF) << 40;
u |= uint64_t(address[1] & 0xFF) << 32;
u |= uint64_t(address[2] & 0xFF) << 24;
u |= uint64_t(address[3] & 0xFF) << 16;
u |= uint64_t(address[4] & 0xFF) << 8;
u |= uint64_t(address[5] & 0xFF) << 0;
return u;
}
float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void ESP32BLETracker::setup() { void ESP32BLETracker::setup() {
@ -114,10 +103,20 @@ void ESP32BLETracker::loop() {
if (this->scan_result_index_ && // if it looks like we have a scan result we will take the lock if (this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
uint32_t index = this->scan_result_index_; uint32_t index = this->scan_result_index_;
if (index) {
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
} }
bool bulk_parsed = false;
for (auto *listener : this->listeners_) {
bulk_parsed |= listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
for (auto *client : this->clients_) {
bulk_parsed |= client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
}
if (!bulk_parsed) {
for (size_t i = 0; i < index; i++) { for (size_t i = 0; i < index; i++) {
ESPBTDevice device; ESPBTDevice device;
device.parse_scan_rst(this->scan_result_buffer_[i]); device.parse_scan_rst(this->scan_result_buffer_[i]);
@ -141,8 +140,8 @@ void ESP32BLETracker::loop() {
this->print_bt_device_info(device); this->print_bt_device_info(device);
} }
} }
this->scan_result_index_ = 0;
} }
this->scan_result_index_ = 0;
xSemaphoreGive(this->scan_result_lock_); xSemaphoreGive(this->scan_result_lock_);
} }
@ -585,7 +584,7 @@ std::string ESPBTDevice::address_str() const {
this->address_[3], this->address_[4], this->address_[5]); this->address_[3], this->address_[4], this->address_[5]);
return mac; return mac;
} }
uint64_t ESPBTDevice::address_uint64() const { return ble_addr_to_uint64(this->address_); } uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); }
void ESP32BLETracker::dump_config() { void ESP32BLETracker::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Tracker:"); ESP_LOGCONFIG(TAG, "BLE Tracker:");

View file

@ -113,6 +113,9 @@ class ESPBTDeviceListener {
public: public:
virtual void on_scan_end() {} virtual void on_scan_end() {}
virtual bool parse_device(const ESPBTDevice &device) = 0; virtual bool parse_device(const ESPBTDevice &device) = 0;
virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
return false;
};
void set_parent(ESP32BLETracker *parent) { parent_ = parent; } void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
protected: protected:

View file

@ -16,7 +16,7 @@ void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) {
if (tiny_gps.date.year() < 2019) if (tiny_gps.date.year() < 2019)
return; return;
time::ESPTime val{}; ESPTime val{};
val.year = tiny_gps.date.year(); val.year = tiny_gps.date.year();
val.month = tiny_gps.date.month(); val.month = tiny_gps.date.month();
val.day_of_month = tiny_gps.date.day(); val.day_of_month = tiny_gps.date.day();

View file

@ -1,5 +1,10 @@
import logging import logging
import io
from pathlib import Path
import re
import requests
from esphome import core from esphome import core
from esphome.components import display, font from esphome.components import display, font
import esphome.config_validation as cv import esphome.config_validation as cv
@ -7,15 +12,19 @@ import esphome.codegen as cg
from esphome.const import ( from esphome.const import (
CONF_DITHER, CONF_DITHER,
CONF_FILE, CONF_FILE,
CONF_ICON,
CONF_ID, CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID, CONF_RAW_DATA_ID,
CONF_RESIZE, CONF_RESIZE,
CONF_SOURCE,
CONF_TYPE, CONF_TYPE,
) )
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = "image"
DEPENDENCIES = ["display"] DEPENDENCIES = ["display"]
MULTI_CONF = True MULTI_CONF = True
@ -31,9 +40,58 @@ IMAGE_TYPE = {
CONF_USE_TRANSPARENCY = "use_transparency" CONF_USE_TRANSPARENCY = "use_transparency"
# If the MDI file cannot be downloaded within this time, abort.
MDI_DOWNLOAD_TIMEOUT = 30 # seconds
SOURCE_LOCAL = "local"
SOURCE_MDI = "mdi"
Image_ = display.display_ns.class_("Image") Image_ = display.display_ns.class_("Image")
def _compute_local_icon_path(value) -> Path:
base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN / "mdi"
return base_dir / f"{value[CONF_ICON]}.svg"
def download_mdi(value):
mdi_id = value[CONF_ICON]
path = _compute_local_icon_path(value)
if path.is_file():
return value
url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg"
_LOGGER.debug("Downloading %s MDI image from %s", mdi_id, url)
try:
req = requests.get(url, timeout=MDI_DOWNLOAD_TIMEOUT)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise cv.Invalid(f"Could not download MDI image {mdi_id} from {url}: {e}")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(req.content)
return value
def validate_cairosvg_installed(value):
"""Validate that cairosvg is installed"""
try:
import cairosvg
except ImportError as err:
raise cv.Invalid(
"Please install the cairosvg python package to use this feature. "
"(pip install cairosvg)"
) from err
major, minor, _ = cairosvg.__version__.split(".")
if major < "2" or major == "2" and minor < "2":
raise cv.Invalid(
"Please update your cairosvg installation to at least 2.2.0. "
"(pip install -U cairosvg)"
)
return value
def validate_cross_dependencies(config): def validate_cross_dependencies(config):
""" """
Validate fields whose possible values depend on other fields. Validate fields whose possible values depend on other fields.
@ -41,6 +99,13 @@ def validate_cross_dependencies(config):
have "use_transparency" set to True. have "use_transparency" set to True.
Also set the default value for those kind of dependent fields. Also set the default value for those kind of dependent fields.
""" """
is_mdi = CONF_FILE in config and config[CONF_FILE][CONF_SOURCE] == SOURCE_MDI
if CONF_TYPE not in config:
if is_mdi:
config[CONF_TYPE] = "TRANSPARENT_BINARY"
else:
config[CONF_TYPE] = "BINARY"
image_type = config[CONF_TYPE] image_type = config[CONF_TYPE]
is_transparent_type = image_type in ["TRANSPARENT_BINARY", "RGBA"] is_transparent_type = image_type in ["TRANSPARENT_BINARY", "RGBA"]
@ -51,16 +116,74 @@ def validate_cross_dependencies(config):
if is_transparent_type and not config[CONF_USE_TRANSPARENCY]: if is_transparent_type and not config[CONF_USE_TRANSPARENCY]:
raise cv.Invalid(f"Image type {image_type} must always be transparent.") raise cv.Invalid(f"Image type {image_type} must always be transparent.")
if is_mdi and config[CONF_TYPE] not in ["BINARY", "TRANSPARENT_BINARY"]:
raise cv.Invalid("MDI images must be binary images.")
return config return config
def validate_file_shorthand(value):
value = cv.string_strict(value)
if value.startswith("mdi:"):
validate_cairosvg_installed(value)
match = re.search(r"mdi:([a-zA-Z0-9\-]+)", value)
if match is None:
raise cv.Invalid("Could not parse mdi icon name.")
icon = match.group(1)
return FILE_SCHEMA(
{
CONF_SOURCE: SOURCE_MDI,
CONF_ICON: icon,
}
)
return FILE_SCHEMA(
{
CONF_SOURCE: SOURCE_LOCAL,
CONF_PATH: value,
}
)
LOCAL_SCHEMA = cv.Schema(
{
cv.Required(CONF_PATH): cv.file_,
}
)
MDI_SCHEMA = cv.All(
{
cv.Required(CONF_ICON): cv.string,
},
download_mdi,
)
TYPED_FILE_SCHEMA = cv.typed_schema(
{
SOURCE_LOCAL: LOCAL_SCHEMA,
SOURCE_MDI: MDI_SCHEMA,
},
key=CONF_SOURCE,
)
def _file_schema(value):
if isinstance(value, str):
return validate_file_shorthand(value)
return TYPED_FILE_SCHEMA(value)
FILE_SCHEMA = cv.Schema(_file_schema)
IMAGE_SCHEMA = cv.Schema( IMAGE_SCHEMA = cv.Schema(
cv.All( cv.All(
{ {
cv.Required(CONF_ID): cv.declare_id(Image_), cv.Required(CONF_ID): cv.declare_id(Image_),
cv.Required(CONF_FILE): cv.file_, cv.Required(CONF_FILE): FILE_SCHEMA,
cv.Optional(CONF_RESIZE): cv.dimensions, cv.Optional(CONF_RESIZE): cv.dimensions,
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(IMAGE_TYPE, upper=True), # Not setting default here on purpose; the default depends on the source type
# (file or mdi), and will be set in the "validate_cross_dependencies" validator.
cv.Optional(CONF_TYPE): cv.enum(IMAGE_TYPE, upper=True),
# Not setting default here on purpose; the default depends on the image type, # Not setting default here on purpose; the default depends on the image type,
# and thus will be set in the "validate_cross_dependencies" validator. # and thus will be set in the "validate_cross_dependencies" validator.
cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean, cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean,
@ -79,19 +202,38 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
async def to_code(config): async def to_code(config):
from PIL import Image from PIL import Image
path = CORE.relative_config_path(config[CONF_FILE]) conf_file = config[CONF_FILE]
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
path = CORE.relative_config_path(conf_file[CONF_PATH])
try: try:
image = Image.open(path) image = Image.open(path)
except Exception as e: except Exception as e:
raise core.EsphomeError(f"Could not load image file {path}: {e}") raise core.EsphomeError(f"Could not load image file {path}: {e}")
width, height = image.size
if CONF_RESIZE in config: if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE]) image.thumbnail(config[CONF_RESIZE])
width, height = image.size elif conf_file[CONF_SOURCE] == SOURCE_MDI:
# Those imports are only needed in case of MDI images; adding them
# to the top would force configurations not using MDI to also have them
# installed for no reason.
from cairosvg import svg2png
svg_file = _compute_local_icon_path(conf_file)
if CONF_RESIZE in config:
req_width, req_height = config[CONF_RESIZE]
svg_image = svg2png(
url=svg_file.as_posix(),
output_width=req_width,
output_height=req_height,
)
else: else:
if width > 500 or height > 500: svg_image = svg2png(url=svg_file.as_posix())
image = Image.open(io.BytesIO(svg_image))
width, height = image.size
if CONF_RESIZE not in config and (width > 500 or height > 500):
_LOGGER.warning( _LOGGER.warning(
'The image "%s" you requested is very big. Please consider' 'The image "%s" you requested is very big. Please consider'
" using the resize parameter.", " using the resize parameter.",

View file

@ -158,15 +158,13 @@ void LCDDisplay::clear() {
for (uint8_t i = 0; i < this->rows_ * this->columns_; i++) for (uint8_t i = 0; i < this->rows_ * this->columns_; i++)
this->buffer_[i] = ' '; this->buffer_[i] = ' ';
} }
#ifdef USE_TIME void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, ESPTime time) {
void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
this->print(column, row, buffer); this->print(column, row, buffer);
} }
void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); } void LCDDisplay::strftime(const char *format, ESPTime time) { this->strftime(0, 0, format, time); }
#endif
void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) { void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) {
location &= 0x7; // we only have 8 locations 0-7 location &= 0x7; // we only have 8 locations 0-7
this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3)); this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3));

View file

@ -1,11 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/time.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
#include <map> #include <map>
#include <vector> #include <vector>
@ -44,13 +40,10 @@ class LCDDisplay : public PollingComponent {
/// Evaluate the printf-format and print the text at column=0 and row=0. /// Evaluate the printf-format and print the text at column=0 and row=0.
void printf(const char *format, ...) __attribute__((format(printf, 2, 3))); void printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
#ifdef USE_TIME
/// Evaluate the strftime-format and print the text at the specified column and row. /// Evaluate the strftime-format and print the text at the specified column and row.
void strftime(uint8_t column, uint8_t row, const char *format, time::ESPTime time) void strftime(uint8_t column, uint8_t row, const char *format, ESPTime time) __attribute__((format(strftime, 4, 0)));
__attribute__((format(strftime, 4, 0)));
/// Evaluate the strftime-format and print the text at column=0 and row=0. /// Evaluate the strftime-format and print the text at column=0 and row=0.
void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); void strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0)));
#endif
/// Load custom char to given location /// Load custom char to given location
void loadchar(uint8_t location, uint8_t charmap[]); void loadchar(uint8_t location, uint8_t charmap[]);

View file

@ -223,16 +223,14 @@ void MAX7219Component::set_intensity(uint8_t intensity) {
} }
void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; } void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }
#ifdef USE_TIME uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) {
uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
return this->print(pos, buffer); return this->print(pos, buffer);
return 0; return 0;
} }
uint8_t MAX7219Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } uint8_t MAX7219Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); }
#endif
} // namespace max7219 } // namespace max7219
} // namespace esphome } // namespace esphome

View file

@ -1,11 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/time.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
@ -46,13 +42,11 @@ class MAX7219Component : public PollingComponent,
/// Print `str` at position 0. /// Print `str` at position 0.
uint8_t print(const char *str); uint8_t print(const char *str);
#ifdef USE_TIME
/// Evaluate the strftime-format and print the result at the given position. /// Evaluate the strftime-format and print the result at the given position.
uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0)));
/// Evaluate the strftime-format and print the result at position 0. /// Evaluate the strftime-format and print the result at position 0.
uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); uint8_t strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0)));
#endif
protected: protected:
void send_byte_(uint8_t a_register, uint8_t data); void send_byte_(uint8_t a_register, uint8_t data);

View file

@ -325,18 +325,16 @@ uint8_t MAX7219Component::printdigitf(const char *format, ...) {
return 0; return 0;
} }
#ifdef USE_TIME uint8_t MAX7219Component::strftimedigit(uint8_t pos, const char *format, ESPTime time) {
uint8_t MAX7219Component::strftimedigit(uint8_t pos, const char *format, time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
return this->printdigit(pos, buffer); return this->printdigit(pos, buffer);
return 0; return 0;
} }
uint8_t MAX7219Component::strftimedigit(const char *format, time::ESPTime time) { uint8_t MAX7219Component::strftimedigit(const char *format, ESPTime time) {
return this->strftimedigit(0, format, time); return this->strftimedigit(0, format, time);
} }
#endif
} // namespace max7219digit } // namespace max7219digit
} // namespace esphome } // namespace esphome

View file

@ -1,16 +1,13 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/time.h"
#include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display_buffer.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include <vector> #include <vector>
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome { namespace esphome {
namespace max7219digit { namespace max7219digit {
@ -88,13 +85,11 @@ class MAX7219Component : public PollingComponent,
/// Print `str` at position 0. /// Print `str` at position 0.
uint8_t printdigit(const char *str); uint8_t printdigit(const char *str);
#ifdef USE_TIME
/// Evaluate the strftime-format and print the result at the given position. /// Evaluate the strftime-format and print the result at the given position.
uint8_t strftimedigit(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); uint8_t strftimedigit(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0)));
/// Evaluate the strftime-format and print the result at position 0. /// Evaluate the strftime-format and print the result at position 0.
uint8_t strftimedigit(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); uint8_t strftimedigit(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0)));
#endif
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; }

View file

@ -4,6 +4,8 @@
#include <vector> #include <vector>
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/time.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include "nextion_base.h" #include "nextion_base.h"
#include "nextion_component.h" #include "nextion_component.h"
@ -19,10 +21,6 @@
#endif #endif
#endif #endif
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome { namespace esphome {
namespace nextion { namespace nextion {
@ -318,13 +316,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
* Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor.
*/ */
void set_component_font(const char *component, uint8_t font_id) override; void set_component_font(const char *component, uint8_t font_id) override;
#ifdef USE_TIME
/** /**
* Send the current time to the nextion display. * Send the current time to the nextion display.
* @param time The time instance to send (get this with id(my_time).now() ). * @param time The time instance to send (get this with id(my_time).now() ).
*/ */
void set_nextion_rtc_time(time::ESPTime time); void set_nextion_rtc_time(ESPTime time);
#endif
/** /**
* Show the page with a given name. * Show the page with a given name.

View file

@ -219,8 +219,7 @@ void Nextion::filled_circle(int center_x, int center_y, int radius, Color color)
display::ColorUtil::color_to_565(color)); display::ColorUtil::color_to_565(color));
} }
#ifdef USE_TIME void Nextion::set_nextion_rtc_time(ESPTime time) {
void Nextion::set_nextion_rtc_time(time::ESPTime time) {
this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year); this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year);
this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month); this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month);
this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month); this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month);
@ -228,7 +227,6 @@ void Nextion::set_nextion_rtc_time(time::ESPTime time) {
this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute); this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute);
this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second); this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second);
} }
#endif
} // namespace nextion } // namespace nextion
} // namespace esphome } // namespace esphome

View file

@ -37,7 +37,7 @@ void PCF85063Component::read_time() {
ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
return; return;
} }
time::ESPTime rtc_time{ ESPTime rtc_time{
.second = uint8_t(pcf85063_.reg.second + 10 * pcf85063_.reg.second_10), .second = uint8_t(pcf85063_.reg.second + 10 * pcf85063_.reg.second_10),
.minute = uint8_t(pcf85063_.reg.minute + 10u * pcf85063_.reg.minute_10), .minute = uint8_t(pcf85063_.reg.minute + 10u * pcf85063_.reg.minute_10),
.hour = uint8_t(pcf85063_.reg.hour + 10u * pcf85063_.reg.hour_10), .hour = uint8_t(pcf85063_.reg.hour + 10u * pcf85063_.reg.hour_10),

View file

@ -22,7 +22,7 @@ class SNTPComponent : public time::RealTimeClock {
this->server_2_ = server_2; this->server_2_ = server_2;
this->server_3_ = server_3; this->server_3_ = server_3;
} }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
void update() override; void update() override;
void loop() override; void loop() override;

View file

@ -37,7 +37,7 @@ num_t EquatorialCoordinate::declination_rad() const { return radians(declination
num_t HorizontalCoordinate::elevation_rad() const { return radians(elevation); } num_t HorizontalCoordinate::elevation_rad() const { return radians(elevation); }
num_t HorizontalCoordinate::azimuth_rad() const { return radians(azimuth); } num_t HorizontalCoordinate::azimuth_rad() const { return radians(azimuth); }
num_t julian_day(time::ESPTime moment) { num_t julian_day(ESPTime moment) {
// p. 59 // p. 59
// UT -> JD, TT -> JDE // UT -> JD, TT -> JDE
int y = moment.year; int y = moment.year;
@ -54,7 +54,7 @@ num_t julian_day(time::ESPTime moment) {
int b = 2 - a + a / 4; int b = 2 - a + a / 4;
return ((int) (365.25 * (y + 4716))) + ((int) (30.6001 * (m + 1))) + d + b - 1524.5; return ((int) (365.25 * (y + 4716))) + ((int) (30.6001 * (m + 1))) + d + b - 1524.5;
} }
num_t delta_t(time::ESPTime moment) { num_t delta_t(ESPTime moment) {
// approximation for 2005-2050 from NASA (https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html) // approximation for 2005-2050 from NASA (https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html)
int t = moment.year - 2000; int t = moment.year - 2000;
return 62.92 + t * (0.32217 + t * 0.005589); return 62.92 + t * (0.32217 + t * 0.005589);
@ -199,7 +199,7 @@ struct SunAtLocation {
// see chapter 12, p. 87 // see chapter 12, p. 87
num_t jd = moment.jd(); num_t jd = moment.jd();
// eq 12.1, p.87; jd for 0h UT of this date // eq 12.1, p.87; jd for 0h UT of this date
time::ESPTime moment_0h = moment.dt; ESPTime moment_0h = moment.dt;
moment_0h.hour = moment_0h.minute = moment_0h.second = 0; moment_0h.hour = moment_0h.minute = moment_0h.second = 0;
num_t jd0 = Moment{moment_0h}.jd(); num_t jd0 = Moment{moment_0h}.jd();
num_t t = (jd0 - 2451545) / 36525; num_t t = (jd0 - 2451545) / 36525;
@ -227,9 +227,9 @@ struct SunAtLocation {
return HorizontalCoordinate{degrees(elevation_rad), degrees(azimuth_rad) + 180}; return HorizontalCoordinate{degrees(elevation_rad), degrees(azimuth_rad) + 180};
} }
optional<time::ESPTime> sunrise(time::ESPTime date, num_t zenith) const { return event(true, date, zenith); } optional<ESPTime> sunrise(ESPTime date, num_t zenith) const { return event(true, date, zenith); }
optional<time::ESPTime> sunset(time::ESPTime date, num_t zenith) const { return event(false, date, zenith); } optional<ESPTime> sunset(ESPTime date, num_t zenith) const { return event(false, date, zenith); }
optional<time::ESPTime> event(bool rise, time::ESPTime date, num_t zenith) const { optional<ESPTime> event(bool rise, ESPTime date, num_t zenith) const {
// couldn't get the method described in chapter 15 to work, // couldn't get the method described in chapter 15 to work,
// so instead this is based on the algorithm in time4j // so instead this is based on the algorithm in time4j
// https://github.com/MenoData/Time4J/blob/master/base/src/main/java/net/time4j/calendar/astro/StdSolarCalculator.java // https://github.com/MenoData/Time4J/blob/master/base/src/main/java/net/time4j/calendar/astro/StdSolarCalculator.java
@ -244,7 +244,7 @@ struct SunAtLocation {
new_h = *x; new_h = *x;
} while (std::abs(new_h - old_h) >= 15); } while (std::abs(new_h - old_h) >= 15);
time_t new_timestamp = m.timestamp + (time_t) new_h; time_t new_timestamp = m.timestamp + (time_t) new_h;
return time::ESPTime::from_epoch_local(new_timestamp); return ESPTime::from_epoch_local(new_timestamp);
} }
protected: protected:
@ -263,14 +263,14 @@ struct SunAtLocation {
return hour_angle; return hour_angle;
} }
time::ESPTime local_event_(time::ESPTime date, int hour) const { ESPTime local_event_(ESPTime date, int hour) const {
// input date should be in UTC, and hour/minute/second fields 0 // input date should be in UTC, and hour/minute/second fields 0
num_t added_d = hour / 24.0 - location.longitude / 360; num_t added_d = hour / 24.0 - location.longitude / 360;
num_t jd = julian_day(date) + added_d; num_t jd = julian_day(date) + added_d;
num_t eot = SunAtTime(jd).equation_of_time() * 240; num_t eot = SunAtTime(jd).equation_of_time() * 240;
time_t new_timestamp = (time_t) (date.timestamp + added_d * 86400 - eot); time_t new_timestamp = (time_t) (date.timestamp + added_d * 86400 - eot);
return time::ESPTime::from_epoch_utc(new_timestamp); return ESPTime::from_epoch_utc(new_timestamp);
} }
}; };
@ -287,7 +287,7 @@ HorizontalCoordinate Sun::calc_coords_() {
*/ */
return sun.true_coordinate(m); return sun.true_coordinate(m);
} }
optional<time::ESPTime> Sun::calc_event_(time::ESPTime date, bool rising, double zenith) { optional<ESPTime> Sun::calc_event_(ESPTime date, bool rising, double zenith) {
SunAtLocation sun{location_}; SunAtLocation sun{location_};
if (!date.is_valid()) if (!date.is_valid())
return {}; return {};
@ -301,24 +301,20 @@ optional<time::ESPTime> Sun::calc_event_(time::ESPTime date, bool rising, double
// We're calculating *next* sunrise/sunset, but calculated event // We're calculating *next* sunrise/sunset, but calculated event
// is today, so try again tomorrow // is today, so try again tomorrow
time_t new_timestamp = today.timestamp + 24 * 60 * 60; time_t new_timestamp = today.timestamp + 24 * 60 * 60;
today = time::ESPTime::from_epoch_utc(new_timestamp); today = ESPTime::from_epoch_utc(new_timestamp);
it = sun.event(rising, today, zenith); it = sun.event(rising, today, zenith);
} }
return it; return it;
} }
optional<time::ESPTime> Sun::calc_event_(bool rising, double zenith) { optional<ESPTime> Sun::calc_event_(bool rising, double zenith) {
auto it = Sun::calc_event_(this->time_->utcnow(), rising, zenith); auto it = Sun::calc_event_(this->time_->utcnow(), rising, zenith);
return it; return it;
} }
optional<time::ESPTime> Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); } optional<ESPTime> Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); }
optional<time::ESPTime> Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); } optional<ESPTime> Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); }
optional<time::ESPTime> Sun::sunrise(time::ESPTime date, double elevation) { optional<ESPTime> Sun::sunrise(ESPTime date, double elevation) { return this->calc_event_(date, true, 90 - elevation); }
return this->calc_event_(date, true, 90 - elevation); optional<ESPTime> Sun::sunset(ESPTime date, double elevation) { return this->calc_event_(date, false, 90 - elevation); }
}
optional<time::ESPTime> Sun::sunset(time::ESPTime date, double elevation) {
return this->calc_event_(date, false, 90 - elevation);
}
double Sun::elevation() { return this->calc_coords_().elevation; } double Sun::elevation() { return this->calc_coords_().elevation; }
double Sun::azimuth() { return this->calc_coords_().azimuth; } double Sun::azimuth() { return this->calc_coords_().azimuth; }

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/automation.h" #include "esphome/core/time.h"
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
namespace esphome { namespace esphome {
@ -26,7 +28,7 @@ struct GeoLocation {
}; };
struct Moment { struct Moment {
time::ESPTime dt; ESPTime dt;
num_t jd() const; num_t jd() const;
num_t jde() const; num_t jde() const;
@ -57,18 +59,18 @@ class Sun {
void set_latitude(double latitude) { location_.latitude = latitude; } void set_latitude(double latitude) { location_.latitude = latitude; }
void set_longitude(double longitude) { location_.longitude = longitude; } void set_longitude(double longitude) { location_.longitude = longitude; }
optional<time::ESPTime> sunrise(double elevation); optional<ESPTime> sunrise(double elevation);
optional<time::ESPTime> sunset(double elevation); optional<ESPTime> sunset(double elevation);
optional<time::ESPTime> sunrise(time::ESPTime date, double elevation); optional<ESPTime> sunrise(ESPTime date, double elevation);
optional<time::ESPTime> sunset(time::ESPTime date, double elevation); optional<ESPTime> sunset(ESPTime date, double elevation);
double elevation(); double elevation();
double azimuth(); double azimuth();
protected: protected:
internal::HorizontalCoordinate calc_coords_(); internal::HorizontalCoordinate calc_coords_();
optional<time::ESPTime> calc_event_(bool rising, double zenith); optional<ESPTime> calc_event_(bool rising, double zenith);
optional<time::ESPTime> calc_event_(time::ESPTime date, bool rising, double zenith); optional<ESPTime> calc_event_(ESPTime date, bool rising, double zenith);
time::RealTimeClock *time_; time::RealTimeClock *time_;
internal::GeoLocation location_; internal::GeoLocation location_;

View file

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/time.h"
#include "esphome/components/sun/sun.h" #include "esphome/components/sun/sun.h"
#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/text_sensor/text_sensor.h"
@ -15,7 +17,7 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent {
void set_format(const std::string &format) { format_ = format; } void set_format(const std::string &format) { format_ = format; }
void update() override { void update() override {
optional<time::ESPTime> res; optional<ESPTime> res;
if (this->sunrise_) { if (this->sunrise_) {
res = this->parent_->sunrise(this->elevation_); res = this->parent_->sunrise(this->elevation_);
} else { } else {

View file

@ -1,5 +1,7 @@
#include "automation.h" #include "automation.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes> #include <cinttypes>
namespace esphome { namespace esphome {

View file

@ -1,7 +1,9 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/time.h"
#include "real_time_clock.h" #include "real_time_clock.h"
#include <vector> #include <vector>

View file

@ -52,171 +52,5 @@ void RealTimeClock::apply_timezone_() {
tzset(); tzset();
} }
size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) {
struct tm c_tm = this->to_c_tm();
return ::strftime(buffer, buffer_len, format, &c_tm);
}
ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) {
ESPTime res{};
res.second = uint8_t(c_tm->tm_sec);
res.minute = uint8_t(c_tm->tm_min);
res.hour = uint8_t(c_tm->tm_hour);
res.day_of_week = uint8_t(c_tm->tm_wday + 1);
res.day_of_month = uint8_t(c_tm->tm_mday);
res.day_of_year = uint16_t(c_tm->tm_yday + 1);
res.month = uint8_t(c_tm->tm_mon + 1);
res.year = uint16_t(c_tm->tm_year + 1900);
res.is_dst = bool(c_tm->tm_isdst);
res.timestamp = c_time;
return res;
}
struct tm ESPTime::to_c_tm() {
struct tm c_tm {};
c_tm.tm_sec = this->second;
c_tm.tm_min = this->minute;
c_tm.tm_hour = this->hour;
c_tm.tm_mday = this->day_of_month;
c_tm.tm_mon = this->month - 1;
c_tm.tm_year = this->year - 1900;
c_tm.tm_wday = this->day_of_week - 1;
c_tm.tm_yday = this->day_of_year - 1;
c_tm.tm_isdst = this->is_dst;
return c_tm;
}
std::string ESPTime::strftime(const std::string &format) {
std::string timestr;
timestr.resize(format.size() * 4);
struct tm c_tm = this->to_c_tm();
size_t len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
while (len == 0) {
timestr.resize(timestr.size() * 2);
len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
}
timestr.resize(len);
return timestr;
}
template<typename T> bool increment_time_value(T &current, uint16_t begin, uint16_t end) {
current++;
if (current >= end) {
current = begin;
return true;
}
return false;
}
static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); }
static uint8_t days_in_month(uint8_t month, uint16_t year) {
static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
uint8_t days = DAYS_IN_MONTH[month];
if (month == 2 && is_leap_year(year))
return 29;
return days;
}
void ESPTime::increment_second() {
this->timestamp++;
if (!increment_time_value(this->second, 0, 60))
return;
// second roll-over, increment minute
if (!increment_time_value(this->minute, 0, 60))
return;
// minute roll-over, increment hour
if (!increment_time_value(this->hour, 0, 24))
return;
// hour roll-over, increment day
increment_time_value(this->day_of_week, 1, 8);
if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) {
// day of month roll-over, increment month
increment_time_value(this->month, 1, 13);
}
uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365;
if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) {
// day of year roll-over, increment year
this->year++;
}
}
void ESPTime::increment_day() {
this->timestamp += 86400;
// increment day
increment_time_value(this->day_of_week, 1, 8);
if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) {
// day of month roll-over, increment month
increment_time_value(this->month, 1, 13);
}
uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365;
if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) {
// day of year roll-over, increment year
this->year++;
}
}
void ESPTime::recalc_timestamp_utc(bool use_day_of_year) {
time_t res = 0;
if (!this->fields_in_range()) {
this->timestamp = -1;
return;
}
for (int i = 1970; i < this->year; i++)
res += is_leap_year(i) ? 366 : 365;
if (use_day_of_year) {
res += this->day_of_year - 1;
} else {
for (int i = 1; i < this->month; i++)
res += days_in_month(i, this->year);
res += this->day_of_month - 1;
}
res *= 24;
res += this->hour;
res *= 60;
res += this->minute;
res *= 60;
res += this->second;
this->timestamp = res;
}
int32_t ESPTime::timezone_offset() {
int32_t offset = 0;
time_t now = ::time(nullptr);
auto local = ESPTime::from_epoch_local(now);
auto utc = ESPTime::from_epoch_utc(now);
bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year;
if (utc.minute > local.minute) {
local.minute += 60;
local.hour -= 1;
}
offset += (local.minute - utc.minute) * 60;
if (negative) {
offset -= (utc.hour - local.hour) * 3600;
} else {
if (utc.hour > local.hour) {
local.hour += 24;
}
offset += (local.hour - utc.hour) * 3600;
}
return offset;
}
bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; }
bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; }
bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; }
bool ESPTime::operator>=(ESPTime other) { return this->timestamp >= other.timestamp; }
bool ESPTime::operator>(ESPTime other) { return this->timestamp > other.timestamp; }
} // namespace time } // namespace time
} // namespace esphome } // namespace esphome

View file

@ -1,106 +1,15 @@
#pragma once #pragma once
#include <bitset>
#include <cstdlib>
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <bitset> #include "esphome/core/time.h"
#include <cstdlib>
#include <ctime>
namespace esphome { namespace esphome {
namespace time { namespace time {
/// A more user-friendly version of struct tm from time.h
struct ESPTime {
/** seconds after the minute [0-60]
* @note second is generally 0-59; the extra range is to accommodate leap seconds.
*/
uint8_t second;
/// minutes after the hour [0-59]
uint8_t minute;
/// hours since midnight [0-23]
uint8_t hour;
/// day of the week; sunday=1 [1-7]
uint8_t day_of_week;
/// day of the month [1-31]
uint8_t day_of_month;
/// day of the year [1-366]
uint16_t day_of_year;
/// month; january=1 [1-12]
uint8_t month;
/// year
uint16_t year;
/// daylight saving time flag
bool is_dst;
/// unix epoch time (seconds since UTC Midnight January 1, 1970)
time_t timestamp;
/** Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument.
* Up to buffer_len bytes are written.
*
* @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime
*/
size_t strftime(char *buffer, size_t buffer_len, const char *format);
/** Convert this ESPTime struct to a string as specified by the format argument.
* @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime
*
* @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
* microcontrollers.
*/
std::string strftime(const std::string &format);
/// Check if this ESPTime is valid (all fields in range and year is greater than 2018)
bool is_valid() const { return this->year >= 2019 && this->fields_in_range(); }
/// Check if all time fields of this ESPTime are in range.
bool fields_in_range() const {
return this->second < 61 && this->minute < 60 && this->hour < 24 && this->day_of_week > 0 &&
this->day_of_week < 8 && this->day_of_month > 0 && this->day_of_month < 32 && this->day_of_year > 0 &&
this->day_of_year < 367 && this->month > 0 && this->month < 13;
}
/// 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);
/** Convert an UTC epoch timestamp to a local time ESPTime instance.
*
* @param epoch Seconds since 1st January 1970. In UTC.
* @return The generated ESPTime
*/
static ESPTime from_epoch_local(time_t epoch) {
struct tm *c_tm = ::localtime(&epoch);
return ESPTime::from_c_tm(c_tm, epoch);
}
/** Convert an UTC epoch timestamp to a UTC time ESPTime instance.
*
* @param epoch Seconds since 1st January 1970. In UTC.
* @return The generated ESPTime
*/
static ESPTime from_epoch_utc(time_t epoch) {
struct tm *c_tm = ::gmtime(&epoch);
return ESPTime::from_c_tm(c_tm, epoch);
}
/// Recalculate the timestamp field from the other fields of this ESPTime instance (must be UTC).
void recalc_timestamp_utc(bool use_day_of_year = true);
/// Convert this ESPTime instance back to a tm struct.
struct tm to_c_tm();
static int32_t timezone_offset();
/// Increment this clock instance by one second.
void increment_second();
/// Increment this clock instance by one day.
void increment_day();
bool operator<(ESPTime other);
bool operator<=(ESPTime other);
bool operator==(ESPTime other);
bool operator>=(ESPTime other);
bool operator>(ESPTime other);
};
/// The RealTimeClock class exposes common timekeeping functions via the device's local real-time clock. /// The RealTimeClock class exposes common timekeeping functions via the device's local real-time clock.
/// ///
/// \note /// \note

View file

@ -368,16 +368,14 @@ uint8_t TM1637Display::printf(const char *format, ...) {
return 0; return 0;
} }
#ifdef USE_TIME uint8_t TM1637Display::strftime(uint8_t pos, const char *format, ESPTime time) {
uint8_t TM1637Display::strftime(uint8_t pos, const char *format, time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
return this->print(pos, buffer); return this->print(pos, buffer);
return 0; return 0;
} }
uint8_t TM1637Display::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } uint8_t TM1637Display::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); }
#endif
} // namespace tm1637 } // namespace tm1637
} // namespace esphome } // namespace esphome

View file

@ -3,13 +3,10 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/time.h"
#include <vector> #include <vector>
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#endif #endif
@ -61,12 +58,10 @@ class TM1637Display : public PollingComponent {
void add_tm1637_key(TM1637Key *tm1637_key) { this->tm1637_keys_.push_back(tm1637_key); } void add_tm1637_key(TM1637Key *tm1637_key) { this->tm1637_keys_.push_back(tm1637_key); }
#endif #endif
#ifdef USE_TIME
/// Evaluate the strftime-format and print the result at the given position. /// Evaluate the strftime-format and print the result at the given position.
uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0)));
/// Evaluate the strftime-format and print the result at position 0. /// Evaluate the strftime-format and print the result at position 0.
uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); uint8_t strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0)));
#endif
protected: protected:
void bit_delay_(); void bit_delay_();

View file

@ -211,16 +211,14 @@ uint8_t TM1638Component::printf(const char *format, ...) {
return 0; return 0;
} }
#ifdef USE_TIME uint8_t TM1638Component::strftime(uint8_t pos, const char *format, ESPTime time) {
uint8_t TM1638Component::strftime(uint8_t pos, const char *format, time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
return this->print(pos, buffer); return this->print(pos, buffer);
return 0; return 0;
} }
uint8_t TM1638Component::strftime(const char *format, time::ESPTime time) { return this->strftime(0, format, time); } uint8_t TM1638Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); }
#endif
//////////////// SPI //////////////// //////////////// SPI ////////////////

View file

@ -1,16 +1,13 @@
#pragma once #pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/automation.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/time.h"
#include <vector> #include <vector>
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome { namespace esphome {
namespace tm1638 { namespace tm1638 {
@ -52,12 +49,10 @@ class TM1638Component : public PollingComponent {
void loop() override; void loop() override;
uint8_t get_keys(); uint8_t get_keys();
#ifdef USE_TIME
/// Evaluate the strftime-format and print the result at the given position. /// Evaluate the strftime-format and print the result at the given position.
uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0))); uint8_t strftime(uint8_t pos, const char *format, ESPTime time) __attribute__((format(strftime, 3, 0)));
/// Evaluate the strftime-format and print the result at position 0. /// Evaluate the strftime-format and print the result at position 0.
uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); uint8_t strftime(const char *format, ESPTime time) __attribute__((format(strftime, 2, 0)));
#endif
void set_led(int led_pos, bool led_on_off); void set_led(int led_pos, bool led_on_off);

View file

@ -508,7 +508,7 @@ void Tuya::send_wifi_status_() {
void Tuya::send_local_time_() { void Tuya::send_local_time_() {
std::vector<uint8_t> payload; std::vector<uint8_t> payload;
auto *time_id = *this->time_id_; auto *time_id = *this->time_id_;
time::ESPTime now = time_id->now(); ESPTime now = time_id->now();
if (now.is_valid()) { if (now.is_valid()) {
uint8_t year = now.year - 2000; uint8_t year = now.year - 2000;
uint8_t month = now.month; uint8_t month = now.month;

View file

@ -7,6 +7,7 @@
#ifdef USE_TIME #ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
#include "esphome/core/time.h"
#endif #endif
#include <vector> #include <vector>

171
esphome/core/time.cpp Normal file
View file

@ -0,0 +1,171 @@
#include "time.h" // NOLINT
namespace esphome {
size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) {
struct tm c_tm = this->to_c_tm();
return ::strftime(buffer, buffer_len, format, &c_tm);
}
ESPTime ESPTime::from_c_tm(struct tm *c_tm, time_t c_time) {
ESPTime res{};
res.second = uint8_t(c_tm->tm_sec);
res.minute = uint8_t(c_tm->tm_min);
res.hour = uint8_t(c_tm->tm_hour);
res.day_of_week = uint8_t(c_tm->tm_wday + 1);
res.day_of_month = uint8_t(c_tm->tm_mday);
res.day_of_year = uint16_t(c_tm->tm_yday + 1);
res.month = uint8_t(c_tm->tm_mon + 1);
res.year = uint16_t(c_tm->tm_year + 1900);
res.is_dst = bool(c_tm->tm_isdst);
res.timestamp = c_time;
return res;
}
struct tm ESPTime::to_c_tm() {
struct tm c_tm {};
c_tm.tm_sec = this->second;
c_tm.tm_min = this->minute;
c_tm.tm_hour = this->hour;
c_tm.tm_mday = this->day_of_month;
c_tm.tm_mon = this->month - 1;
c_tm.tm_year = this->year - 1900;
c_tm.tm_wday = this->day_of_week - 1;
c_tm.tm_yday = this->day_of_year - 1;
c_tm.tm_isdst = this->is_dst;
return c_tm;
}
std::string ESPTime::strftime(const std::string &format) {
std::string timestr;
timestr.resize(format.size() * 4);
struct tm c_tm = this->to_c_tm();
size_t len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
while (len == 0) {
timestr.resize(timestr.size() * 2);
len = ::strftime(&timestr[0], timestr.size(), format.c_str(), &c_tm);
}
timestr.resize(len);
return timestr;
}
void ESPTime::increment_second() {
this->timestamp++;
if (!increment_time_value(this->second, 0, 60))
return;
// second roll-over, increment minute
if (!increment_time_value(this->minute, 0, 60))
return;
// minute roll-over, increment hour
if (!increment_time_value(this->hour, 0, 24))
return;
// hour roll-over, increment day
increment_time_value(this->day_of_week, 1, 8);
if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) {
// day of month roll-over, increment month
increment_time_value(this->month, 1, 13);
}
uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365;
if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) {
// day of year roll-over, increment year
this->year++;
}
}
void ESPTime::increment_day() {
this->timestamp += 86400;
// increment day
increment_time_value(this->day_of_week, 1, 8);
if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) {
// day of month roll-over, increment month
increment_time_value(this->month, 1, 13);
}
uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365;
if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) {
// day of year roll-over, increment year
this->year++;
}
}
void ESPTime::recalc_timestamp_utc(bool use_day_of_year) {
time_t res = 0;
if (!this->fields_in_range()) {
this->timestamp = -1;
return;
}
for (int i = 1970; i < this->year; i++)
res += is_leap_year(i) ? 366 : 365;
if (use_day_of_year) {
res += this->day_of_year - 1;
} else {
for (int i = 1; i < this->month; i++)
res += days_in_month(i, this->year);
res += this->day_of_month - 1;
}
res *= 24;
res += this->hour;
res *= 60;
res += this->minute;
res *= 60;
res += this->second;
this->timestamp = res;
}
int32_t ESPTime::timezone_offset() {
int32_t offset = 0;
time_t now = ::time(nullptr);
auto local = ESPTime::from_epoch_local(now);
auto utc = ESPTime::from_epoch_utc(now);
bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year;
if (utc.minute > local.minute) {
local.minute += 60;
local.hour -= 1;
}
offset += (local.minute - utc.minute) * 60;
if (negative) {
offset -= (utc.hour - local.hour) * 3600;
} else {
if (utc.hour > local.hour) {
local.hour += 24;
}
offset += (local.hour - utc.hour) * 3600;
}
return offset;
}
bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; }
bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; }
bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; }
bool ESPTime::operator>=(ESPTime other) { return this->timestamp >= other.timestamp; }
bool ESPTime::operator>(ESPTime other) { return this->timestamp > other.timestamp; }
template<typename T> bool increment_time_value(T &current, uint16_t begin, uint16_t end) {
current++;
if (current >= end) {
current = begin;
return true;
}
return false;
}
static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); }
static uint8_t days_in_month(uint8_t month, uint16_t year) {
static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
uint8_t days = DAYS_IN_MONTH[month];
if (month == 2 && is_leap_year(year))
return 29;
return days;
}
} // namespace esphome

105
esphome/core/time.h Normal file
View file

@ -0,0 +1,105 @@
#pragma once
#include <cstdlib>
#include <ctime>
#include <string>
namespace esphome {
template<typename T> bool increment_time_value(T &current, uint16_t begin, uint16_t end);
static bool is_leap_year(uint32_t year);
static uint8_t days_in_month(uint8_t month, uint16_t year);
/// A more user-friendly version of struct tm from time.h
struct ESPTime {
/** seconds after the minute [0-60]
* @note second is generally 0-59; the extra range is to accommodate leap seconds.
*/
uint8_t second;
/// minutes after the hour [0-59]
uint8_t minute;
/// hours since midnight [0-23]
uint8_t hour;
/// day of the week; sunday=1 [1-7]
uint8_t day_of_week;
/// day of the month [1-31]
uint8_t day_of_month;
/// day of the year [1-366]
uint16_t day_of_year;
/// month; january=1 [1-12]
uint8_t month;
/// year
uint16_t year;
/// daylight saving time flag
bool is_dst;
/// unix epoch time (seconds since UTC Midnight January 1, 1970)
time_t timestamp;
/** Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument.
* Up to buffer_len bytes are written.
*
* @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime
*/
size_t strftime(char *buffer, size_t buffer_len, const char *format);
/** Convert this ESPTime struct to a string as specified by the format argument.
* @see https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html#index-strftime
*
* @warning This method uses dynamically allocated strings which can cause heap fragmentation with some
* microcontrollers.
*/
std::string strftime(const std::string &format);
/// Check if this ESPTime is valid (all fields in range and year is greater than 2018)
bool is_valid() const { return this->year >= 2019 && this->fields_in_range(); }
/// Check if all time fields of this ESPTime are in range.
bool fields_in_range() const {
return this->second < 61 && this->minute < 60 && this->hour < 24 && this->day_of_week > 0 &&
this->day_of_week < 8 && this->day_of_month > 0 && this->day_of_month < 32 && this->day_of_year > 0 &&
this->day_of_year < 367 && this->month > 0 && this->month < 13;
}
/// 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);
/** Convert an UTC epoch timestamp to a local time ESPTime instance.
*
* @param epoch Seconds since 1st January 1970. In UTC.
* @return The generated ESPTime
*/
static ESPTime from_epoch_local(time_t epoch) {
struct tm *c_tm = ::localtime(&epoch);
return ESPTime::from_c_tm(c_tm, epoch);
}
/** Convert an UTC epoch timestamp to a UTC time ESPTime instance.
*
* @param epoch Seconds since 1st January 1970. In UTC.
* @return The generated ESPTime
*/
static ESPTime from_epoch_utc(time_t epoch) {
struct tm *c_tm = ::gmtime(&epoch);
return ESPTime::from_c_tm(c_tm, epoch);
}
/// Recalculate the timestamp field from the other fields of this ESPTime instance (must be UTC).
void recalc_timestamp_utc(bool use_day_of_year = true);
/// Convert this ESPTime instance back to a tm struct.
struct tm to_c_tm();
static int32_t timezone_offset();
/// Increment this clock instance by one second.
void increment_second();
/// Increment this clock instance by one day.
void increment_day();
bool operator<(ESPTime other);
bool operator<=(ESPTime other);
bool operator==(ESPTime other);
bool operator>=(ESPTime other);
bool operator>(ESPTime other);
};
} // namespace esphome

View file

@ -10,7 +10,7 @@ platformio==6.1.7 # When updating platformio, also update Dockerfile
esptool==4.6 esptool==4.6
click==8.1.3 click==8.1.3
esphome-dashboard==20230516.0 esphome-dashboard==20230516.0
aioesphomeapi==13.9.0 aioesphomeapi==14.0.0
zeroconf==0.63.0 zeroconf==0.63.0
# esp-idf requires this, but doesn't bundle it by default # esp-idf requires this, but doesn't bundle it by default

View file

@ -1,2 +1,3 @@
pillow>4.0.0 pillow>4.0.0
cairosvg>=2.2.0
cryptography>=2.0.0,<4 cryptography>=2.0.0,<4

View file

@ -488,6 +488,14 @@ binary_sensor:
esp32_ble_tracker: esp32_ble_tracker:
on_ble_advertise: on_ble_advertise:
- mac_address:
- AA:BB:CC:DD:EE:FF
- FF:EE:DD:CC:BB:AA
then:
# yamllint disable rule:line-length
- lambda: !lambda |-
ESP_LOGD("main", "The device address (%s) exists in list", x.address_str().c_str());
# yamllint enable rule:line-length
- mac_address: AC:37:43:77:5F:4C - mac_address: AC:37:43:77:5F:4C
then: then:
# yamllint disable rule:line-length # yamllint disable rule:line-length
@ -680,6 +688,13 @@ image:
type: RGB565 type: RGB565
use_transparency: no use_transparency: no
- id: mdi_alert
file: mdi:alert-circle-outline
resize: 50x50
- id: another_alert_icon
file: mdi:alert-outline
type: BINARY
cap1188: cap1188:
id: cap1188_component id: cap1188_component
address: 0x29 address: 0x29