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 d52a687..5622c28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.0.0) -set(ARDUINO_BOARD "ESP32 Dev Module [esp32.esp32]") +if(NOT ARDUINO_BOARD) + set(ARDUINO_BOARD "ESP32 Dev Module [esp32.esp32]") +endif() project(nuki_hub CXX) @@ -40,8 +42,8 @@ include_directories(${PROJECT_NAME} ) set(SRCFILES - Pins.h Config.h + NukiDeviceId.cpp CharBuffer.cpp Network.cpp MqttReceiver.h @@ -54,6 +56,8 @@ set(SRCFILES networkDevices/ClientSyncW5500.cpp networkDevices/espMqttClientW5500.cpp networkDevices/IPConfiguration.cpp + AccessLevel.h + LockActionResult.h QueryCommand.h NukiWrapper.cpp NukiOpenerWrapper.cpp @@ -89,7 +93,7 @@ set(SRCFILES lib/BleScanner/src/BleScanner.cpp lib/MqttLogger/src/MqttLogger.cpp lib/AsyncTCP/src/AsyncTCP.cpp -) + ) file(GLOB_RECURSE SRCFILESREC lib/NimBLE-Arduino/src/*.c diff --git a/Config.h b/Config.h index 36f828a..f958528 100644 --- a/Config.h +++ b/Config.h @@ -1,6 +1,6 @@ #pragma once -#define NUKI_HUB_VERSION "8.22-pre-1" +#define NUKI_HUB_VERSION "8.23" #define MQTT_QOS_LEVEL 1 #define MQTT_CLEAN_SESSIONS false diff --git a/Gpio.cpp b/Gpio.cpp index 0a00697..c9dedfa 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -1,7 +1,6 @@ #include #include "Gpio.h" #include "Arduino.h" -#include "Pins.h" #include "Logger.h" #include "PreferencesKeys.h" #include "RestartReason.h" @@ -289,10 +288,6 @@ void Gpio::setPinOutput(const uint8_t& pin, const uint8_t& state) digitalWrite(pin, state); } -#define TRIGGER_LOCK_PIN 32 -#define TRIGGER_UNLOCK_PIN 33 -#define TRIGGER_UNLATCH_PIN 27 - void Gpio::migrateObsoleteSetting() { _pinConfiguration.clear(); 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/Network.cpp b/Network.cpp index 9b77546..e2001d6 100644 --- a/Network.cpp +++ b/Network.cpp @@ -20,6 +20,12 @@ Network::Network(Preferences *preferences, Gpio* gpio, const String& maintenance _buffer(buffer), _bufferSize(bufferSize) { + // Remove obsolete W5500 hardware detection configuration + if(_preferences->getInt(preference_network_hardware_gpio) != 0) + { + _preferences->remove(preference_network_hardware_gpio); + } + _inst = this; _hostname = _preferences->getString(preference_hostname); @@ -739,7 +745,7 @@ void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* n "reset", uidString, "_reset", - "Reset", + "Restart NUKI Hub", name, baseTopic, mqtt_topic_reset, 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/NukiDeviceId.cpp b/NukiDeviceId.cpp new file mode 100644 index 0000000..a6e6ecc --- /dev/null +++ b/NukiDeviceId.cpp @@ -0,0 +1,44 @@ +#include +#include +#include "NukiDeviceId.h" +#include "PreferencesKeys.h" + +NukiDeviceId::NukiDeviceId(Preferences* preferences, const std::string& preferencesId) +: _preferences(preferences), + _preferencesId(preferencesId) +{ + _deviceId = _preferences->getUInt(_preferencesId.c_str()); + + if(_deviceId == 0) + { + assignNewId(); + } +} + +uint32_t NukiDeviceId::get() +{ + return _deviceId; +} + +void NukiDeviceId::assignId(const uint32_t& id) +{ + _deviceId = id; + _preferences->putUInt(_preferencesId.c_str(), id); +} + +void NukiDeviceId::assignNewId() +{ + assignId(getRandomId()); +} + +uint32_t NukiDeviceId::getRandomId() +{ + uint8_t rnd[4]; + for(int i=0; i<4; i++) + { + rnd[i] = random(255); + } + uint32_t deviceId; + memcpy(&deviceId, &rnd, sizeof(deviceId)); + return deviceId; +} diff --git a/NukiDeviceId.h b/NukiDeviceId.h new file mode 100644 index 0000000..296f04a --- /dev/null +++ b/NukiDeviceId.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class NukiDeviceId +{ +public: + NukiDeviceId(Preferences* preferences, const std::string& preferencesId); + + uint32_t get(); + + void assignId(const uint32_t& id); + void assignNewId(); + +private: + uint32_t getRandomId(); + + Preferences* _preferences; + const std::string _preferencesId; + uint32_t _deviceId = 0; +}; \ No newline at end of file diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index fd0bc55..79c0aef 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -7,15 +7,20 @@ #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) +NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), - _nukiOpener(deviceName, id), + _deviceId(deviceId), + _nukiOpener(deviceName, _deviceId->get()), _bleScanner(scanner), _network(network), _gpio(gpio), _preferences(preferences) { + Log->print("Device id opener: "); + Log->println(_deviceId->get()); + nukiOpenerInst = this; memset(&_lastKeyTurnerState, sizeof(NukiLock::KeyTurnerState), 0); @@ -55,6 +60,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) { @@ -298,6 +304,7 @@ void NukiOpenerWrapper::setPin(const uint16_t pin) void NukiOpenerWrapper::unpair() { _nukiOpener.unPairNuki(); + _deviceId->assignNewId(); _paired = false; } @@ -465,11 +472,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 +532,8 @@ void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action, const int& 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 +559,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 276c3cc..b616977 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -6,11 +6,13 @@ #include "NukiDataTypes.h" #include "BleScanner.h" #include "Gpio.h" +#include "AccessLevel.h" +#include "NukiDeviceId.h" class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler { public: - NukiOpenerWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences); + NukiOpenerWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences); virtual ~NukiOpenerWrapper(); void initialize(); @@ -43,7 +45,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, const int& pin); @@ -69,6 +71,7 @@ private: NukiOpener::LockAction lockActionToEnum(const char* str); // char array at least 14 characters std::string _deviceName; + NukiDeviceId* _deviceId = nullptr; NukiOpener::NukiOpener _nukiOpener; BleScanner::Scanner* _bleScanner = nullptr; NetworkOpener* _network = nullptr; @@ -85,6 +88,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 c4946d4..05abfc8 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -7,15 +7,20 @@ #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) +NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), + _deviceId(deviceId), _bleScanner(scanner), - _nukiLock(deviceName, id), + _nukiLock(deviceName, _deviceId->get()), _network(network), _gpio(gpio), _preferences(preferences) { + Log->print("Device id lock: "); + Log->println(_deviceId->get()); + nukiInst = this; memset(&_lastKeyTurnerState, sizeof(NukiLock::KeyTurnerState), 0); @@ -56,6 +61,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) { @@ -115,7 +121,7 @@ void NukiWrapper::update() { if (!_paired) { - Log->println(F("Nuki start pairing")); + Log->println(F("Nuki lock start pairing")); _network->publishBleAddress(""); Nuki::AuthorizationIdType idType = _preferences->getBool(preference_register_as_app) ? @@ -284,6 +290,7 @@ void NukiWrapper::setPin(const uint16_t pin) void NukiWrapper::unpair() { _nukiLock.unPairNuki(); + _deviceId->assignNewId(); _paired = false; } @@ -433,11 +440,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 +498,8 @@ void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin) 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 +553,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 5572354..c8fd14f 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -6,11 +6,14 @@ #include "BleScanner.h" #include "NukiLock.h" #include "Gpio.h" +#include "AccessLevel.h" +#include "LockActionResult.h" +#include "NukiDeviceId.h" class NukiWrapper : public Nuki::SmartlockEventHandler { public: - NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences); + NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences); virtual ~NukiWrapper(); void initialize(const bool& firstStart); @@ -40,7 +43,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, const int& pin); @@ -67,6 +70,7 @@ private: NukiLock::LockAction lockActionToEnum(const char* str); // char array at least 14 characters std::string _deviceName; + NukiDeviceId* _deviceId = nullptr; NukiLock::NukiLock _nukiLock; BleScanner::Scanner* _bleScanner = nullptr; NetworkLock* _network = nullptr; @@ -105,6 +109,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/Pins.h b/Pins.h deleted file mode 100644 index e29cf79..0000000 --- a/Pins.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#define TRIGGER_LOCK_PIN 32 -#define TRIGGER_UNLOCK_PIN 33 -#define TRIGGER_UNLATCH_PIN 27 diff --git a/PreferencesKeys.h b/PreferencesKeys.h index 81009e1..a620e6f 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -3,7 +3,8 @@ #include #define preference_started_before "run" -#define preference_deviceId "deviceId" +#define preference_device_id_lock "deviceId" +#define preference_device_id_opener "deviceIdOp" #define preference_mqtt_broker "mqttbroker" #define preference_mqtt_broker_port "mqttport" #define preference_mqtt_user "mqttuser" @@ -25,6 +26,7 @@ #define preference_ip_gateway "ipgtw" #define preference_ip_dns_server "dnssrv" #define preference_network_hardware "nwhw" +#define preference_network_hardware_gpio "nwhwdt" // obsolete #define preference_rssi_publish_interval "rssipb" #define preference_hostname "hostname" #define preference_network_timeout "nettmout" @@ -36,13 +38,14 @@ #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" #define preference_cred_user "crdusr" #define preference_cred_password "crdpass" #define preference_publish_authdata "pubauth" -#define preference_gpio_locking_enabled "gpiolck" +#define preference_gpio_locking_enabled "gpiolck" // obsolete #define preference_gpio_configuration "gpiocfg" #define preference_publish_debug_info "pubdbg" #define preference_presence_detection_timeout "prdtimeout" @@ -56,7 +59,7 @@ class DebugPreferences private: std::vector _keys = { - preference_started_before, preference_deviceId, preference_mqtt_broker, preference_mqtt_broker_port, + preference_started_before, preference_device_id_lock, preference_device_id_opener, preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_lock_enabled, preference_mqtt_lock_path, preference_opener_enabled, preference_mqtt_opener_path, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_mqtt_ca, @@ -64,9 +67,10 @@ private: preference_ip_dhcp_enabled, preference_ip_address, preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware, preference_rssi_publish_interval, preference_hostname, preference_network_timeout, preference_restart_on_disconnect, - preference_restart_timer, preference_restart_ble_beacon_lost, preference_query_interval_lockstate, + 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/README.md b/README.md index 9b411d2..3c50e76 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Supported devices:
NUKI Smart Lock 1.0
NUKI Smart Lock 2.0
NUKI Smart Lock 3.0
-NUKI Smart Lock 3.0 Pro
+NUKI Smart Lock 3.0 Pro (read FAQ below)
NUKI Opener
NUKI Keypad 1.0
NUKI Keypad 2.0 @@ -23,6 +23,9 @@ As an alternative to Wifi, the following ESP32 modules with wired ethernet are s [M5Stack PoESP32 Unit](https://docs.m5stack.com/en/unit/poesp32)
[LilyGO-T-ETH-POE](https://github.com/Xinyuan-LilyGO/LilyGO-T-ETH-POE)
+
+Note for users upgrading from 8.21 or lower: Please go to "MQTT and Network Configuration" and select +"Wifi only" as the network device (unless you use other network hardware). ## Installation @@ -53,8 +56,8 @@ Note: It is possible to run NUKI Hub alongside a NUKI Bridge. This is not recomm ## Support If you haven't ordered your NUKI product yet, you can support me by using my referrer code when placing your order:
-REFXQ847A4ZDG
-This will also give you a 30€ discount for your order. +REFW628ZPQW3R
+This will also give you a 10% discount on your order. This project is free to use for everyone. However if you feel like donating, you can buy me a coffee at ko-fi.com: @@ -176,14 +179,27 @@ For example, to add a code: ## GPIO lock control (optional) -The lock can be controlled via GPIO. For security reasons, this has to be enabled in -the configuration portal (check "Enable control via GPIO" in the NUKI configuration -section). The Pins use pullup configuration, so they have to be connected to ground to -trigger the action.

-The Pin configuration is:
-32: Lock
-33: Unlock
-27: Unlatch +The lock can be controlled via GPIO. To enable GPIO control, go the the "GPIO Configuration" page where each GPIO +can be configured for a specific role: + +- Disabled: The GPIO is disabled +- Input: Lock: When connect to Ground, a lock command is sent to the lock +- Input: Unlock: When connect to Ground, an unlock command is sent to the lock +- Input: Unlatch: When connect to Ground, an unlatch command is sent to the lock +- Input: Electric strike actuation: When connect to Ground, an electric strike actuation command is sent to the opener (open door for configured amount of time) +- Input: Activate RTO: When connect to Ground, Ring-to-open is activated (opener) +- Input: Activate CM: When connect to Ground, Continuous mode is activated (opener) +- Input: Deactivate RTO/CM: Disable RTO or CM, depending on which is active +- Output: High when locked: Outputs a high signal when the door is locked +- Output: High when unlocked: Outputs a high signal when the door is unlocked +- Output: High when motor blocked: Outputs a high signal when the motor is blocked (lock) +- Output: High when RTO active: Outputs a high signal when ring-to-open is active (opener) +- Output: High when CM active: Outputs a high signal when continuous mode is active (opener) +- Output: High when RTO or CM active: Outputs a high signal when either ring-to-open or continuous mode is active (opener) + +Note: The old setting "Enable control via GPIO" is removed. If you had enabled this setting before upgrading to 8.22, the PINs are automatically configured to be +compatible with the previously hard-coded PINs. + ## Connecting via LAN (Optional) @@ -219,12 +235,14 @@ If this still doesn't fix the disconnects and the ESP becomes unreachable, the after a configured amount of time. ### Pairing with the Lock (or Opener) doesn't work -First, try erasing the flash and then (re-)flash the firmware. To erase the flash, use the espressif download tool and click the "Erase" button. +First, make sure the firmware version of the NUKI device is up-to-date, older versions have issues pairing
+Next, try erasing the flash and then (re-)flash the firmware. To erase the flash, use the espressif download tool and click the "Erase" button. Afterwards flash the firmware as described in the readme within the 7z file.

Also, there are reports that ESP32 "DEVKIT1" module don't work and pairing is not possible. The reason is unknown, but if you use such a module, try a different one.

Reported as working are:
+[M5Stack ATOM Lite](https://shop.m5stack.com/products/atom-lite-esp32-development-kit)
ESP32-WROOM-32D (DEVKIT V4)
ESP32-WROOM-32E

@@ -235,6 +253,11 @@ Also, check that pairing is allowed. In the smartphone app, go to Settings --> F ## FAQ +### NUKI Hub doesn't work when the Wifi on my NUKI Smartlock Pro 3.0 is turned on. + +This is by design and according to NUKI part of the specification of the Pro lock: You can user either the built-in Wifi or a Bridge (whic NUKI Hub registers as). +Using both at the same time doesn't work. + ### Certain functionality doesn't work (e. g. changing configuration, setting keypad codes) Some functionality is restricted by the lock (or opener) firmware and is only accessible when the PIN is provided. When setting up the lock (or opener), you have to set a PIN in the smartphone. @@ -246,13 +269,17 @@ See previous point, this needs the correct PIN to be configured. ### Using home assistant, it's only possible to lock or unlock the door, but not to unlatch it Unlatching can be triggered using the lock.open service. +### When controlling two locks (or openers) connected to two ESPs, both devices react to the same command. When using Home Asistant, the same status is display for both locks. + +When using multiple NUKI devices, different paths for each device have to be configured. Navigate to "NUKI Configuration" and change the "MQTT NUKI Smartlock Path" +or "MQTT NUKI Opener Path" under "Basic NUKI Configuration" for at least one of the devices. ## Development VM Since setting up the toolchain can be difficult, I've uploaded a virtual machine (vmware image) that is setup to compile NUKI Hub: -https://mega.nz/file/8uRkDKyS#F0FNVJZTsUdcTMhiJIB47Fm-7YqKuyQs15E5zznmroc +https://drive.google.com/file/d/1fUVYHDtxXAZOAfQ321iRNIwkqFwuDsBp/view?usp=share_link User and password for the VM are both "nuki" and "nuki". The source is checked out at ~/projects/nuki_hub, the cmake build directory is build. So to compile, run the following commands: diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index e6ca370..5a79f5e 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) @@ -361,11 +362,6 @@ bool WebCfgServer::processArgs(String& message) _preferences->putBool(preference_restart_on_disconnect, (value == "1")); configChanged = true; } - else if(key == "RSTTMR") - { - _preferences->putInt(preference_restart_timer, value.toInt()); - configChanged = true; - } else if(key == "MQTTLOG") { _preferences->putBool(preference_mqtt_log_enabled, (value == "1")); @@ -411,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()); @@ -761,7 +762,6 @@ void WebCfgServer::buildMqttConfigHtml(String &response) printInputField(response, "RSSI", "RSSI Publish interval (seconds; -1 to disable)", _preferences->getInt(preference_rssi_publish_interval), 6); printInputField(response, "NETTIMEOUT", "Network Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5); printCheckBox(response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect)); - printInputField(response, "RSTTMR", "Restart timer (minutes; -1 to disable)", _preferences->getInt(preference_restart_timer), 10); printCheckBox(response, "MQTTLOG", "Enable MQTT logging", _preferences->getBool(preference_mqtt_log_enabled)); response.concat(""); response.concat("* If no encryption is configured for the MQTT broker, leave empty. Only supported for WiFi connections.

"); @@ -803,10 +803,11 @@ void WebCfgServer::buildNukiConfigHtml(String &response) response.concat("

Advanced NUKI Configuration

"); response.concat(""); - printCheckBox(response, "REGAPP", "Register as app (on: register as app, off: register as bridge; needs re-pairing if changed)", _preferences->getBool(preference_register_as_app)); 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); @@ -815,6 +816,7 @@ void WebCfgServer::buildNukiConfigHtml(String &response) 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", "Register as app (on: register as app, off: register as bridge; 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("
"); @@ -998,6 +1000,8 @@ void WebCfgServer::buildHtmlHeader(String &response) // response.concat(""); response.concat(""); response.concat("NUKI Hub"); + + srand(millis()); } void WebCfgServer::printInputField(String& response, @@ -1295,6 +1299,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 = ""); diff --git a/main.cpp b/main.cpp index b9119df..5e0166c 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,4 @@ #include "Arduino.h" -#include "Pins.h" #include "NukiWrapper.h" #include "NetworkLock.h" #include "WebCfgServer.h" @@ -14,6 +13,7 @@ #include "Config.h" #include "RestartReason.h" #include "CharBuffer.h" +#include "NukiDeviceId.h" Network* network = nullptr; NetworkLock* networkLock = nullptr; @@ -23,6 +23,8 @@ BleScanner::Scanner* bleScanner = nullptr; NukiWrapper* nuki = nullptr; NukiOpenerWrapper* nukiOpener = nullptr; PresenceDetection* presenceDetection = nullptr; +NukiDeviceId* deviceIdLock = nullptr; +NukiDeviceId* deviceIdOpener = nullptr; Preferences* preferences = nullptr; EthServer* ethServer = nullptr; Gpio* gpio = nullptr; @@ -113,18 +115,6 @@ void setupTasks() xTaskCreatePinnedToCore(presenceDetectionTask, "prdet", 896, NULL, 5, &presenceDetectionTaskHandle, 1); } -uint32_t getRandomId() -{ - uint8_t rnd[4]; - for(int i=0; i<4; i++) - { - rnd[i] = random(255); - } - uint32_t deviceId; - memcpy(&deviceId, &rnd, sizeof(deviceId)); - return deviceId; -} - void initEthServer(const NetworkDeviceType device) { switch (device) @@ -154,11 +144,6 @@ bool initPreferences() preferences->putBool(preference_lock_enabled, true); } - if(preferences->getInt(preference_restart_timer) == 0) - { - preferences->putInt(preference_restart_timer, -1); - } - return firstStart; } @@ -173,11 +158,22 @@ void setup() initializeRestartReason(); + + uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener); + + deviceIdLock = new NukiDeviceId(preferences, preference_device_id_lock); + deviceIdOpener = new NukiDeviceId(preferences, preference_device_id_opener); + + if(deviceIdLock->get() != 0 && devIdOpener == 0) + { + deviceIdOpener->assignId(deviceIdLock->get()); + } + CharBuffer::initialize(); - if(preferences->getInt(preference_restart_timer) > 0) + if(preferences->getInt(preference_restart_timer) != 0) { - restartTs = preferences->getInt(preference_restart_timer) * 60 * 1000; + preferences->remove(preference_restart_timer); } gpio = new Gpio(preferences); @@ -201,13 +197,6 @@ void setup() networkOpener->initialize(); } - uint32_t deviceId = preferences->getUInt(preference_deviceId); - if(deviceId == 0) - { - deviceId = getRandomId(); - preferences->putUInt(preference_deviceId, deviceId); - } - initEthServer(network->networkDeviceType()); bleScanner = new BleScanner::Scanner(); @@ -217,21 +206,14 @@ void setup() Log->println(lockEnabled ? F("NUKI Lock enabled") : F("NUKI Lock disabled")); if(lockEnabled) { - nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, networkLock, gpio, preferences); + nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, gpio, preferences); nuki->initialize(firstStart); - - - -// if(preferences->getBool(preference_gpio_locking_enabled)) -// { -// Gpio::init(nuki); -// } } Log->println(openerEnabled ? F("NUKI Opener enabled") : F("NUKI Opener disabled")); if(openerEnabled) { - nukiOpener = new NukiOpenerWrapper("NukiHub", deviceId, bleScanner, networkOpener, gpio, preferences); + nukiOpener = new NukiOpenerWrapper("NukiHub", deviceIdOpener, bleScanner, networkOpener, gpio, preferences); nukiOpener->initialize(); } @@ -245,4 +227,6 @@ void setup() } void loop() -{} \ No newline at end of file +{ + delay(60000); +} \ No newline at end of file diff --git a/networkDevices/W5500Device.cpp b/networkDevices/W5500Device.cpp index 8c6ab43..615a938 100644 --- a/networkDevices/W5500Device.cpp +++ b/networkDevices/W5500Device.cpp @@ -1,7 +1,6 @@ #include #include #include "W5500Device.h" -#include "../Pins.h" #include "../PreferencesKeys.h" #include "../Logger.h" #include "../MqttTopics.h" diff --git a/webflash/nuki_hub.bin b/webflash/nuki_hub.bin index 2e39fed..4aa5bab 100644 Binary files a/webflash/nuki_hub.bin and b/webflash/nuki_hub.bin differ