diff --git a/.gitignore b/.gitignore index 2be016c9..6e63376d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build cmake-build-debug cmake-build-release +cmake-build-release-s3 diff --git a/CMakeLists.txt b/CMakeLists.txt index 25903e0d..5622c28d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Config.h b/Config.h index f9585280..9926ccce 100644 --- a/Config.h +++ b/Config.h @@ -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 \ No newline at end of file diff --git a/Gpio.cpp b/Gpio.cpp index 301c360e..9af20a31 100644 --- a/Gpio.cpp +++ b/Gpio.cpp @@ -1,13 +1,15 @@ #include #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 &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& 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 callback) +void Gpio::inputCallback(const int &pin) +{ + _inst->notify(GpioAction::GeneralInput, pin); +} + +void Gpio::addCallback(std::function callback) { _callbacks.push_back(callback); } @@ -213,49 +255,49 @@ void Gpio::addCallback(std::function 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); } + diff --git a/Gpio.h b/Gpio.h index 768d7173..a770a2ca 100644 --- a/Gpio.h +++ b/Gpio.h @@ -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 @@ class Gpio void migrateObsoleteSetting(); - void addCallback(std::function callback); + void addCallback(std::function callback); void loadPinConfiguration(); void savePinConfiguration(const std::vector& pinConfiguration); const std::vector& availablePins() const; const std::vector& pinConfiguration() const; + const PinRole getPinRole(const int& pin) const; String getRoleDescription(PinRole role) const; void getConfigurationText(String& text, const std::vector& pinConfiguration, const String& linebreak = "\n") const; @@ -63,7 +68,8 @@ class Gpio 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 _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 }; const std::vector _allRoles = @@ -81,6 +87,9 @@ class Gpio PinRole::OutputHighRtoActive, PinRole::OutputHighCmActive, PinRole::OutputHighRtoOrCmActive, + PinRole::GeneralInputPullDown, + PinRole::GeneralInputPullUp, + PinRole::GeneralOutput }; std::vector _pinConfiguration; @@ -94,7 +103,7 @@ class Gpio static void IRAM_ATTR isrActivateCM(); static void IRAM_ATTR isrDeactivateRtoCm(); - std::vector> _callbacks; + std::vector> _callbacks; static Gpio* _inst; static unsigned long _debounceTs; diff --git a/MqttTopics.h b/MqttTopics.h index b80feee0..b5ae1ffd 100644 --- a/MqttTopics.h +++ b/MqttTopics.h @@ -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" \ No newline at end of file +#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" diff --git a/Network.cpp b/Network.cpp index 6457daa4..0e4cbbe0 100644 --- a/Network.cpp +++ b/Network.cpp @@ -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 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; +} diff --git a/Network.h b/Network.h index 1ef54fc5..71e5faa6 100644 --- a/Network.h +++ b/Network.h @@ -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 @@ class Network void setKeepAliveCallback(std::function reconnectTick); void addReconnectedCallback(std::function 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 @@ class Network 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 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 @@ class Network bool _mqttEnabled = true; static unsigned long _ignoreSubscriptionsTs; long _rssiPublishInterval = 0; + std::map _gpioTs; char* _buffer; const size_t _bufferSize; diff --git a/NukiOpenerWrapper.cpp b/NukiOpenerWrapper.cpp index e649e904..79c0aef0 100644 --- a/NukiOpenerWrapper.cpp +++ b/NukiOpenerWrapper.cpp @@ -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) { diff --git a/NukiOpenerWrapper.h b/NukiOpenerWrapper.h index 125bb7ed..b6169774 100644 --- a/NukiOpenerWrapper.h +++ b/NukiOpenerWrapper.h @@ -48,7 +48,7 @@ class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler 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); diff --git a/NukiWrapper.cpp b/NukiWrapper.cpp index a5df7454..05abfc8a 100644 --- a/NukiWrapper.cpp +++ b/NukiWrapper.cpp @@ -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) { diff --git a/NukiWrapper.h b/NukiWrapper.h index 78b51d89..c8fd14fb 100644 --- a/NukiWrapper.h +++ b/NukiWrapper.h @@ -46,7 +46,7 @@ class NukiWrapper : public Nuki::SmartlockEventHandler 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); diff --git a/README.md b/README.md index 3c50e760..eaa53b0c 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/RestartReason.h b/RestartReason.h index 17752a0e..9871c3f7 100644 --- a/RestartReason.h +++ b/RestartReason.h @@ -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; } \ No newline at end of file diff --git a/lib/BleScanner/src/BleScanner.cpp b/lib/BleScanner/src/BleScanner.cpp index be78ddbe..4fdba82a 100644 --- a/lib/BleScanner/src/BleScanner.cpp +++ b/lib/BleScanner/src/BleScanner.cpp @@ -36,6 +36,8 @@ void Scanner::update() { return; } + bleScan->clearResults(); + bool result = bleScan->start(scanDuration, nullptr, false); if (!result) { scanErrors++; diff --git a/lib/gpio2go/CMakeLists.txt b/lib/gpio2go/CMakeLists.txt new file mode 100644 index 00000000..6fbd13c6 --- /dev/null +++ b/lib/gpio2go/CMakeLists.txt @@ -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}) + diff --git a/lib/gpio2go/LICENSE b/lib/gpio2go/LICENSE new file mode 100644 index 00000000..cef1a2b9 --- /dev/null +++ b/lib/gpio2go/LICENSE @@ -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. diff --git a/lib/gpio2go/README.md b/lib/gpio2go/README.md new file mode 100644 index 00000000..b173d346 --- /dev/null +++ b/lib/gpio2go/README.md @@ -0,0 +1 @@ +# gpio2go \ No newline at end of file diff --git a/lib/gpio2go/main.cpp b/lib/gpio2go/main.cpp new file mode 100644 index 00000000..0c6330bc --- /dev/null +++ b/lib/gpio2go/main.cpp @@ -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); + } +} \ No newline at end of file diff --git a/lib/gpio2go/src/Gpio2Go.cpp b/lib/gpio2go/src/Gpio2Go.cpp new file mode 100644 index 00000000..e3e49655 --- /dev/null +++ b/lib/gpio2go/src/Gpio2Go.cpp @@ -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 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> Gpio2Go::subscriptions; \ No newline at end of file diff --git a/lib/gpio2go/src/Gpio2Go.h b/lib/gpio2go/src/Gpio2Go.h new file mode 100644 index 00000000..13391195 --- /dev/null +++ b/lib/gpio2go/src/Gpio2Go.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#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 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> DRAM_ATTR subscriptions; +}; diff --git a/lib/gpio2go/src/InterruptMode.h b/lib/gpio2go/src/InterruptMode.h new file mode 100644 index 00000000..15517c85 --- /dev/null +++ b/lib/gpio2go/src/InterruptMode.h @@ -0,0 +1,10 @@ +#pragma once + +enum class InterruptMode +{ + Rising = 0x01, + Falling = 0x02, + Change = 0x03, + OnLow = 0x04, + OnHigh = 0x05 +}; \ No newline at end of file diff --git a/lib/gpio2go/src/PinMode.h b/lib/gpio2go/src/PinMode.h new file mode 100644 index 00000000..9753e38a --- /dev/null +++ b/lib/gpio2go/src/PinMode.h @@ -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 \ No newline at end of file diff --git a/main.cpp b/main.cpp index 46863b76..ac712f26 100644 --- a/main.cpp +++ b/main.cpp @@ -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) { diff --git a/networkDevices/NetworkDevice.h b/networkDevices/NetworkDevice.h index 6d55e3c3..8c872465 100644 --- a/networkDevices/NetworkDevice.h +++ b/networkDevices/NetworkDevice.h @@ -50,7 +50,6 @@ class NetworkDevice virtual uint16_t mqttSubscribe(const char* topic, uint8_t qos) = 0; protected: - const uint16_t _mqttMaxBufferSize = 6144; const String _hostname; const IPConfiguration* _ipConfiguration = nullptr; }; \ No newline at end of file