Merge pull request #169 from technyon/gpio

Make GPIOs user-configurable
This commit is contained in:
Jan-Ole Schümann
2023-04-08 08:25:29 +02:00
committed by GitHub
14 changed files with 658 additions and 92 deletions

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#define NUKI_HUB_VERSION "8.21" #define NUKI_HUB_VERSION "8.22-pre-1"
#define MQTT_QOS_LEVEL 1 #define MQTT_QOS_LEVEL 1
#define MQTT_CLEAN_SESSIONS false #define MQTT_CLEAN_SESSIONS false

292
Gpio.cpp
View File

@@ -3,42 +3,296 @@
#include "Arduino.h" #include "Arduino.h"
#include "Pins.h" #include "Pins.h"
#include "Logger.h" #include "Logger.h"
#include "PreferencesKeys.h"
#include "RestartReason.h"
Gpio* Gpio::_inst = nullptr; Gpio* Gpio::_inst = nullptr;
NukiWrapper* Gpio::_nuki = nullptr; unsigned long Gpio::_debounceTs = 0;
unsigned long Gpio::_lockedTs = 0;
const uint Gpio::_debounceTime = 1000; const uint Gpio::_debounceTime = 1000;
void Gpio::init(NukiWrapper* nuki) Gpio::Gpio(Preferences* preferences)
: _preferences(preferences)
{ {
_nuki = nuki; _inst = this;
loadPinConfiguration();
pinMode(TRIGGER_LOCK_PIN, INPUT_PULLUP); if(_preferences->getBool(preference_gpio_locking_enabled))
pinMode(TRIGGER_UNLOCK_PIN, INPUT_PULLUP); {
pinMode(TRIGGER_UNLATCH_PIN, INPUT_PULLUP); migrateObsoleteSetting();
}
attachInterrupt(TRIGGER_LOCK_PIN, isrLock, FALLING); _inst->init();
attachInterrupt(TRIGGER_UNLOCK_PIN, isrUnlock, FALLING); }
attachInterrupt(TRIGGER_UNLATCH_PIN, isrUnlatch, FALLING);
void Gpio::init()
{
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:
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;
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;
}
}
}
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[std::max(pinConfiguration.size() * 2, _preferences->getBytesLength(preference_gpio_configuration))];
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::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:
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";
}
}
void Gpio::getConfigurationText(String& text, const std::vector<PinEntry>& pinConfiguration, const String& linebreak) const
{
for(const auto& entry : pinConfiguration)
{
if(entry.role != PinRole::Disabled)
{
text.concat("GPIO ");
text.concat(entry.pin);
if(entry.pin < 10)
{
text.concat(' ');
}
text.concat(": ");
text.concat(getRoleDescription(entry.role));
text.concat(linebreak);
}
}
}
const std::vector<PinRole>& Gpio::getAllRoles() const
{
return _allRoles;
}
void Gpio::notify(const GpioAction &action)
{
for(auto& callback : _callbacks)
{
callback(action);
}
}
void Gpio::addCallback(std::function<void(const GpioAction&)> callback)
{
_callbacks.push_back(callback);
} }
void Gpio::isrLock() void Gpio::isrLock()
{ {
if(millis() < _lockedTs) return; if(millis() < _debounceTs) return;
_nuki->lock(); _inst->notify(GpioAction::Lock);
_lockedTs = millis() + _debounceTime; _debounceTs = millis() + _debounceTime;
} }
void Gpio::isrUnlock() void Gpio::isrUnlock()
{ {
if(millis() < _lockedTs) return; if(millis() < _debounceTs) return;
_nuki->unlock(); _inst->notify(GpioAction::Unlock);
_lockedTs = millis() + _debounceTime; _debounceTs = millis() + _debounceTime;
} }
void Gpio::isrUnlatch() void Gpio::isrUnlatch()
{ {
if(millis() < _lockedTs) return; if(millis() < _debounceTs) return;
_nuki->unlatch(); _inst->notify(GpioAction::Unlatch);
_lockedTs = millis() + _debounceTime; _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);
}
#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);
} }

91
Gpio.h
View File

@@ -1,22 +1,103 @@
#pragma once #pragma once
#include <functional>
#include <Preferences.h>
#include <vector>
#include "NukiWrapper.h" enum class PinRole
{
Disabled,
InputLock,
InputUnlock,
InputUnlatch,
InputElectricStrikeActuation,
InputActivateRTO,
InputActivateCM,
InputDeactivateRtoCm,
OutputHighLocked,
OutputHighUnlocked,
OutputHighMotorBlocked,
OutputHighRtoActive,
OutputHighCmActive,
OutputHighRtoOrCmActive
};
enum class GpioAction
{
Lock,
Unlock,
Unlatch,
ElectricStrikeActuation,
ActivateRTO,
ActivateCM,
DeactivateRtoCm
};
struct PinEntry
{
uint8_t pin = 0;
PinRole role = PinRole::Disabled;
};
class Gpio class Gpio
{ {
public: public:
Gpio() = delete; Gpio(Preferences* preferences);
static void init(NukiWrapper* nuki); static void init();
void migrateObsoleteSetting();
void addCallback(std::function<void(const GpioAction&)> callback);
void loadPinConfiguration();
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 String& linebreak = "\n") const;
const std::vector<PinRole>& getAllRoles() const;
void setPinOutput(const uint8_t& pin, const uint8_t& state);
private: private:
void notify(const GpioAction& action);
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::InputElectricStrikeActuation,
PinRole::InputActivateRTO,
PinRole::InputActivateCM,
PinRole::InputDeactivateRtoCm,
PinRole::OutputHighLocked,
PinRole::OutputHighUnlocked,
PinRole::OutputHighRtoActive,
PinRole::OutputHighCmActive,
PinRole::OutputHighRtoOrCmActive,
};
std::vector<PinEntry> _pinConfiguration;
static const uint _debounceTime; static const uint _debounceTime;
static void IRAM_ATTR isrLock(); static void IRAM_ATTR isrLock();
static void IRAM_ATTR isrUnlock(); static void IRAM_ATTR isrUnlock();
static void IRAM_ATTR isrUnlatch(); 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<std::function<void(const GpioAction&)>> _callbacks;
static Gpio* _inst; static Gpio* _inst;
static NukiWrapper* _nuki; static unsigned long _debounceTs;
static unsigned long _lockedTs;
Preferences* _preferences = nullptr;
}; };

View File

@@ -46,21 +46,14 @@ void Network::setupDevice()
_ipConfiguration = new IPConfiguration(_preferences); _ipConfiguration = new IPConfiguration(_preferences);
int hardwareDetect = _preferences->getInt(preference_network_hardware); 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 : ")); Log->println(hardwareDetect);
Log->print(F("Hardware detect GPIO: ")); Log->println(hardwareDetectGpio);
if(hardwareDetect == 0) if(hardwareDetect == 0)
{ {
hardwareDetect = 2; hardwareDetect = 1;
_preferences->putInt(preference_network_hardware, hardwareDetect); _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) if(strcmp(WiFi_fallbackDetect, "wifi_fallback") == 0)
{ {
@@ -77,12 +70,8 @@ void Network::setupDevice()
_networkDeviceType = NetworkDeviceType::WiFi; _networkDeviceType = NetworkDeviceType::WiFi;
break; break;
case 2: case 2:
Log->print(F("Using PIN ")); Log->print(F("Generic W5500"));
Log->print(hardwareDetectGpio); _networkDeviceType = NetworkDeviceType::W5500;
Log->println(F(" for network device selection"));
pinMode(hardwareDetectGpio, INPUT_PULLUP);
_networkDeviceType = digitalRead(hardwareDetectGpio) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500;
break; break;
case 3: case 3:
Log->println(F("W5500 on M5Stack Atom POE")); Log->println(F("W5500 on M5Stack Atom POE"));

View File

@@ -8,11 +8,12 @@
NukiOpenerWrapper* nukiOpenerInst; 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), : _deviceName(deviceName),
_nukiOpener(deviceName, id), _nukiOpener(deviceName, id),
_bleScanner(scanner), _bleScanner(scanner),
_network(network), _network(network),
_gpio(gpio),
_preferences(preferences) _preferences(preferences)
{ {
nukiOpenerInst = this; nukiOpenerInst = this;
@@ -26,6 +27,8 @@ NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, uint32_t id,
network->setLockActionReceivedCallback(nukiOpenerInst->onLockActionReceivedCallback); network->setLockActionReceivedCallback(nukiOpenerInst->onLockActionReceivedCallback);
network->setConfigUpdateReceivedCallback(nukiOpenerInst->onConfigUpdateReceivedCallback); network->setConfigUpdateReceivedCallback(nukiOpenerInst->onConfigUpdateReceivedCallback);
network->setKeypadCommandReceivedCallback(nukiOpenerInst->onKeypadCommandReceivedCallback); network->setKeypadCommandReceivedCallback(nukiOpenerInst->onKeypadCommandReceivedCallback);
_gpio->addCallback(NukiOpenerWrapper::gpioActionCallback);
} }
@@ -252,6 +255,36 @@ void NukiOpenerWrapper::update()
memcpy(&_lastKeyTurnerState, &_keyTurnerState, sizeof(NukiOpener::OpenerState)); 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() bool NukiOpenerWrapper::isPinSet()
{ {
return _nukiOpener.getSecurityPincode() != 0; return _nukiOpener.getSecurityPincode() != 0;
@@ -301,6 +334,7 @@ void NukiOpenerWrapper::updateKeyTurnerState()
else else
{ {
_network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState); _network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState);
updateGpioOutputs();
if(_keyTurnerState.nukiState == NukiOpener::State::ContinuousMode) if(_keyTurnerState.nukiState == NukiOpener::State::ContinuousMode)
{ {
@@ -448,6 +482,25 @@ void NukiOpenerWrapper::onKeypadCommandReceivedCallback(const char *command, con
nukiOpenerInst->onKeypadCommandReceived(command, id, name, code, enabled); 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) void NukiOpenerWrapper::onConfigUpdateReceived(const char *topic, const char *value)
{ {
if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) if(strcmp(topic, mqtt_topic_config_button_enabled) == 0)
@@ -692,3 +745,31 @@ void NukiOpenerWrapper::disableWatchdog()
{ {
_restartBeaconTimeout = -1; _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;
}
}
}

View File

@@ -5,16 +5,22 @@
#include "NukiOpenerConstants.h" #include "NukiOpenerConstants.h"
#include "NukiDataTypes.h" #include "NukiDataTypes.h"
#include "BleScanner.h" #include "BleScanner.h"
#include "Gpio.h"
class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler
{ {
public: 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(); virtual ~NukiOpenerWrapper();
void initialize(); void initialize();
void update(); void update();
void electricStrikeActuation();
void activateRTO();
void activateCM();
void deactivateRtoCm();
bool isPinSet(); bool isPinSet();
void setPin(const uint16_t pin); void setPin(const uint16_t pin);
@@ -40,6 +46,7 @@ private:
static bool onLockActionReceivedCallback(const char* value); static bool onLockActionReceivedCallback(const char* value);
static void onConfigUpdateReceivedCallback(const char* topic, 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 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 onConfigUpdateReceived(const char* topic, const char* value);
void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled); void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
@@ -50,6 +57,8 @@ private:
void updateKeypad(); void updateKeypad();
void postponeBleWatchdog(); void postponeBleWatchdog();
void updateGpioOutputs();
void readConfig(); void readConfig();
void readAdvancedConfig(); void readAdvancedConfig();
@@ -61,9 +70,10 @@ private:
std::string _deviceName; std::string _deviceName;
NukiOpener::NukiOpener _nukiOpener; NukiOpener::NukiOpener _nukiOpener;
BleScanner::Scanner* _bleScanner; BleScanner::Scanner* _bleScanner = nullptr;
NetworkOpener* _network; NetworkOpener* _network = nullptr;
Preferences* _preferences; Gpio* _gpio = nullptr;
Preferences* _preferences = nullptr;
int _intervalLockstate = 0; // seconds int _intervalLockstate = 0; // seconds
int _intervalBattery = 0; // seconds int _intervalBattery = 0; // seconds
int _intervalConfig = 60 * 60; // seconds int _intervalConfig = 60 * 60; // seconds

View File

@@ -8,11 +8,12 @@
NukiWrapper* nukiInst; 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), : _deviceName(deviceName),
_bleScanner(scanner), _bleScanner(scanner),
_nukiLock(deviceName, id), _nukiLock(deviceName, id),
_network(network), _network(network),
_gpio(gpio),
_preferences(preferences) _preferences(preferences)
{ {
nukiInst = this; nukiInst = this;
@@ -26,6 +27,8 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, uint32_t id, BleScanner:
network->setLockActionReceivedCallback(nukiInst->onLockActionReceivedCallback); network->setLockActionReceivedCallback(nukiInst->onLockActionReceivedCallback);
network->setConfigUpdateReceivedCallback(nukiInst->onConfigUpdateReceivedCallback); network->setConfigUpdateReceivedCallback(nukiInst->onConfigUpdateReceivedCallback);
network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback); network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback);
_gpio->addCallback(NukiWrapper::gpioActionCallback);
} }
@@ -307,6 +310,7 @@ void NukiWrapper::updateKeyTurnerState()
_retryLockstateCount = 0; _retryLockstateCount = 0;
_network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState); _network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState);
updateGpioOutputs();
char lockStateStr[20]; char lockStateStr[20];
lockstateToString(_keyTurnerState.lockState, lockStateStr); lockstateToString(_keyTurnerState.lockState, lockStateStr);
@@ -446,6 +450,22 @@ void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uin
nukiInst->onKeypadCommandReceived(command, id, name, code, enabled); 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) void NukiWrapper::onConfigUpdateReceived(const char *topic, const char *value)
{ {
if(strcmp(topic, mqtt_topic_config_button_enabled) == 0) if(strcmp(topic, mqtt_topic_config_button_enabled) == 0)
@@ -719,3 +739,29 @@ void NukiWrapper::disableWatchdog()
{ {
_restartBeaconTimeout = -1; _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;
case PinRole::OutputHighMotorBlocked:
_gpio->setPinOutput(entry.pin, lockState == LockState::MotorBlocked ? HIGH : LOW);
break;
}
}
}

View File

@@ -5,11 +5,12 @@
#include "NukiDataTypes.h" #include "NukiDataTypes.h"
#include "BleScanner.h" #include "BleScanner.h"
#include "NukiLock.h" #include "NukiLock.h"
#include "Gpio.h"
class NukiWrapper : public Nuki::SmartlockEventHandler class NukiWrapper : public Nuki::SmartlockEventHandler
{ {
public: 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(); virtual ~NukiWrapper();
void initialize(const bool& firstStart); void initialize(const bool& firstStart);
@@ -42,6 +43,8 @@ private:
static bool onLockActionReceivedCallback(const char* value); static bool onLockActionReceivedCallback(const char* value);
static void onConfigUpdateReceivedCallback(const char* topic, 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 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 onConfigUpdateReceived(const char* topic, const char* value);
void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled); 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 updateKeypad();
void postponeBleWatchdog(); void postponeBleWatchdog();
void updateGpioOutputs();
void readConfig(); void readConfig();
void readAdvancedConfig(); void readAdvancedConfig();
@@ -63,8 +68,9 @@ private:
std::string _deviceName; std::string _deviceName;
NukiLock::NukiLock _nukiLock; NukiLock::NukiLock _nukiLock;
BleScanner::Scanner* _bleScanner; BleScanner::Scanner* _bleScanner = nullptr;
NetworkLock* _network; NetworkLock* _network = nullptr;
Gpio* _gpio = nullptr;
Preferences* _preferences; Preferences* _preferences;
int _intervalLockstate = 0; // seconds int _intervalLockstate = 0; // seconds
int _intervalBattery = 0; // seconds int _intervalBattery = 0; // seconds

View File

@@ -25,7 +25,6 @@
#define preference_ip_gateway "ipgtw" #define preference_ip_gateway "ipgtw"
#define preference_ip_dns_server "dnssrv" #define preference_ip_dns_server "dnssrv"
#define preference_network_hardware "nwhw" #define preference_network_hardware "nwhw"
#define preference_network_hardware_gpio "nwhwdt"
#define preference_rssi_publish_interval "rssipb" #define preference_rssi_publish_interval "rssipb"
#define preference_hostname "hostname" #define preference_hostname "hostname"
#define preference_network_timeout "nettmout" #define preference_network_timeout "nettmout"
@@ -44,6 +43,7 @@
#define preference_cred_password "crdpass" #define preference_cred_password "crdpass"
#define preference_publish_authdata "pubauth" #define preference_publish_authdata "pubauth"
#define preference_gpio_locking_enabled "gpiolck" #define preference_gpio_locking_enabled "gpiolck"
#define preference_gpio_configuration "gpiocfg"
#define preference_publish_debug_info "pubdbg" #define preference_publish_debug_info "pubdbg"
#define preference_presence_detection_timeout "prdtimeout" #define preference_presence_detection_timeout "prdtimeout"
#define preference_has_mac_saved "hasmac" #define preference_has_mac_saved "hasmac"
@@ -62,13 +62,13 @@ private:
preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_mqtt_ca, 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_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_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_hostname, preference_network_timeout, preference_restart_on_disconnect,
preference_restart_timer, preference_restart_ble_beacon_lost, preference_query_interval_lockstate, preference_restart_timer, preference_restart_ble_beacon_lost, preference_query_interval_lockstate,
preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad,
preference_keypad_control_enabled, preference_register_as_app, preference_command_nr_of_retries, preference_keypad_control_enabled, preference_register_as_app, preference_command_nr_of_retries,
preference_command_retry_delay, preference_cred_user, preference_cred_password, preference_publish_authdata, 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, preference_has_mac_saved, preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2,
}; };
std::vector<char*> _redact = std::vector<char*> _redact =
@@ -81,7 +81,7 @@ private:
{ {
preference_started_before, preference_mqtt_log_enabled, preference_lock_enabled, preference_opener_enabled, 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_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 const bool isRedacted(const char* key) const

View File

@@ -198,11 +198,10 @@ W5x00 SCK to GPIO18<br>
W5x00 MISO to GPIOGPIO19<br> W5x00 MISO to GPIOGPIO19<br>
W5x00 MOSI to GPIO23<br> W5x00 MOSI to GPIO23<br>
W5x00 CS/SS to GPIO5 W5x00 CS/SS to GPIO5
- Additionally connect:<br> - Optionally connect:<br>
W5x00 reset to GPIO33 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.<br> 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.<br>
Note: Encrypted MQTT is only available for Wifi and LAN8720 modules, W5x00 modules don't support encryption 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 (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. is the easiest option, since it has USB for flashing onboard.

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)
{ {
@@ -35,13 +36,13 @@ WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Net
_pinsConfigured = true; _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; _brokerConfigured = _preferences->getString(preference_mqtt_broker).length() > 0 && _preferences->getInt(preference_mqtt_broker_port) > 0;
@@ -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();
@@ -298,11 +324,6 @@ bool WebCfgServer::processArgs(String& message)
_preferences->putInt(preference_network_hardware, value.toInt()); _preferences->putInt(preference_network_hardware, value.toInt());
configChanged = true; configChanged = true;
} }
else if(key == "NWHWDT")
{
_preferences->putInt(preference_network_hardware_gpio, value.toInt());
configChanged = true;
}
else if(key == "RSSI") else if(key == "RSSI")
{ {
_preferences->putInt(preference_rssi_publish_interval, value.toInt()); _preferences->putInt(preference_rssi_publish_interval, value.toInt());
@@ -425,11 +446,6 @@ bool WebCfgServer::processArgs(String& message)
_preferences->putBool(preference_publish_authdata, (value == "1")); _preferences->putBool(preference_publish_authdata, (value == "1"));
configChanged = true; configChanged = true;
} }
else if(key == "GPLCK")
{
_preferences->putBool(preference_gpio_locking_enabled, (value == "1"));
configChanged = true;
}
else if(key == "REGAPP") else if(key == "REGAPP")
{ {
_preferences->putBool(preference_register_as_app, (value == "1")); _preferences->putBool(preference_register_as_app, (value == "1"));
@@ -523,6 +539,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 +617,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");
@@ -713,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, "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); 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, "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, "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); 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)); printCheckBox(response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect));
@@ -771,7 +815,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, "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); 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, "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, "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); printInputField(response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10);
response.concat("</table>"); response.concat("</table>");
@@ -780,6 +823,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);
@@ -872,6 +938,8 @@ void WebCfgServer::buildInfoHtml(String &response)
response.concat(uxTaskGetStackHighWaterMark(presenceDetectionTaskHandle)); response.concat(uxTaskGetStackHighWaterMark(presenceDetectionTaskHandle));
response.concat("\n"); response.concat("\n");
_gpio->getConfigurationText(response, _gpio->pinConfiguration());
response.concat("Restart reason FW: "); response.concat("Restart reason FW: ");
response.concat(getRestartReason()); response.concat(getRestartReason());
response.concat( "\n"); response.concat( "\n");
@@ -1203,7 +1271,7 @@ const std::vector<std::pair<String, String>> WebCfgServer::getNetworkDetectionOp
std::vector<std::pair<String, String>> options; std::vector<std::pair<String, String>> options;
options.push_back(std::make_pair("1", "Wifi only")); 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("3", "M5Stack Atom POE (W5500)"));
options.push_back(std::make_pair("4", "Olimex ESP32-POE / ESP-POE-ISO")); options.push_back(std::make_pair("4", "Olimex ESP32-POE / ESP-POE-ISO"));
options.push_back(std::make_pair("5", "WT32-ETH01")); options.push_back(std::make_pair("5", "WT32-ETH01"));
@@ -1213,16 +1281,31 @@ const std::vector<std::pair<String, String>> WebCfgServer::getNetworkDetectionOp
return options; return options;
} }
const std::vector<std::pair<String, String>> WebCfgServer::getNetworkGpioOptions() const const std::vector<std::pair<String, String>> WebCfgServer::getGpioOptions() const
{ {
std::vector<std::pair<String, String>> options; std::vector<std::pair<String, String>> options;
for(int i=16; i <= 33; i++) const auto& roles = _gpio->getAllRoles();
for(const auto& role : roles)
{ {
String txt = "Detect W5500 via GPIO "; options.push_back( std::make_pair(String((int)role), _gpio->getRoleDescription(role)));
txt.concat(i);
options.push_back(std::make_pair(String(i), txt));
} }
return options; 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);
@@ -56,7 +59,8 @@ private:
void buildNavigationButton(String& response, const char* caption, const char* targetPath, const char* labelText = ""); void buildNavigationButton(String& response, const char* caption, const char* targetPath, const char* labelText = "");
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>> 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 +69,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

@@ -25,6 +25,7 @@ NukiOpenerWrapper* nukiOpener = nullptr;
PresenceDetection* presenceDetection = nullptr; PresenceDetection* presenceDetection = nullptr;
Preferences* preferences = nullptr; Preferences* preferences = nullptr;
EthServer* ethServer = nullptr; EthServer* ethServer = nullptr;
Gpio* gpio = nullptr;
bool lockEnabled = false; bool lockEnabled = false;
bool openerEnabled = false; bool openerEnabled = false;
@@ -169,6 +170,7 @@ void setup()
Log->print(F("NUKI Hub version ")); Log->println(NUKI_HUB_VERSION); Log->print(F("NUKI Hub version ")); Log->println(NUKI_HUB_VERSION);
bool firstStart = initPreferences(); bool firstStart = initPreferences();
initializeRestartReason(); initializeRestartReason();
CharBuffer::initialize(); CharBuffer::initialize();
@@ -207,26 +209,33 @@ void setup()
bleScanner->initialize("NukiHub"); bleScanner->initialize("NukiHub");
bleScanner->setScanDuration(10); bleScanner->setScanDuration(10);
gpio = new Gpio(preferences);
String gpioDesc;
gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration(), "\n\r");
Serial.print(gpioDesc.c_str());
Log->println(lockEnabled ? F("NUKI Lock enabled") : F("NUKI Lock disabled")); Log->println(lockEnabled ? F("NUKI Lock enabled") : F("NUKI Lock disabled"));
if(lockEnabled) if(lockEnabled)
{ {
nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, networkLock, preferences); nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, networkLock, gpio, preferences);
nuki->initialize(firstStart); nuki->initialize(firstStart);
if(preferences->getBool(preference_gpio_locking_enabled))
{
Gpio::init(nuki); // if(preferences->getBool(preference_gpio_locking_enabled))
} // {
// Gpio::init(nuki);
// }
} }
Log->println(openerEnabled ? F("NUKI Opener enabled") : F("NUKI Opener disabled")); Log->println(openerEnabled ? F("NUKI Opener enabled") : F("NUKI Opener disabled"));
if(openerEnabled) if(openerEnabled)
{ {
nukiOpener = new NukiOpenerWrapper("NukiHub", deviceId, bleScanner, networkOpener, preferences); nukiOpener = new NukiOpenerWrapper("NukiHub", deviceId, bleScanner, networkOpener, gpio, preferences);
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);