diff --git a/AccessLevel.h b/AccessLevel.h new file mode 100644 index 0000000..b1eb39e --- /dev/null +++ b/AccessLevel.h @@ -0,0 +1,8 @@ +#pragma once + +enum class AccessLevel +{ + Full = 0, + LockOnly = 1, + ReadOnly = 2 +}; \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8169027..b939c42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,8 @@ set(SRCFILES networkDevices/ClientSyncW5500.cpp networkDevices/espMqttClientW5500.cpp networkDevices/IPConfiguration.cpp + AccessLevel.h + LockActionResult.h QueryCommand.h NukiWrapper.cpp NukiOpenerWrapper.cpp diff --git a/LockActionResult.h b/LockActionResult.h new file mode 100644 index 0000000..355c94a --- /dev/null +++ b/LockActionResult.h @@ -0,0 +1,9 @@ +#pragma once + +enum class LockActionResult +{ + Success, + UnknownAction, + AccessDenied, + Failed +}; \ No newline at end of file diff --git a/NetworkLock.cpp b/NetworkLock.cpp index 6e332a7..e6d5e0f 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -102,16 +102,36 @@ void NetworkLock::onMqttDataReceived(const char* topic, byte* payload, const uns if(comparePrefixedPath(topic, mqtt_topic_lock_action)) { - if(strcmp(value, "") == 0 || strcmp(value, "--") == 0 || strcmp(value, "ack") == 0 || strcmp(value, "unknown_action") == 0) return; + if(strcmp(value, "") == 0 || + strcmp(value, "--") == 0 || + strcmp(value, "ack") == 0 || + strcmp(value, "unknown_action") == 0 || + strcmp(value, "denied") == 0 || + strcmp(value, "error") == 0) return; Log->print(F("Lock action received: ")); Log->println(value); - bool success = false; + LockActionResult lockActionResult = LockActionResult::Failed; if(_lockActionReceivedCallback != NULL) { - success = _lockActionReceivedCallback(value); + lockActionResult = _lockActionReceivedCallback(value); + } + + switch(lockActionResult) + { + case LockActionResult::Success: + publishString(mqtt_topic_lock_action, "ack"); + break; + case LockActionResult::UnknownAction: + publishString(mqtt_topic_lock_action, "unknown_action"); + break; + case LockActionResult::AccessDenied: + publishString(mqtt_topic_lock_action, "denied"); + break; + case LockActionResult::Failed: + publishString(mqtt_topic_lock_action, "error"); + break; } - publishString(mqtt_topic_lock_action, success ? "ack" : "unknown_action"); } if(comparePrefixedPath(topic, mqtt_topic_keypad_command_action)) @@ -458,7 +478,7 @@ void NetworkLock::publishKeypadCommandResult(const char* result) publishString(mqtt_topic_keypad_command_result, result); } -void NetworkLock::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) +void NetworkLock::setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; } diff --git a/NetworkLock.h b/NetworkLock.h index 47fb12f..95f0e88 100644 --- a/NetworkLock.h +++ b/NetworkLock.h @@ -10,6 +10,7 @@ #include "NukiLockConstants.h" #include "Network.h" #include "QueryCommand.h" +#include "LockActionResult.h" #define LOCK_LOG_JSON_BUFFER_SIZE 2048 @@ -38,7 +39,7 @@ public: void publishKeypad(const std::list& entries, uint maxKeypadCodeCount); void publishKeypadCommandResult(const char* result); - void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); + void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled)); @@ -84,7 +85,7 @@ private: char* _buffer; size_t _bufferSize; - bool (*_lockActionReceivedCallback)(const char* value) = nullptr; + LockActionResult (*_lockActionReceivedCallback)(const char* value) = nullptr; void (*_configUpdateReceivedCallback)(const char* path, const char* value) = nullptr; void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr; }; diff --git a/NetworkOpener.cpp b/NetworkOpener.cpp index ab2fabe..60438dd 100644 --- a/NetworkOpener.cpp +++ b/NetworkOpener.cpp @@ -93,16 +93,36 @@ void NetworkOpener::onMqttDataReceived(const char* topic, byte* payload, const u if(comparePrefixedPath(topic, mqtt_topic_lock_action)) { - if(strcmp((char*)payload, "") == 0 || strcmp(value, "--") == 0 || strcmp(value, "ack") == 0 || strcmp(value, "unknown_action") == 0) return; + if(strcmp(value, "") == 0 || + strcmp(value, "--") == 0 || + strcmp(value, "ack") == 0 || + strcmp(value, "unknown_action") == 0 || + strcmp(value, "denied") == 0 || + strcmp(value, "error") == 0) return; - Log->print(F("Opener lock action received: ")); + Log->print(F("Lock action received: ")); Log->println(value); - bool success = false; + LockActionResult lockActionResult = LockActionResult::Failed; if(_lockActionReceivedCallback != NULL) { - success = _lockActionReceivedCallback(value); + lockActionResult = _lockActionReceivedCallback(value); + } + + switch(lockActionResult) + { + case LockActionResult::Success: + publishString(mqtt_topic_lock_action, "ack"); + break; + case LockActionResult::UnknownAction: + publishString(mqtt_topic_lock_action, "unknown_action"); + break; + case LockActionResult::AccessDenied: + publishString(mqtt_topic_lock_action, "denied"); + break; + case LockActionResult::Failed: + publishString(mqtt_topic_lock_action, "error"); + break; } - publishString(mqtt_topic_lock_action, success ? "ack" : "unknown_action"); } if(comparePrefixedPath(topic, mqtt_topic_keypad_command_action)) @@ -508,7 +528,7 @@ void NetworkOpener::publishKeypadCommandResult(const char* result) publishString(mqtt_topic_keypad_command_result, result); } -void NetworkOpener::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) +void NetworkOpener::setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; } diff --git a/NetworkOpener.h b/NetworkOpener.h index 2f42d53..67eaa1a 100644 --- a/NetworkOpener.h +++ b/NetworkOpener.h @@ -36,7 +36,7 @@ public: void publishKeypad(const std::list& entries, uint maxKeypadCodeCount); void publishKeypadCommandResult(const char* result); - void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); + void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled)); @@ -86,7 +86,7 @@ private: char* _buffer; const size_t _bufferSize; - bool (*_lockActionReceivedCallback)(const char* value) = nullptr; + LockActionResult (*_lockActionReceivedCallback)(const char* value) = nullptr; void (*_configUpdateReceivedCallback)(const char* path, const char* value) = nullptr; void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr; }; diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index df4df28..d99557e 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -7,6 +7,7 @@ #include NukiOpenerWrapper* nukiOpenerInst; +AccessLevel NukiOpenerWrapper::_accessLevel = AccessLevel::ReadOnly; NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), @@ -55,6 +56,7 @@ 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); if(_retryDelay <= 100) { @@ -465,11 +467,33 @@ NukiOpener::LockAction NukiOpenerWrapper::lockActionToEnum(const char *str) return (NukiOpener::LockAction)0xff; } -bool NukiOpenerWrapper::onLockActionReceivedCallback(const char *value) +LockActionResult NukiOpenerWrapper::onLockActionReceivedCallback(const char *value) { NukiOpener::LockAction action = nukiOpenerInst->lockActionToEnum(value); - nukiOpenerInst->_nextLockAction = action; - return (int)action != 0xff; + if((int)action == 0xff) + { + return LockActionResult::UnknownAction; + } + + switch(_accessLevel) + { + case AccessLevel::Full: + nukiOpenerInst->_nextLockAction = action; + return LockActionResult::Success; + 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; + } } void NukiOpenerWrapper::onConfigUpdateReceivedCallback(const char *topic, const char *value) @@ -503,6 +527,8 @@ void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action) void NukiOpenerWrapper::onConfigUpdateReceived(const char *topic, const char *value) { + if(_accessLevel != AccessLevel::Full) return; + if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) { bool newValue = atoi(value) > 0; @@ -528,6 +554,8 @@ 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(!_hasKeypad) { if(_configRead) diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index da80621..c0e1345 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -6,6 +6,7 @@ #include "NukiDataTypes.h" #include "BleScanner.h" #include "Gpio.h" +#include "AccessLevel.h" class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler { @@ -43,7 +44,7 @@ public: void notify(NukiOpener::EventType eventType) override; private: - static bool onLockActionReceivedCallback(const char* value); + static LockActionResult onLockActionReceivedCallback(const char* value); static void onConfigUpdateReceivedCallback(const char* topic, const char* value); static void onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled); static void gpioActionCallback(const GpioAction& action); @@ -85,6 +86,7 @@ private: int _retryDelay = 0; int _retryCount = 0; int _retryLockstateCount = 0; + static AccessLevel _accessLevel; unsigned long _nextRetryTs = 0; std::vector _keypadCodeIds; diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 8b527f8..84d5254 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -7,6 +7,7 @@ #include NukiWrapper* nukiInst; +AccessLevel NukiWrapper::_accessLevel = AccessLevel::ReadOnly; NukiWrapper::NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), @@ -56,6 +57,7 @@ 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) { @@ -433,11 +435,34 @@ NukiLock::LockAction NukiWrapper::lockActionToEnum(const char *str) return (NukiLock::LockAction)0xff; } -bool NukiWrapper::onLockActionReceivedCallback(const char *value) +LockActionResult NukiWrapper::onLockActionReceivedCallback(const char *value) { NukiLock::LockAction action = nukiInst->lockActionToEnum(value); - nukiInst->_nextLockAction = action; - return (int)action != 0xff; + + if((int)action == 0xff) + { + return LockActionResult::UnknownAction; + } + + switch(_accessLevel) + { + case AccessLevel::Full: + nukiInst->_nextLockAction = action; + return LockActionResult::Success; + 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; + } } void NukiWrapper::onConfigUpdateReceivedCallback(const char *topic, const char *value) @@ -468,6 +493,8 @@ void NukiWrapper::gpioActionCallback(const GpioAction &action) void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) { + if(_accessLevel != AccessLevel::Full) return; + if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) { bool newValue = atoi(value) > 0; @@ -521,6 +548,8 @@ 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(!_hasKeypad) { if(_configRead) diff --git a/NukiWrapper.h b/NukiWrapper.h index e553f15..f15c72c 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -6,6 +6,8 @@ #include "BleScanner.h" #include "NukiLock.h" #include "Gpio.h" +#include "AccessLevel.h" +#include "LockActionResult.h" class NukiWrapper : public Nuki::SmartlockEventHandler { @@ -40,7 +42,7 @@ public: void notify(Nuki::EventType eventType) override; private: - static bool onLockActionReceivedCallback(const char* value); + static LockActionResult onLockActionReceivedCallback(const char* value); static void onConfigUpdateReceivedCallback(const char* topic, const char* value); static void onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled); static void gpioActionCallback(const GpioAction& action); @@ -105,6 +107,7 @@ 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 d001983..827a6a4 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -37,6 +37,7 @@ #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_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" @@ -67,7 +68,8 @@ 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_register_as_app, preference_command_nr_of_retries, + preference_keypad_control_enabled, 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, diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index c4e2c58..3de2bc1 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -5,6 +5,7 @@ #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) @@ -406,6 +407,11 @@ 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()); @@ -801,6 +807,8 @@ 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); @@ -1289,6 +1297,17 @@ 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::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 4a3b5d7..78049e3 100644 --- a/WebCfgServer.h +++ b/WebCfgServer.h @@ -60,6 +60,7 @@ 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 = "");