From 742186f3d595b8a57732c38886bedcd49b801fd3 Mon Sep 17 00:00:00 2001 From: iranl Date: Mon, 11 Mar 2024 22:30:39 +0100 Subject: [PATCH 1/3] Granular Access Level Control --- AccessLevel.h | 9 -- CMakeLists.txt | 1 - NukiOpenerWrapper.cpp | 62 ++++----- NukiOpenerWrapper.h | 13 +- NukiWrapper.cpp | 69 ++++----- NukiWrapper.h | 12 +- PreferencesKeys.h | 36 ++++- README.md | 14 +- WebCfgServer.cpp | 300 ++++++++++++++++++++++++++++------------ WebCfgServer.h | 2 +- WebCfgServerConstants.h | 2 +- main.cpp | 83 ++++++++++- 12 files changed, 417 insertions(+), 186 deletions(-) delete mode 100644 AccessLevel.h diff --git a/AccessLevel.h b/AccessLevel.h deleted file mode 100644 index a2bdb05..0000000 --- a/AccessLevel.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -enum class AccessLevel -{ - Full = 0, - LockOnly = 1, - ReadOnly = 2, - LockAndUnlock = 3 -}; \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 03c54ca..6ae3a59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,6 @@ set(SRCFILES networkDevices/ClientSyncW5500.cpp networkDevices/espMqttClientW5500.cpp networkDevices/IPConfiguration.cpp - AccessLevel.h LockActionResult.h QueryCommand.h NukiWrapper.cpp diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index 0398fbb..fd4867f 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -7,7 +7,6 @@ #include NukiOpenerWrapper* nukiOpenerInst; -AccessLevel NukiOpenerWrapper::_accessLevel = AccessLevel::ReadOnly; NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), @@ -52,7 +51,7 @@ void NukiOpenerWrapper::initialize() _intervalConfig = _preferences->getInt(preference_query_interval_configuration); _intervalBattery = _preferences->getInt(preference_query_interval_battery); _intervalKeypad = _preferences->getInt(preference_query_interval_keypad); - _keypadEnabled = _preferences->getBool(preference_keypad_control_enabled); + _keypadEnabled = _preferences->getBool(preference_keypad_info_enabled); _publishAuthData = _preferences->getBool(preference_publish_authdata); _maxKeypadCodeCount = _preferences->getUInt(preference_opener_max_keypad_code_count); _restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost); @@ -60,7 +59,15 @@ void NukiOpenerWrapper::initialize() _nrOfRetries = _preferences->getInt(preference_command_nr_of_retries); _retryDelay = _preferences->getInt(preference_command_retry_delay); _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; - _accessLevel = (AccessLevel)_preferences->getInt(preference_access_level); + + _aclActRTO = _preferences->getBool(preference_acl_act_rto); + _aclDeactRTO = _preferences->getBool(preference_acl_deact_rto); + _aclESA = _preferences->getBool(preference_acl_act_esa); + _aclActCM = _preferences->getBool(preference_acl_act_cm); + _aclDeactCM = _preferences->getBool(preference_acl_deact_cm); + _aclFob1 = _preferences->getBool(preference_acl_opn_fob1); + _aclFob2 = _preferences->getBool(preference_acl_opn_fob2); + _aclFob3 = _preferences->getBool(preference_acl_opn_fob3); if(_retryDelay <= 100) { @@ -404,8 +411,8 @@ void NukiOpenerWrapper::updateConfig() void NukiOpenerWrapper::updateAuthData() { - if(_nukiOpener.getSecurityPincode() == 0) return; - + if(_nukiOpener.getSecurityPincode() == 0) return; + Nuki::CmdResult result = _nukiOpener.retrieveLogEntries(0, 0, 0, true); if(result != Nuki::CmdResult::Success) { @@ -434,6 +441,8 @@ void NukiOpenerWrapper::updateAuthData() void NukiOpenerWrapper::updateKeypad() { + if(_preferences->getBool(preference_keypad_info_enabled)) return; + Log->print(F("Querying opener keypad: ")); Nuki::CmdResult result = _nukiOpener.retrieveKeypadEntries(0, 0xffff); printCommandResult(result); @@ -489,34 +498,13 @@ LockActionResult NukiOpenerWrapper::onLockActionReceivedCallback(const char *val { return LockActionResult::UnknownAction; } - - switch(_accessLevel) + + if((action == NukiOpener::LockAction::ActivateRTO && _preferences->getBool(preference_acl_act_rto)) || (action == NukiOpener::LockAction::DeactivateRTO && _preferences->getBool(preference_acl_deact_rto)) || (action == NukiOpener::LockAction::ElectricStrikeActuation && _preferences->getBool(preference_acl_act_esa)) || (action == NukiOpener::LockAction::ActivateCM && _preferences->getBool(preference_acl_act_cm)) || (action == NukiOpener::LockAction::DeactivateCM && _preferences->getBool(preference_acl_deact_cm)) || (action == NukiOpener::LockAction::FobAction1 && _preferences->getBool(preference_acl_opn_fob1)) || (action == NukiOpener::LockAction::FobAction2 && _preferences->getBool(preference_acl_opn_fob2)) || (action == NukiOpener::LockAction::FobAction3 && _preferences->getBool(preference_acl_opn_fob3))) { - case AccessLevel::Full: - nukiOpenerInst->_nextLockAction = action; - return LockActionResult::Success; - break; - case AccessLevel::LockAndUnlock: - if(action == NukiOpener::LockAction::ActivateRTO || action == NukiOpener::LockAction::ActivateCM || action == NukiOpener::LockAction::DeactivateRTO || action == NukiOpener::LockAction::DeactivateCM) - { - nukiOpenerInst->_nextLockAction = action; - return LockActionResult::Success; - } - return LockActionResult::AccessDenied; - break; - case AccessLevel::LockOnly: - if(action == NukiOpener::LockAction::DeactivateRTO || action == NukiOpener::LockAction::DeactivateCM) - { - nukiOpenerInst->_nextLockAction = action; - return LockActionResult::Success; - } - return LockActionResult::AccessDenied; - break; - case AccessLevel::ReadOnly: - default: - return LockActionResult::AccessDenied; - break; + return LockActionResult::Success; } + + return LockActionResult::AccessDenied; } void NukiOpenerWrapper::onConfigUpdateReceivedCallback(const char *topic, const char *value) @@ -556,7 +544,7 @@ void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action, const int& void NukiOpenerWrapper::onConfigUpdateReceived(const char *topic, const char *value) { - if(_accessLevel != AccessLevel::Full) return; + if(!_preferences->getBool(preference_admin_config_enabled)) return; if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) { @@ -583,7 +571,11 @@ void NukiOpenerWrapper::onConfigUpdateReceived(const char *topic, const char *va void NukiOpenerWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code, const int& enabled) { - if(_accessLevel != AccessLevel::Full) return; + if(!_preferences->getBool(preference_keypad_control_enabled)) + { + _network->publishKeypadCommandResult("KeypadControlDisabled"); + return; + } if(!_hasKeypad) { @@ -759,11 +751,11 @@ void NukiOpenerWrapper::setupHASS() if (_preferences->getBool(preference_opener_continuous_mode)) { - _network->publishHASSConfig("Opener",baseTopic.c_str(),(char*)_nukiConfig.name,uidString, "deactivateCM","activateCM","electricStrikeActuation"); + _network->publishHASSConfig("Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, "deactivateCM", "activateCM", "electricStrikeActuation"); } else { - _network->publishHASSConfig("Opener",baseTopic.c_str(),(char*)_nukiConfig.name,uidString, "deactivateRTO","activateRTO","electricStrikeActuation"); + _network->publishHASSConfig("Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, "deactivateRTO", "activateRTO", "electricStrikeActuation"); } _hassSetupCompleted = true; diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index 619ce04..0c92e45 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -6,7 +6,6 @@ #include "NukiDataTypes.h" #include "BleScanner.h" #include "Gpio.h" -#include "AccessLevel.h" #include "NukiDeviceId.h" class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler @@ -70,7 +69,7 @@ private: void printCommandResult(Nuki::CmdResult result); - NukiOpener::LockAction lockActionToEnum(const char* str); // char array at least 14 characters + NukiOpener::LockAction lockActionToEnum(const char* str); // char array at least 14 charactersz std::string _deviceName; NukiDeviceId* _deviceId = nullptr; @@ -90,7 +89,6 @@ private: int _retryDelay = 0; int _retryCount = 0; int _retryLockstateCount = 0; - static AccessLevel _accessLevel; unsigned long _nextRetryTs = 0; std::vector _keypadCodeIds; @@ -106,7 +104,14 @@ private: bool _nukiAdvancedConfigValid = false; bool _hassEnabled = false; bool _hassSetupCompleted = false; - + bool _aclActRTO = false; + bool _aclDeactRTO = false; + bool _aclESA = false; + bool _aclActCM = false; + bool _aclDeactCM = false; + bool _aclFob1 = false; + bool _aclFob2 = false; + bool _aclFob3 = false; bool _paired = false; bool _statusUpdated = false; bool _hasKeypad = false; diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 6e2d0ce..8481e08 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -7,7 +7,6 @@ #include NukiWrapper* nukiInst; -AccessLevel NukiWrapper::_accessLevel = AccessLevel::ReadOnly; NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), @@ -53,7 +52,7 @@ void NukiWrapper::initialize(const bool& firstStart) _intervalConfig = _preferences->getInt(preference_query_interval_configuration); _intervalBattery = _preferences->getInt(preference_query_interval_battery); _intervalKeypad = _preferences->getInt(preference_query_interval_keypad); - _keypadEnabled = _preferences->getBool(preference_keypad_control_enabled); + _keypadEnabled = _preferences->getBool(preference_keypad_info_enabled); _publishAuthData = _preferences->getBool(preference_publish_authdata); _maxKeypadCodeCount = _preferences->getUInt(preference_lock_max_keypad_code_count); _restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost); @@ -61,15 +60,32 @@ void NukiWrapper::initialize(const bool& firstStart) _nrOfRetries = _preferences->getInt(preference_command_nr_of_retries); _retryDelay = _preferences->getInt(preference_command_retry_delay); _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; - _accessLevel = (AccessLevel)_preferences->getInt(preference_access_level); if(firstStart) { _preferences->putInt(preference_command_nr_of_retries, 3); _preferences->putInt(preference_command_retry_delay, 1000); _preferences->putInt(preference_restart_ble_beacon_lost, 60); + _preferences->putBool(preference_admin_config_enabled, true); + _preferences->putBool(preference_acl_lock, true); + _preferences->putBool(preference_acl_unlock, true); + _preferences->putBool(preference_acl_unlatch, true); + _preferences->putBool(preference_acl_lockngo, true); + _preferences->putBool(preference_acl_lockngo_unlatch, true); + _preferences->putBool(preference_acl_fulllock, true); + _preferences->putBool(preference_acl_lck_fob1, true); + _preferences->putBool(preference_acl_lck_fob2, true); + _preferences->putBool(preference_acl_lck_fob3, true); + _preferences->putBool(preference_acl_act_rto, true); + _preferences->putBool(preference_acl_deact_rto, true); + _preferences->putBool(preference_acl_act_esa, true); + _preferences->putBool(preference_acl_act_cm, true); + _preferences->putBool(preference_acl_deact_cm, true); + _preferences->putBool(preference_acl_opn_fob1, true); + _preferences->putBool(preference_acl_opn_fob2, true); + _preferences->putBool(preference_acl_opn_fob3, true); } - + if(_retryDelay <= 100) { _retryDelay = 100; @@ -324,7 +340,7 @@ void NukiWrapper::updateKeyTurnerState() } return; } - + _retryLockstateCount = 0; if(_publishAuthData) @@ -375,7 +391,7 @@ void NukiWrapper::updateConfig() void NukiWrapper::updateAuthData() { if(_nukiLock.getSecurityPincode() == 0) return; - + Nuki::CmdResult result = _nukiLock.retrieveLogEntries(0, 0, 0, true); if(result != Nuki::CmdResult::Success) { @@ -404,6 +420,8 @@ void NukiWrapper::updateAuthData() void NukiWrapper::updateKeypad() { + if(_preferences->getBool(preference_keypad_info_enabled)) return; + Log->print(F("Querying lock keypad: ")); Nuki::CmdResult result = _nukiLock.retrieveKeypadEntries(0, 0xffff); printCommandResult(result); @@ -462,33 +480,12 @@ LockActionResult NukiWrapper::onLockActionReceivedCallback(const char *value) return LockActionResult::UnknownAction; } - switch(_accessLevel) + if((action == NukiLock::LockAction::Lock && _aclLock) || (action == NukiLock::LockAction::Unlock && _aclUnlock) || (action == NukiLock::LockAction::Unlatch && _aclUnlatch) || (action == NukiLock::LockAction::LockNgo && _aclLockNGo) || (action == NukiLock::LockAction::LockNgoUnlatch && _aclLockNGoU) || (action == NukiLock::LockAction::FullLock && _aclFLock) || (action == NukiLock::LockAction::FobAction1 && _aclFob1) || (action == NukiLock::LockAction::FobAction2 && _aclFob2) || (action == NukiLock::LockAction::FobAction3 && _aclFob3)) { - case AccessLevel::Full: - nukiInst->_nextLockAction = action; - return LockActionResult::Success; - break; - case AccessLevel::LockAndUnlock: - if(action == NukiLock::LockAction::Lock || action == NukiLock::LockAction::Unlock || action == NukiLock::LockAction::LockNgo || action == NukiLock::LockAction::FullLock) - { - nukiInst->_nextLockAction = action; - return LockActionResult::Success; - } - return LockActionResult::AccessDenied; - break; - case AccessLevel::LockOnly: - if(action == NukiLock::LockAction::Lock) - { - nukiInst->_nextLockAction = action; - return LockActionResult::Success; - } - return LockActionResult::AccessDenied; - break; - case AccessLevel::ReadOnly: - default: - return LockActionResult::AccessDenied; - break; + return LockActionResult::Success; } + + return LockActionResult::AccessDenied; } void NukiWrapper::onConfigUpdateReceivedCallback(const char *topic, const char *value) @@ -525,7 +522,7 @@ void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin) void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) { - if(_accessLevel != AccessLevel::Full) return; + if(!_preferences->getBool(preference_admin_config_enabled)) return; if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) { @@ -580,7 +577,11 @@ void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) void NukiWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code, const int& enabled) { - if(_accessLevel != AccessLevel::Full) return; + if(!_preferences->getBool(preference_keypad_control_enabled)) + { + _network->publishKeypadCommandResult("KeypadControlDisabled"); + return; + } if(!_hasKeypad) { @@ -742,7 +743,7 @@ void NukiWrapper::setupHASS() char uidString[20]; itoa(_nukiConfig.nukiId, uidString, 16); - _network->publishHASSConfig("SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, hasDoorSensor(), _hasKeypad, _publishAuthData,"lock", "unlock", "unlatch"); + _network->publishHASSConfig("SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, hasDoorSensor(), _hasKeypad, _publishAuthData, "lock", "unlock", "unlatch"); _hassSetupCompleted = true; Log->println("HASS setup for lock completed."); diff --git a/NukiWrapper.h b/NukiWrapper.h index c20ad2f..02d722d 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -6,7 +6,6 @@ #include "BleScanner.h" #include "NukiLock.h" #include "Gpio.h" -#include "AccessLevel.h" #include "LockActionResult.h" #include "NukiDeviceId.h" @@ -99,7 +98,15 @@ private: bool _nukiAdvancedConfigValid = false; bool _hassEnabled = false; bool _hassSetupCompleted = false; - + bool _aclLock = false; + bool _aclUnlock = false; + bool _aclUnlatch = false; + bool _aclLockNGo = false; + bool _aclLockNGoU = false; + bool _aclFLock = false; + bool _aclFob1 = false; + bool _aclFob2 = false; + bool _aclFob3 = false; bool _paired = false; bool _statusUpdated = false; bool _hasKeypad = false; @@ -111,7 +118,6 @@ private: int _retryCount = 0; int _retryLockstateCount = 0; long _rssiPublishInterval = 0; - static AccessLevel _accessLevel; unsigned long _nextRetryTs = 0; unsigned long _nextLockStateUpdateTs = 0; unsigned long _nextBatteryReportTs = 0; diff --git a/PreferencesKeys.h b/PreferencesKeys.h index 891bf13..a0ceea9 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -42,14 +42,33 @@ #define preference_query_interval_configuration "configInterval" #define preference_query_interval_battery "batInterval" #define preference_query_interval_keypad "kpInterval" -#define preference_keypad_control_enabled "kpEnabled" #define preference_access_level "accLvl" +#define preference_admin_config_enabled "aclConfigEnabled" +#define preference_keypad_info_enabled "kpInfoEnabled" +#define preference_keypad_control_enabled "kpCntrlEnabled" +#define preference_publish_authdata "pubAuth" +#define preference_acl_lock "aclLckLck" +#define preference_acl_unlock "aclLckUnlck" +#define preference_acl_unlatch "aclLckUnltch" +#define preference_acl_lockngo "aclLckLnG" +#define preference_acl_lockngo_unlatch "aclLckLnGwU" +#define preference_acl_fulllock "aclLckFlck" +#define preference_acl_lck_fob1 "aclLckFob1" +#define preference_acl_lck_fob2 "aclLckFob2" +#define preference_acl_lck_fob3 "aclLckFob3" +#define preference_acl_act_rto "aclOpnActRTO" +#define preference_acl_deact_rto "aclOpnDeactRTO" +#define preference_acl_act_esa "aclOpnActESA" +#define preference_acl_act_cm "aclOpnActCM" +#define preference_acl_deact_cm "aclOpnDeactCM" +#define preference_acl_opn_fob1 "aclOpnFob1" +#define preference_acl_opn_fob2 "aclOpnFob2" +#define preference_acl_opn_fob3 "aclOpnFob3" #define preference_register_as_app "regAsApp" // true = register as hub; false = register as app #define preference_command_nr_of_retries "nrRetry" #define preference_command_retry_delay "rtryDelay" #define preference_cred_user "crdusr" #define preference_cred_password "crdpass" -#define preference_publish_authdata "pubauth" #define preference_gpio_locking_enabled "gpiolck" // obsolete #define preference_gpio_configuration "gpiocfg" #define preference_publish_debug_info "pubdbg" @@ -75,8 +94,11 @@ private: preference_hostname, preference_network_timeout, preference_restart_on_disconnect, preference_restart_ble_beacon_lost, preference_query_interval_lockstate, preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, - preference_keypad_control_enabled, preference_access_level, - preference_register_as_app, preference_command_nr_of_retries, + preference_keypad_control_enabled, preference_admin_config_enabled, preference_keypad_info_enabled, preference_acl_lock, + preference_acl_unlock, preference_acl_unlatch, preference_acl_lockngo, preference_acl_lockngo_unlatch, preference_acl_fulllock, + preference_acl_lck_fob1, preference_acl_lck_fob2, preference_acl_lck_fob3, preference_acl_act_rto, preference_acl_deact_rto, + preference_acl_act_esa, preference_acl_act_cm, preference_acl_deact_cm, preference_acl_opn_fob1, preference_acl_opn_fob2, preference_acl_opn_fob3, + preference_access_level, preference_register_as_app, preference_command_nr_of_retries, preference_command_retry_delay, preference_cred_user, preference_cred_password, preference_publish_authdata, preference_publish_debug_info, preference_presence_detection_timeout, preference_has_mac_saved, preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2, preference_latest_version, @@ -90,7 +112,11 @@ private: std::vector _boolPrefs = { preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_opener_enabled, preference_opener_continuous_mode, - preference_restart_on_disconnect, preference_keypad_control_enabled, preference_register_as_app, preference_ip_dhcp_enabled, + preference_restart_on_disconnect, preference_keypad_control_enabled, preference_admin_config_enabled, preference_keypad_info_enabled, preference_acl_lock, + preference_acl_unlock, preference_acl_unlatch, preference_acl_lockngo, preference_acl_lockngo_unlatch, preference_acl_fulllock, + preference_acl_lck_fob1, preference_acl_lck_fob2, preference_acl_lck_fob3, preference_acl_act_rto, preference_acl_deact_rto, + preference_acl_act_esa, preference_acl_act_cm, preference_acl_deact_cm, preference_acl_opn_fob1, preference_acl_opn_fob2, preference_acl_opn_fob3, + preference_register_as_app, preference_ip_dhcp_enabled, preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info, preference_network_wifi_fallback_disabled }; diff --git a/README.md b/README.md index d04b469..d70f968 100644 --- a/README.md +++ b/README.md @@ -136,16 +136,24 @@ In a browser navigate to the IP address assigned to the ESP32. - Query interval lock state: Set to a positive integer to set the maximum amount of seconds between actively querying the Nuki device for the current lock state, default 1800. - Query interval configuration: Set to a positive integer to set the maximum amount of seconds between actively querying the Nuki device for the current configuration, default 3600. - Query interval battery: Set to a positive integer to set the maximum amount of seconds between actively querying the Nuki device for the current battery state, default 1800. -- Access level: Allows restricting the allowed lock actions through MQTT. Set to "Full" to enable all lock actions including unlatch/ESA, set to "Lock and unlock operation only" to disable unlatch/ESA, set to "Lock operation only" to disable unlatch/ESA and unlock and set to "Read only" to disable changing lock state completely. Note: GPIO control is not restricted through this setting. - Query interval keypad (Only available when a Keypad is detected): Set to a positive integer to set the maximum amount of seconds between actively querying the Nuki device for the current keypad state, default 1800. -- Enable keypad control via MQTT (Only available when a Keypad is detected): Enable to allow configuration of keypad codes through MQTT, see the "Keypad control" section of this README - Number of retries if command failed: Set to a positive integer to define the amount of times the Nuki Hub retries sending commands to the Nuki Lock or Opener when commands are not acknowledged by the device, default 3. - Delay between retries: Set to the amount of milliseconds the Nuki Hub waits between resending not acknowledged commands, default 100. -- Publish auth data: Enable to publish authorization date to the MQTT topic lock/log. Requires the Nuki security code / PIN to be set, see "Nuki Lock PIN / Nuki Opener PIN" below. - Nuki Bridge is running alongside Nuki Hub: Enable to allow Nuki Hub to co-exist with a Nuki Bridge by registering Nuki Hub as an (smartphone) app instead of a bridge. Changing this setting will require re-pairing. Enabling this setting is strongly discouraged as described in the "Pairing with a Nuki Lock or Opener" section of this README - Presence detection timeout: Set to a positive integer to set the amount of seconds between updates to the presence/devices MQTT topic with the list of detected bluetooth devices, set to -1 to disable presence detection, default 60. - Restart if bluetooth beacons not received: Set to a positive integer to restart the Nuki Hub after the set amount of seconds has passed without receiving a bluetooth beacon from the Nuki device, set to -1 to disable, default 60. Because the bluetooth stack of the ESP32 can silently fail it is not recommended to disable this setting. +### Access Level Configuration + +#### Nuki General Access Control +- Change Lock/Opener configuration: Allows changing the Nuki Lock/Opener configuration through MQTT. +- Publish keypad codes information (Only available when a Keypad is detected): Enable to publish information about keypad codes through MQTT, see the "Keypad control" section of this README +- Add, modify and delete keypad codes (Only available when a Keypad is detected): Enable to allow configuration of keypad codes through MQTT, see the "Keypad control" section of this README +- Publish auth data: Enable to publish authorization data to the MQTT topic lock/log. Requires the Nuki security code / PIN to be set, see "Nuki Lock PIN / Nuki Opener PIN" below. + +#### Nuki Lock/Opener Access Control +- Enable or disable executing each available lock action for the Nuki Lock and Nuki Opener through MQTT. Note: GPIO control is not restricted through this setting. + ### Credentials #### Credentials diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 37e8756..745920b 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -5,7 +5,6 @@ #include "Logger.h" #include "Config.h" #include "RestartReason.h" -#include "AccessLevel.h" #include WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, Gpio* gpio, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal) @@ -71,6 +70,14 @@ void WebCfgServer::initialize() } sendFavicon(); }); + _server.on("/acclvl", [&]() { + if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { + return _server.requestAuthentication(); + } + String response = ""; + buildAccLvlHtml(response); + _server.send(200, "text/html", response); + }); _server.on("/cred", [&]() { if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { return _server.requestAuthentication(); @@ -427,11 +434,6 @@ bool WebCfgServer::processArgs(String& message) _preferences->putInt(preference_query_interval_battery, value.toInt()); configChanged = true; } - else if(key == "ACCLVL") - { - _preferences->putInt(preference_access_level, value.toInt()); - configChanged = true; - } else if(key == "KPINT") { _preferences->putInt(preference_query_interval_keypad, value.toInt()); @@ -467,6 +469,101 @@ bool WebCfgServer::processArgs(String& message) _preferences->putBool(preference_publish_authdata, (value == "1")); configChanged = true; } + else if(key == "ACLCONFIG") + { + _preferences->putBool(preference_admin_config_enabled, (value == "1")); + configChanged = true; + } + else if(key == "KPPUB") + { + _preferences->putBool(preference_keypad_info_enabled, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKLCK") + { + _preferences->putBool(preference_acl_lock, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKUNLCK") + { + _preferences->putBool(preference_acl_unlock, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKUNLTCH") + { + _preferences->putBool(preference_acl_unlatch, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKLNG") + { + _preferences->putBool(preference_acl_lockngo, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKLNGU") + { + _preferences->putBool(preference_acl_lockngo_unlatch, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKFLLCK") + { + _preferences->putBool(preference_acl_fulllock, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKFOB1") + { + _preferences->putBool(preference_acl_lck_fob1, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKFOB2") + { + _preferences->putBool(preference_acl_lck_fob2, (value == "1")); + configChanged = true; + } + else if(key == "ACLLCKFOB3") + { + _preferences->putBool(preference_acl_lck_fob3, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNUNLCK") + { + _preferences->putBool(preference_acl_act_rto, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNLCK") + { + _preferences->putBool(preference_acl_deact_rto, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNUNLTCH") + { + _preferences->putBool(preference_acl_act_esa, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNUNLCKCM") + { + _preferences->putBool(preference_acl_act_cm, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNLCKCM") + { + _preferences->putBool(preference_acl_deact_cm, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNFOB1") + { + _preferences->putBool(preference_acl_opn_fob1, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNFOB2") + { + _preferences->putBool(preference_acl_opn_fob2, (value == "1")); + configChanged = true; + } + else if(key == "ACLOPNFOB3") + { + _preferences->putBool(preference_acl_opn_fob3, (value == "1")); + configChanged = true; + } else if(key == "REGAPP") { _preferences->putBool(preference_register_as_app, (value == "1")); @@ -624,7 +721,7 @@ void WebCfgServer::buildHtml(String& response) char lockstateArr[20]; NukiOpener::lockstateToString(_nukiOpener->keyTurnerState().lockState, lockstateArr); printParameter(response, "Nuki Opener paired", _nukiOpener->isPaired() ? ("Yes (BLE Address " + _nukiOpener->getBleAddress().toString() + ")").c_str() : "No"); - + if(_nukiOpener->keyTurnerState().nukiState == NukiOpener::State::ContinuousMode) { printParameter(response, "Nuki Opener state", "Open (Continuous Mode)"); @@ -643,30 +740,29 @@ void WebCfgServer::buildHtml(String& response) //} } - response.concat("

"); - - response.concat("

MQTT and Network Configuration

"); + response.concat("
"); + response.concat(""); if(_allowRestartToPortal) { - response.concat("

Wi-Fi

"); + response.concat(""); } - response.concat(""); + response.concat("

MQTT and Network Configuration

"); buildNavigationButton(response, "Edit", "/mqttconfig", _brokerConfigured ? "" : "(!) Please configure MQTT broker"); - - response.concat("

Nuki Configuration

"); + response.concat("

Nuki Configuration

"); buildNavigationButton(response, "Edit", "/nukicfg"); - - response.concat("

Credentials

"); + response.concat("

Access Level Configuration

"); + buildNavigationButton(response, "Edit", "/acclvl"); + response.concat("

Credentials

"); buildNavigationButton(response, "Edit", "/cred", _pinsConfigured ? "" : "(!) Please configure PIN"); - - response.concat("

GPIO Configuration

"); + response.concat("

GPIO Configuration

"); buildNavigationButton(response, "Edit", "/gpiocfg"); - - response.concat("

Firmware update

"); + response.concat("

Firmware update

"); buildNavigationButton(response, "Open", "/ota"); + response.concat("

Wi-Fi

"); buildNavigationButton(response, "Restart and configure Wi-Fi", "/wifi"); + response.concat("
"); } @@ -674,36 +770,36 @@ void WebCfgServer::buildCredHtml(String &response) { buildHtmlHeader(response); - response.concat("
"); + response.concat(""); response.concat("

Credentials

"); response.concat(""); printInputField(response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, false, true); printInputField(response, "CREDPASS", "Password", "*", 30, true, true); printInputField(response, "CREDPASSRE", "Retype password", "*", 30, true); response.concat("
"); - response.concat("
"); - response.concat("
"); + response.concat("
"); + response.concat(""); if(_nuki != nullptr) { - response.concat("

"); + response.concat("

"); response.concat("

Nuki Lock PIN

"); response.concat(""); printInputField(response, "NUKIPIN", "PIN Code (# to clear)", "*", 20, true); response.concat("
"); - response.concat("
"); - response.concat("
"); + response.concat("
"); + response.concat(""); } if(_nukiOpener != nullptr) { - response.concat("

"); + response.concat("

"); response.concat("

Nuki Opener PIN

"); response.concat(""); printInputField(response, "NUKIOPPIN", "PIN Code (# to clear)", "*", 20, true); response.concat("
"); - response.concat("
"); - response.concat("
"); + response.concat("
"); + response.concat(""); } _confirmCode = generateConfirmCode(); @@ -732,7 +828,7 @@ void WebCfgServer::buildCredHtml(String &response) response.concat(""); response.concat("
"); } - response.concat(""); + response.concat(""); } void WebCfgServer::buildOtaHtml(String &response, bool errored) @@ -742,7 +838,7 @@ void WebCfgServer::buildOtaHtml(String &response, bool errored) if(millis() < 60000) { response.concat("OTA functionality not ready. Please wait a moment and reload."); - response.concat(""); + response.concat(""); return; } @@ -750,7 +846,7 @@ void WebCfgServer::buildOtaHtml(String &response, bool errored) response.concat("
Over-the-air update errored. Please check the logs for more info

"); } - response.concat("
Choose the updated nuki_hub.bin file to upload:
"); + response.concat("Choose the updated nuki_hub.bin file to upload:
"); response.concat("
"); if(_preferences->getBool(preference_check_updates)) @@ -771,12 +867,12 @@ void WebCfgServer::buildOtaHtml(String &response, bool errored) response.concat(" button.addEventListener('click',hideshow,false);"); response.concat(" function hideshow() {"); response.concat(" document.getElementById('upform').style.visibility = 'hidden';"); - response.concat(" document.getElementById('gitdiv').style.visibility = 'hidden';"); + response.concat(" document.getElementById('gitdiv').style.visibility = 'hidden';"); response.concat(" document.getElementById('msgdiv').style.visibility = 'visible';"); response.concat(" }"); response.concat("});"); response.concat(""); - response.concat(""); + response.concat(""); } void WebCfgServer::buildOtaCompletedHtml(String &response) @@ -789,13 +885,13 @@ void WebCfgServer::buildOtaCompletedHtml(String &response) response.concat(" setTimeout(\"location.href = '/';\",10000);"); response.concat("});"); response.concat(""); - response.concat(""); + response.concat(""); } void WebCfgServer::buildMqttConfigHtml(String &response) { buildHtmlHeader(response); - response.concat("
"); + response.concat(""); response.concat("

Basic MQTT and Network Configuration

"); response.concat(""); printInputField(response, "HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100); @@ -832,17 +928,67 @@ void WebCfgServer::buildMqttConfigHtml(String &response) printInputField(response, "DNSSRV", "DNS Server", _preferences->getString(preference_ip_dns_server).c_str(), 15); response.concat("
"); - response.concat("
"); - response.concat("
"); - response.concat(""); + response.concat("
"); + response.concat(""); + response.concat(""); } +void WebCfgServer::buildAccLvlHtml(String &response) +{ + buildHtmlHeader(response); + + response.concat("
"); + response.concat("

Nuki General Access Control

"); + response.concat(""); + printCheckBox(response, "ACLCONFIG", "Change Lock/Opener configuration", _preferences->getBool(preference_admin_config_enabled)); + if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad())) + { + printCheckBox(response, "KPPUB", "Publish keypad codes information", _preferences->getBool(preference_keypad_info_enabled)); + printCheckBox(response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled)); + } + printCheckBox(response, "PUBAUTH", "Publish authorisation log (may reduce battery life)", _preferences->getBool(preference_publish_authdata)); + response.concat("
SettingEnabled

"); + if(_nuki != nullptr) + { + response.concat("

Nuki Lock Access Control

"); + response.concat(""); + + printCheckBox(response, "ACLLCKLCK", "Lock", _preferences->getBool(preference_acl_lock)); + printCheckBox(response, "ACLLCKUNLCK", "Unlock", _preferences->getBool(preference_acl_unlock)); + printCheckBox(response, "ACLLCKUNLTCH", "Unlatch", _preferences->getBool(preference_acl_unlatch)); + printCheckBox(response, "ACLLCKLNG", "Lock N Go", _preferences->getBool(preference_acl_lockngo)); + printCheckBox(response, "ACLLCKLNGU", "Lock N Go Unlatch", _preferences->getBool(preference_acl_lockngo_unlatch)); + printCheckBox(response, "ACLLCKFLLCK", "Full Lock", _preferences->getBool(preference_acl_fulllock)); + printCheckBox(response, "ACLLCKFOB1", "Fob Action 1", _preferences->getBool(preference_acl_lck_fob1)); + printCheckBox(response, "ACLLCKFOB2", "Fob Action 2", _preferences->getBool(preference_acl_lck_fob2)); + printCheckBox(response, "ACLLCKFOB3", "Fob Action 3", _preferences->getBool(preference_acl_lck_fob3)); + response.concat("
ActionAllowed

"); + } + if(_nukiOpener != nullptr) + { + response.concat("

Nuki Opener Access Control

"); + response.concat(""); + + printCheckBox(response, "ACLOPNUNLCK", "Activate Ring-to-Open", _preferences->getBool(preference_acl_act_rto)); + printCheckBox(response, "ACLOPNLCK", "Deactivate Ring-to-Open", _preferences->getBool(preference_acl_deact_rto)); + printCheckBox(response, "ACLOPNUNLTCH", "Electric Strike Actuation", _preferences->getBool(preference_acl_act_esa)); + printCheckBox(response, "ACLOPNUNLCKCM", "Activate Continuous Mode", _preferences->getBool(preference_acl_act_cm)); + printCheckBox(response, "ACLOPNLCKCM", "Deactivate Continuous Mode", _preferences->getBool(preference_acl_deact_cm)); + printCheckBox(response, "ACLOPNFOB1", "Fob Action 1", _preferences->getBool(preference_acl_opn_fob1)); + printCheckBox(response, "ACLOPNFOB2", "Fob Action 2", _preferences->getBool(preference_acl_opn_fob2)); + printCheckBox(response, "ACLOPNFOB3", "Fob Action 3", _preferences->getBool(preference_acl_opn_fob3)); + response.concat("
ActionAllowed

"); + } + response.concat("
"); + response.concat("
"); + response.concat(""); +} void WebCfgServer::buildNukiConfigHtml(String &response) { buildHtmlHeader(response); - response.concat("
"); + response.concat(""); response.concat("

Basic Nuki Configuration

"); response.concat(""); printCheckBox(response, "LOCKENA", "Nuki Smartlock enabled", _preferences->getBool(preference_lock_enabled)); @@ -863,30 +1009,26 @@ void WebCfgServer::buildNukiConfigHtml(String &response) printInputField(response, "LSTINT", "Query interval lock state (seconds)", _preferences->getInt(preference_query_interval_lockstate), 10); printInputField(response, "CFGINT", "Query interval configuration (seconds)", _preferences->getInt(preference_query_interval_configuration), 10); printInputField(response, "BATINT", "Query interval battery (seconds)", _preferences->getInt(preference_query_interval_battery), 10); - printDropDown(response, "ACCLVL", "Access level", String(_preferences->getInt(preference_access_level)), getAccessLevelOptions()); - if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad())) { printInputField(response, "KPINT", "Query interval keypad (seconds)", _preferences->getInt(preference_query_interval_keypad), 10); - printCheckBox(response, "KPENA", "Enable keypad control via MQTT", _preferences->getBool(preference_keypad_control_enabled)); } printInputField(response, "NRTRY", "Number of retries if command failed", _preferences->getInt(preference_command_nr_of_retries), 10); printInputField(response, "TRYDLY", "Delay between retries (milliseconds)", _preferences->getInt(preference_command_retry_delay), 10); - printCheckBox(response, "PUBAUTH", "Publish auth data (May reduce battery life)", _preferences->getBool(preference_publish_authdata)); printCheckBox(response, "REGAPP", "Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_as_app)); printInputField(response, "PRDTMO", "Presence detection timeout (seconds; -1 to disable)", _preferences->getInt(preference_presence_detection_timeout), 10); printInputField(response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10); response.concat("
"); - response.concat("
"); - response.concat("
"); - response.concat(""); + response.concat("
"); + response.concat(""); + response.concat(""); } void WebCfgServer::buildGpioConfigHtml(String &response) { buildHtmlHeader(response); - response.concat("
"); + response.concat(""); response.concat("

GPIO Configuration

"); response.concat(""); @@ -900,26 +1042,26 @@ void WebCfgServer::buildGpioConfigHtml(String &response) } response.concat("
"); - response.concat("
"); - response.concat("
"); - response.concat(""); + response.concat("
"); + response.concat(""); + response.concat(""); } void WebCfgServer::buildConfirmHtml(String &response, const String &message, uint32_t redirectDelay) { String delay(redirectDelay); - response.concat("\n"); - response.concat("\n"); - response.concat("Nuki Hub\n"); + response.concat("\n"); + response.concat("\n"); + response.concat("Nuki Hub\n"); response.concat(""); - response.concat("\n\n"); - response.concat("\n"); + response.concat("\n\n"); + response.concat("\n"); response.concat(message); - response.concat(""); + response.concat(""); } void WebCfgServer::buildConfigureWifiHtml(String &response) @@ -930,7 +1072,7 @@ void WebCfgServer::buildConfigureWifiHtml(String &response) response.concat("Click confirm to restart ESP into Wi-Fi configuration mode. After restart, connect to ESP access point to reconfigure Wi-Fi.

"); buildNavigationButton(response, "Confirm", "/wifimanager"); - response.concat(""); + response.concat(""); } void WebCfgServer::buildInfoHtml(String &response) @@ -1007,7 +1149,7 @@ void WebCfgServer::buildInfoHtml(String &response) response.concat(getEspRestartReason()); response.concat("\n"); - response.concat(" "); + response.concat(" "); } void WebCfgServer::processUnpair(bool opener) @@ -1050,13 +1192,13 @@ void WebCfgServer::processUnpair(bool opener) void WebCfgServer::buildHtmlHeader(String &response) { - response.concat(""); + response.concat(""); response.concat(""); // response.concat(""); response.concat(""); - response.concat("Nuki Hub"); + response.concat("Nuki Hub"); srand(millis()); } @@ -1084,13 +1226,13 @@ void WebCfgServer::printInputField(String& response, } response.concat(""); - response.concat(""); response.concat(""); @@ -1113,12 +1255,12 @@ void WebCfgServer::printCheckBox(String &response, const char *token, const char response.concat(description); response.concat(""); - response.concat(""); - response.concat(""); - response.concat(" "); + response.concat(""); response.concat(""); } @@ -1356,18 +1498,6 @@ const std::vector> WebCfgServer::getGpioOptions() cons return options; } -const std::vector> WebCfgServer::getAccessLevelOptions() const -{ - std::vector> options; - - options.push_back(std::make_pair(std::to_string((int)AccessLevel::Full).c_str(), "Full")); - options.push_back(std::make_pair(std::to_string((int)AccessLevel::LockAndUnlock).c_str(), "Lock and unlock operation only")); - options.push_back(std::make_pair(std::to_string((int)AccessLevel::LockOnly).c_str(), "Lock operation only")); - options.push_back(std::make_pair(std::to_string((int)AccessLevel::ReadOnly).c_str(), "Read only")); - - return options; -} - String WebCfgServer::getPreselectionForGpio(const uint8_t &pin) { const std::vector& pinConfiguration = _gpio->pinConfiguration(); diff --git a/WebCfgServer.h b/WebCfgServer.h index 78049e3..7686433 100644 --- a/WebCfgServer.h +++ b/WebCfgServer.h @@ -37,6 +37,7 @@ private: bool processArgs(String& message); void processGpioArgs(); void buildHtml(String& response); + void buildAccLvlHtml(String& response); void buildCredHtml(String& response); void buildOtaHtml(String& response, bool errored); void buildOtaCompletedHtml(String& response); @@ -60,7 +61,6 @@ private: const std::vector> getNetworkDetectionOptions() const; const std::vector> getGpioOptions() const; - const std::vector> getAccessLevelOptions() const; String getPreselectionForGpio(const uint8_t& pin); void printParameter(String& response, const char* description, const char* value, const char *link = ""); diff --git a/WebCfgServerConstants.h b/WebCfgServerConstants.h index cef453f..b27a84a 100644 --- a/WebCfgServerConstants.h +++ b/WebCfgServerConstants.h @@ -3,7 +3,7 @@ // escaped by https://www.cescaper.com/ // source: https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css -const char stylecss[] = ":root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047}@media (prefers-color-scheme:dark){:root{--nc-tx-1:#ffffff;--nc-tx-2:#eeeeee;--nc-bg-1:#000000;--nc-bg-2:#111111;--nc-bg-3:#222222;--nc-lk-1:#3291FF;--nc-lk-2:#0070F3;--nc-lk-tx:#FFFFFF;--nc-ac-1:#7928CA;--nc-ac-tx:#FFFFFF}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,form,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:normal;overflow-wrap:anywhere;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr:hover{cursor:help}blockquote{padding:1.5rem;background:var(--nc-bg-2);border-left:5px solid var(--nc-bg-3)}abbr{cursor:help}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{background:var(--nc-bg-2);border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(0px - (50vw - 50%)) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}header>:last-child{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono)}code,kbd,pre,samp{background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9rem}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto}pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}code pre{display:inline;background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details{padding:.6rem 1rem;background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px}summary{cursor:pointer;font-weight:700}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}details[open]>:last-child{margin-bottom:0}dt{font-weight:700}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border:1px solid var(--nc-bg-3);border-radius:4px}legend{padding:0.5rem}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr:nth-child(even){background:var(--nc-bg-2)}table caption{font-weight:700;margin-bottom:.5rem}textarea{max-width:100%}ol,ul{padding-left:2rem}li{margin-top:.4rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}td>input{margin-top:0px;margin-bottom:0px}td>textarea{margin-top:0px;margin-bottom:0px}td>select{margin-top:0px;margin-bottom:0px}"; +const char stylecss[] = ":root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047}@media (prefers-color-scheme:dark){:root{--nc-tx-1:#ffffff;--nc-tx-2:#eeeeee;--nc-bg-1:#000000;--nc-bg-2:#111111;--nc-bg-3:#222222;--nc-lk-1:#3291FF;--nc-lk-2:#0070F3;--nc-lk-tx:#FFFFFF;--nc-ac-1:#7928CA;--nc-ac-tx:#FFFFFF}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,form,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:normal;overflow-wrap:anywhere;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr:hover{cursor:help}blockquote{padding:1.5rem;background:var(--nc-bg-2);border-left:5px solid var(--nc-bg-3)}abbr{cursor:help}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{background:var(--nc-bg-2);border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(0px - (50vw - 50%)) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}header>:last-child{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono)}code,kbd,pre,samp{background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9rem}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto}pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}code pre{display:inline;background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details{padding:.6rem 1rem;background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px}summary{cursor:pointer;font-weight:700}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}details[open]>:last-child{margin-bottom:0}dt{font-weight:700}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border:1px solid var(--nc-bg-3);border-radius:4px}legend{padding:0.5rem}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr:nth-child(even){background:var(--nc-bg-2)}table caption{font-weight:700;margin-bottom:.5rem}textarea{max-width:100%}ol,ul{padding-left:2rem}li{margin-top:.4rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}td>input{margin-top:0px;margin-bottom:0px}td>textarea{margin-top:0px;margin-bottom:0px}td>select{margin-top:0px;margin-bottom:0px}#tblnav td,th{border:0;border-bottom:1px solid;}.tdbtn{text-align:center;vertical-align: middle;}"; // converted to char array by https://notisrac.github.io/FileToCArray/ const unsigned char favicon_32x32[] = { diff --git a/main.cpp b/main.cpp index b4898cc..2da60f5 100644 --- a/main.cpp +++ b/main.cpp @@ -153,11 +153,84 @@ bool initPreferences() if(configVer < (atof(NUKI_HUB_VERSION) * 100)) { - //Example - //if (configVer < 833) - //{ - //MIGRATE SETTINGS - //} + if (configVer < 834) + { + if(preferences->getInt(preference_keypad_control_enabled)) + { + preferences->putBool(preference_keypad_info_enabled, true); + } + else + { + preferences->putBool(preference_keypad_info_enabled, false); + } + + switch(preferences->getInt(preference_access_level)) + { + case 0: + preferences->putBool(preference_keypad_control_enabled, true); + preferences->putBool(preference_admin_config_enabled, true); + preferences->putBool(preference_acl_lock, true); + preferences->putBool(preference_acl_unlock, true); + preferences->putBool(preference_acl_unlatch, true); + preferences->putBool(preference_acl_lockngo, true); + preferences->putBool(preference_acl_lockngo_unlatch, true); + preferences->putBool(preference_acl_fulllock, true); + preferences->putBool(preference_acl_lck_fob1, true); + preferences->putBool(preference_acl_lck_fob2, true); + preferences->putBool(preference_acl_lck_fob3, true); + preferences->putBool(preference_acl_act_rto, true); + preferences->putBool(preference_acl_deact_rto, true); + preferences->putBool(preference_acl_act_esa, true); + preferences->putBool(preference_acl_act_cm, true); + preferences->putBool(preference_acl_deact_cm, true); + preferences->putBool(preference_acl_opn_fob1, true); + preferences->putBool(preference_acl_opn_fob2, true); + preferences->putBool(preference_acl_opn_fob3, true); + break; + case 1: + preferences->putBool(preference_keypad_control_enabled, false); + preferences->putBool(preference_admin_config_enabled, false); + preferences->putBool(preference_acl_lock, true); + preferences->putBool(preference_acl_unlock, false); + preferences->putBool(preference_acl_unlatch, false); + preferences->putBool(preference_acl_lockngo, false); + preferences->putBool(preference_acl_lockngo_unlatch, false); + preferences->putBool(preference_acl_fulllock, true); + preferences->putBool(preference_acl_lck_fob1, false); + preferences->putBool(preference_acl_lck_fob2, false); + preferences->putBool(preference_acl_lck_fob3, false); + preferences->putBool(preference_acl_act_rto, false); + preferences->putBool(preference_acl_deact_rto, true); + preferences->putBool(preference_acl_act_esa, false); + preferences->putBool(preference_acl_act_cm, false); + preferences->putBool(preference_acl_deact_cm, true); + preferences->putBool(preference_acl_opn_fob1, false); + preferences->putBool(preference_acl_opn_fob2, false); + preferences->putBool(preference_acl_opn_fob3, false); + break; + case 3: + preferences->putBool(preference_keypad_control_enabled, false); + preferences->putBool(preference_admin_config_enabled, false); + preferences->putBool(preference_acl_lock, true); + preferences->putBool(preference_acl_unlock, true); + preferences->putBool(preference_acl_unlatch, false); + preferences->putBool(preference_acl_lockngo, true); + preferences->putBool(preference_acl_lockngo_unlatch, false); + preferences->putBool(preference_acl_fulllock, true); + preferences->putBool(preference_acl_lck_fob1, false); + preferences->putBool(preference_acl_lck_fob2, false); + preferences->putBool(preference_acl_lck_fob3, false); + preferences->putBool(preference_acl_act_rto, true); + preferences->putBool(preference_acl_deact_rto, true); + preferences->putBool(preference_acl_act_esa, false); + preferences->putBool(preference_acl_act_cm, true); + preferences->putBool(preference_acl_deact_cm, true); + preferences->putBool(preference_acl_opn_fob1, false); + preferences->putBool(preference_acl_opn_fob2, false); + preferences->putBool(preference_acl_opn_fob3, false); + break; + } + } preferences->putInt(preference_config_version, atof(NUKI_HUB_VERSION) * 100); } From 5d545e5704c27f101ac13b8cb4a3a3d120da5c39 Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 12 Mar 2024 20:24:17 +0100 Subject: [PATCH 2/3] Update --- NukiOpenerWrapper.cpp | 17 +++++++---------- NukiOpenerWrapper.h | 9 +-------- NukiWrapper.cpp | 1 + NukiWrapper.h | 10 +--------- 4 files changed, 10 insertions(+), 27 deletions(-) diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index fd4867f..32bed68 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -7,6 +7,7 @@ #include NukiOpenerWrapper* nukiOpenerInst; +Preferences* nukiOpenerPreferences = nullptr; NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), @@ -60,15 +61,6 @@ void NukiOpenerWrapper::initialize() _retryDelay = _preferences->getInt(preference_command_retry_delay); _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000; - _aclActRTO = _preferences->getBool(preference_acl_act_rto); - _aclDeactRTO = _preferences->getBool(preference_acl_deact_rto); - _aclESA = _preferences->getBool(preference_acl_act_esa); - _aclActCM = _preferences->getBool(preference_acl_act_cm); - _aclDeactCM = _preferences->getBool(preference_acl_deact_cm); - _aclFob1 = _preferences->getBool(preference_acl_opn_fob1); - _aclFob2 = _preferences->getBool(preference_acl_opn_fob2); - _aclFob3 = _preferences->getBool(preference_acl_opn_fob3); - if(_retryDelay <= 100) { _retryDelay = 100; @@ -499,11 +491,16 @@ LockActionResult NukiOpenerWrapper::onLockActionReceivedCallback(const char *val return LockActionResult::UnknownAction; } - if((action == NukiOpener::LockAction::ActivateRTO && _preferences->getBool(preference_acl_act_rto)) || (action == NukiOpener::LockAction::DeactivateRTO && _preferences->getBool(preference_acl_deact_rto)) || (action == NukiOpener::LockAction::ElectricStrikeActuation && _preferences->getBool(preference_acl_act_esa)) || (action == NukiOpener::LockAction::ActivateCM && _preferences->getBool(preference_acl_act_cm)) || (action == NukiOpener::LockAction::DeactivateCM && _preferences->getBool(preference_acl_deact_cm)) || (action == NukiOpener::LockAction::FobAction1 && _preferences->getBool(preference_acl_opn_fob1)) || (action == NukiOpener::LockAction::FobAction2 && _preferences->getBool(preference_acl_opn_fob2)) || (action == NukiOpener::LockAction::FobAction3 && _preferences->getBool(preference_acl_opn_fob3))) + nukiOpenerPreferences = new Preferences(); + nukiOpenerPreferences->begin("nukihub", true); + + if((action == NukiOpener::LockAction::ActivateRTO && nukiOpenerPreferences->getBool(preferenceaclactrto)) || (action == NukiOpener::LockAction::DeactivateRTO && nukiOpenerPreferences->getBool(preferenceacldeactrto)) || (action == NukiOpener::LockAction::ElectricStrikeActuation && nukiOpenerPreferences->getBool(preferenceaclactesa)) || (action == NukiOpener::LockAction::ActivateCM && nukiOpenerPreferences->getBool(preferenceaclactcm)) || (action == NukiOpener::LockAction::DeactivateCM && nukiOpenerPreferences->getBool(preferenceacldeactcm)) || (action == NukiOpener::LockAction::FobAction1 && nukiOpenerPreferences->getBool(preferenceaclopnfob1)) || (action == NukiOpener::LockAction::FobAction2 && nukiOpenerPreferences->getBool(preferenceaclopnfob2)) || (action == NukiOpener::LockAction::FobAction3 && nukiOpenerPreferences->getBool(preferenceaclopnfob3))) { + nukiOpenerPreferences->end(); return LockActionResult::Success; } + nukiOpenerPreferences->end(); return LockActionResult::AccessDenied; } diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index 0c92e45..fd5e4b3 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -104,14 +104,7 @@ private: bool _nukiAdvancedConfigValid = false; bool _hassEnabled = false; bool _hassSetupCompleted = false; - bool _aclActRTO = false; - bool _aclDeactRTO = false; - bool _aclESA = false; - bool _aclActCM = false; - bool _aclDeactCM = false; - bool _aclFob1 = false; - bool _aclFob2 = false; - bool _aclFob3 = false; + bool _paired = false; bool _statusUpdated = false; bool _hasKeypad = false; diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 8481e08..0c1626b 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -7,6 +7,7 @@ #include NukiWrapper* nukiInst; +Preferences* preferences = nullptr; NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), diff --git a/NukiWrapper.h b/NukiWrapper.h index 02d722d..7ff26f4 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -98,15 +98,7 @@ private: bool _nukiAdvancedConfigValid = false; bool _hassEnabled = false; bool _hassSetupCompleted = false; - bool _aclLock = false; - bool _aclUnlock = false; - bool _aclUnlatch = false; - bool _aclLockNGo = false; - bool _aclLockNGoU = false; - bool _aclFLock = false; - bool _aclFob1 = false; - bool _aclFob2 = false; - bool _aclFob3 = false; + bool _paired = false; bool _statusUpdated = false; bool _hasKeypad = false; From b0eaf942f443e69f352b4fcaf47c3ba11b126ae4 Mon Sep 17 00:00:00 2001 From: iranl Date: Tue, 12 Mar 2024 20:30:42 +0100 Subject: [PATCH 3/3] Update --- NukiOpenerWrapper.cpp | 2 +- NukiWrapper.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index 32bed68..0021d8d 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -494,7 +494,7 @@ LockActionResult NukiOpenerWrapper::onLockActionReceivedCallback(const char *val nukiOpenerPreferences = new Preferences(); nukiOpenerPreferences->begin("nukihub", true); - if((action == NukiOpener::LockAction::ActivateRTO && nukiOpenerPreferences->getBool(preferenceaclactrto)) || (action == NukiOpener::LockAction::DeactivateRTO && nukiOpenerPreferences->getBool(preferenceacldeactrto)) || (action == NukiOpener::LockAction::ElectricStrikeActuation && nukiOpenerPreferences->getBool(preferenceaclactesa)) || (action == NukiOpener::LockAction::ActivateCM && nukiOpenerPreferences->getBool(preferenceaclactcm)) || (action == NukiOpener::LockAction::DeactivateCM && nukiOpenerPreferences->getBool(preferenceacldeactcm)) || (action == NukiOpener::LockAction::FobAction1 && nukiOpenerPreferences->getBool(preferenceaclopnfob1)) || (action == NukiOpener::LockAction::FobAction2 && nukiOpenerPreferences->getBool(preferenceaclopnfob2)) || (action == NukiOpener::LockAction::FobAction3 && nukiOpenerPreferences->getBool(preferenceaclopnfob3))) + if((action == NukiOpener::LockAction::ActivateRTO && nukiOpenerPreferences->getBool(preference_acl_act_rto)) || (action == NukiOpener::LockAction::DeactivateRTO && nukiOpenerPreferences->getBool(preference_acl_deact_rto)) || (action == NukiOpener::LockAction::ElectricStrikeActuation && nukiOpenerPreferences->getBool(preference_acl_act_esa)) || (action == NukiOpener::LockAction::ActivateCM && nukiOpenerPreferences->getBool(preference_acl_act_cm)) || (action == NukiOpener::LockAction::DeactivateCM && nukiOpenerPreferences->getBool(preference_acl_deact_cm)) || (action == NukiOpener::LockAction::FobAction1 && nukiOpenerPreferences->getBool(preference_acl_opn_fob1)) || (action == NukiOpener::LockAction::FobAction2 && nukiOpenerPreferences->getBool(preference_acl_opn_fob2)) || (action == NukiOpener::LockAction::FobAction3 && nukiOpenerPreferences->getBool(preference_acl_opn_fob3))) { nukiOpenerPreferences->end(); return LockActionResult::Success; diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 0c1626b..e2a6329 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -7,7 +7,7 @@ #include NukiWrapper* nukiInst; -Preferences* preferences = nullptr; +Preferences* nukiLockPreferences = nullptr; NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), @@ -481,11 +481,16 @@ LockActionResult NukiWrapper::onLockActionReceivedCallback(const char *value) return LockActionResult::UnknownAction; } - if((action == NukiLock::LockAction::Lock && _aclLock) || (action == NukiLock::LockAction::Unlock && _aclUnlock) || (action == NukiLock::LockAction::Unlatch && _aclUnlatch) || (action == NukiLock::LockAction::LockNgo && _aclLockNGo) || (action == NukiLock::LockAction::LockNgoUnlatch && _aclLockNGoU) || (action == NukiLock::LockAction::FullLock && _aclFLock) || (action == NukiLock::LockAction::FobAction1 && _aclFob1) || (action == NukiLock::LockAction::FobAction2 && _aclFob2) || (action == NukiLock::LockAction::FobAction3 && _aclFob3)) + nukiLockPreferences = new Preferences(); + nukiLockPreferences->begin("nukihub", true); + + if((action == NukiLock::LockAction::Lock && nukiLockPreferences->getBool(preference_acl_lock)) || (action == NukiLock::LockAction::Unlock && nukiLockPreferences->getBool(preference_acl_unlock)) || (action == NukiLock::LockAction::Unlatch && nukiLockPreferences->getBool(preference_acl_unlatch)) || (action == NukiLock::LockAction::LockNgo && nukiLockPreferences->getBool(preference_acl_lockngo)) || (action == NukiLock::LockAction::LockNgoUnlatch && nukiLockPreferences->getBool(preference_acl_lockngo_unlatch)) || (action == NukiLock::LockAction::FullLock && nukiLockPreferences->getBool(preference_acl_fulllock)) || (action == NukiLock::LockAction::FobAction1 && nukiLockPreferences->getBool(preference_acl_lck_fob1)) || (action == NukiLock::LockAction::FobAction2 && nukiLockPreferences->getBool(preference_acl_lck_fob2)) || (action == NukiLock::LockAction::FobAction3 && nukiLockPreferences->getBool(preference_acl_lck_fob3))) { + nukiLockPreferences->end(); return LockActionResult::Success; } + nukiLockPreferences->end(); return LockActionResult::AccessDenied; }