gpio configuration via web interface
This commit is contained in:
165
Gpio.cpp
165
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<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()
|
||||
{
|
||||
if(millis() < _lockedTs) return;
|
||||
@@ -65,47 +183,4 @@ void Gpio::isrUnlatch()
|
||||
if(millis() < _lockedTs) return;
|
||||
_nuki->unlatch();
|
||||
_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
19
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<PinEntry>& pinConfiguration);
|
||||
|
||||
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:
|
||||
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;
|
||||
static const uint _debounceTime;
|
||||
|
||||
@@ -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:
|
||||
|
||||
111
WebCfgServer.cpp
111
WebCfgServer.cpp
@@ -7,11 +7,12 @@
|
||||
#include "RestartReason.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),
|
||||
_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<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()
|
||||
{
|
||||
if(_otaStartTs > 0 && (millis() - _otaStartTs) > 120000)
|
||||
@@ -575,6 +627,9 @@ void WebCfgServer::buildHtml(String& response)
|
||||
response.concat("<BR><BR><h3>Credentials</h3>");
|
||||
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>");
|
||||
buildNavigationButton(response, "Open", "/ota");
|
||||
|
||||
@@ -780,6 +835,29 @@ void WebCfgServer::buildNukiConfigHtml(String &response)
|
||||
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)
|
||||
{
|
||||
String delay(redirectDelay);
|
||||
@@ -1226,3 +1304,32 @@ const std::vector<std::pair<String, String>> WebCfgServer::getNetworkGpioOptions
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<std::pair<String, String>> getNetworkDetectionOptions() 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 = "");
|
||||
|
||||
@@ -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;
|
||||
|
||||
5
main.cpp
5
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);
|
||||
|
||||
Reference in New Issue
Block a user