Skip to content

Commit

Permalink
Merge pull request #61 from uriyacovy/feature/security-pin-entity
Browse files Browse the repository at this point in the history
Security Pin Entity and some other small improvements
  • Loading branch information
uriyacovy authored Nov 1, 2024
2 parents 257e9cc + 843c3f2 commit 55b9043
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .github/test-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ lock:

led_brightness:
name: "Nuki LED Brightness"
security_pin:
name: "Nuki Security Pin"

single_buton_press_action:
name: "Nuki Single Button Press Action"
Expand All @@ -83,7 +85,6 @@ lock:
fob_action_3:
name: "Nuki Fob Action 3"

security_pin: 1234
pairing_mode_timeout: 300s
event: "nuki"

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,8 @@ MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# ESPHome
__pycache__
.esphome
/*.yaml
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ lock:
unpair:
name: "Nuki Unpair Device"

security_pin:
name: "Nuki Security Pin"

# Optional: Settings
security_pin: 1234
pairing_mode_timeout: 300s
event: "nuki"

Expand Down Expand Up @@ -263,15 +265,16 @@ context:
- Auto Lock: Immediately
- Automatic Updates

**Select:**
**Select Input:**
- Single Button Press Action
- Double Button Press Action
- Fob Action 1
- Fob Action 2
- Fob Action 3

**Number:**
**Number Input:**
- LED Brightness
- Security Pin

**Button:**
- Unpair Device
Expand Down
28 changes: 22 additions & 6 deletions components/nuki_lock/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import esphome.config_validation as cv
from esphome import automation
from esphome.components import lock, binary_sensor, text_sensor, sensor, switch, button, number, select
from esphome.components.number import NUMBER_MODES
from esphome.const import (
CONF_ID,
CONF_BATTERY_LEVEL,
Expand All @@ -12,7 +13,8 @@
UNIT_PERCENT,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
CONF_TRIGGER_ID
CONF_TRIGGER_ID,
CONF_MODE,
)

AUTO_LOAD = ["binary_sensor", "text_sensor", "sensor", "switch", "button", "number", "select"]
Expand Down Expand Up @@ -41,12 +43,15 @@
CONF_AUTO_UNLOCK_DISABLED_SWITCH = "auto_unlock_disabled"
CONF_IMMEDIATE_AUTO_LOCK_ENABLED_SWITCH = "immediate_auto_lock_enabled"
CONF_AUTO_UPDATE_ENABLED_SWITCH = "auto_update_enabled"

CONF_SINGLE_BUTTON_PRESS_ACTION_SELECT = "single_buton_press_action"
CONF_DOUBLE_BUTTON_PRESS_ACTION_SELECT = "double_buton_press_action"
CONF_FOB_ACTION_1_SELECT = "fob_action_1"
CONF_FOB_ACTION_2_SELECT = "fob_action_2"
CONF_FOB_ACTION_3_SELECT = "fob_action_3"

CONF_LED_BRIGHTNESS_NUMBER = "led_brightness"
CONF_SECURITY_PIN_NUMBER = "security_pin"

CONF_BUTTON_PRESS_ACTION_SELECT_OPTIONS = [
"No Action",
Expand All @@ -67,7 +72,6 @@
]

CONF_PAIRING_MODE_TIMEOUT = "pairing_mode_timeout"
CONF_SECURITY_PIN = "security_pin"
CONF_EVENT = "event"

CONF_SET_PAIRING_MODE = "pairing_mode"
Expand All @@ -93,6 +97,7 @@
NukiLockImmediateAutoLockEnabledSwitch = nuki_lock_ns.class_("NukiLockImmediateAutoLockEnabledSwitch", switch.Switch, cg.Component)
NukiLockAutoUpdateEnabledSwitch = nuki_lock_ns.class_("NukiLockAutoUpdateEnabledSwitch", switch.Switch, cg.Component)
NukiLockLedBrightnessNumber = nuki_lock_ns.class_("NukiLockLedBrightnessNumber", number.Number, cg.Component)
NukiLockSecurityPinNumber = nuki_lock_ns.class_("NukiLockSecurityPinNumber", number.Number, cg.Component)
NukiLockSingleButtonPressActionSelect = nuki_lock_ns.class_("NukiLockSingleButtonPressActionSelect", select.Select, cg.Component)
NukiLockDoubleButtonPressActionSelect = nuki_lock_ns.class_("NukiLockDoubleButtonPressActionSelect", select.Select, cg.Component)
NukiLockFobAction1Select = nuki_lock_ns.class_("NukiLockFobAction1Select", select.Select, cg.Component)
Expand Down Expand Up @@ -125,6 +130,7 @@
),
cv.Optional(CONF_BATTERY_CRITICAL): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon="mdi:battery-alert-variant-outline",
),
cv.Optional(CONF_DOOR_SENSOR): binary_sensor.binary_sensor_schema(
Expand All @@ -143,6 +149,7 @@

cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
unit_of_measurement=UNIT_PERCENT,
icon="mdi:battery-50",
),
Expand Down Expand Up @@ -232,6 +239,12 @@
icon="mdi:brightness-6",
),

cv.Optional(CONF_SECURITY_PIN_NUMBER): number.number_schema(
NukiLockSecurityPinNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:shield-key",
).extend({ cv.Optional(CONF_MODE, default="BOX"): cv.enum(NUMBER_MODES, upper=True), }),

cv.Optional(CONF_SINGLE_BUTTON_PRESS_ACTION_SELECT): select.select_schema(
NukiLockSingleButtonPressActionSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
Expand All @@ -258,7 +271,6 @@
icon="mdi:gesture-tap",
),

cv.Optional(CONF_SECURITY_PIN, default=0): cv.uint16_t,
cv.Optional(CONF_PAIRING_MODE_TIMEOUT, default="300s"): cv.positive_time_period_seconds,
cv.Optional(CONF_EVENT, default="nuki"): cv.string,

Expand Down Expand Up @@ -286,9 +298,6 @@ async def to_code(config):
await lock.register_lock(var, config)

# Component Settings
if CONF_SECURITY_PIN in config:
cg.add(var.set_security_pin(config[CONF_SECURITY_PIN]))

if CONF_PAIRING_MODE_TIMEOUT in config:
cg.add(var.set_pairing_mode_timeout(config[CONF_PAIRING_MODE_TIMEOUT]))

Expand Down Expand Up @@ -340,6 +349,13 @@ async def to_code(config):
await cg.register_parented(n, config[CONF_ID])
cg.add(var.set_led_brightness_number(n))

if security_pin := config.get(CONF_SECURITY_PIN_NUMBER):
n = await number.new_number(
security_pin, min_value=0, max_value=65535, step=1
)
await cg.register_parented(n, config[CONF_ID])
cg.add(var.set_security_pin_number(n))

# Switch
if pairing_mode := config.get(CONF_PAIRING_MODE_SWITCH):
s = await switch.new_switch(pairing_mode)
Expand Down
82 changes: 67 additions & 15 deletions components/nuki_lock/nuki_lock.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/core/preferences.h"

#ifdef USE_API
#include "esphome/components/api/custom_api_device.h"
#endif

#include "nuki_lock.h"

namespace esphome {
namespace nuki_lock {

uint32_t global_nuki_lock_id = 1912044085ULL;

lock::LockState NukiLockComponent::nuki_to_lock_state(NukiLock::LockState nukiLockState) {
switch(nukiLockState) {
case NukiLock::LockState::Locked:
Expand Down Expand Up @@ -100,6 +107,18 @@ std::string NukiLockComponent::fob_action_to_string(uint8_t action)
return "No Action";
}

void NukiLockComponent::save_settings()
{
NukiLockSettings settings {
this->security_pin_
};

if (!this->pref_.save(&settings))
{
ESP_LOGW(TAG, "Failed to save settings");
}
}

void NukiLockComponent::update_status()
{
this->status_update_ = false;
Expand Down Expand Up @@ -367,6 +386,12 @@ void NukiLockComponent::process_log_entries(const std::list<NukiLock::LogEntry>&
auth_name[sizeName] = '\0';
}

if (std::string(auth_name) == "")
{
memset(auth_name, 0, sizeof(auth_name));
memcpy(auth_name, "Manual", strlen("Manual"));
}

if(log.index > auth_index)
{
auth_index = log.index;
Expand All @@ -388,7 +413,7 @@ void NukiLockComponent::process_log_entries(const std::list<NukiLock::LogEntry>&
std::map<std::string, std::string> event_data;
event_data["index"] = std::to_string(log.index);
event_data["authorizationId"] = std::to_string(log.authId);
event_data["authorizationName"] = auth_name;
event_data["authorizationName"] = this->auth_name_;

if(this->auth_entries_.count(log.authId) > 0)
{
Expand Down Expand Up @@ -482,6 +507,7 @@ void NukiLockComponent::process_log_entries(const std::list<NukiLock::LogEntry>&
}

// Send as Home Assistant Event
#ifdef USE_API
if(log.index > this->last_rolling_log_id)
{
this->last_rolling_log_id = log.index;
Expand All @@ -490,6 +516,7 @@ void NukiLockComponent::process_log_entries(const std::list<NukiLock::LogEntry>&
ESP_LOGD(TAG, "Send event to Home Assistant on %s", event_);
capi->fire_homeassistant_event(event_, event_data);
}
#endif
}
}

Expand Down Expand Up @@ -532,13 +559,40 @@ bool NukiLockComponent::execute_lock_action(NukiLock::LockAction lock_action) {
}
}

void NukiLockComponent::set_security_pin(uint16_t security_pin)
{
this->security_pin_ = security_pin;

bool result = this->nuki_lock_.saveSecurityPincode(this->security_pin_);
if (result) {
ESP_LOGI(TAG, "Set pincode done");
} else {
ESP_LOGE(TAG, "Set pincode failed!");
}

#ifdef USE_NUMBER
if (this->security_pin_number_ != nullptr)
this->security_pin_number_->publish_state(this->security_pin_);
#endif
}

void NukiLockComponent::setup() {
ESP_LOGI(TAG, "Starting NUKI Lock...");

// Increase Watchdog Timeout
// Fixes Pairing Crash
esp_task_wdt_init(15, false);

// Restore settings from flash
this->pref_ = global_preferences->make_preference<NukiLockSettings>(global_nuki_lock_id);

NukiLockSettings recovered;
if (!this->pref_.load(&recovered))
{
recovered = {0};
}
this->set_security_pin(recovered.security_pin);

this->traits.set_supported_states(
std::set<lock::LockState> {
lock::LOCK_STATE_NONE,
Expand All @@ -557,15 +611,6 @@ void NukiLockComponent::setup() {
this->nuki_lock_.initialize();
this->nuki_lock_.setConnectTimeout(BLE_CONNECT_TIMEOUT_SEC);
this->nuki_lock_.setConnectRetries(BLE_CONNECT_TIMEOUT_RETRIES);

if(this->security_pin_ > 0) {
bool result = this->nuki_lock_.saveSecurityPincode(this->security_pin_);
if (result) {
ESP_LOGI(TAG, "Set pincode done");
} else {
ESP_LOGE(TAG, "Set pincode failed!");
}
}

if (this->nuki_lock_.isPairedWithLock()) {
this->status_update_ = true;
Expand All @@ -592,11 +637,13 @@ void NukiLockComponent::setup() {

this->publish_state(lock::LOCK_STATE_NONE);

register_service(&NukiLockComponent::lock_n_go, "lock_n_go");
register_service(&NukiLockComponent::print_keypad_entries, "print_keypad_entries");
register_service(&NukiLockComponent::add_keypad_entry, "add_keypad_entry", {"name", "code"});
register_service(&NukiLockComponent::update_keypad_entry, "update_keypad_entry", {"id", "name", "code", "enabled"});
register_service(&NukiLockComponent::delete_keypad_entry, "delete_keypad_entry", {"id"});
#ifdef USE_API
this->custom_api_device_.register_service(&NukiLockComponent::lock_n_go, "lock_n_go");
this->custom_api_device_.register_service(&NukiLockComponent::print_keypad_entries, "print_keypad_entries");
this->custom_api_device_.register_service(&NukiLockComponent::add_keypad_entry, "add_keypad_entry", {"name", "code"});
this->custom_api_device_.register_service(&NukiLockComponent::update_keypad_entry, "update_keypad_entry", {"id", "name", "code", "enabled"});
this->custom_api_device_.register_service(&NukiLockComponent::delete_keypad_entry, "delete_keypad_entry", {"id"});
#endif
}

void NukiLockComponent::update() {
Expand Down Expand Up @@ -1263,6 +1310,11 @@ void NukiLockAutoUpdateEnabledSwitch::write_state(bool state) {
void NukiLockLedBrightnessNumber::control(float value) {
this->parent_->set_config_number("led_brightness", value);
}
void NukiLockSecurityPinNumber::control(float value) {
this->publish_state(value);
this->parent_->set_security_pin(value);
this->parent_->save_settings();
}
#endif

// Callbacks
Expand Down
Loading

0 comments on commit 55b9043

Please sign in to comment.