Merge branch 'gpio' of github.com:technyon/nuki_hub into gpio

This commit is contained in:
technyon
2023-06-03 21:20:33 +02:00
24 changed files with 338 additions and 108 deletions

8
AccessLevel.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
enum class AccessLevel
{
Full = 0,
LockOnly = 1,
ReadOnly = 2
};

View File

@@ -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

View File

@@ -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

View File

@@ -1,7 +1,6 @@
#include <esp32-hal.h>
#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();

9
LockActionResult.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
enum class LockActionResult
{
Success,
UnknownAction,
AccessDenied,
Failed
};

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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<NukiLock::KeypadEntry>& 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;
};

View File

@@ -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;
}

View File

@@ -36,7 +36,7 @@ public:
void publishKeypad(const std::list<NukiLock::KeypadEntry>& 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;
};

44
NukiDeviceId.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include <cstring>
#include <Arduino.h>
#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;
}

22
NukiDeviceId.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
#include <Preferences.h>
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;
};

View File

@@ -7,15 +7,20 @@
#include <NukiOpenerUtils.h>
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)

View File

@@ -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<uint16_t> _keypadCodeIds;

View File

@@ -7,15 +7,20 @@
#include <NukiLockUtils.h>
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)

View File

@@ -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;

5
Pins.h
View File

@@ -1,5 +0,0 @@
#pragma once
#define TRIGGER_LOCK_PIN 32
#define TRIGGER_UNLOCK_PIN 33
#define TRIGGER_UNLATCH_PIN 27

View File

@@ -3,7 +3,8 @@
#include <vector>
#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<char*> _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,

View File

@@ -10,7 +10,7 @@ Supported devices:<br>
NUKI Smart Lock 1.0<br>
NUKI Smart Lock 2.0<br>
NUKI Smart Lock 3.0<br>
NUKI Smart Lock 3.0 Pro<br>
NUKI Smart Lock 3.0 Pro (read FAQ below)<br>
NUKI Opener<br>
NUKI Keypad 1.0<br>
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)<br>
[LilyGO-T-ETH-POE](https://github.com/Xinyuan-LilyGO/LilyGO-T-ETH-POE)<br>
<br>
<b>Note for users upgrading from 8.21 or lower:</b> 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:<br>
REFXQ847A4ZDG<br>
This will also give you a 30€ discount for your order.
REFW628ZPQW3R<br>
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.<br><br>
The Pin configuration is:<br>
32: Lock<br>
33: Unlock<br>
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<br>
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.
<br><br>
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.
<br><br>
Reported as working are:<br>
[M5Stack ATOM Lite](https://shop.m5stack.com/products/atom-lite-esp32-development-kit)<br>
ESP32-WROOM-32D (DEVKIT V4)<br>
ESP32-WROOM-32E<br>
<br>
@@ -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:

View File

@@ -5,6 +5,7 @@
#include "Logger.h"
#include "Config.h"
#include "RestartReason.h"
#include "AccessLevel.h"
#include <esp_task_wdt.h>
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("</table>");
response.concat("* If no encryption is configured for the MQTT broker, leave empty. Only supported for WiFi connections.<br><br>");
@@ -803,10 +803,11 @@ void WebCfgServer::buildNukiConfigHtml(String &response)
response.concat("<h3>Advanced NUKI Configuration</h3>");
response.concat("<table>");
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("</table>");
@@ -998,6 +1000,8 @@ void WebCfgServer::buildHtmlHeader(String &response)
// response.concat("</style>");
response.concat("<link rel='stylesheet' href='/style.css'>");
response.concat("<TITLE>NUKI Hub</TITLE></HEAD><BODY>");
srand(millis());
}
void WebCfgServer::printInputField(String& response,
@@ -1295,6 +1299,17 @@ const std::vector<std::pair<String, String>> WebCfgServer::getGpioOptions() cons
return options;
}
const std::vector<std::pair<String, String>> WebCfgServer::getAccessLevelOptions() const
{
std::vector<std::pair<String, String>> 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<PinEntry>& pinConfiguration = _gpio->pinConfiguration();

View File

@@ -60,6 +60,7 @@ private:
const std::vector<std::pair<String, String>> getNetworkDetectionOptions() const;
const std::vector<std::pair<String, String>> getGpioOptions() const;
const std::vector<std::pair<String, String>> getAccessLevelOptions() const;
String getPreselectionForGpio(const uint8_t& pin);
void printParameter(String& response, const char* description, const char* value, const char *link = "");

View File

@@ -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()
{}
{
delay(60000);
}

View File

@@ -1,7 +1,6 @@
#include <Arduino.h>
#include <WiFi.h>
#include "W5500Device.h"
#include "../Pins.h"
#include "../PreferencesKeys.h"
#include "../Logger.h"
#include "../MqttTopics.h"

Binary file not shown.