From 29c664e3ba9c23a8669075cab14d96909914a4fe Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 10:43:40 +0200 Subject: [PATCH 1/9] make Gpio configuration dynamic --- Gpio.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++---- Gpio.h | 28 +++++++++++++++- PreferencesKeys.h | 1 + main.cpp | 11 ++++--- 4 files changed, 109 insertions(+), 12 deletions(-) diff --git a/Gpio.cpp b/Gpio.cpp index db790f4..cdb12af 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -3,23 +3,47 @@ #include "Arduino.h" #include "Pins.h" #include "Logger.h" +#include "PreferencesKeys.h" Gpio* Gpio::_inst = nullptr; NukiWrapper* Gpio::_nuki = nullptr; unsigned long Gpio::_lockedTs = 0; const uint Gpio::_debounceTime = 1000; +Gpio::Gpio(Preferences* preferences, NukiWrapper* nuki) +: _preferences(preferences) +{ + loadPinConfiguration(); + + _inst = this; + _inst->init(nuki); +} + void Gpio::init(NukiWrapper* nuki) { _nuki = nuki; - pinMode(TRIGGER_LOCK_PIN, INPUT_PULLUP); - pinMode(TRIGGER_UNLOCK_PIN, INPUT_PULLUP); - pinMode(TRIGGER_UNLATCH_PIN, INPUT_PULLUP); - - attachInterrupt(TRIGGER_LOCK_PIN, isrLock, FALLING); - attachInterrupt(TRIGGER_UNLOCK_PIN, isrUnlock, FALLING); - attachInterrupt(TRIGGER_UNLATCH_PIN, isrUnlatch, FALLING); + for(const auto& entry : _inst->_pinConfiguration) + { + switch(entry.role) + { + case PinRole::InputLock: + pinMode(entry.pin, INPUT_PULLUP); + attachInterrupt(entry.pin, isrLock, FALLING); + break; + case PinRole::InputUnlock: + pinMode(entry.pin, INPUT_PULLUP); + attachInterrupt(entry.pin, isrUnlock, FALLING); + break; + case PinRole::InputUnlatch: + pinMode(entry.pin, INPUT_PULLUP); + attachInterrupt(entry.pin, isrUnlatch, FALLING); + break; + default: + pinMode(entry.pin, OUTPUT); + break; + } + } } void Gpio::isrLock() @@ -42,3 +66,46 @@ void Gpio::isrUnlatch() _nuki->unlatch(); _lockedTs = millis() + _debounceTime; } + +const std::vector& Gpio::availablePins() const +{ + return _availablePins; +} + +void Gpio::loadPinConfiguration() +{ + uint8_t serialized[_availablePins.size() * 2]; + + size_t size = _preferences->getBytes(preference_gpio_configuration, serialized, _availablePins.size() * 2); + if(size == 0) + { + return; + } + + size_t numEntries = size / 2; + + _pinConfiguration.clear(); + _pinConfiguration.reserve(numEntries); + for(int i=0; i < numEntries; i++) + { + PinEntry entry; + entry.pin = i * 2; + entry.role = (PinRole)(i * 2 +1); + _pinConfiguration.push_back(entry); + } +} + +void Gpio::savePinConfiguration(const std::vector &pinConfiguration) +{ + uint8_t serialized[pinConfiguration.size() * 2]; + + int len = pinConfiguration.size(); + for(int i=0; i < len; i++) + { + const auto& entry = pinConfiguration[i]; + serialized[i * 2] = entry.pin; + serialized[i * 2 + 1] = (int8_t)entry.role; + } + + _preferences->putBytes(preference_gpio_configuration, serialized, sizeof(serialized)); +} diff --git a/Gpio.h b/Gpio.h index 6d4383e..2ae5eef 100644 --- a/Gpio.h +++ b/Gpio.h @@ -3,13 +3,37 @@ #include "NukiWrapper.h" +enum class PinRole +{ + Undefined, + InputLock, + InputUnlock, + InputUnlatch, + OutputHighLocked, + OutputHighUnlocked, +}; + +struct PinEntry +{ + uint8_t pin = 0; + PinRole role = PinRole::Undefined; +}; + class Gpio { public: - Gpio() = delete; + Gpio(Preferences* preferences, NukiWrapper* nuki); static void init(NukiWrapper* nuki); + void loadPinConfiguration(); + void savePinConfiguration(const std::vector& pinConfiguration); + + const std::vector& availablePins() const; + private: + const std::vector _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 }; + + std::vector _pinConfiguration; static const uint _debounceTime; static void IRAM_ATTR isrLock(); @@ -19,4 +43,6 @@ private: static Gpio* _inst; static NukiWrapper* _nuki; static unsigned long _lockedTs; + + Preferences* _preferences = nullptr; }; diff --git a/PreferencesKeys.h b/PreferencesKeys.h index 4aed7cb..be0caee 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -44,6 +44,7 @@ #define preference_cred_password "crdpass" #define preference_publish_authdata "pubauth" #define preference_gpio_locking_enabled "gpiolck" +#define preference_gpio_configuration "gpiocfg" #define preference_publish_debug_info "pubdbg" #define preference_presence_detection_timeout "prdtimeout" #define preference_has_mac_saved "hasmac" diff --git a/main.cpp b/main.cpp index 7fe8644..e697b6f 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,7 @@ NukiOpenerWrapper* nukiOpener = nullptr; PresenceDetection* presenceDetection = nullptr; Preferences* preferences = nullptr; EthServer* ethServer = nullptr; +Gpio* gpio = nullptr; bool lockEnabled = false; bool openerEnabled = false; @@ -213,10 +214,12 @@ void setup() nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, networkLock, preferences); nuki->initialize(firstStart); - if(preferences->getBool(preference_gpio_locking_enabled)) - { - Gpio::init(nuki); - } + gpio = new Gpio(preferences, nuki); + +// if(preferences->getBool(preference_gpio_locking_enabled)) +// { +// Gpio::init(nuki); +// } } Log->println(openerEnabled ? F("NUKI Opener enabled") : F("NUKI Opener disabled")); From e3237a648cea13f1f91e4edefb6ccb6bad4a3a7e Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 13:10:44 +0200 Subject: [PATCH 2/9] gpio configuration via web interface --- Gpio.cpp | 165 ++++++++++++++++++++++++++++++++++------------- Gpio.h | 19 +++++- RestartReason.h | 3 + WebCfgServer.cpp | 111 ++++++++++++++++++++++++++++++- WebCfgServer.h | 16 +++-- main.cpp | 5 +- 6 files changed, 264 insertions(+), 55 deletions(-) diff --git a/Gpio.cpp b/Gpio.cpp index cdb12af..42f85d3 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -13,9 +13,9 @@ const uint Gpio::_debounceTime = 1000; Gpio::Gpio(Preferences* preferences, NukiWrapper* nuki) : _preferences(preferences) { + _inst = this; loadPinConfiguration(); - _inst = this; _inst->init(nuki); } @@ -25,6 +25,13 @@ void Gpio::init(NukiWrapper* nuki) for(const auto& entry : _inst->_pinConfiguration) { + const auto it = std::find(_inst->availablePins().begin(), _inst->availablePins().end(), entry.pin); + + if(it == _inst->availablePins().end()) + { + continue; + } + switch(entry.role) { case PinRole::InputLock: @@ -46,6 +53,117 @@ void Gpio::init(NukiWrapper* nuki) } } +const std::vector& Gpio::availablePins() const +{ + return _availablePins; +} + +void Gpio::loadPinConfiguration() +{ + size_t storedLength = _preferences->getBytesLength(preference_gpio_configuration); + if(storedLength == 0) + { + return; + } + + uint8_t serialized[storedLength]; + memset(serialized, 0, sizeof(serialized)); + + size_t size = _preferences->getBytes(preference_gpio_configuration, serialized, sizeof(serialized)); + + if(size == 0) + { + return; + } + + size_t numEntries = size / 2; + + _pinConfiguration.clear(); + _pinConfiguration.reserve(numEntries); + + for(int i=0; i < numEntries; i++) + { + PinEntry entry; + entry.pin = serialized[i * 2]; + entry.role = (PinRole) serialized[(i * 2 + 1)]; + if(entry.role != PinRole::Disabled) + { + _pinConfiguration.push_back(entry); + } + } +} + +void Gpio::savePinConfiguration(const std::vector &pinConfiguration) +{ + int8_t serialized[pinConfiguration.size() * 2]; + memset(serialized, 0, sizeof(serialized)); + + int len = pinConfiguration.size(); + for(int i=0; i < len; i++) + { + const auto& entry = pinConfiguration[i]; + + if(entry.role != PinRole::Disabled) + { + serialized[i * 2] = entry.pin; + serialized[i * 2 + 1] = (int8_t) entry.role; + } + } + + _preferences->putBytes(preference_gpio_configuration, serialized, sizeof(serialized)); +} + +const std::vector &Gpio::pinConfiguration() const +{ + return _pinConfiguration; +} + +String Gpio::getRoleDescription(PinRole role) const +{ + switch(role) + { + case PinRole::Disabled: + return "Disabled"; + case PinRole::InputLock: + return "Input: Lock"; + case PinRole::InputUnlock: + return "Input: Unlock"; + case PinRole::InputUnlatch: + return "Input: Unlatch"; + case PinRole::OutputHighLocked: + return "Output: High when locked"; + case PinRole::OutputHighUnlocked: + return "Output: High when unlocked"; + default: + return "Unknown"; + } +} + +void Gpio::getConfigurationText(String& text, const std::vector& pinConfiguration) const +{ + text.clear(); + + for(const auto& entry : pinConfiguration) + { + if(entry.role != PinRole::Disabled) + { + text.concat(entry.pin); + if(entry.pin < 10) + { + text.concat(' '); + } + text.concat(": "); + text.concat(getRoleDescription(entry.role)); + text.concat("\n\r"); + } + } +} + +const std::vector& Gpio::getAllRoles() const +{ + return _allRoles; +} + void Gpio::isrLock() { if(millis() < _lockedTs) return; @@ -65,47 +183,4 @@ void Gpio::isrUnlatch() if(millis() < _lockedTs) return; _nuki->unlatch(); _lockedTs = millis() + _debounceTime; -} - -const std::vector& Gpio::availablePins() const -{ - return _availablePins; -} - -void Gpio::loadPinConfiguration() -{ - uint8_t serialized[_availablePins.size() * 2]; - - size_t size = _preferences->getBytes(preference_gpio_configuration, serialized, _availablePins.size() * 2); - if(size == 0) - { - return; - } - - size_t numEntries = size / 2; - - _pinConfiguration.clear(); - _pinConfiguration.reserve(numEntries); - for(int i=0; i < numEntries; i++) - { - PinEntry entry; - entry.pin = i * 2; - entry.role = (PinRole)(i * 2 +1); - _pinConfiguration.push_back(entry); - } -} - -void Gpio::savePinConfiguration(const std::vector &pinConfiguration) -{ - uint8_t serialized[pinConfiguration.size() * 2]; - - int len = pinConfiguration.size(); - for(int i=0; i < len; i++) - { - const auto& entry = pinConfiguration[i]; - serialized[i * 2] = entry.pin; - serialized[i * 2 + 1] = (int8_t)entry.role; - } - - _preferences->putBytes(preference_gpio_configuration, serialized, sizeof(serialized)); -} +} \ No newline at end of file diff --git a/Gpio.h b/Gpio.h index 2ae5eef..a2b8ff6 100644 --- a/Gpio.h +++ b/Gpio.h @@ -5,7 +5,7 @@ enum class PinRole { - Undefined, + Disabled, InputLock, InputUnlock, InputUnlatch, @@ -16,7 +16,7 @@ enum class PinRole struct PinEntry { uint8_t pin = 0; - PinRole role = PinRole::Undefined; + PinRole role = PinRole::Disabled; }; class Gpio @@ -29,9 +29,24 @@ public: void savePinConfiguration(const std::vector& pinConfiguration); const std::vector& availablePins() const; + const std::vector& pinConfiguration() const; + + String getRoleDescription(PinRole role) const; + void getConfigurationText(String& text, const std::vector& pinConfiguration) const; + + const std::vector& getAllRoles() const; private: const std::vector _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 }; + const std::vector _allRoles = + { + PinRole::Disabled, + PinRole::InputLock, + PinRole::InputUnlock, + PinRole::InputUnlatch, + PinRole::OutputHighLocked, + PinRole::OutputHighUnlocked + }; std::vector _pinConfiguration; static const uint _debounceTime; diff --git a/RestartReason.h b/RestartReason.h index 4542da3..17752a0 100644 --- a/RestartReason.h +++ b/RestartReason.h @@ -12,6 +12,7 @@ enum class RestartReason ReconfigureLAN8720, NetworkDeviceCriticalFailure, ConfigurationUpdated, + GpioConfigurationUpdated, RestartTimer, OTACompleted, OTATimeout, @@ -69,6 +70,8 @@ inline static String getRestartReason() return "NetworkDeviceCriticalFailure"; case RestartReason::ConfigurationUpdated: return "ConfigurationUpdated"; + case RestartReason::GpioConfigurationUpdated: + return "GpioConfigurationUpdated"; case RestartReason::RestartTimer: return "RestartTimer"; case RestartReason::OTACompleted: diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index d002db4..20a1835 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -7,11 +7,12 @@ #include "RestartReason.h" #include -WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal) +WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, Gpio* gpio, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal) : _server(ethServer), _nuki(nuki), _nukiOpener(nukiOpener), _network(network), + _gpio(gpio), _preferences(preferences), _allowRestartToPortal(allowRestartToPortal) { @@ -93,6 +94,14 @@ void WebCfgServer::initialize() buildNukiConfigHtml(response); _server.send(200, "text/html", response); }); + _server.on("/gpiocfg", [&]() { + if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { + return _server.requestAuthentication(); + } + String response = ""; + buildGpioConfigHtml(response); + _server.send(200, "text/html", response); + }); _server.on("/wifi", [&]() { if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { return _server.requestAuthentication(); @@ -128,7 +137,8 @@ void WebCfgServer::initialize() _network->reconfigureDevice(); } }); - _server.on("/savecfg", [&]() { + _server.on("/savecfg", [&]() + { if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { return _server.requestAuthentication(); } @@ -152,6 +162,22 @@ void WebCfgServer::initialize() waitAndProcess(false, 1000); } }); + _server.on("/savegpiocfg", [&]() + { + if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { + return _server.requestAuthentication(); + } + processGpioArgs(); + + String response = ""; + buildConfirmHtml(response, ""); + _server.send(200, "text/html", response); + Log->println(F("Restarting")); + + waitAndProcess(true, 1000); + restartEsp(RestartReason::GpioConfigurationUpdated); + }); + _server.on("/ota", [&]() { if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { return _server.requestAuthentication(); @@ -523,6 +549,32 @@ bool WebCfgServer::processArgs(String& message) return configChanged; } + +void WebCfgServer::processGpioArgs() +{ + int count = _server.args(); + + std::vector pinConfiguration; + + for(int index = 0; index < count; index++) + { + String key = _server.argName(index); + String value = _server.arg(index); + + PinRole role = (PinRole)value.toInt(); + if(role != PinRole::Disabled) + { + PinEntry entry; + entry.pin = key.toInt(); + entry.role = role; + pinConfiguration.push_back(entry); + } + } + + _gpio->savePinConfiguration(pinConfiguration); +} + + void WebCfgServer::update() { if(_otaStartTs > 0 && (millis() - _otaStartTs) > 120000) @@ -575,6 +627,9 @@ void WebCfgServer::buildHtml(String& response) response.concat("

Credentials

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

GPIO Configuration

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

Firmware update

"); buildNavigationButton(response, "Open", "/ota"); @@ -780,6 +835,29 @@ void WebCfgServer::buildNukiConfigHtml(String &response) response.concat(""); } +void WebCfgServer::buildGpioConfigHtml(String &response) +{ + buildHtmlHeader(response); + + response.concat("
"); + response.concat("

GPIO Configuration

"); + response.concat(""); + + const auto& availablePins = _gpio->availablePins(); + for(const auto& pin : availablePins) + { + String pinStr = String(pin); + String pinDesc = "Gpio " + pinStr; + + printDropDown(response, pinStr.c_str(), pinDesc.c_str(), getPreselectionForGpio(pin), getGpioOptions()); + } + + response.concat("
"); + response.concat("
"); + response.concat("
"); + response.concat(""); +} + void WebCfgServer::buildConfirmHtml(String &response, const String &message, uint32_t redirectDelay) { String delay(redirectDelay); @@ -1226,3 +1304,32 @@ const std::vector> WebCfgServer::getNetworkGpioOptions return options; } + +const std::vector> WebCfgServer::getGpioOptions() const +{ + std::vector> options; + + const auto& roles = _gpio->getAllRoles(); + + for(const auto& role : roles) + { + options.push_back( std::make_pair(String((int)role), _gpio->getRoleDescription(role))); + } + + return options; +} + +String WebCfgServer::getPreselectionForGpio(const uint8_t &pin) +{ + const std::vector& pinConfiguration = _gpio->pinConfiguration(); + + for(const auto& entry : pinConfiguration) + { + if(pin == entry.pin) + { + return String((int8_t)entry.role); + } + } + + return String((int8_t)PinRole::Disabled); +} diff --git a/WebCfgServer.h b/WebCfgServer.h index 6356280..a7ad107 100644 --- a/WebCfgServer.h +++ b/WebCfgServer.h @@ -6,6 +6,7 @@ #include "NetworkLock.h" #include "NukiOpenerWrapper.h" #include "Ota.h" +#include "Gpio.h" extern TaskHandle_t networkTaskHandle; extern TaskHandle_t nukiTaskHandle; @@ -26,7 +27,7 @@ enum class TokenType class WebCfgServer { public: - WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal); + WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, Gpio* gpio, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal); ~WebCfgServer() = default; void initialize(); @@ -34,12 +35,14 @@ public: private: bool processArgs(String& message); + void processGpioArgs(); void buildHtml(String& response); void buildCredHtml(String& response); void buildOtaHtml(String& response, bool errored); void buildOtaCompletedHtml(String& response); void buildMqttConfigHtml(String& response); void buildNukiConfigHtml(String& response); + void buildGpioConfigHtml(String& response); void buildConfirmHtml(String& response, const String &message, uint32_t redirectDelay = 5); void buildConfigureWifiHtml(String& response); void buildInfoHtml(String& response); @@ -57,6 +60,8 @@ private: const std::vector> getNetworkDetectionOptions() const; const std::vector> getNetworkGpioOptions() const; + const std::vector> getGpioOptions() const; + String getPreselectionForGpio(const uint8_t& pin); void printParameter(String& response, const char* description, const char* value, const char *link = ""); @@ -65,10 +70,11 @@ private: void handleOtaUpload(); WebServer _server; - NukiWrapper* _nuki; - NukiOpenerWrapper* _nukiOpener; - Network* _network; - Preferences* _preferences; + NukiWrapper* _nuki = nullptr; + NukiOpenerWrapper* _nukiOpener = nullptr; + Network* _network = nullptr; + Gpio* _gpio = nullptr; + Preferences* _preferences = nullptr; Ota _ota; bool _hasCredentials = false; diff --git a/main.cpp b/main.cpp index e697b6f..a8850d9 100644 --- a/main.cpp +++ b/main.cpp @@ -215,6 +215,9 @@ void setup() nuki->initialize(firstStart); gpio = new Gpio(preferences, nuki); + String gpioDesc; + gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration()); + Serial.print(gpioDesc.c_str()); // if(preferences->getBool(preference_gpio_locking_enabled)) // { @@ -229,7 +232,7 @@ void setup() nukiOpener->initialize(); } - webCfgServer = new WebCfgServer(nuki, nukiOpener, network, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi); + webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi); webCfgServer->initialize(); presenceDetection = new PresenceDetection(preferences, bleScanner, network, CharBuffer::get(), CHAR_BUFFER_SIZE); From a30b7168695eba042a33062b38823718a4999c5f Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 14:00:05 +0200 Subject: [PATCH 3/9] set gpio output depending on lock state --- Gpio.cpp | 63 ++++++++++++++++++++++++++++++++++++------------- Gpio.h | 28 +++++++++++++++++----- NukiWrapper.cpp | 45 ++++++++++++++++++++++++++++++++++- NukiWrapper.h | 12 +++++++--- main.cpp | 12 ++++++---- 5 files changed, 128 insertions(+), 32 deletions(-) diff --git a/Gpio.cpp b/Gpio.cpp index 42f85d3..51df9d9 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -6,23 +6,20 @@ #include "PreferencesKeys.h" Gpio* Gpio::_inst = nullptr; -NukiWrapper* Gpio::_nuki = nullptr; -unsigned long Gpio::_lockedTs = 0; +unsigned long Gpio::_debounceTs = 0; const uint Gpio::_debounceTime = 1000; -Gpio::Gpio(Preferences* preferences, NukiWrapper* nuki) +Gpio::Gpio(Preferences* preferences) : _preferences(preferences) { _inst = this; loadPinConfiguration(); - _inst->init(nuki); + _inst->init(); } -void Gpio::init(NukiWrapper* nuki) +void Gpio::init() { - _nuki = nuki; - for(const auto& entry : _inst->_pinConfiguration) { const auto it = std::find(_inst->availablePins().begin(), _inst->availablePins().end(), entry.pin); @@ -118,6 +115,19 @@ const std::vector &Gpio::pinConfiguration() const return _pinConfiguration; } +PinRole Gpio::getPinRole(uint8_t pin) +{ + for(const auto& entry : _pinConfiguration) + { + if(entry.pin == pin) + { + return entry.role; + } + } + + return PinRole::Disabled; +} + String Gpio::getRoleDescription(PinRole role) const { switch(role) @@ -147,6 +157,7 @@ void Gpio::getConfigurationText(String& text, const std::vector& pinCo { if(entry.role != PinRole::Disabled) { + text.concat("GPIO "); text.concat(entry.pin); if(entry.pin < 10) { @@ -164,23 +175,41 @@ const std::vector& Gpio::getAllRoles() const return _allRoles; } +void Gpio::notify(const GpioAction &action) +{ + for(auto& callback : _callbacks) + { + callback(action); + } +} + +void Gpio::addCallback(std::function callback) +{ + _callbacks.push_back(callback); +} + void Gpio::isrLock() { - if(millis() < _lockedTs) return; - _nuki->lock(); - _lockedTs = millis() + _debounceTime; + if(millis() < _debounceTs) return; + _inst->notify(GpioAction::Lock); + _debounceTs = millis() + _debounceTime; } void Gpio::isrUnlock() { - if(millis() < _lockedTs) return; - _nuki->unlock(); - _lockedTs = millis() + _debounceTime; + if(millis() < _debounceTs) return; + _inst->notify(GpioAction::Unlock); + _debounceTs = millis() + _debounceTime; } void Gpio::isrUnlatch() { - if(millis() < _lockedTs) return; - _nuki->unlatch(); - _lockedTs = millis() + _debounceTime; -} \ No newline at end of file + if(millis() < _debounceTs) return; + _inst->notify(GpioAction::Unlatch); + _debounceTs = millis() + _debounceTime; +} + +void Gpio::setPinOutput(const uint8_t& pin, const uint8_t& state) +{ + digitalWrite(pin, state); +} diff --git a/Gpio.h b/Gpio.h index a2b8ff6..e36f498 100644 --- a/Gpio.h +++ b/Gpio.h @@ -1,7 +1,8 @@ #pragma once - -#include "NukiWrapper.h" +#include +#include +#include enum class PinRole { @@ -13,6 +14,13 @@ enum class PinRole OutputHighUnlocked, }; +enum class GpioAction +{ + Lock, + Unlock, + Unlatch +}; + struct PinEntry { uint8_t pin = 0; @@ -22,8 +30,10 @@ struct PinEntry class Gpio { public: - Gpio(Preferences* preferences, NukiWrapper* nuki); - static void init(NukiWrapper* nuki); + Gpio(Preferences* preferences); + static void init(); + + void addCallback(std::function callback); void loadPinConfiguration(); void savePinConfiguration(const std::vector& pinConfiguration); @@ -31,12 +41,17 @@ public: const std::vector& availablePins() const; const std::vector& pinConfiguration() const; + PinRole getPinRole(uint8_t pin); String getRoleDescription(PinRole role) const; void getConfigurationText(String& text, const std::vector& pinConfiguration) const; const std::vector& getAllRoles() const; + void setPinOutput(const uint8_t& pin, const uint8_t& state); + private: + void notify(const GpioAction& action); + const std::vector _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 }; const std::vector _allRoles = { @@ -55,9 +70,10 @@ private: static void IRAM_ATTR isrUnlock(); static void IRAM_ATTR isrUnlatch(); + std::vector> _callbacks; + static Gpio* _inst; - static NukiWrapper* _nuki; - static unsigned long _lockedTs; + static unsigned long _debounceTs; Preferences* _preferences = nullptr; }; diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 24ac52a..28c517b 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -8,11 +8,12 @@ NukiWrapper* nukiInst; -NukiWrapper::NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkLock* network, Preferences* preferences) +NukiWrapper::NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), _bleScanner(scanner), _nukiLock(deviceName, id), _network(network), + _gpio(gpio), _preferences(preferences) { nukiInst = this; @@ -26,6 +27,8 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner: network->setLockActionReceivedCallback(nukiInst->onLockActionReceivedCallback); network->setConfigUpdateReceivedCallback(nukiInst->onConfigUpdateReceivedCallback); network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback); + + _gpio->addCallback(NukiWrapper::gpioActionCallback); } @@ -307,6 +310,7 @@ void NukiWrapper::updateKeyTurnerState() _retryLockstateCount = 0; _network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState); + updateGpioOutputs(); char lockStateStr[20]; lockstateToString(_keyTurnerState.lockState, lockStateStr); @@ -446,6 +450,22 @@ void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uin nukiInst->onKeypadCommandReceived(command, id, name, code, enabled); } +void NukiWrapper::gpioActionCallback(const GpioAction &action) +{ + switch(action) + { + case GpioAction::Lock: + nukiInst->lock(); + break; + case GpioAction::Unlock: + nukiInst->unlock(); + break; + case GpioAction::Unlatch: + nukiInst->unlatch(); + break; + } +} + void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value) { if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) @@ -719,3 +739,26 @@ void NukiWrapper::disableWatchdog() { _restartBeaconTimeout = -1; } + +void NukiWrapper::updateGpioOutputs() +{ + using namespace NukiLock; + + const auto& pinConfiguration = _gpio->pinConfiguration(); + + const LockState& lockState = _keyTurnerState.lockState; + + for(const auto& entry : pinConfiguration) + { + switch(entry.role) + { + case PinRole::OutputHighLocked: + _gpio->setPinOutput(entry.pin, lockState == LockState::Locked || lockState == LockState::Locking ? HIGH : LOW); + break; + case PinRole::OutputHighUnlocked: + _gpio->setPinOutput(entry.pin, lockState == LockState::Locked || lockState == LockState::Locking ? LOW : HIGH); + break; + } + } +} + diff --git a/NukiWrapper.h b/NukiWrapper.h index 081530f..e553f15 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -5,11 +5,12 @@ #include "NukiDataTypes.h" #include "BleScanner.h" #include "NukiLock.h" +#include "Gpio.h" class NukiWrapper : public Nuki::SmartlockEventHandler { public: - NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkLock* network, Preferences* preferences); + NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences); virtual ~NukiWrapper(); void initialize(const bool& firstStart); @@ -42,6 +43,8 @@ private: static bool 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); + void onConfigUpdateReceived(const char* topic, const char* value); void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled); @@ -52,6 +55,8 @@ private: void updateKeypad(); void postponeBleWatchdog(); + void updateGpioOutputs(); + void readConfig(); void readAdvancedConfig(); @@ -63,8 +68,9 @@ private: std::string _deviceName; NukiLock::NukiLock _nukiLock; - BleScanner::Scanner* _bleScanner; - NetworkLock* _network; + BleScanner::Scanner* _bleScanner = nullptr; + NetworkLock* _network = nullptr; + Gpio* _gpio = nullptr; Preferences* _preferences; int _intervalLockstate = 0; // seconds int _intervalBattery = 0; // seconds diff --git a/main.cpp b/main.cpp index a8850d9..a5f2ab2 100644 --- a/main.cpp +++ b/main.cpp @@ -208,16 +208,18 @@ void setup() bleScanner->initialize("NukiHub"); bleScanner->setScanDuration(10); + gpio = new Gpio(preferences); + String gpioDesc; + gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration()); + Serial.print(gpioDesc.c_str()); + Log->println(lockEnabled ? F("NUKI Lock enabled") : F("NUKI Lock disabled")); if(lockEnabled) { - nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, networkLock, preferences); + nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, networkLock, gpio, preferences); nuki->initialize(firstStart); - gpio = new Gpio(preferences, nuki); - String gpioDesc; - gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration()); - Serial.print(gpioDesc.c_str()); + // if(preferences->getBool(preference_gpio_locking_enabled)) // { From a7837507417e36577db030a2cd2099cd645936a7 Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 15:39:09 +0200 Subject: [PATCH 4/9] add motor blocked gpio output --- Gpio.cpp | 2 ++ Gpio.h | 1 + NukiWrapper.cpp | 3 +++ 3 files changed, 6 insertions(+) diff --git a/Gpio.cpp b/Gpio.cpp index 51df9d9..af70ce7 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -144,6 +144,8 @@ String Gpio::getRoleDescription(PinRole role) const return "Output: High when locked"; case PinRole::OutputHighUnlocked: return "Output: High when unlocked"; + case PinRole::OutputHighMotorBlocked: + return "Output: High when motor blocked"; default: return "Unknown"; } diff --git a/Gpio.h b/Gpio.h index e36f498..242849e 100644 --- a/Gpio.h +++ b/Gpio.h @@ -12,6 +12,7 @@ enum class PinRole InputUnlatch, OutputHighLocked, OutputHighUnlocked, + OutputHighMotorBlocked, }; enum class GpioAction diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index 28c517b..8b527f8 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -758,6 +758,9 @@ void NukiWrapper::updateGpioOutputs() case PinRole::OutputHighUnlocked: _gpio->setPinOutput(entry.pin, lockState == LockState::Locked || lockState == LockState::Locking ? LOW : HIGH); break; + case PinRole::OutputHighMotorBlocked: + _gpio->setPinOutput(entry.pin, lockState == LockState::MotorBlocked ? HIGH : LOW); + break; } } } From 0ae0fb6265124e10654d6139b3ee584988d85698 Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 16:23:51 +0200 Subject: [PATCH 5/9] add opener gpio control --- Gpio.cpp | 65 ++++++++++++++++++++++++++++++++++--------- Gpio.h | 19 +++++++++++-- NukiOpenerWrapper.cpp | 54 ++++++++++++++++++++++++++++++++++- NukiOpenerWrapper.h | 16 ++++++++--- WebCfgServer.cpp | 8 +++--- main.cpp | 2 +- 6 files changed, 139 insertions(+), 25 deletions(-) diff --git a/Gpio.cpp b/Gpio.cpp index af70ce7..7917cdd 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -43,6 +43,22 @@ void Gpio::init() pinMode(entry.pin, INPUT_PULLUP); attachInterrupt(entry.pin, isrUnlatch, FALLING); break; + case PinRole::InputElectricStrikeActuation: + pinMode(entry.pin, INPUT_PULLUP); + attachInterrupt(entry.pin, isrElectricStrikeActuation, FALLING); + break; + case PinRole::InputActivateRTO: + pinMode(entry.pin, INPUT_PULLUP); + attachInterrupt(entry.pin, isrActivateRTO, FALLING); + break; + case PinRole::InputActivateCM: + pinMode(entry.pin, INPUT_PULLUP); + attachInterrupt(entry.pin, isrActivateCM, FALLING); + break; + case PinRole::InputDeactivateRtoCm: + pinMode(entry.pin, INPUT_PULLUP); + attachInterrupt(entry.pin, isrDeactivateRtoCm, FALLING); + break; default: pinMode(entry.pin, OUTPUT); break; @@ -115,19 +131,6 @@ const std::vector &Gpio::pinConfiguration() const return _pinConfiguration; } -PinRole Gpio::getPinRole(uint8_t pin) -{ - for(const auto& entry : _pinConfiguration) - { - if(entry.pin == pin) - { - return entry.role; - } - } - - return PinRole::Disabled; -} - String Gpio::getRoleDescription(PinRole role) const { switch(role) @@ -140,6 +143,14 @@ String Gpio::getRoleDescription(PinRole role) const return "Input: Unlock"; case PinRole::InputUnlatch: return "Input: Unlatch"; + case PinRole::InputElectricStrikeActuation: + return "Input: Electric strike actuation"; + case PinRole::InputActivateRTO: + return "Input: Activate RTO"; + case PinRole::InputActivateCM: + return "Input: Activate CM"; + case PinRole::InputDeactivateRtoCm: + return "Input: Deactivate RTO/CM"; case PinRole::OutputHighLocked: return "Output: High when locked"; case PinRole::OutputHighUnlocked: @@ -211,6 +222,34 @@ void Gpio::isrUnlatch() _debounceTs = millis() + _debounceTime; } +void Gpio::isrElectricStrikeActuation() +{ + if(millis() < _debounceTs) return; + _inst->notify(GpioAction::ElectricStrikeActuation); + _debounceTs = millis() + _debounceTime; +} + +void Gpio::isrActivateRTO() +{ + if(millis() < _debounceTs) return; + _inst->notify(GpioAction::ActivateRTO); + _debounceTs = millis() + _debounceTime; +} + +void Gpio::isrActivateCM() +{ + if(millis() < _debounceTs) return; + _inst->notify(GpioAction::ActivateCM); + _debounceTs = millis() + _debounceTime; +} + +void Gpio::isrDeactivateRtoCm() +{ + if(millis() < _debounceTs) return; + _inst->notify(GpioAction::DeactivateRtoCm); + _debounceTs = millis() + _debounceTime; +} + void Gpio::setPinOutput(const uint8_t& pin, const uint8_t& state) { digitalWrite(pin, state); diff --git a/Gpio.h b/Gpio.h index 242849e..dd1246f 100644 --- a/Gpio.h +++ b/Gpio.h @@ -10,6 +10,10 @@ enum class PinRole InputLock, InputUnlock, InputUnlatch, + InputElectricStrikeActuation, + InputActivateRTO, + InputActivateCM, + InputDeactivateRtoCm, OutputHighLocked, OutputHighUnlocked, OutputHighMotorBlocked, @@ -19,7 +23,11 @@ enum class GpioAction { Lock, Unlock, - Unlatch + Unlatch, + ElectricStrikeActuation, + ActivateRTO, + ActivateCM, + DeactivateRtoCm }; struct PinEntry @@ -42,7 +50,6 @@ public: const std::vector& availablePins() const; const std::vector& pinConfiguration() const; - PinRole getPinRole(uint8_t pin); String getRoleDescription(PinRole role) const; void getConfigurationText(String& text, const std::vector& pinConfiguration) const; @@ -60,6 +67,10 @@ private: PinRole::InputLock, PinRole::InputUnlock, PinRole::InputUnlatch, + PinRole::InputElectricStrikeActuation, + PinRole::InputActivateRTO, + PinRole::InputActivateCM, + PinRole::InputDeactivateRtoCm, PinRole::OutputHighLocked, PinRole::OutputHighUnlocked }; @@ -70,6 +81,10 @@ private: static void IRAM_ATTR isrLock(); static void IRAM_ATTR isrUnlock(); static void IRAM_ATTR isrUnlatch(); + static void IRAM_ATTR isrElectricStrikeActuation(); + static void IRAM_ATTR isrActivateRTO(); + static void IRAM_ATTR isrActivateCM(); + static void IRAM_ATTR isrDeactivateRtoCm(); std::vector> _callbacks; diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index cc33c25..3fa37a0 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -8,11 +8,12 @@ NukiOpenerWrapper* nukiOpenerInst; -NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkOpener* network, Preferences* preferences) +NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences) : _deviceName(deviceName), _nukiOpener(deviceName, id), _bleScanner(scanner), _network(network), + _gpio(gpio), _preferences(preferences) { nukiOpenerInst = this; @@ -26,6 +27,8 @@ NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, uint32_t id, network->setLockActionReceivedCallback(nukiOpenerInst->onLockActionReceivedCallback); network->setConfigUpdateReceivedCallback(nukiOpenerInst->onConfigUpdateReceivedCallback); network->setKeypadCommandReceivedCallback(nukiOpenerInst->onKeypadCommandReceivedCallback); + + _gpio->addCallback(NukiOpenerWrapper::gpioActionCallback); } @@ -252,6 +255,36 @@ void NukiOpenerWrapper::update() memcpy(&_lastKeyTurnerState, &_keyTurnerState, sizeof(NukiOpener::OpenerState)); } + +void NukiOpenerWrapper::electricStrikeActuation() +{ + _nextLockAction = NukiOpener::LockAction::ElectricStrikeActuation; +} + +void NukiOpenerWrapper::activateRTO() +{ + _nextLockAction = NukiOpener::LockAction::ActivateRTO; +} + +void NukiOpenerWrapper::activateCM() +{ + _nextLockAction = NukiOpener::LockAction::ActivateCM; +} + +void NukiOpenerWrapper::deactivateRtoCm() +{ + if(_keyTurnerState.nukiState == NukiOpener::State::ContinuousMode) + { + _nextLockAction = NukiOpener::LockAction::DeactivateCM; + return; + } + + if(_keyTurnerState.lockState == NukiOpener::LockState::RTOactive) + { + _nextLockAction = NukiOpener::LockAction::DeactivateRTO; + } +} + bool NukiOpenerWrapper::isPinSet() { return _nukiOpener.getSecurityPincode() != 0; @@ -448,6 +481,25 @@ void NukiOpenerWrapper::onKeypadCommandReceivedCallback(const char *command, con nukiOpenerInst->onKeypadCommandReceived(command, id, name, code, enabled); } +void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action) +{ + switch(action) + { + case GpioAction::ElectricStrikeActuation: + nukiOpenerInst->electricStrikeActuation(); + break; + case GpioAction::ActivateRTO: + nukiOpenerInst->activateRTO(); + break; + case GpioAction::ActivateCM: + nukiOpenerInst->activateCM(); + break; + case GpioAction::DeactivateRtoCm: + nukiOpenerInst->deactivateRtoCm(); + break; + } +} + void NukiOpenerWrapper::onConfigUpdateReceived(const char *topic, const char *value) { if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index 01a903a..fe8b681 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -5,16 +5,22 @@ #include "NukiOpenerConstants.h" #include "NukiDataTypes.h" #include "BleScanner.h" +#include "Gpio.h" class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler { public: - NukiOpenerWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkOpener* network, Preferences* preferences); + NukiOpenerWrapper(const std::string& deviceName, uint32_t id, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences); virtual ~NukiOpenerWrapper(); void initialize(); void update(); + void electricStrikeActuation(); + void activateRTO(); + void activateCM(); + void deactivateRtoCm(); + bool isPinSet(); void setPin(const uint16_t pin); @@ -40,6 +46,7 @@ private: static bool 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); void onConfigUpdateReceived(const char* topic, const char* value); void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled); @@ -61,9 +68,10 @@ private: std::string _deviceName; NukiOpener::NukiOpener _nukiOpener; - BleScanner::Scanner* _bleScanner; - NetworkOpener* _network; - Preferences* _preferences; + BleScanner::Scanner* _bleScanner = nullptr; + NetworkOpener* _network = nullptr; + Gpio* _gpio = nullptr; + Preferences* _preferences = nullptr; int _intervalLockstate = 0; // seconds int _intervalBattery = 0; // seconds int _intervalConfig = 60 * 60; // seconds diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 20a1835..9b96f41 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -36,13 +36,13 @@ WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Net _pinsConfigured = true; - if(_nuki != nullptr) + if(_nuki != nullptr && !_nuki->isPinSet()) { - _pinsConfigured = _pinsConfigured && _nuki->isPinSet(); + _pinsConfigured = false; } - if(_nukiOpener != nullptr) + if(_nukiOpener != nullptr && !_nukiOpener->isPinSet()) { - _pinsConfigured = _pinsConfigured && _nukiOpener->isPinSet(); + _pinsConfigured = false; } _brokerConfigured = _preferences->getString(preference_mqtt_broker).length() > 0 && _preferences->getInt(preference_mqtt_broker_port) > 0; diff --git a/main.cpp b/main.cpp index a5f2ab2..15357b7 100644 --- a/main.cpp +++ b/main.cpp @@ -230,7 +230,7 @@ void setup() Log->println(openerEnabled ? F("NUKI Opener enabled") : F("NUKI Opener disabled")); if(openerEnabled) { - nukiOpener = new NukiOpenerWrapper("NukiHub", deviceId, bleScanner, networkOpener, preferences); + nukiOpener = new NukiOpenerWrapper("NukiHub", deviceId, bleScanner, networkOpener, gpio, preferences); nukiOpener->initialize(); } From 10c662fb5f48615968cda49984718ea467214dcd Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 17:01:17 +0200 Subject: [PATCH 6/9] add output of opener states via gpio --- Gpio.cpp | 6 ++++++ Gpio.h | 8 +++++++- NukiOpenerWrapper.cpp | 29 +++++++++++++++++++++++++++++ NukiOpenerWrapper.h | 2 ++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Gpio.cpp b/Gpio.cpp index 7917cdd..3c9c1af 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -157,6 +157,12 @@ String Gpio::getRoleDescription(PinRole role) const return "Output: High when unlocked"; case PinRole::OutputHighMotorBlocked: return "Output: High when motor blocked"; + case PinRole::OutputHighRtoActive: + return "Output: High when RTO active"; + case PinRole::OutputHighCmActive: + return "Output: High when CM active"; + case PinRole::OutputHighRtoOrCmActive: + return "Output: High when RTO or CM active"; default: return "Unknown"; } diff --git a/Gpio.h b/Gpio.h index dd1246f..5b0e115 100644 --- a/Gpio.h +++ b/Gpio.h @@ -17,6 +17,9 @@ enum class PinRole OutputHighLocked, OutputHighUnlocked, OutputHighMotorBlocked, + OutputHighRtoActive, + OutputHighCmActive, + OutputHighRtoOrCmActive }; enum class GpioAction @@ -72,7 +75,10 @@ private: PinRole::InputActivateCM, PinRole::InputDeactivateRtoCm, PinRole::OutputHighLocked, - PinRole::OutputHighUnlocked + PinRole::OutputHighUnlocked, + PinRole::OutputHighRtoActive, + PinRole::OutputHighCmActive, + PinRole::OutputHighRtoOrCmActive, }; std::vector _pinConfiguration; diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index 3fa37a0..df4df28 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -334,6 +334,7 @@ void NukiOpenerWrapper::updateKeyTurnerState() else { _network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState); + updateGpioOutputs(); if(_keyTurnerState.nukiState == NukiOpener::State::ContinuousMode) { @@ -744,3 +745,31 @@ void NukiOpenerWrapper::disableWatchdog() { _restartBeaconTimeout = -1; } + +void NukiOpenerWrapper::updateGpioOutputs() +{ + using namespace NukiOpener; + + const auto& pinConfiguration = _gpio->pinConfiguration(); + + const LockState& lockState = _keyTurnerState.lockState; + + bool rtoActive = _keyTurnerState.lockState == LockState::RTOactive; + bool cmActive = _keyTurnerState.nukiState == State::ContinuousMode; + + for(const auto& entry : pinConfiguration) + { + switch(entry.role) + { + case PinRole::OutputHighRtoActive: + _gpio->setPinOutput(entry.pin, rtoActive ? HIGH : LOW); + break; + case PinRole::OutputHighCmActive: + _gpio->setPinOutput(entry.pin, cmActive ? HIGH : LOW); + break; + case PinRole::OutputHighRtoOrCmActive: + _gpio->setPinOutput(entry.pin, rtoActive || cmActive ? HIGH : LOW); + break; + } + } +} \ No newline at end of file diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index fe8b681..da80621 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -57,6 +57,8 @@ private: void updateKeypad(); void postponeBleWatchdog(); + void updateGpioOutputs(); + void readConfig(); void readAdvancedConfig(); From f21ff7d1e92b5caafeef78ebedb323e6e85633dc Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 18:43:42 +0200 Subject: [PATCH 7/9] add migration for obsolete gpio setting --- Gpio.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++++- Gpio.h | 2 ++ PreferencesKeys.h | 4 ++-- WebCfgServer.cpp | 6 ------ main.cpp | 1 + 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Gpio.cpp b/Gpio.cpp index 3c9c1af..df2099d 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -4,6 +4,7 @@ #include "Pins.h" #include "Logger.h" #include "PreferencesKeys.h" +#include "RestartReason.h" Gpio* Gpio::_inst = nullptr; unsigned long Gpio::_debounceTs = 0; @@ -15,6 +16,11 @@ Gpio::Gpio(Preferences* preferences) _inst = this; loadPinConfiguration(); + if(_preferences->getBool(preference_gpio_locking_enabled)) + { + migrateObsoleteSetting(); + } + _inst->init(); } @@ -108,7 +114,7 @@ void Gpio::loadPinConfiguration() void Gpio::savePinConfiguration(const std::vector &pinConfiguration) { - int8_t serialized[pinConfiguration.size() * 2]; + int8_t serialized[std::max(pinConfiguration.size() * 2, _preferences->getBytesLength(preference_gpio_configuration))]; memset(serialized, 0, sizeof(serialized)); int len = pinConfiguration.size(); @@ -123,6 +129,13 @@ void Gpio::savePinConfiguration(const std::vector &pinConfiguration) } } + for(int8_t v : serialized) + { + Serial.print((int)v); + Serial.print(" "); + } + Serial.println(); + _preferences->putBytes(preference_gpio_configuration, serialized, sizeof(serialized)); } @@ -260,3 +273,35 @@ 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(); + + PinEntry entry1; + entry1.pin = 27; + entry1.role = PinRole::InputUnlatch; + + PinEntry entry2; + entry2.pin = 32; + entry2.role = PinRole::InputLock; + + PinEntry entry3; + entry3.pin = 33; + entry3.role = PinRole::InputUnlock; + + _pinConfiguration.push_back(entry1); + _pinConfiguration.push_back(entry2); + _pinConfiguration.push_back(entry3); + + savePinConfiguration(_pinConfiguration); + + _preferences->remove(preference_gpio_locking_enabled); + Log->println("Migrated gpio control setting"); + delay(200); + restartEsp(RestartReason::GpioConfigurationUpdated); +} diff --git a/Gpio.h b/Gpio.h index 5b0e115..b67eed8 100644 --- a/Gpio.h +++ b/Gpio.h @@ -45,6 +45,8 @@ public: Gpio(Preferences* preferences); static void init(); + void migrateObsoleteSetting(); + void addCallback(std::function callback); void loadPinConfiguration(); diff --git a/PreferencesKeys.h b/PreferencesKeys.h index be0caee..ca6c3aa 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -69,7 +69,7 @@ private: 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_command_retry_delay, preference_cred_user, preference_cred_password, preference_publish_authdata, - preference_gpio_locking_enabled, preference_publish_debug_info, preference_presence_detection_timeout, + 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, }; std::vector _redact = @@ -82,7 +82,7 @@ private: { preference_started_before, preference_mqtt_log_enabled, preference_lock_enabled, preference_opener_enabled, preference_restart_on_disconnect, preference_keypad_control_enabled, preference_register_as_app, preference_ip_dhcp_enabled, - preference_publish_authdata, preference_gpio_locking_enabled, preference_has_mac_saved, preference_publish_debug_info + preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info }; const bool isRedacted(const char* key) const diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 9b96f41..044f7c9 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -451,11 +451,6 @@ bool WebCfgServer::processArgs(String& message) _preferences->putBool(preference_publish_authdata, (value == "1")); configChanged = true; } - else if(key == "GPLCK") - { - _preferences->putBool(preference_gpio_locking_enabled, (value == "1")); - configChanged = true; - } else if(key == "REGAPP") { _preferences->putBool(preference_register_as_app, (value == "1")); @@ -826,7 +821,6 @@ 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, "GPLCK", "Enable control via GPIO", _preferences->getBool(preference_gpio_locking_enabled)); 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(""); diff --git a/main.cpp b/main.cpp index 15357b7..e88522e 100644 --- a/main.cpp +++ b/main.cpp @@ -170,6 +170,7 @@ void setup() Log->print(F("NUKI Hub version ")); Log->println(NUKI_HUB_VERSION); bool firstStart = initPreferences(); + initializeRestartReason(); CharBuffer::initialize(); From c2ce5a151dcabab881221a843079182b87370bfa Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 19:03:20 +0200 Subject: [PATCH 8/9] make wifi default network device, remove W5500 GPIO detection --- Gpio.cpp | 7 ------- Network.cpp | 17 +++-------------- PreferencesKeys.h | 3 +-- README.md | 5 ++--- WebCfgServer.cpp | 22 +--------------------- WebCfgServer.h | 1 - 6 files changed, 7 insertions(+), 48 deletions(-) diff --git a/Gpio.cpp b/Gpio.cpp index df2099d..90e961c 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -129,13 +129,6 @@ void Gpio::savePinConfiguration(const std::vector &pinConfiguration) } } - for(int8_t v : serialized) - { - Serial.print((int)v); - Serial.print(" "); - } - Serial.println(); - _preferences->putBytes(preference_gpio_configuration, serialized, sizeof(serialized)); } diff --git a/Network.cpp b/Network.cpp index 690ee56..4156a88 100644 --- a/Network.cpp +++ b/Network.cpp @@ -46,21 +46,14 @@ void Network::setupDevice() _ipConfiguration = new IPConfiguration(_preferences); int hardwareDetect = _preferences->getInt(preference_network_hardware); - int hardwareDetectGpio = _preferences->getInt(preference_network_hardware_gpio); Log->print(F("Hardware detect : ")); Log->println(hardwareDetect); - Log->print(F("Hardware detect GPIO: ")); Log->println(hardwareDetectGpio); if(hardwareDetect == 0) { - hardwareDetect = 2; + hardwareDetect = 1; _preferences->putInt(preference_network_hardware, hardwareDetect); } - if(hardwareDetectGpio == 0) - { - hardwareDetectGpio = 26; - _preferences->putInt(preference_network_hardware_gpio, hardwareDetectGpio); - } if(strcmp(WiFi_fallbackDetect, "wifi_fallback") == 0) { @@ -77,12 +70,8 @@ void Network::setupDevice() _networkDeviceType = NetworkDeviceType::WiFi; break; case 2: - Log->print(F("Using PIN ")); - Log->print(hardwareDetectGpio); - Log->println(F(" for network device selection")); - - pinMode(hardwareDetectGpio, INPUT_PULLUP); - _networkDeviceType = digitalRead(hardwareDetectGpio) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500; + Log->print(F("Generic W5500")); + _networkDeviceType = NetworkDeviceType::W5500; break; case 3: Log->println(F("W5500 on M5Stack Atom POE")); diff --git a/PreferencesKeys.h b/PreferencesKeys.h index ca6c3aa..81009e1 100644 --- a/PreferencesKeys.h +++ b/PreferencesKeys.h @@ -25,7 +25,6 @@ #define preference_ip_gateway "ipgtw" #define preference_ip_dns_server "dnssrv" #define preference_network_hardware "nwhw" -#define preference_network_hardware_gpio "nwhwdt" #define preference_rssi_publish_interval "rssipb" #define preference_hostname "hostname" #define preference_network_timeout "nettmout" @@ -63,7 +62,7 @@ private: preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_mqtt_hass_discovery, preference_ip_dhcp_enabled, preference_ip_address, preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, - preference_network_hardware, preference_network_hardware_gpio, preference_rssi_publish_interval, + 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_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, diff --git a/README.md b/README.md index 58b1506..9b411d2 100644 --- a/README.md +++ b/README.md @@ -198,11 +198,10 @@ W5x00 SCK to GPIO18
W5x00 MISO to GPIOGPIO19
W5x00 MOSI to GPIO23
W5x00 CS/SS to GPIO5 -- Additionally connect:
+- Optionally connect:
W5x00 reset to GPIO33 -- Last but not least, on the ESP32 bridge GPIO26 and GND. This let's the firmware know that a LAN Module is connected -Wifi is now disabled, and the module doesn't boot into WifiManager anymore.
+Now connect via Wifi and change the network hardware to "Generic W5500". If the W5500 hwardware isn't detected, Wifi is used as a fallback.
Note: Encrypted MQTT is only available for Wifi and LAN8720 modules, W5x00 modules don't support encryption (that leaves Olimex, WT32-ETH01 and M5Stack PoESP32 Unit if encryption is desired). If encryption is needed, Olimex is the easiest option, since it has USB for flashing onboard. diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 044f7c9..0e14c58 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -324,11 +324,6 @@ bool WebCfgServer::processArgs(String& message) _preferences->putInt(preference_network_hardware, value.toInt()); configChanged = true; } - else if(key == "NWHWDT") - { - _preferences->putInt(preference_network_hardware_gpio, value.toInt()); - configChanged = true; - } else if(key == "RSSI") { _preferences->putInt(preference_rssi_publish_interval, value.toInt()); @@ -763,7 +758,6 @@ void WebCfgServer::buildMqttConfigHtml(String &response) printTextarea(response, "MQTTCRT", "MQTT SSL Client Certificate (*, optional)", _preferences->getString(preference_mqtt_crt).c_str(), TLS_CERT_MAX_SIZE, _network->encryptionSupported(), true); printTextarea(response, "MQTTKEY", "MQTT SSL Client Key (*, optional)", _preferences->getString(preference_mqtt_key).c_str(), TLS_KEY_MAX_SIZE, _network->encryptionSupported(), true); printDropDown(response, "NWHW", "Network hardware", String(_preferences->getInt(preference_network_hardware)), getNetworkDetectionOptions()); - printDropDown(response, "NWHWDT", "Network hardware detection", String(_preferences->getInt(preference_network_hardware_gpio)), getNetworkGpioOptions()); 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)); @@ -1275,7 +1269,7 @@ const std::vector> WebCfgServer::getNetworkDetectionOp std::vector> options; options.push_back(std::make_pair("1", "Wifi only")); - options.push_back(std::make_pair("2", "Detect W5500 (GPIO CS=5; SCK=18; MISO=19; MOSI=23; RST=33)")); + options.push_back(std::make_pair("2", "Generic W5500")); options.push_back(std::make_pair("3", "M5Stack Atom POE (W5500)")); options.push_back(std::make_pair("4", "Olimex ESP32-POE / ESP-POE-ISO")); options.push_back(std::make_pair("5", "WT32-ETH01")); @@ -1285,20 +1279,6 @@ const std::vector> WebCfgServer::getNetworkDetectionOp return options; } -const std::vector> WebCfgServer::getNetworkGpioOptions() const -{ - std::vector> options; - - for(int i=16; i <= 33; i++) - { - String txt = "Detect W5500 via GPIO "; - txt.concat(i); - options.push_back(std::make_pair(String(i), txt)); - } - - return options; -} - const std::vector> WebCfgServer::getGpioOptions() const { std::vector> options; diff --git a/WebCfgServer.h b/WebCfgServer.h index a7ad107..4a3b5d7 100644 --- a/WebCfgServer.h +++ b/WebCfgServer.h @@ -59,7 +59,6 @@ private: void buildNavigationButton(String& response, const char* caption, const char* targetPath, const char* labelText = ""); const std::vector> getNetworkDetectionOptions() const; - const std::vector> getNetworkGpioOptions() const; const std::vector> getGpioOptions() const; String getPreselectionForGpio(const uint8_t& pin); From 16a7f6fc7800575d18065c092c2319fcfb5588f9 Mon Sep 17 00:00:00 2001 From: technyon Date: Fri, 7 Apr 2023 21:17:16 +0200 Subject: [PATCH 9/9] add gpio configuration to sysinfo page --- Config.h | 2 +- Gpio.cpp | 6 ++---- Gpio.h | 2 +- WebCfgServer.cpp | 2 ++ main.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Config.h b/Config.h index 36f9084..36f828a 100644 --- a/Config.h +++ b/Config.h @@ -1,6 +1,6 @@ #pragma once -#define NUKI_HUB_VERSION "8.21" +#define NUKI_HUB_VERSION "8.22-pre-1" #define MQTT_QOS_LEVEL 1 #define MQTT_CLEAN_SESSIONS false diff --git a/Gpio.cpp b/Gpio.cpp index 90e961c..e3680cc 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -174,10 +174,8 @@ String Gpio::getRoleDescription(PinRole role) const } } -void Gpio::getConfigurationText(String& text, const std::vector& pinConfiguration) const +void Gpio::getConfigurationText(String& text, const std::vector& pinConfiguration, const String& linebreak) const { - text.clear(); - for(const auto& entry : pinConfiguration) { if(entry.role != PinRole::Disabled) @@ -190,7 +188,7 @@ void Gpio::getConfigurationText(String& text, const std::vector& pinCo } text.concat(": "); text.concat(getRoleDescription(entry.role)); - text.concat("\n\r"); + text.concat(linebreak); } } } diff --git a/Gpio.h b/Gpio.h index b67eed8..768d717 100644 --- a/Gpio.h +++ b/Gpio.h @@ -56,7 +56,7 @@ public: const std::vector& pinConfiguration() const; String getRoleDescription(PinRole role) const; - void getConfigurationText(String& text, const std::vector& pinConfiguration) const; + void getConfigurationText(String& text, const std::vector& pinConfiguration, const String& linebreak = "\n") const; const std::vector& getAllRoles() const; diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 0e14c58..e6ca370 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -938,6 +938,8 @@ void WebCfgServer::buildInfoHtml(String &response) response.concat(uxTaskGetStackHighWaterMark(presenceDetectionTaskHandle)); response.concat("\n"); + _gpio->getConfigurationText(response, _gpio->pinConfiguration()); + response.concat("Restart reason FW: "); response.concat(getRestartReason()); response.concat( "\n"); diff --git a/main.cpp b/main.cpp index e88522e..97dc63b 100644 --- a/main.cpp +++ b/main.cpp @@ -211,7 +211,7 @@ void setup() gpio = new Gpio(preferences); String gpioDesc; - gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration()); + gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration(), "\n\r"); Serial.print(gpioDesc.c_str()); Log->println(lockEnabled ? F("NUKI Lock enabled") : F("NUKI Lock disabled"));