mirror of
https://github.com/esphome/esphome.git
synced 2024-12-29 08:51:43 +01:00
Merge commit '5f0892dec43a4c1fdbb3d3853bac0f2a0fee1bbd' into optolink
This commit is contained in:
commit
44fa332931
55 changed files with 1018 additions and 663 deletions
|
@ -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}/* \
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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:");
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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[]);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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(×tr[0], timestr.size(), format.c_str(), &c_tm);
|
|
||||||
while (len == 0) {
|
|
||||||
timestr.resize(timestr.size() * 2);
|
|
||||||
len = ::strftime(×tr[0], timestr.size(), format.c_str(), &c_tm);
|
|
||||||
}
|
|
||||||
timestr.resize(len);
|
|
||||||
return timestr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> bool increment_time_value(T ¤t, 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_();
|
||||||
|
|
|
@ -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 ////////////////
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
171
esphome/core/time.cpp
Normal 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(×tr[0], timestr.size(), format.c_str(), &c_tm);
|
||||||
|
while (len == 0) {
|
||||||
|
timestr.resize(timestr.size() * 2);
|
||||||
|
len = ::strftime(×tr[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 ¤t, 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
105
esphome/core/time.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
template<typename T> bool increment_time_value(T ¤t, 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue