gpio configuration via web interface

This commit is contained in:
technyon
2023-04-07 13:10:44 +02:00
parent 29c664e3ba
commit e3237a648c
6 changed files with 264 additions and 55 deletions

163
Gpio.cpp
View File

@@ -13,9 +13,9 @@ const uint Gpio::_debounceTime = 1000;
Gpio::Gpio(Preferences* preferences, NukiWrapper* nuki) Gpio::Gpio(Preferences* preferences, NukiWrapper* nuki)
: _preferences(preferences) : _preferences(preferences)
{ {
_inst = this;
loadPinConfiguration(); loadPinConfiguration();
_inst = this;
_inst->init(nuki); _inst->init(nuki);
} }
@@ -25,6 +25,13 @@ void Gpio::init(NukiWrapper* nuki)
for(const auto& entry : _inst->_pinConfiguration) 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) switch(entry.role)
{ {
case PinRole::InputLock: case PinRole::InputLock:
@@ -46,6 +53,117 @@ void Gpio::init(NukiWrapper* nuki)
} }
} }
const std::vector<uint8_t>& 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<PinEntry> &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<PinEntry> &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<PinEntry>& 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<PinRole>& Gpio::getAllRoles() const
{
return _allRoles;
}
void Gpio::isrLock() void Gpio::isrLock()
{ {
if(millis() < _lockedTs) return; if(millis() < _lockedTs) return;
@@ -66,46 +184,3 @@ void Gpio::isrUnlatch()
_nuki->unlatch(); _nuki->unlatch();
_lockedTs = millis() + _debounceTime; _lockedTs = millis() + _debounceTime;
} }
const std::vector<uint8_t>& 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<PinEntry> &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));
}

19
Gpio.h
View File

@@ -5,7 +5,7 @@
enum class PinRole enum class PinRole
{ {
Undefined, Disabled,
InputLock, InputLock,
InputUnlock, InputUnlock,
InputUnlatch, InputUnlatch,
@@ -16,7 +16,7 @@ enum class PinRole
struct PinEntry struct PinEntry
{ {
uint8_t pin = 0; uint8_t pin = 0;
PinRole role = PinRole::Undefined; PinRole role = PinRole::Disabled;
}; };
class Gpio class Gpio
@@ -29,9 +29,24 @@ public:
void savePinConfiguration(const std::vector<PinEntry>& pinConfiguration); void savePinConfiguration(const std::vector<PinEntry>& pinConfiguration);
const std::vector<uint8_t>& availablePins() const; const std::vector<uint8_t>& availablePins() const;
const std::vector<PinEntry>& pinConfiguration() const;
String getRoleDescription(PinRole role) const;
void getConfigurationText(String& text, const std::vector<PinEntry>& pinConfiguration) const;
const std::vector<PinRole>& getAllRoles() const;
private: private:
const std::vector<uint8_t> _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 }; const std::vector<uint8_t> _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 };
const std::vector<PinRole> _allRoles =
{
PinRole::Disabled,
PinRole::InputLock,
PinRole::InputUnlock,
PinRole::InputUnlatch,
PinRole::OutputHighLocked,
PinRole::OutputHighUnlocked
};
std::vector<PinEntry> _pinConfiguration; std::vector<PinEntry> _pinConfiguration;
static const uint _debounceTime; static const uint _debounceTime;

View File

@@ -12,6 +12,7 @@ enum class RestartReason
ReconfigureLAN8720, ReconfigureLAN8720,
NetworkDeviceCriticalFailure, NetworkDeviceCriticalFailure,
ConfigurationUpdated, ConfigurationUpdated,
GpioConfigurationUpdated,
RestartTimer, RestartTimer,
OTACompleted, OTACompleted,
OTATimeout, OTATimeout,
@@ -69,6 +70,8 @@ inline static String getRestartReason()
return "NetworkDeviceCriticalFailure"; return "NetworkDeviceCriticalFailure";
case RestartReason::ConfigurationUpdated: case RestartReason::ConfigurationUpdated:
return "ConfigurationUpdated"; return "ConfigurationUpdated";
case RestartReason::GpioConfigurationUpdated:
return "GpioConfigurationUpdated";
case RestartReason::RestartTimer: case RestartReason::RestartTimer:
return "RestartTimer"; return "RestartTimer";
case RestartReason::OTACompleted: case RestartReason::OTACompleted:

View File

@@ -7,11 +7,12 @@
#include "RestartReason.h" #include "RestartReason.h"
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
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), : _server(ethServer),
_nuki(nuki), _nuki(nuki),
_nukiOpener(nukiOpener), _nukiOpener(nukiOpener),
_network(network), _network(network),
_gpio(gpio),
_preferences(preferences), _preferences(preferences),
_allowRestartToPortal(allowRestartToPortal) _allowRestartToPortal(allowRestartToPortal)
{ {
@@ -93,6 +94,14 @@ void WebCfgServer::initialize()
buildNukiConfigHtml(response); buildNukiConfigHtml(response);
_server.send(200, "text/html", 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", [&]() { _server.on("/wifi", [&]() {
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
return _server.requestAuthentication(); return _server.requestAuthentication();
@@ -128,7 +137,8 @@ void WebCfgServer::initialize()
_network->reconfigureDevice(); _network->reconfigureDevice();
} }
}); });
_server.on("/savecfg", [&]() { _server.on("/savecfg", [&]()
{
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
return _server.requestAuthentication(); return _server.requestAuthentication();
} }
@@ -152,6 +162,22 @@ void WebCfgServer::initialize()
waitAndProcess(false, 1000); 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", [&]() { _server.on("/ota", [&]() {
if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) {
return _server.requestAuthentication(); return _server.requestAuthentication();
@@ -523,6 +549,32 @@ bool WebCfgServer::processArgs(String& message)
return configChanged; return configChanged;
} }
void WebCfgServer::processGpioArgs()
{
int count = _server.args();
std::vector<PinEntry> 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() void WebCfgServer::update()
{ {
if(_otaStartTs > 0 && (millis() - _otaStartTs) > 120000) if(_otaStartTs > 0 && (millis() - _otaStartTs) > 120000)
@@ -575,6 +627,9 @@ void WebCfgServer::buildHtml(String& response)
response.concat("<BR><BR><h3>Credentials</h3>"); response.concat("<BR><BR><h3>Credentials</h3>");
buildNavigationButton(response, "Edit", "/cred", _pinsConfigured ? "" : "<font color=\"#f07000\"><em>(!) Please configure PIN</em></font>"); buildNavigationButton(response, "Edit", "/cred", _pinsConfigured ? "" : "<font color=\"#f07000\"><em>(!) Please configure PIN</em></font>");
response.concat("<BR><BR><h3>GPIO Configuration</h3>");
buildNavigationButton(response, "Edit", "/gpiocfg");
response.concat("<BR><BR><h3>Firmware update</h3>"); response.concat("<BR><BR><h3>Firmware update</h3>");
buildNavigationButton(response, "Open", "/ota"); buildNavigationButton(response, "Open", "/ota");
@@ -780,6 +835,29 @@ void WebCfgServer::buildNukiConfigHtml(String &response)
response.concat("</BODY></HTML>"); response.concat("</BODY></HTML>");
} }
void WebCfgServer::buildGpioConfigHtml(String &response)
{
buildHtmlHeader(response);
response.concat("<FORM ACTION=savegpiocfg method='POST'>");
response.concat("<h3>GPIO Configuration</h3>");
response.concat("<table>");
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("</table>");
response.concat("<br><INPUT TYPE=SUBMIT NAME=\"submit\" VALUE=\"Save\">");
response.concat("</FORM>");
response.concat("</BODY></HTML>");
}
void WebCfgServer::buildConfirmHtml(String &response, const String &message, uint32_t redirectDelay) void WebCfgServer::buildConfirmHtml(String &response, const String &message, uint32_t redirectDelay)
{ {
String delay(redirectDelay); String delay(redirectDelay);
@@ -1226,3 +1304,32 @@ const std::vector<std::pair<String, String>> WebCfgServer::getNetworkGpioOptions
return options; return options;
} }
const std::vector<std::pair<String, String>> WebCfgServer::getGpioOptions() const
{
std::vector<std::pair<String, String>> 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<PinEntry>& pinConfiguration = _gpio->pinConfiguration();
for(const auto& entry : pinConfiguration)
{
if(pin == entry.pin)
{
return String((int8_t)entry.role);
}
}
return String((int8_t)PinRole::Disabled);
}

View File

@@ -6,6 +6,7 @@
#include "NetworkLock.h" #include "NetworkLock.h"
#include "NukiOpenerWrapper.h" #include "NukiOpenerWrapper.h"
#include "Ota.h" #include "Ota.h"
#include "Gpio.h"
extern TaskHandle_t networkTaskHandle; extern TaskHandle_t networkTaskHandle;
extern TaskHandle_t nukiTaskHandle; extern TaskHandle_t nukiTaskHandle;
@@ -26,7 +27,7 @@ enum class TokenType
class WebCfgServer class WebCfgServer
{ {
public: 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; ~WebCfgServer() = default;
void initialize(); void initialize();
@@ -34,12 +35,14 @@ public:
private: private:
bool processArgs(String& message); bool processArgs(String& message);
void processGpioArgs();
void buildHtml(String& response); void buildHtml(String& response);
void buildCredHtml(String& response); void buildCredHtml(String& response);
void buildOtaHtml(String& response, bool errored); void buildOtaHtml(String& response, bool errored);
void buildOtaCompletedHtml(String& response); void buildOtaCompletedHtml(String& response);
void buildMqttConfigHtml(String& response); void buildMqttConfigHtml(String& response);
void buildNukiConfigHtml(String& response); void buildNukiConfigHtml(String& response);
void buildGpioConfigHtml(String& response);
void buildConfirmHtml(String& response, const String &message, uint32_t redirectDelay = 5); void buildConfirmHtml(String& response, const String &message, uint32_t redirectDelay = 5);
void buildConfigureWifiHtml(String& response); void buildConfigureWifiHtml(String& response);
void buildInfoHtml(String& response); void buildInfoHtml(String& response);
@@ -57,6 +60,8 @@ private:
const std::vector<std::pair<String, String>> getNetworkDetectionOptions() const; const std::vector<std::pair<String, String>> getNetworkDetectionOptions() const;
const std::vector<std::pair<String, String>> getNetworkGpioOptions() const; const std::vector<std::pair<String, String>> getNetworkGpioOptions() const;
const std::vector<std::pair<String, String>> getGpioOptions() const;
String getPreselectionForGpio(const uint8_t& pin);
void printParameter(String& response, const char* description, const char* value, const char *link = ""); void printParameter(String& response, const char* description, const char* value, const char *link = "");
@@ -65,10 +70,11 @@ private:
void handleOtaUpload(); void handleOtaUpload();
WebServer _server; WebServer _server;
NukiWrapper* _nuki; NukiWrapper* _nuki = nullptr;
NukiOpenerWrapper* _nukiOpener; NukiOpenerWrapper* _nukiOpener = nullptr;
Network* _network; Network* _network = nullptr;
Preferences* _preferences; Gpio* _gpio = nullptr;
Preferences* _preferences = nullptr;
Ota _ota; Ota _ota;
bool _hasCredentials = false; bool _hasCredentials = false;

View File

@@ -215,6 +215,9 @@ void setup()
nuki->initialize(firstStart); nuki->initialize(firstStart);
gpio = new Gpio(preferences, nuki); gpio = new Gpio(preferences, nuki);
String gpioDesc;
gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration());
Serial.print(gpioDesc.c_str());
// if(preferences->getBool(preference_gpio_locking_enabled)) // if(preferences->getBool(preference_gpio_locking_enabled))
// { // {
@@ -229,7 +232,7 @@ void setup()
nukiOpener->initialize(); 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(); webCfgServer->initialize();
presenceDetection = new PresenceDetection(preferences, bleScanner, network, CharBuffer::get(), CHAR_BUFFER_SIZE); presenceDetection = new PresenceDetection(preferences, bleScanner, network, CharBuffer::get(), CHAR_BUFFER_SIZE);