Merge pull request #194 from technyon/gpio

Gpio
This commit is contained in:
Jan-Ole Schümann
2023-06-08 18:33:40 +02:00
committed by GitHub
25 changed files with 710 additions and 66 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
build
cmake-build-debug
cmake-build-release
cmake-build-release-s3

View File

@@ -86,6 +86,9 @@ set(SRCFILES
lib/nuki_ble/src/NukiUtils.cpp
lib/nuki_ble/src/NukiLockUtils.cpp
lib/nuki_ble/src/NukiOpenerUtils.cpp
lib/gpio2go/src/Gpio2Go.cpp
lib/gpio2go/src/InterruptMode.h
lib/gpio2go/src/PinMode.h
lib/BleScanner/src/BleInterfaces.h
lib/BleScanner/src/BleScanner.cpp
lib/MqttLogger/src/MqttLogger.cpp

View File

@@ -1,6 +1,8 @@
#pragma once
#define NUKI_HUB_VERSION "8.23"
#define NUKI_HUB_VERSION "8.24"
#define MQTT_QOS_LEVEL 1
#define MQTT_CLEAN_SESSIONS false
#define GPIO_DEBOUNCE_TIME 200

View File

@@ -1,13 +1,15 @@
#include <esp32-hal.h>
#include "Gpio.h"
#include "Config.h"
#include "Arduino.h"
#include "Logger.h"
#include "PreferencesKeys.h"
#include "RestartReason.h"
#include "lib/gpio2go/src/Gpio2Go.h"
Gpio* Gpio::_inst = nullptr;
unsigned long Gpio::_debounceTs = 0;
const uint Gpio::_debounceTime = 1000;
const uint Gpio::_debounceTime = GPIO_DEBOUNCE_TIME;
Gpio::Gpio(Preferences* preferences)
: _preferences(preferences)
@@ -64,10 +66,27 @@ void Gpio::init()
pinMode(entry.pin, INPUT_PULLUP);
attachInterrupt(entry.pin, isrDeactivateRtoCm, FALLING);
break;
case PinRole::OutputHighLocked:
case PinRole::OutputHighUnlocked:
case PinRole::OutputHighMotorBlocked:
case PinRole::OutputHighRtoActive:
case PinRole::OutputHighCmActive:
case PinRole::OutputHighRtoOrCmActive:
case PinRole::GeneralOutput:
pinMode(entry.pin, OUTPUT);
break;
case PinRole::GeneralInputPullDown:
Gpio2Go::configurePin(entry.pin, PinMode::InputPullDown, InterruptMode::Change, 300);
break;
case PinRole::GeneralInputPullUp:
Gpio2Go::configurePin(entry.pin, PinMode::InputPullup, InterruptMode::Change, 300);
break;
default:
pinMode(entry.pin, OUTPUT);
break;
}
Gpio2Go::subscribe(Gpio::inputCallback);
}
}
@@ -136,6 +155,18 @@ const std::vector<PinEntry> &Gpio::pinConfiguration() const
return _pinConfiguration;
}
const PinRole Gpio::getPinRole(const int &pin) const
{
for(const auto& pinEntry : _pinConfiguration)
{
if(pinEntry.pin == pin)
{
return pinEntry.role;
}
}
return PinRole::Disabled;
}
String Gpio::getRoleDescription(PinRole role) const
{
switch(role)
@@ -168,6 +199,12 @@ String Gpio::getRoleDescription(PinRole role) const
return "Output: High when CM active";
case PinRole::OutputHighRtoOrCmActive:
return "Output: High when RTO or CM active";
case PinRole::GeneralOutput:
return "General output";
case PinRole::GeneralInputPullDown:
return "General input (Pull-down)";
case PinRole::GeneralInputPullUp:
return "General input (Pull-up)";
default:
return "Unknown";
}
@@ -197,15 +234,20 @@ const std::vector<PinRole>& Gpio::getAllRoles() const
return _allRoles;
}
void Gpio::notify(const GpioAction &action)
void Gpio::notify(const GpioAction &action, const int& pin)
{
for(auto& callback : _callbacks)
{
callback(action);
callback(action, pin);
}
}
void Gpio::addCallback(std::function<void(const GpioAction&)> callback)
void Gpio::inputCallback(const int &pin)
{
_inst->notify(GpioAction::GeneralInput, pin);
}
void Gpio::addCallback(std::function<void(const GpioAction&, const int&)> callback)
{
_callbacks.push_back(callback);
}
@@ -213,49 +255,49 @@ void Gpio::addCallback(std::function<void(const GpioAction&)> callback)
void Gpio::isrLock()
{
if(millis() < _debounceTs) return;
_inst->notify(GpioAction::Lock);
_inst->notify(GpioAction::Lock, -1);
_debounceTs = millis() + _debounceTime;
}
void Gpio::isrUnlock()
{
if(millis() < _debounceTs) return;
_inst->notify(GpioAction::Unlock);
_inst->notify(GpioAction::Unlock, -1);
_debounceTs = millis() + _debounceTime;
}
void Gpio::isrUnlatch()
{
if(millis() < _debounceTs) return;
_inst->notify(GpioAction::Unlatch);
_inst->notify(GpioAction::Unlatch, -1);
_debounceTs = millis() + _debounceTime;
}
void Gpio::isrElectricStrikeActuation()
{
if(millis() < _debounceTs) return;
_inst->notify(GpioAction::ElectricStrikeActuation);
_inst->notify(GpioAction::ElectricStrikeActuation, -1);
_debounceTs = millis() + _debounceTime;
}
void Gpio::isrActivateRTO()
{
if(millis() < _debounceTs) return;
_inst->notify(GpioAction::ActivateRTO);
_inst->notify(GpioAction::ActivateRTO, -1);
_debounceTs = millis() + _debounceTime;
}
void Gpio::isrActivateCM()
{
if(millis() < _debounceTs) return;
_inst->notify(GpioAction::ActivateCM);
_inst->notify(GpioAction::ActivateCM, -1);
_debounceTs = millis() + _debounceTime;
}
void Gpio::isrDeactivateRtoCm()
{
if(millis() < _debounceTs) return;
_inst->notify(GpioAction::DeactivateRtoCm);
_inst->notify(GpioAction::DeactivateRtoCm, -1);
_debounceTs = millis() + _debounceTime;
}
@@ -291,3 +333,4 @@ void Gpio::migrateObsoleteSetting()
delay(200);
restartEsp(RestartReason::GpioConfigurationUpdated);
}

19
Gpio.h
View File

@@ -19,7 +19,10 @@ enum class PinRole
OutputHighMotorBlocked,
OutputHighRtoActive,
OutputHighCmActive,
OutputHighRtoOrCmActive
OutputHighRtoOrCmActive,
GeneralOutput,
GeneralInputPullDown,
GeneralInputPullUp
};
enum class GpioAction
@@ -30,7 +33,8 @@ enum class GpioAction
ElectricStrikeActuation,
ActivateRTO,
ActivateCM,
DeactivateRtoCm
DeactivateRtoCm,
GeneralInput
};
struct PinEntry
@@ -47,13 +51,14 @@ public:
void migrateObsoleteSetting();
void addCallback(std::function<void(const GpioAction&)> callback);
void addCallback(std::function<void(const GpioAction&, const int&)> callback);
void loadPinConfiguration();
void savePinConfiguration(const std::vector<PinEntry>& pinConfiguration);
const std::vector<uint8_t>& availablePins() const;
const std::vector<PinEntry>& pinConfiguration() const;
const PinRole getPinRole(const int& pin) const;
String getRoleDescription(PinRole role) const;
void getConfigurationText(String& text, const std::vector<PinEntry>& pinConfiguration, const String& linebreak = "\n") const;
@@ -63,7 +68,8 @@ public:
void setPinOutput(const uint8_t& pin, const uint8_t& state);
private:
void notify(const GpioAction& action);
void IRAM_ATTR notify(const GpioAction& action, const int& pin);
static void inputCallback(const int & pin);
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 =
@@ -81,6 +87,9 @@ private:
PinRole::OutputHighRtoActive,
PinRole::OutputHighCmActive,
PinRole::OutputHighRtoOrCmActive,
PinRole::GeneralInputPullDown,
PinRole::GeneralInputPullUp,
PinRole::GeneralOutput
};
std::vector<PinEntry> _pinConfiguration;
@@ -94,7 +103,7 @@ private:
static void IRAM_ATTR isrActivateCM();
static void IRAM_ATTR isrDeactivateRtoCm();
std::vector<std::function<void(const GpioAction&)>> _callbacks;
std::vector<std::function<void(const GpioAction&, const int&)>> _callbacks;
static Gpio* _inst;
static unsigned long _debounceTs;

View File

@@ -60,4 +60,9 @@
#define mqtt_topic_restart_reason_fw "/maintenance/restartReasonNukiHub"
#define mqtt_topic_restart_reason_esp "/maintenance/restartReasonNukiEsp"
#define mqtt_topic_mqtt_connection_state "/maintenance/mqttConnectionState"
#define mqtt_topic_network_device "/maintenance/networkDevice"
#define mqtt_topic_network_device "/maintenance/networkDevice"
#define mqtt_topic_gpio_prefix "/gpio"
#define mqtt_topic_gpio_pin "/pin_"
#define mqtt_topic_gpio_role "/role"
#define mqtt_topic_gpio_state "/state"

View File

@@ -14,8 +14,9 @@ bool _versionPublished = false;
RTC_NOINIT_ATTR char WiFi_fallbackDetect[14];
Network::Network(Preferences *preferences, const String& maintenancePathPrefix, char* buffer, size_t bufferSize)
Network::Network(Preferences *preferences, Gpio* gpio, const String& maintenancePathPrefix, char* buffer, size_t bufferSize)
: _preferences(preferences),
_gpio(gpio),
_buffer(buffer),
_bufferSize(bufferSize)
{
@@ -35,7 +36,8 @@ Network::Network(Preferences *preferences, const String& maintenancePathPrefix,
_maintenancePathPrefix[i] = maintenancePathPrefix.charAt(i);
}
String connectionStateTopic = _preferences->getString(preference_mqtt_lock_path) + mqtt_topic_mqtt_connection_state;
_lockPath = _preferences->getString(preference_mqtt_lock_path);
String connectionStateTopic = _lockPath + mqtt_topic_mqtt_connection_state;
memset(_mqttConnectionStateTopic, 0, sizeof(_mqttConnectionStateTopic));
len = connectionStateTopic.length();
@@ -210,6 +212,45 @@ void Network::initialize()
}
_publishDebugInfo = _preferences->getBool(preference_publish_debug_info);
char gpioPath[250];
bool rebGpio = rebuildGpio();
if(rebGpio)
{
Log->println(F("Rebuild MQTT GPIO structure"));
}
for (const auto &pinEntry: _gpio->pinConfiguration())
{
switch (pinEntry.role)
{
case PinRole::GeneralInputPullDown:
case PinRole::GeneralInputPullUp:
if(rebGpio)
{
buildMqttPath(gpioPath, {mqtt_topic_gpio_prefix, (mqtt_topic_gpio_pin + std::to_string(pinEntry.pin)).c_str(), mqtt_topic_gpio_role});
publishString(_lockPath.c_str(), gpioPath, "input");
buildMqttPath(gpioPath, {mqtt_topic_gpio_prefix, (mqtt_topic_gpio_pin + std::to_string(pinEntry.pin)).c_str(), mqtt_topic_gpio_state});
publishString(_lockPath.c_str(), gpioPath, std::to_string(digitalRead(pinEntry.pin)).c_str());
}
break;
case PinRole::GeneralOutput:
if(rebGpio)
{
buildMqttPath(gpioPath, {mqtt_topic_gpio_prefix, (mqtt_topic_gpio_pin + std::to_string(pinEntry.pin)).c_str(), mqtt_topic_gpio_role});
publishString(_lockPath.c_str(), gpioPath, "output");
buildMqttPath(gpioPath, {mqtt_topic_gpio_prefix, (mqtt_topic_gpio_pin + std::to_string(pinEntry.pin)).c_str(), mqtt_topic_gpio_state});
publishString(_lockPath.c_str(), gpioPath, "0");
}
buildMqttPath(gpioPath, {mqtt_topic_gpio_prefix, (mqtt_topic_gpio_pin + std::to_string(pinEntry.pin)).c_str(), mqtt_topic_gpio_state});
subscribe(_lockPath.c_str(), gpioPath);
break;
}
}
_gpio->addCallback([this](const GpioAction& action, const int& pin)
{
gpioActionCallback(action, pin);
});
}
bool Network::update()
@@ -309,6 +350,26 @@ bool Network::update()
_lastMaintenanceTs = ts;
}
for(const auto& gpioTs : _gpioTs)
{
uint8_t pin = gpioTs.first;
unsigned long ts = gpioTs.second;
if(ts != 0 && ((millis() - ts) >= GPIO_DEBOUNCE_TIME))
{
_gpioTs[pin] = 0;
uint8_t pinState = digitalRead(pin) == HIGH ? 1 : 0;
char gpioPath[250];
buildMqttPath(gpioPath, {mqtt_topic_gpio_prefix, (mqtt_topic_gpio_pin + std::to_string(pin)).c_str(), mqtt_topic_gpio_state});
publishInt(_lockPath.c_str(), gpioPath, pinState);
Log->print(F("GPIO "));
Log->print(pin);
Log->print(F(" (Input) --> "));
Log->println(pinState);
}
}
return true;
}
@@ -444,36 +505,40 @@ bool Network::reconnect()
void Network::subscribe(const char* prefix, const char *path)
{
char prefixedPath[500];
buildMqttPath(prefix, path, prefixedPath);
buildMqttPath(prefixedPath, { prefix, path });
_subscribedTopics.push_back(prefixedPath);
}
void Network::initTopic(const char *prefix, const char *path, const char *value)
{
char prefixedPath[500];
buildMqttPath(prefix, path, prefixedPath);
buildMqttPath(prefixedPath, { prefix, path });
String pathStr = prefixedPath;
String valueStr = value;
_initTopics[pathStr] = valueStr;
}
void Network::buildMqttPath(const char* prefix, const char* path, char* outPath)
void Network::buildMqttPath(char* outPath, std::initializer_list<const char*> paths)
{
int offset = 0;
int i=0;
while(prefix[i] != 0x00)
{
outPath[offset] = prefix[i];
++offset;
++i;
}
int pathCount = 0;
i=0;
while(path[i] != 0x00)
for(const char* path : paths)
{
outPath[offset] = path[i];
++i;
++offset;
if(pathCount > 0 && path[0] != '/')
{
outPath[offset] = '/';
++offset;
}
int i = 0;
while(path[i] != 0)
{
outPath[offset] = path[i];
++offset;
++i;
}
++pathCount;
}
outPath[offset] = 0x00;
@@ -486,11 +551,6 @@ void Network::registerMqttReceiver(MqttReceiver* receiver)
void Network::onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
{
if(millis() < _ignoreSubscriptionsTs)
{
return;
}
uint8_t value[50] = {0};
size_t l = min(len, sizeof(value)-1);
@@ -502,14 +562,57 @@ void Network::onMqttDataReceivedCallback(const espMqttClientTypes::MessageProper
_inst->onMqttDataReceived(properties, topic, value, len, index, total);
}
void Network::onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
void Network::onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total)
{
parseGpioTopics(properties, topic, payload, len, index, total);
if(millis() < _ignoreSubscriptionsTs)
{
return;
}
for(auto receiver : _mqttReceivers)
{
receiver->onMqttDataReceived(topic, (byte*)payload, index);
}
}
void Network::parseGpioTopics(const espMqttClientTypes::MessageProperties &properties, const char *topic, const uint8_t *payload, size_t& len, size_t& index, size_t& total)
{
char gpioPath[250];
buildMqttPath(gpioPath, {_lockPath.c_str(), mqtt_topic_gpio_prefix, mqtt_topic_gpio_pin});
// /nuki_t/gpio/pin_17/state
size_t gpioLen = strlen(gpioPath);
if(strncmp(gpioPath, topic, gpioLen) == 0)
{
char pinStr[3] = {0};
pinStr[0] = topic[gpioLen];
if(topic[gpioLen+1] != '/')
{
pinStr[1] = topic[gpioLen+1];
}
int pin = std::atoi(pinStr);
if(_gpio->getPinRole(pin) == PinRole::GeneralOutput)
{
const uint8_t pinState = strcmp((const char*)payload, "1") == 0 ? HIGH : LOW;
Log->print(F("GPIO "));
Log->print(pin);
Log->print(F(" (Output) --> "));
Log->println(pinState);
digitalWrite(pin, pinState);
}
}
}
void Network::gpioActionCallback(const GpioAction &action, const int &pin)
{
_gpioTs[pin] = millis();
}
void Network::reconfigureDevice()
{
_device->reconfigure();
@@ -547,7 +650,7 @@ void Network::publishFloat(const char* prefix, const char* topic, const float va
char str[30];
dtostrf(value, 0, precision, str);
char path[200] = {0};
buildMqttPath(prefix, topic, path);
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
}
@@ -556,7 +659,7 @@ void Network::publishInt(const char* prefix, const char *topic, const int value)
char str[30];
itoa(value, str, 10);
char path[200] = {0};
buildMqttPath(prefix, topic, path);
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
}
@@ -565,7 +668,7 @@ void Network::publishUInt(const char* prefix, const char *topic, const unsigned
char str[30];
utoa(value, str, 10);
char path[200] = {0};
buildMqttPath(prefix, topic, path);
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
}
@@ -574,7 +677,7 @@ void Network::publishULong(const char* prefix, const char *topic, const unsigned
char str[30];
utoa(value, str, 10);
char path[200] = {0};
buildMqttPath(prefix, topic, path);
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
}
@@ -583,14 +686,14 @@ void Network::publishBool(const char* prefix, const char *topic, const bool valu
char str[2] = {0};
str[0] = value ? '1' : '0';
char path[200] = {0};
buildMqttPath(prefix, topic, path);
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
}
bool Network::publishString(const char* prefix, const char *topic, const char *value)
{
char path[200] = {0};
buildMqttPath(prefix, topic, path);
buildMqttPath(path, { prefix, topic });
return _device->mqttPublish(path, MQTT_QOS_LEVEL, true, value) > 0;
}
@@ -1244,3 +1347,8 @@ void Network::disableMqtt()
_device->disableMqtt();
_mqttEnabled = false;
}
NetworkDevice *Network::device()
{
return _device;
}

View File

@@ -7,6 +7,7 @@
#include "MqttReceiver.h"
#include "networkDevices/IPConfiguration.h"
#include "MqttTopics.h"
#include "Gpio.h"
enum class NetworkDeviceType
{
@@ -23,7 +24,7 @@ enum class NetworkDeviceType
class Network
{
public:
explicit Network(Preferences* preferences, const String& maintenancePathPrefix, char* buffer, size_t bufferSize);
explicit Network(Preferences* preferences, Gpio* gpio, const String& maintenancePathPrefix, char* buffer, size_t bufferSize);
void initialize();
bool update();
@@ -70,9 +71,13 @@ public:
void setKeepAliveCallback(std::function<void()> reconnectTick);
void addReconnectedCallback(std::function<void()> reconnectedCallback);
NetworkDevice* device();
private:
static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
void onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
void onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total);
void parseGpioTopics(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total);
void gpioActionCallback(const GpioAction& action, const int& pin);
void setupDevice();
bool reconnect();
@@ -97,14 +102,16 @@ private:
void onMqttConnect(const bool& sessionPresent);
void onMqttDisconnect(const espMqttClientTypes::DisconnectReason& reason);
void buildMqttPath(const char* prefix, const char* path, char* outPath);
void buildMqttPath(char* outPath, std::initializer_list<const char*> paths);
static Network* _inst;
const char* _lastWillPayload = "offline";
char _mqttConnectionStateTopic[211] = {0};
String _lockPath;
Preferences* _preferences;
Gpio* _gpio;
IPConfiguration* _ipConfiguration = nullptr;
String _hostname;
char _hostnameArr[101] = {0};
@@ -133,6 +140,7 @@ private:
bool _mqttEnabled = true;
static unsigned long _ignoreSubscriptionsTs;
long _rssiPublishInterval = 0;
std::map<uint8_t, unsigned long> _gpioTs;
char* _buffer;
const size_t _bufferSize;

View File

@@ -511,7 +511,7 @@ void NukiOpenerWrapper::onKeypadCommandReceivedCallback(const char *command, con
nukiOpenerInst->onKeypadCommandReceived(command, id, name, code, enabled);
}
void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action)
void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{
switch(action)
{

View File

@@ -48,7 +48,7 @@ private:
static LockActionResult onLockActionReceivedCallback(const char* value);
static void onConfigUpdateReceivedCallback(const char* topic, const char* value);
static void onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
static void gpioActionCallback(const GpioAction& action);
static void gpioActionCallback(const GpioAction& action, const int& pin);
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);

View File

@@ -480,7 +480,7 @@ void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uin
nukiInst->onKeypadCommandReceived(command, id, name, code, enabled);
}
void NukiWrapper::gpioActionCallback(const GpioAction &action)
void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{
switch(action)
{

View File

@@ -46,7 +46,7 @@ private:
static LockActionResult onLockActionReceivedCallback(const char* value);
static void onConfigUpdateReceivedCallback(const char* topic, const char* value);
static void onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
static void gpioActionCallback(const GpioAction& action);
static void gpioActionCallback(const GpioAction& action, const int& pin);
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);

View File

@@ -196,6 +196,9 @@ can be configured for a specific role:
- Output: High when RTO active: Outputs a high signal when ring-to-open is active (opener)
- Output: High when CM active: Outputs a high signal when continuous mode is active (opener)
- Output: High when RTO or CM active: Outputs a high signal when either ring-to-open or continuous mode is active (opener)
- General input (pull-down): The pin is configured in pull-down configuration and its state is published to the "gpio/pin_x/state" topic
- General input (pull-up): The pin is configured in pull-up configuration and its state is published to the "gpio/pin_x/state" topic
- Genral output: The pin is set to high or low depending on the "gpio/pin/x/state" topic
Note: The old setting "Enable control via GPIO" is removed. If you had enabled this setting before upgrading to 8.22, the PINs are automatically configured to be
compatible with the previously hard-coded PINs.

View File

@@ -25,24 +25,37 @@ enum class RestartReason
#define RESTART_REASON_VALID_DETECT 0xa00ab00bc00bd00d;
extern int restartReason;
extern uint64_t restartReasonValid;
extern uint64_t restartReasonValidDetect;
extern bool rebuildGpioRequested;
extern RestartReason currentRestartReason;
extern bool restartReason_isValid;
inline static void restartEsp(RestartReason reason)
{
if(reason == RestartReason::GpioConfigurationUpdated)
{
rebuildGpioRequested = true;
}
restartReason = (int)reason;
restartReasonValid = RESTART_REASON_VALID_DETECT;
restartReasonValidDetect = RESTART_REASON_VALID_DETECT;
ESP.restart();
}
inline static void initializeRestartReason()
{
uint64_t cmp = RESTART_REASON_VALID_DETECT;
if(restartReasonValid == cmp)
restartReason_isValid = (restartReasonValidDetect == cmp);
if(restartReason_isValid)
{
currentRestartReason = (RestartReason)restartReason;
memset(&restartReasonValid, 0, sizeof(restartReasonValid));
memset(&restartReasonValidDetect, 0, sizeof(restartReasonValidDetect));
}
else
{
rebuildGpioRequested = false;
}
}
@@ -121,4 +134,11 @@ inline static String getEspRestartReason()
default:
return "Unknown: " + (int)reason;
}
}
inline bool rebuildGpio()
{
bool rebGpio = rebuildGpioRequested;
rebuildGpioRequested = false;
return restartReason_isValid && rebGpio;
}

View File

@@ -36,6 +36,8 @@ void Scanner::update() {
return;
}
bleScan->clearResults();
bool result = bleScan->start(scanDuration, nullptr, false);
if (!result) {
scanErrors++;

View File

@@ -0,0 +1,58 @@
cmake_minimum_required(VERSION 3.0.0)
if(NOT ARDUINO_BOARD)
set(ARDUINO_BOARD "ESP32 Dev Module [esp32.esp32]")
endif()
project(gpio2go CXX)
# ARDUHAL_LOG_LEVEL_NONE, define ARDUHAL_LOG_LEVEL_ERROR, define ARDUHAL_LOG_LEVEL_WARN, define ARDUHAL_LOG_LEVEL_INFO,
# define ARDUHAL_LOG_LEVEL_DEBUG, define ARDUHAL_LOG_LEVEL_VERBOSE
set(LOG_LEVEL ARDUHAL_LOG_LEVEL_NONE)
#add_compile_definitions(DEBUG_SENSE_NUKI)
#add_compile_definitions(DEBUG_NUKI_COMMAND)
#add_compile_definitions(DEBUG_NUKI_CONNECT)
#add_compile_definitions(DEBUG_NUKI_COMMUNICATION)
#add_compile_definitions(DEBUG_NUKI_HEX_DATA)
#add_compile_definitions(DEBUG_NUKI_READABLE_DATA)
add_compile_definitions(ESP_PLATFORM)
add_compile_definitions(ESP32)
add_compile_definitions(ARDUINO_ARCH_ESP32)
include_directories(${PROJECT_NAME}
PRIVATE
src
)
set(SRCFILES
src/PinMode.h
src/Gpio2Go.cpp
src/InterruptMode.h
)
file(GLOB_RECURSE SRCFILESREC
)
add_executable(${PROJECT_NAME}
main.cpp
${SRCFILES}
${SRCFILESREC}
)
target_compile_definitions(${PROJECT_NAME}
PRIVATE
ARDUHAL_LOG_LEVEL=${LOG_LEVEL}
CORE_DEBUG_LEVEL=${LOG_LEVEL}
)
target_link_arduino_libraries(${PROJECT_NAME}
PRIVATE
core
)
target_enable_arduino_upload(${PROJECT_NAME})

21
lib/gpio2go/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Jan-Ole Schümann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
lib/gpio2go/README.md Normal file
View File

@@ -0,0 +1 @@
# gpio2go

38
lib/gpio2go/main.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include "Arduino.h"
#include "Gpio2Go.h"
#define INPUT_PIN 21
bool hasMessage = false;
String message;
void inputCb(const int & pin)
{
message = "";
message.concat("Input, Pin ");
message.concat(pin);
message.concat(" ");
message.concat(", state ");
message.concat(digitalRead(INPUT_PIN) ? "High" : "Low");
hasMessage = true;
}
void setup()
{
Serial.begin(115200);
delay(1100);
Serial.println(F("Started"));
Gpio2Go::configurePin(INPUT_PIN, PinMode::InputPullup, InterruptMode::Change, 200);
Gpio2Go::subscribe(inputCb);
}
void loop()
{
delay(100);
if(hasMessage)
{
hasMessage = false;
Serial.println(message);
}
}

242
lib/gpio2go/src/Gpio2Go.cpp Normal file
View File

@@ -0,0 +1,242 @@
#include "Gpio2Go.h"
void Gpio2Go::configurePin(int pin, PinMode pin_Mode, InterruptMode interrupt_Mode, uint16_t timeoutAfterTriggerMS)
{
timeoutDurations[pin - GPIO2GO_NR_FIRST_PIN] = timeoutAfterTriggerMS;
switch(pin_Mode)
{
case PinMode::InputPullup:
pinMode(pin, INPUT_PULLUP);
attachIsr(pin, interrupt_Mode);
break;
case PinMode::InputPullDown:
pinMode(pin, INPUT_PULLDOWN);
attachIsr(pin, interrupt_Mode);
break;
case PinMode::Output:
pinMode(pin, OUTPUT);
break;
}
}
void Gpio2Go::subscribe(std::function<void(const int &)> callback)
{
subscriptions.push_back(callback);
}
unsigned long Gpio2Go::getLastTriggeredMillis(const int &pin)
{
if(pin >= GPIO2GO_NR_FIRST_PIN && pin <= (GPIO2GO_NR_OF_PINS + GPIO2GO_NR_FIRST_PIN))
{
return lastTriggeredTimestamps[pin - GPIO2GO_NR_FIRST_PIN];
}
return -1;
}
void Gpio2Go::attachIsr(int pin, InterruptMode interruptMode)
{
switch(pin)
{
case 2:
attachInterrupt(2, isrGpio2, resolveInterruptMode(interruptMode));
break;
case 4:
attachInterrupt(4, isrGpio4, resolveInterruptMode(interruptMode));
break;
case 5:
attachInterrupt(5, isrGpio5, resolveInterruptMode(interruptMode));
break;
case 13:
attachInterrupt(13, isrGpio13, resolveInterruptMode(interruptMode));
break;
case 14:
attachInterrupt(14, isrGpio14, resolveInterruptMode(interruptMode));
break;
case 15:
attachInterrupt(15, isrGpio15, resolveInterruptMode(interruptMode));
break;
case 16:
attachInterrupt(16, isrGpio16, resolveInterruptMode(interruptMode));
break;
case 17:
attachInterrupt(17, isrGpio17, resolveInterruptMode(interruptMode));
break;
case 18:
attachInterrupt(18, isrGpio18, resolveInterruptMode(interruptMode));
break;
case 19:
attachInterrupt(19, isrGpio19, resolveInterruptMode(interruptMode));
break;
case 20:
attachInterrupt(20, isrGpio20, resolveInterruptMode(interruptMode));
break;
case 21:
attachInterrupt(21, isrGpio21, resolveInterruptMode(interruptMode));
break;
case 22:
attachInterrupt(22, isrGpio22, resolveInterruptMode(interruptMode));
break;
case 23:
attachInterrupt(23, isrGpio23, resolveInterruptMode(interruptMode));
break;
case 24:
attachInterrupt(24, isrGpio24, resolveInterruptMode(interruptMode));
break;
case 25:
attachInterrupt(25, isrGpio25, resolveInterruptMode(interruptMode));
break;
case 26:
attachInterrupt(26, isrGpio26, resolveInterruptMode(interruptMode));
break;
case 27:
attachInterrupt(27, isrGpio27, resolveInterruptMode(interruptMode));
break;
case 32:
attachInterrupt(32, isrGpio32, resolveInterruptMode(interruptMode));
break;
case 33:
attachInterrupt(33, isrGpio33, resolveInterruptMode(interruptMode));
break;
default:
throw std::runtime_error("Gpio2Go: Unsupported pin.");
}
}
int Gpio2Go::resolveInterruptMode(InterruptMode interruptMode)
{
switch(interruptMode)
{
case InterruptMode::Rising:
return RISING;
case InterruptMode::Falling:
return FALLING;
case InterruptMode::Change:
return CHANGE;
case InterruptMode::OnLow:
return ONLOW;
case InterruptMode::OnHigh:
return ONHIGH;
default:
throw std::runtime_error("Gpio2Go: Unsupported interrupt mode.");
}
}
void Gpio2Go::isrHandler(int pin)
{
unsigned long timeout = lastTriggeredTimestamps[pin - GPIO2GO_NR_FIRST_PIN];
if(timeoutDurations[pin - GPIO2GO_NR_FIRST_PIN] != 0 && (millis() - timeout) < timeoutDurations[pin - GPIO2GO_NR_FIRST_PIN]) return;
lastTriggeredTimestamps[pin - GPIO2GO_NR_FIRST_PIN] = millis();
bool state = digitalRead(pin) == HIGH;
for(const auto& callback : subscriptions)
{
callback(pin);
}
}
void Gpio2Go::isrGpio2()
{
isrHandler(2);
}
void Gpio2Go::isrGpio4()
{
isrHandler(4);
}
void Gpio2Go::isrGpio5()
{
isrHandler(5);
}
void Gpio2Go::isrGpio13()
{
isrHandler(13);
}
void Gpio2Go::isrGpio14()
{
isrHandler(14);
}
void Gpio2Go::isrGpio15()
{
isrHandler(15);
}
void Gpio2Go::isrGpio16()
{
isrHandler(16);
}
void Gpio2Go::isrGpio17()
{
isrHandler(17);
}
void Gpio2Go::isrGpio18()
{
isrHandler(18);
}
void Gpio2Go::isrGpio19()
{
isrHandler(19);
}
void Gpio2Go::isrGpio20()
{
isrHandler(20);
}
void Gpio2Go::isrGpio21()
{
isrHandler(21);
}
void Gpio2Go::isrGpio22()
{
isrHandler(22);
}
void Gpio2Go::isrGpio23()
{
isrHandler(23);
}
void Gpio2Go::isrGpio24()
{
isrHandler(24);
}
void Gpio2Go::isrGpio25()
{
isrHandler(25);
}
void Gpio2Go::isrGpio26()
{
isrHandler(26);
}
void Gpio2Go::isrGpio27()
{
isrHandler(27);
}
void Gpio2Go::isrGpio32()
{
isrHandler(32);
}
void Gpio2Go::isrGpio33()
{
isrHandler(33);
}
unsigned long Gpio2Go::lastTriggeredTimestamps[] = {0};
uint16_t Gpio2Go::timeoutDurations[] = {0};
std::vector<std::function<void(const int&)>> Gpio2Go::subscriptions;

49
lib/gpio2go/src/Gpio2Go.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <Arduino.h>
#include <vector>
#include "esp_attr.h"
#include "PinMode.h"
#include "InterruptMode.h"
#define GPIO2GO_NR_OF_PINS 31
#define GPIO2GO_NR_FIRST_PIN 2
class Gpio2Go
{
public:
static void configurePin(int pin, PinMode pin_Mode, InterruptMode interrupt_Mode, uint16_t timeoutAfterTriggerMS);
static void subscribe(std::function<void(const int&)> callback);
unsigned long getLastTriggeredMillis(const int& pin);
private:
static void attachIsr(int pin, InterruptMode interruptMode);
static int resolveInterruptMode(InterruptMode interruptMode);
static void IRAM_ATTR isrHandler(int pin);
static void IRAM_ATTR isrGpio2();
static void IRAM_ATTR isrGpio4();
static void IRAM_ATTR isrGpio5();
static void IRAM_ATTR isrGpio13();
static void IRAM_ATTR isrGpio14();
static void IRAM_ATTR isrGpio15();
static void IRAM_ATTR isrGpio16();
static void IRAM_ATTR isrGpio17();
static void IRAM_ATTR isrGpio18();
static void IRAM_ATTR isrGpio19();
static void IRAM_ATTR isrGpio20();
static void IRAM_ATTR isrGpio21();
static void IRAM_ATTR isrGpio22();
static void IRAM_ATTR isrGpio23();
static void IRAM_ATTR isrGpio24();
static void IRAM_ATTR isrGpio25();
static void IRAM_ATTR isrGpio26();
static void IRAM_ATTR isrGpio27();
static void IRAM_ATTR isrGpio32();
static void IRAM_ATTR isrGpio33();
static unsigned long DRAM_ATTR lastTriggeredTimestamps[GPIO2GO_NR_OF_PINS];
static uint16_t DRAM_ATTR timeoutDurations[GPIO2GO_NR_OF_PINS];
static std::vector<std::function<void(const int&)>> DRAM_ATTR subscriptions;
};

View File

@@ -0,0 +1,10 @@
#pragma once
enum class InterruptMode
{
Rising = 0x01,
Falling = 0x02,
Change = 0x03,
OnLow = 0x04,
OnHigh = 0x05
};

20
lib/gpio2go/src/PinMode.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
enum class PinMode
{
Output = 0x03,
InputPullup = 0x05,
InputPullDown = 0x09
};
//#define INPUT 0x01
//// Changed OUTPUT from 0x02 to behave the same as Arduino pinMode(pin,OUTPUT)
//// where you can read the state of pin even when it is set as OUTPUT
//#define OUTPUT 0x03
//#define PULLUP 0x04
//#define INPUT_PULLUP 0x05
//#define PULLDOWN 0x08
//#define INPUT_PULLDOWN 0x09
//#define OPEN_DRAIN 0x10
//#define OUTPUT_OPEN_DRAIN 0x12
//#define ANALOG 0xC0

View File

@@ -34,7 +34,9 @@ bool openerEnabled = false;
unsigned long restartTs = (2^32) - 5 * 60000;
RTC_NOINIT_ATTR int restartReason;
RTC_NOINIT_ATTR uint64_t restartReasonValid;
RTC_NOINIT_ATTR uint64_t restartReasonValidDetect;
RTC_NOINIT_ATTR bool rebuildGpioRequested;
bool restartReason_isValid;
RestartReason currentRestartReason = RestartReason::NotApplicable;
TaskHandle_t networkTaskHandle = nullptr;
@@ -176,11 +178,16 @@ void setup()
preferences->remove(preference_restart_timer);
}
gpio = new Gpio(preferences);
String gpioDesc;
gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration(), "\n\r");
Serial.print(gpioDesc.c_str());
lockEnabled = preferences->getBool(preference_lock_enabled);
openerEnabled = preferences->getBool(preference_opener_enabled);
const String mqttLockPath = preferences->getString(preference_mqtt_lock_path);
network = new Network(preferences, mqttLockPath, CharBuffer::get(), CHAR_BUFFER_SIZE);
network = new Network(preferences, gpio, mqttLockPath, CharBuffer::get(), CHAR_BUFFER_SIZE);
network->initialize();
networkLock = new NetworkLock(network, preferences, CharBuffer::get(), CHAR_BUFFER_SIZE);
@@ -198,11 +205,6 @@ void setup()
bleScanner->initialize("NukiHub");
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"));
if(lockEnabled)
{

View File

@@ -50,7 +50,6 @@ public:
virtual uint16_t mqttSubscribe(const char* topic, uint8_t qos) = 0;
protected:
const uint16_t _mqttMaxBufferSize = 6144;
const String _hostname;
const IPConfiguration* _ipConfiguration = nullptr;
};