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