mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Add new Lock core component (#2958)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
62b366a5ec
commit
21803607e7
41 changed files with 1558 additions and 3 deletions
|
@ -89,6 +89,7 @@ esphome/components/json/* @OttoWinter
|
|||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
|
|
|
@ -41,6 +41,7 @@ service APIConnection {
|
|||
rpc number_command (NumberCommandRequest) returns (void) {}
|
||||
rpc select_command (SelectCommandRequest) returns (void) {}
|
||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
|
@ -956,6 +957,63 @@ message SelectCommandRequest {
|
|||
string state = 2;
|
||||
}
|
||||
|
||||
|
||||
// ==================== LOCK ====================
|
||||
enum LockState {
|
||||
LOCK_STATE_NONE = 0;
|
||||
LOCK_STATE_LOCKED = 1;
|
||||
LOCK_STATE_UNLOCKED = 2;
|
||||
LOCK_STATE_JAMMED = 3;
|
||||
LOCK_STATE_LOCKING = 4;
|
||||
LOCK_STATE_UNLOCKING = 5;
|
||||
}
|
||||
enum LockCommand {
|
||||
LOCK_UNLOCK = 0;
|
||||
LOCK_LOCK = 1;
|
||||
LOCK_OPEN = 2;
|
||||
}
|
||||
message ListEntitiesLockResponse {
|
||||
option (id) = 58;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
bool assumed_state = 8;
|
||||
|
||||
bool supports_open = 9;
|
||||
bool requires_code = 10;
|
||||
|
||||
# Not yet implemented:
|
||||
string code_format = 11;
|
||||
}
|
||||
message LockStateResponse {
|
||||
option (id) = 59;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
LockState state = 2;
|
||||
}
|
||||
message LockCommandRequest {
|
||||
option (id) = 60;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
LockCommand command = 2;
|
||||
|
||||
# Not yet implemented:
|
||||
bool has_code = 3;
|
||||
string code = 4;
|
||||
}
|
||||
|
||||
// ==================== BUTTON ====================
|
||||
message ListEntitiesButtonResponse {
|
||||
option (id) = 61;
|
||||
|
@ -980,3 +1038,4 @@ message ButtonCommandRequest {
|
|||
|
||||
fixed32 key = 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -700,6 +700,49 @@ void APIConnection::button_command(const ButtonCommandRequest &msg) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
LockStateResponse resp{};
|
||||
resp.key = a_lock->get_object_id_hash();
|
||||
resp.state = static_cast<enums::LockState>(state);
|
||||
return this->send_lock_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_lock_info(lock::Lock *a_lock) {
|
||||
ListEntitiesLockResponse msg;
|
||||
msg.key = a_lock->get_object_id_hash();
|
||||
msg.object_id = a_lock->get_object_id();
|
||||
msg.name = a_lock->get_name();
|
||||
msg.unique_id = get_default_unique_id("lock", a_lock);
|
||||
msg.icon = a_lock->get_icon();
|
||||
msg.assumed_state = a_lock->traits.get_assumed_state();
|
||||
msg.disabled_by_default = a_lock->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category());
|
||||
msg.supports_open = a_lock->traits.get_supports_open();
|
||||
msg.requires_code = a_lock->traits.get_requires_code();
|
||||
return this->send_list_entities_lock_response(msg);
|
||||
}
|
||||
void APIConnection::lock_command(const LockCommandRequest &msg) {
|
||||
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
|
||||
if (a_lock == nullptr)
|
||||
return;
|
||||
|
||||
switch (msg.command) {
|
||||
case enums::LOCK_UNLOCK:
|
||||
a_lock->unlock();
|
||||
break;
|
||||
case enums::LOCK_LOCK:
|
||||
a_lock->lock();
|
||||
break;
|
||||
case enums::LOCK_OPEN:
|
||||
a_lock->open();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
|
|
|
@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection {
|
|||
#ifdef USE_BUTTON
|
||||
bool send_button_info(button::Button *button);
|
||||
void button_command(const ButtonCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
|
||||
bool send_lock_info(lock::Lock *a_lock);
|
||||
void lock_command(const LockCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
|
|
|
@ -278,6 +278,36 @@ template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode
|
|||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_STATE_NONE:
|
||||
return "LOCK_STATE_NONE";
|
||||
case enums::LOCK_STATE_LOCKED:
|
||||
return "LOCK_STATE_LOCKED";
|
||||
case enums::LOCK_STATE_UNLOCKED:
|
||||
return "LOCK_STATE_UNLOCKED";
|
||||
case enums::LOCK_STATE_JAMMED:
|
||||
return "LOCK_STATE_JAMMED";
|
||||
case enums::LOCK_STATE_LOCKING:
|
||||
return "LOCK_STATE_LOCKING";
|
||||
case enums::LOCK_STATE_UNLOCKING:
|
||||
return "LOCK_STATE_UNLOCKING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockCommand value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_UNLOCK:
|
||||
return "LOCK_UNLOCK";
|
||||
case enums::LOCK_LOCK:
|
||||
return "LOCK_LOCK";
|
||||
case enums::LOCK_OPEN:
|
||||
return "LOCK_OPEN";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
|
@ -4186,6 +4216,234 @@ void SelectCommandRequest::dump_to(std::string &out) const {
|
|||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->assumed_state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->supports_open = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->requires_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->object_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->code_format = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_bool(8, this->assumed_state);
|
||||
buffer.encode_bool(9, this->supports_open);
|
||||
buffer.encode_bool(10, this->requires_code);
|
||||
buffer.encode_string(11, this->code_format);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesLockResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unique_id: ");
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" assumed_state: ");
|
||||
out.append(YESNO(this->assumed_state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_open: ");
|
||||
out.append(YESNO(this->supports_open));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" requires_code: ");
|
||||
out.append(YESNO(this->requires_code));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" code_format: ");
|
||||
out.append("'").append(this->code_format).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->state = value.as_enum<enums::LockState>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void LockStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::LockState>(2, this->state);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void LockStateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("LockStateResponse {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" state: ");
|
||||
out.append(proto_enum_to_string<enums::LockState>(this->state));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->command = value.as_enum<enums::LockCommand>();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->has_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->code = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void LockCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::LockCommand>(2, this->command);
|
||||
buffer.encode_bool(3, this->has_code);
|
||||
buffer.encode_string(4, this->code);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void LockCommandRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("LockCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" command: ");
|
||||
out.append(proto_enum_to_string<enums::LockCommand>(this->command));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_code: ");
|
||||
out.append(YESNO(this->has_code));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" code: ");
|
||||
out.append("'").append(this->code).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
|
@ -4248,7 +4506,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesButtonResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
|
@ -4298,7 +4556,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ButtonCommandRequest::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ButtonCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
|
|
|
@ -128,6 +128,19 @@ enum NumberMode : uint32_t {
|
|||
NUMBER_MODE_BOX = 1,
|
||||
NUMBER_MODE_SLIDER = 2,
|
||||
};
|
||||
enum LockState : uint32_t {
|
||||
LOCK_STATE_NONE = 0,
|
||||
LOCK_STATE_LOCKED = 1,
|
||||
LOCK_STATE_UNLOCKED = 2,
|
||||
LOCK_STATE_JAMMED = 3,
|
||||
LOCK_STATE_LOCKING = 4,
|
||||
LOCK_STATE_UNLOCKING = 5,
|
||||
};
|
||||
enum LockCommand : uint32_t {
|
||||
LOCK_UNLOCK = 0,
|
||||
LOCK_LOCK = 1,
|
||||
LOCK_OPEN = 2,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
|
@ -1049,6 +1062,58 @@ class SelectCommandRequest : public ProtoMessage {
|
|||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesLockResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
bool assumed_state{false};
|
||||
bool supports_open{false};
|
||||
bool requires_code{false};
|
||||
std::string code_format{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LockStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
enums::LockState state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LockCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
enums::LockCommand command{};
|
||||
bool has_code{false};
|
||||
std::string code{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesButtonResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
|
|
|
@ -282,6 +282,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
|
|||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_lock_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesLockResponse>(msg, 58);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool APIServerConnectionBase::send_lock_state_response(const LockStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_lock_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<LockStateResponse>(msg, 59);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
@ -523,6 +541,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_select_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 60: {
|
||||
#ifdef USE_LOCK
|
||||
LockCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_lock_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
@ -771,6 +800,19 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest &
|
|||
this->button_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->lock_command(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -130,6 +130,15 @@ class APIServerConnectionBase : public ProtoService {
|
|||
#ifdef USE_SELECT
|
||||
virtual void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state_response(const LockStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_command_request(const LockCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
|
||||
#endif
|
||||
|
@ -180,6 +189,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
virtual void button_command(const ButtonCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void lock_command(const LockCommandRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
|
@ -221,6 +233,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_BUTTON
|
||||
void on_button_command_request(const ButtonCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_command_request(const LockCommandRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
|
|
@ -263,6 +263,15 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void APIServer::on_lock_update(lock::Lock *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_lock_state(obj, obj->state);
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
|
|
@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
|
|||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj, const std::string &state) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
|
|
|
@ -35,6 +35,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
|
|||
return this->client_->send_text_sensor_info(text_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
|
|
|
@ -48,6 +48,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@ bool InitialStateIterator::on_select(select::Select *select) {
|
|||
return this->client_->send_select_state(select, select->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
|
||||
#endif
|
||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
|
||||
|
|
|
@ -45,6 +45,9 @@ class InitialStateIterator : public ComponentIterator {
|
|||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
#endif
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
|
|
|
@ -212,6 +212,21 @@ void ComponentIterator::advance() {
|
|||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
case IteratorState::LOCK:
|
||||
if (this->at_ >= App.get_locks().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *a_lock = App.get_locks()[this->at_];
|
||||
if (a_lock->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_lock(a_lock);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState::MAX:
|
||||
if (this->on_end()) {
|
||||
|
|
|
@ -56,6 +56,9 @@ class ComponentIterator {
|
|||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual bool on_select(select::Select *select) = 0;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual bool on_lock(lock::Lock *a_lock) = 0;
|
||||
#endif
|
||||
virtual bool on_end();
|
||||
|
||||
|
@ -99,6 +102,9 @@ class ComponentIterator {
|
|||
#endif
|
||||
#ifdef USE_SELECT
|
||||
SELECT,
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
LOCK,
|
||||
#endif
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
|
|
|
@ -7,6 +7,7 @@ from esphome.const import (
|
|||
CONF_ID,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_STATE,
|
||||
CONF_ON_OPEN,
|
||||
CONF_POSITION,
|
||||
CONF_POSITION_COMMAND_TOPIC,
|
||||
CONF_POSITION_STATE_TOPIC,
|
||||
|
@ -74,7 +75,6 @@ CoverClosedTrigger = cover_ns.class_(
|
|||
"CoverClosedTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
CONF_ON_OPEN = "on_open"
|
||||
CONF_ON_CLOSED = "on_closed"
|
||||
|
||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
|
|
102
esphome/components/lock/__init__.py
Normal file
102
esphome/components/lock/__init__.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_ON_LOCK,
|
||||
CONF_ON_UNLOCK,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
lock_ns = cg.esphome_ns.namespace("lock")
|
||||
Lock = lock_ns.class_("Lock", cg.EntityBase)
|
||||
LockPtr = Lock.operator("ptr")
|
||||
LockCall = lock_ns.class_("LockCall")
|
||||
|
||||
UnlockAction = lock_ns.class_("UnlockAction", automation.Action)
|
||||
LockAction = lock_ns.class_("LockAction", automation.Action)
|
||||
OpenAction = lock_ns.class_("OpenAction", automation.Action)
|
||||
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
|
||||
|
||||
LockCondition = lock_ns.class_("LockCondition", Condition)
|
||||
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
|
||||
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
|
||||
|
||||
LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
|
||||
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_lock_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_LOCK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_UNLOCK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
async def register_lock(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_lock(var))
|
||||
await setup_lock_core_(var, config)
|
||||
|
||||
|
||||
LOCK_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Lock),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("lock.unlock", UnlockAction, LOCK_ACTION_SCHEMA)
|
||||
@automation.register_action("lock.lock", LockAction, LOCK_ACTION_SCHEMA)
|
||||
@automation.register_action("lock.open", OpenAction, LOCK_ACTION_SCHEMA)
|
||||
async def lock_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
async def to_code(config):
|
||||
cg.add_global(lock_ns.using)
|
||||
cg.add_define("USE_LOCK")
|
87
esphome/components/lock/automation.h
Normal file
87
esphome/components/lock/automation.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lock {
|
||||
|
||||
template<typename... Ts> class LockAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit LockAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void play(Ts... x) override { this->lock_->lock(); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class UnlockAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit UnlockAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void play(Ts... x) override { this->lock_->unlock(); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class OpenAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit OpenAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void play(Ts... x) override { this->lock_->open(); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LockCondition : public Condition<Ts...> {
|
||||
public:
|
||||
LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(Ts... x) override {
|
||||
auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED;
|
||||
return this->parent_->state == check_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
Lock *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
class LockLockTrigger : public Trigger<> {
|
||||
public:
|
||||
LockLockTrigger(Lock *a_lock) {
|
||||
a_lock->add_on_state_callback([this, a_lock]() {
|
||||
if (a_lock->state == LockState::LOCK_STATE_LOCKED) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class LockUnlockTrigger : public Trigger<> {
|
||||
public:
|
||||
LockUnlockTrigger(Lock *a_lock) {
|
||||
a_lock->add_on_state_callback([this, a_lock]() {
|
||||
if (a_lock->state == LockState::LOCK_STATE_UNLOCKED) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class LockPublishAction : public Action<Ts...> {
|
||||
public:
|
||||
LockPublishAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
TEMPLATABLE_VALUE(LockState, state)
|
||||
|
||||
void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
} // namespace lock
|
||||
} // namespace esphome
|
109
esphome/components/lock/lock.cpp
Normal file
109
esphome/components/lock/lock.cpp
Normal file
|
@ -0,0 +1,109 @@
|
|||
#include "lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lock {
|
||||
|
||||
static const char *const TAG = "lock";
|
||||
|
||||
const char *lock_state_to_string(LockState state) {
|
||||
switch (state) {
|
||||
case LOCK_STATE_LOCKED:
|
||||
return "LOCKED";
|
||||
case LOCK_STATE_UNLOCKED:
|
||||
return "UNLOCKED";
|
||||
case LOCK_STATE_JAMMED:
|
||||
return "JAMMED";
|
||||
case LOCK_STATE_LOCKING:
|
||||
return "LOCKING";
|
||||
case LOCK_STATE_UNLOCKING:
|
||||
return "UNLOCKING";
|
||||
case LOCK_STATE_NONE:
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {}
|
||||
Lock::Lock() : Lock("") {}
|
||||
LockCall Lock::make_call() { return LockCall(this); }
|
||||
|
||||
void Lock::lock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_LOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
void Lock::unlock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_UNLOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
void Lock::open() {
|
||||
if (traits.get_supports_open()) {
|
||||
ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str());
|
||||
this->open_latch();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' Does not support Open.", this->get_name().c_str());
|
||||
}
|
||||
}
|
||||
void Lock::publish_state(LockState state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
|
||||
this->state = state;
|
||||
this->rtc_.save(&this->state);
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
|
||||
this->state_callback_.call();
|
||||
}
|
||||
|
||||
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
uint32_t Lock::hash_base() { return 856245656UL; }
|
||||
|
||||
void LockCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->state_.has_value()) {
|
||||
const char *state_s = lock_state_to_string(*this->state_);
|
||||
ESP_LOGD(TAG, " State: %s", state_s);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
void LockCall::validate_() {
|
||||
if (this->state_.has_value()) {
|
||||
auto state = *this->state_;
|
||||
if (!this->parent_->traits.supports_state(state)) {
|
||||
ESP_LOGW(TAG, " State %s is not supported by this device!", lock_state_to_string(*this->state_));
|
||||
this->state_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
LockCall &LockCall::set_state(LockState state) {
|
||||
this->state_ = state;
|
||||
return *this;
|
||||
}
|
||||
LockCall &LockCall::set_state(optional<LockState> state) {
|
||||
this->state_ = state;
|
||||
return *this;
|
||||
}
|
||||
LockCall &LockCall::set_state(const std::string &state) {
|
||||
if (str_equals_case_insensitive(state, "LOCKED")) {
|
||||
this->set_state(LOCK_STATE_LOCKED);
|
||||
} else if (str_equals_case_insensitive(state, "UNLOCKED")) {
|
||||
this->set_state(LOCK_STATE_UNLOCKED);
|
||||
} else if (str_equals_case_insensitive(state, "JAMMED")) {
|
||||
this->set_state(LOCK_STATE_JAMMED);
|
||||
} else if (str_equals_case_insensitive(state, "LOCKING")) {
|
||||
this->set_state(LOCK_STATE_LOCKING);
|
||||
} else if (str_equals_case_insensitive(state, "UNLOCKING")) {
|
||||
this->set_state(LOCK_STATE_UNLOCKING);
|
||||
} else if (str_equals_case_insensitive(state, "NONE")) {
|
||||
this->set_state(LOCK_STATE_NONE);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
const optional<LockState> &LockCall::get_state() const { return this->state_; }
|
||||
|
||||
} // namespace lock
|
||||
} // namespace esphome
|
178
esphome/components/lock/lock.h
Normal file
178
esphome/components/lock/lock.h
Normal file
|
@ -0,0 +1,178 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <set>
|
||||
|
||||
namespace esphome {
|
||||
namespace lock {
|
||||
|
||||
class Lock;
|
||||
|
||||
#define LOG_LOCK(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
if ((obj)->traits.get_assumed_state()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||
} \
|
||||
}
|
||||
/// Enum for all states a lock can be in.
|
||||
enum LockState : uint8_t {
|
||||
LOCK_STATE_NONE = 0,
|
||||
LOCK_STATE_LOCKED = 1,
|
||||
LOCK_STATE_UNLOCKED = 2,
|
||||
LOCK_STATE_JAMMED = 3,
|
||||
LOCK_STATE_LOCKING = 4,
|
||||
LOCK_STATE_UNLOCKING = 5
|
||||
};
|
||||
const char *lock_state_to_string(LockState state);
|
||||
|
||||
class LockTraits {
|
||||
public:
|
||||
LockTraits() = default;
|
||||
|
||||
bool get_supports_open() const { return this->supports_open_; }
|
||||
void set_supports_open(bool supports_open) { this->supports_open_ = supports_open; }
|
||||
bool get_requires_code() const { return this->requires_code_; }
|
||||
void set_requires_code(bool requires_code) { this->requires_code_ = requires_code; }
|
||||
bool get_assumed_state() const { return this->assumed_state_; }
|
||||
void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
|
||||
bool supports_state(LockState state) const { return supported_states_.count(state); }
|
||||
std::set<LockState> get_supported_states() const { return supported_states_; }
|
||||
void set_supported_states(std::set<LockState> states) { supported_states_ = std::move(states); }
|
||||
void add_supported_state(LockState state) { supported_states_.insert(state); }
|
||||
|
||||
protected:
|
||||
bool supports_open_{false};
|
||||
bool requires_code_{false};
|
||||
bool assumed_state_{false};
|
||||
std::set<LockState> supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED};
|
||||
};
|
||||
|
||||
/** This class is used to encode all control actions on a lock device.
|
||||
*
|
||||
* It is supposed to be used by all code that wishes to control a lock device (mqtt, api, lambda etc).
|
||||
* Create an instance of this class by calling `id(lock_device).make_call();`. Then set all attributes
|
||||
* with the `set_x` methods. Finally, to apply the changes call `.perform();`.
|
||||
*
|
||||
* The integration that implements the lock device receives this instance with the `control` method.
|
||||
* It should check all the properties it implements and apply them as needed. It should do so by
|
||||
* getting all properties it controls with the getter methods in this class. If the optional value is
|
||||
* set (check with `.has_value()`) that means the user wants to control this property. Get the value
|
||||
* of the optional with the star operator (`*call.get_state()`) and apply it.
|
||||
*/
|
||||
class LockCall {
|
||||
public:
|
||||
LockCall(Lock *parent) : parent_(parent) {}
|
||||
|
||||
/// Set the state of the lock device.
|
||||
LockCall &set_state(LockState state);
|
||||
/// Set the state of the lock device.
|
||||
LockCall &set_state(optional<LockState> state);
|
||||
/// Set the state of the lock device based on a string.
|
||||
LockCall &set_state(const std::string &state);
|
||||
|
||||
void perform();
|
||||
|
||||
const optional<LockState> &get_state() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
Lock *const parent_;
|
||||
optional<LockState> state_;
|
||||
};
|
||||
|
||||
/** Base class for all locks.
|
||||
*
|
||||
* A lock is basically a switch with a combination of a binary sensor (for reporting lock values)
|
||||
* and a write_state method that writes a state to the hardware. Locks can also have an "open"
|
||||
* method to unlatch.
|
||||
*
|
||||
* For integrations: Integrations must implement the method control().
|
||||
* Control will be called with the arguments supplied by the user and should be used
|
||||
* to control all values of the lock.
|
||||
*/
|
||||
class Lock : public EntityBase {
|
||||
public:
|
||||
explicit Lock();
|
||||
explicit Lock(const std::string &name);
|
||||
|
||||
/** Make a lock device control call, this is used to control the lock device, see the LockCall description
|
||||
* for more info.
|
||||
* @return A new LockCall instance targeting this lock device.
|
||||
*/
|
||||
LockCall make_call();
|
||||
|
||||
/** Publish a state to the front-end from the back-end.
|
||||
*
|
||||
* Then the internal value member is set and finally the callbacks are called.
|
||||
*
|
||||
* @param state The new state.
|
||||
*/
|
||||
void publish_state(LockState state);
|
||||
|
||||
/// The current reported state of the lock.
|
||||
LockState state{LOCK_STATE_NONE};
|
||||
|
||||
LockTraits traits;
|
||||
|
||||
/** Turn this lock on. This is called by the front-end.
|
||||
*
|
||||
* For implementing locks, please override control.
|
||||
*/
|
||||
void lock();
|
||||
/** Turn this lock off. This is called by the front-end.
|
||||
*
|
||||
* For implementing locks, please override control.
|
||||
*/
|
||||
void unlock();
|
||||
/** Open (unlatch) this lock. This is called by the front-end.
|
||||
*
|
||||
* For implementing locks, please override control.
|
||||
*/
|
||||
void open();
|
||||
|
||||
/** Set callback for state changes.
|
||||
*
|
||||
* @param callback The void(bool) callback.
|
||||
*/
|
||||
void add_on_state_callback(std::function<void()> &&callback);
|
||||
|
||||
protected:
|
||||
friend LockCall;
|
||||
|
||||
/** Perform the open latch action with hardware. This method is optional to implement
|
||||
* when creating a new lock.
|
||||
*
|
||||
* In the implementation of this method, it is recommended you also call
|
||||
* publish_state with "unlock" to acknowledge that the state was written to the hardware.
|
||||
*/
|
||||
virtual void open_latch() { unlock(); };
|
||||
|
||||
/** Control the lock device, this is a virtual method that each lock integration must implement.
|
||||
*
|
||||
* See more info in LockCall. The integration should check all of its values in this method and
|
||||
* set them accordingly. At the end of the call, the integration must call `publish_state()` to
|
||||
* notify the frontend of a changed state.
|
||||
*
|
||||
* @param call The LockCall instance encoding all attribute changes.
|
||||
*/
|
||||
virtual void control(const LockCall &call) = 0;
|
||||
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
Deduplicator<LockState> publish_dedup_;
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
} // namespace lock
|
||||
} // namespace esphome
|
|
@ -97,6 +97,7 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
|
|||
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
|
||||
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
||||
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
|
||||
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
|
||||
|
||||
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
|
||||
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
|
||||
|
|
55
esphome/components/mqtt/mqtt_lock.cpp
Normal file
55
esphome/components/mqtt/mqtt_lock.cpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
#include "mqtt_lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_LOCK
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.lock";
|
||||
|
||||
using namespace esphome::lock;
|
||||
|
||||
MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void MQTTLockComponent::setup() {
|
||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
||||
if (strcasecmp(payload.c_str(), "LOCK") == 0) {
|
||||
this->lock_->lock();
|
||||
} else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) {
|
||||
this->lock_->unlock();
|
||||
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
|
||||
this->lock_->open();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
this->status_momentary_warning("state", 5000);
|
||||
}
|
||||
});
|
||||
this->lock_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
|
||||
}
|
||||
void MQTTLockComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str());
|
||||
LOG_MQTT_COMPONENT(true, true);
|
||||
}
|
||||
|
||||
std::string MQTTLockComponent::component_type() const { return "lock"; }
|
||||
const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; }
|
||||
void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (this->lock_->traits.get_assumed_state())
|
||||
root[MQTT_OPTIMISTIC] = true;
|
||||
}
|
||||
bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
|
||||
|
||||
bool MQTTLockComponent::publish_state() {
|
||||
std::string payload = lock_state_to_string(this->lock_->state);
|
||||
return this->publish(this->get_state_topic_(), payload);
|
||||
}
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
41
esphome/components/mqtt/mqtt_lock.h
Normal file
41
esphome/components/mqtt/mqtt_lock.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_LOCK
|
||||
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#include "mqtt_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTLockComponent : public mqtt::MQTTComponent {
|
||||
public:
|
||||
explicit MQTTLockComponent(lock::Lock *a_lock);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
|
||||
|
||||
bool send_initial_state() override;
|
||||
|
||||
bool publish_state();
|
||||
|
||||
protected:
|
||||
/// "lock" component type.
|
||||
std::string component_type() const override;
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
lock::Lock *lock_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
23
esphome/components/output/lock/__init__.py
Normal file
23
esphome/components/output/lock/__init__.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output, lock
|
||||
from esphome.const import CONF_ID, CONF_OUTPUT
|
||||
from .. import output_ns
|
||||
|
||||
OutputLock = output_ns.class_("OutputLock", lock.Lock, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(OutputLock),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await lock.register_lock(var, config)
|
||||
|
||||
output_ = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(output_))
|
22
esphome/components/output/lock/output_lock.cpp
Normal file
22
esphome/components/output/lock/output_lock.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "output_lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace output {
|
||||
|
||||
static const char *const TAG = "output.lock";
|
||||
|
||||
void OutputLock::dump_config() { LOG_LOCK("", "Output Lock", this); }
|
||||
|
||||
void OutputLock::control(const lock::LockCall &call) {
|
||||
auto state = *call.get_state();
|
||||
if (state == lock::LOCK_STATE_LOCKED) {
|
||||
this->output_->turn_on();
|
||||
} else if (state == lock::LOCK_STATE_UNLOCKED) {
|
||||
this->output_->turn_off();
|
||||
}
|
||||
this->publish_state(state);
|
||||
}
|
||||
|
||||
} // namespace output
|
||||
} // namespace esphome
|
24
esphome/components/output/lock/output_lock.h
Normal file
24
esphome/components/output/lock/output_lock.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace output {
|
||||
|
||||
class OutputLock : public lock::Lock, public Component {
|
||||
public:
|
||||
void set_output(BinaryOutput *output) { output_ = output; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void control(const lock::LockCall &call) override;
|
||||
|
||||
output::BinaryOutput *output_;
|
||||
};
|
||||
|
||||
} // namespace output
|
||||
} // namespace esphome
|
|
@ -45,6 +45,12 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
|
|||
this->switch_row_(stream, obj);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
this->lock_type_(stream);
|
||||
for (auto *obj : App.get_locks())
|
||||
this->lock_row_(stream, obj);
|
||||
#endif
|
||||
|
||||
req->send(stream);
|
||||
}
|
||||
|
||||
|
@ -310,6 +316,30 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void PrometheusHandler::lock_type_(AsyncResponseStream *stream) {
|
||||
stream->print(F("#TYPE esphome_lock_value GAUGE\n"));
|
||||
stream->print(F("#TYPE esphome_lock_failed GAUGE\n"));
|
||||
}
|
||||
void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
stream->print(F("esphome_lock_failed{id=\""));
|
||||
stream->print(obj->get_object_id().c_str());
|
||||
stream->print(F("\",name=\""));
|
||||
stream->print(obj->get_name().c_str());
|
||||
stream->print(F("\"} 0\n"));
|
||||
// Data itself
|
||||
stream->print(F("esphome_lock_value{id=\""));
|
||||
stream->print(obj->get_object_id().c_str());
|
||||
stream->print(F("\",name=\""));
|
||||
stream->print(obj->get_name().c_str());
|
||||
stream->print(F("\"} "));
|
||||
stream->print(obj->state);
|
||||
stream->print('\n');
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace prometheus
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -76,6 +76,13 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
|
|||
void switch_row_(AsyncResponseStream *stream, switch_::Switch *obj);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
/// Return the type for prometheus
|
||||
void lock_type_(AsyncResponseStream *stream);
|
||||
/// Return the lock Values state as prometheus data point
|
||||
void lock_row_(AsyncResponseStream *stream, lock::Lock *obj);
|
||||
#endif
|
||||
|
||||
web_server_base::WebServerBase *base_;
|
||||
};
|
||||
|
||||
|
|
103
esphome/components/template/lock/__init__.py
Normal file
103
esphome/components/template/lock/__init__.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import lock
|
||||
from esphome.const import (
|
||||
CONF_ASSUMED_STATE,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_LOCK_ACTION,
|
||||
CONF_OPEN_ACTION,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_STATE,
|
||||
CONF_UNLOCK_ACTION,
|
||||
)
|
||||
from .. import template_ns
|
||||
|
||||
TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component)
|
||||
|
||||
LockState = lock.lock_ns.enum("LockState")
|
||||
|
||||
LOCK_STATES = {
|
||||
"LOCKED": LockState.LOCK_STATE_LOCKED,
|
||||
"UNLOCKED": LockState.LOCK_STATE_UNLOCKED,
|
||||
"JAMMED": LockState.LOCK_STATE_JAMMED,
|
||||
"LOCKING": LockState.LOCK_STATE_LOCKING,
|
||||
"UNLOCKING": LockState.LOCK_STATE_UNLOCKING,
|
||||
}
|
||||
|
||||
validate_lock_state = cv.enum(LOCK_STATES, upper=True)
|
||||
|
||||
|
||||
def validate(config):
|
||||
if not config[CONF_OPTIMISTIC] and (
|
||||
CONF_LOCK_ACTION not in config or CONF_UNLOCK_ACTION not in config
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"Either optimistic mode must be enabled, or lock_action and unlock_action must be set, "
|
||||
"to handle the lock being changed."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
lock.LOCK_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TemplateLock),
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_UNLOCK_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_LOCK_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await lock.register_lock(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(LockState)
|
||||
)
|
||||
cg.add(var.set_state_lambda(template_))
|
||||
if CONF_UNLOCK_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION]
|
||||
)
|
||||
if CONF_LOCK_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_lock_trigger(), [], config[CONF_LOCK_ACTION]
|
||||
)
|
||||
if CONF_OPEN_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
|
||||
)
|
||||
cg.add(var.traits.set_supports_open(CONF_OPEN_ACTION in config))
|
||||
cg.add(var.traits.set_assumed_state(config[CONF_ASSUMED_STATE]))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lock.template.publish",
|
||||
lock.LockPublishAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lock.Lock),
|
||||
cv.Required(CONF_STATE): cv.templatable(validate_lock_state),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def lock_template_publish_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_STATE], args, LockState)
|
||||
cg.add(var.set_state(template_))
|
||||
return var
|
59
esphome/components/template/lock/template_lock.cpp
Normal file
59
esphome/components/template/lock/template_lock.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#include "template_lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
using namespace esphome::lock;
|
||||
|
||||
static const char *const TAG = "template.lock";
|
||||
|
||||
TemplateLock::TemplateLock()
|
||||
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
|
||||
|
||||
void TemplateLock::loop() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
this->publish_state(*val);
|
||||
}
|
||||
void TemplateLock::control(const lock::LockCall &call) {
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
}
|
||||
|
||||
auto state = *call.get_state();
|
||||
if (state == LOCK_STATE_LOCKED) {
|
||||
this->prev_trigger_ = this->lock_trigger_;
|
||||
this->lock_trigger_->trigger();
|
||||
} else if (state == LOCK_STATE_UNLOCKED) {
|
||||
this->prev_trigger_ = this->unlock_trigger_;
|
||||
this->unlock_trigger_->trigger();
|
||||
}
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(state);
|
||||
}
|
||||
void TemplateLock::open_latch() {
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
}
|
||||
this->prev_trigger_ = this->open_trigger_;
|
||||
this->open_trigger_->trigger();
|
||||
}
|
||||
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateLock::set_state_lambda(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
|
||||
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
|
||||
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
|
||||
Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; }
|
||||
void TemplateLock::dump_config() {
|
||||
LOG_LOCK("", "Template Lock", this);
|
||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||
}
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
38
esphome/components/template/lock/template_lock.h
Normal file
38
esphome/components/template/lock/template_lock.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateLock : public lock::Lock, public Component {
|
||||
public:
|
||||
TemplateLock();
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_state_lambda(std::function<optional<lock::LockState>()> &&f);
|
||||
Trigger<> *get_lock_trigger() const;
|
||||
Trigger<> *get_unlock_trigger() const;
|
||||
Trigger<> *get_open_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
void loop() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void control(const lock::LockCall &call) override;
|
||||
void open_latch() override;
|
||||
|
||||
optional<std::function<optional<lock::LockState>()>> f_;
|
||||
bool optimistic_{false};
|
||||
Trigger<> *lock_trigger_;
|
||||
Trigger<> *unlock_trigger_;
|
||||
Trigger<> *open_trigger_;
|
||||
Trigger<> *prev_trigger_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
|
@ -152,6 +152,13 @@ void WebServer::setup() {
|
|||
client->send(this->select_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
for (auto *obj : App.get_locks()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->lock_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
|
@ -287,6 +294,20 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
for (auto *obj : App.get_locks()) {
|
||||
if (this->include_internal_ || !obj->is_internal()) {
|
||||
write_row(stream, obj, "lock", "", [](AsyncResponseStream &stream, EntityBase *obj) {
|
||||
lock::Lock *lock = (lock::Lock *) obj;
|
||||
stream.print("<button>Lock</button><button>Unlock</button>");
|
||||
if (lock->traits.get_supports_open()) {
|
||||
stream.print("<button>Open</button>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
||||
"REST API documentation.</p>"));
|
||||
if (this->allow_ota_) {
|
||||
|
@ -763,6 +784,43 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
this->events_.send(this->lock_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "lock-" + obj->get_object_id();
|
||||
root["state"] = lock::lock_state_to_string(value);
|
||||
root["value"] = value;
|
||||
});
|
||||
}
|
||||
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (lock::Lock *obj : App.get_locks()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->lock_json(obj, obj->state);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "lock") {
|
||||
this->defer([obj]() { obj->lock(); });
|
||||
request->send(200);
|
||||
} else if (match.method == "unlock") {
|
||||
this->defer([obj]() { obj->unlock(); });
|
||||
request->send(200);
|
||||
} else if (match.method == "open") {
|
||||
this->defer([obj]() { obj->open(); });
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
}
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
if (request->url() == "/")
|
||||
return true;
|
||||
|
@ -830,6 +888,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
|||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
|
@ -922,6 +985,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
|||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if (match.domain == "lock") {
|
||||
this->handle_lock_request(request, match);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebServer::isRequestHandlerTrivial() { return false; }
|
||||
|
|
|
@ -185,6 +185,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
|||
std::string select_json(select::Select *obj, const std::string &value);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
|
||||
/// Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
|
||||
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the lock state with its value as a JSON string.
|
||||
std::string lock_json(lock::Lock *obj, lock::LockState value);
|
||||
#endif
|
||||
|
||||
/// Override the web handler's canHandle method.
|
||||
bool canHandle(AsyncWebServerRequest *request) override;
|
||||
/// Override the web handler's handleRequest method.
|
||||
|
|
|
@ -334,6 +334,7 @@ CONF_LINE_THICKNESS = "line_thickness"
|
|||
CONF_LINE_TYPE = "line_type"
|
||||
CONF_LOADED_INTEGRATIONS = "loaded_integrations"
|
||||
CONF_LOCAL = "local"
|
||||
CONF_LOCK_ACTION = "lock_action"
|
||||
CONF_LOG_TOPIC = "log_topic"
|
||||
CONF_LOGGER = "logger"
|
||||
CONF_LOGS = "logs"
|
||||
|
@ -426,9 +427,11 @@ CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan"
|
|||
CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched"
|
||||
CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched"
|
||||
CONF_ON_JSON_MESSAGE = "on_json_message"
|
||||
CONF_ON_LOCK = "on_lock"
|
||||
CONF_ON_LOOP = "on_loop"
|
||||
CONF_ON_MESSAGE = "on_message"
|
||||
CONF_ON_MULTI_CLICK = "on_multi_click"
|
||||
CONF_ON_OPEN = "on_open"
|
||||
CONF_ON_PRESS = "on_press"
|
||||
CONF_ON_RAW_VALUE = "on_raw_value"
|
||||
CONF_ON_RELEASE = "on_release"
|
||||
|
@ -442,6 +445,7 @@ CONF_ON_TIME_SYNC = "on_time_sync"
|
|||
CONF_ON_TOUCH = "on_touch"
|
||||
CONF_ON_TURN_OFF = "on_turn_off"
|
||||
CONF_ON_TURN_ON = "on_turn_on"
|
||||
CONF_ON_UNLOCK = "on_unlock"
|
||||
CONF_ON_VALUE = "on_value"
|
||||
CONF_ON_VALUE_RANGE = "on_value_range"
|
||||
CONF_ONE = "one"
|
||||
|
@ -709,6 +713,7 @@ CONF_UART_ID = "uart_id"
|
|||
CONF_UID = "uid"
|
||||
CONF_UNIQUE = "unique"
|
||||
CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement"
|
||||
CONF_UNLOCK_ACTION = "unlock_action"
|
||||
CONF_UPDATE_INTERVAL = "update_interval"
|
||||
CONF_UPDATE_ON_BOOT = "update_on_boot"
|
||||
CONF_URL = "url"
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
|
@ -104,6 +107,10 @@ class Application {
|
|||
void register_select(select::Select *select) { this->selects_.push_back(select); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); }
|
||||
#endif
|
||||
|
||||
/// Register the component in this Application instance.
|
||||
template<class C> C *register_component(C *c) {
|
||||
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
|
||||
|
@ -257,6 +264,15 @@ class Application {
|
|||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
const std::vector<lock::Lock *> &get_locks() { return this->locks_; }
|
||||
lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->locks_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
|
@ -305,6 +321,9 @@ class Application {
|
|||
#ifdef USE_SELECT
|
||||
std::vector<select::Select *> selects_{};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
std::vector<lock::Lock *> locks_{};
|
||||
#endif
|
||||
|
||||
std::string name_;
|
||||
std::string compilation_time_;
|
||||
|
|
|
@ -65,6 +65,12 @@ void Controller::setup_controller(bool include_internal) {
|
|||
obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
for (auto *obj : App.get_locks()) {
|
||||
if (include_internal || !obj->is_internal())
|
||||
obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
|
@ -70,6 +73,9 @@ class Controller {
|
|||
#ifdef USE_SELECT
|
||||
virtual void on_select_update(select::Select *obj, const std::string &state){};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_update(lock::Lock *obj){};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#define USE_GRAPH
|
||||
#define USE_HOMEASSISTANT_TIME
|
||||
#define USE_LIGHT
|
||||
#define USE_LOCK
|
||||
#define USE_LOGGER
|
||||
#define USE_MDNS
|
||||
#define USE_NUMBER
|
||||
|
|
|
@ -598,6 +598,7 @@ def lint_inclusive_language(fname, match):
|
|||
"esphome/components/display/display_buffer.h",
|
||||
"esphome/components/fan/fan.h",
|
||||
"esphome/components/i2c/i2c.h",
|
||||
"esphome/components/lock/lock.h",
|
||||
"esphome/components/mqtt/mqtt_component.h",
|
||||
"esphome/components/number/number.h",
|
||||
"esphome/components/output/binary_output.h",
|
||||
|
|
|
@ -2582,3 +2582,28 @@ select:
|
|||
qr_code:
|
||||
- id: homepage_qr
|
||||
value: https://esphome.io/index.html
|
||||
|
||||
lock:
|
||||
- platform: template
|
||||
id: test_lock1
|
||||
name: "Template Switch"
|
||||
lambda: |-
|
||||
if (id(binary_sensor1).state) {
|
||||
return LOCK_STATE_LOCKED;
|
||||
}else{
|
||||
return LOCK_STATE_UNLOCKED;
|
||||
}
|
||||
optimistic: true
|
||||
assumed_state: no
|
||||
on_unlock:
|
||||
- lock.template.publish:
|
||||
id: test_lock1
|
||||
state: !lambda "return LOCK_STATE_UNLOCKED;"
|
||||
on_lock:
|
||||
- lock.template.publish:
|
||||
id: test_lock1
|
||||
state: !lambda "return LOCK_STATE_LOCKED;"
|
||||
- platform: output
|
||||
name: "Generic Output Lock"
|
||||
id: test_lock2
|
||||
output: pca_6
|
||||
|
|
Loading…
Reference in a new issue