diff --git a/CMakeLists.txt b/CMakeLists.txt index 8223f4b..2704558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,8 @@ include_directories(${PROJECT_NAME} file(GLOB SRCFILES Pins.h + Network.cpp + MqttReceiver.h NetworkLock.cpp NetworkOpener.cpp networkDevices/NetworkDevice.h diff --git a/MqttReceiver.h b/MqttReceiver.h new file mode 100644 index 0000000..3faada5 --- /dev/null +++ b/MqttReceiver.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +class MqttReceiver +{ +public: + virtual void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length) = 0; +}; \ No newline at end of file diff --git a/Network.cpp b/Network.cpp new file mode 100644 index 0000000..0dcbebc --- /dev/null +++ b/Network.cpp @@ -0,0 +1,375 @@ +#include "Network.h" +#include "PreferencesKeys.h" +#include "MqttTopics.h" +#include "networkDevices/W5500Device.h" +#include "networkDevices/WifiDevice.h" + + +Network* Network::_inst = nullptr; + +Network::Network(const NetworkDeviceType networkDevice, Preferences *preferences) +: _preferences(preferences) +{ + _inst = this; + _hostname = _preferences->getString(preference_hostname); + setupDevice(networkDevice); +} + + +void Network::setupDevice(const NetworkDeviceType hardware) +{ + switch(hardware) + { + case NetworkDeviceType::W5500: + Serial.println(F("Network device: W5500")); + _device = new W5500Device(_hostname, _preferences); + break; + case NetworkDeviceType::WiFi: + Serial.println(F("Network device: Builtin WiFi")); + _device = new WifiDevice(_hostname, _preferences); + break; + default: + Serial.println(F("Unknown network device type, defaulting to WiFi")); + _device = new WifiDevice(_hostname, _preferences); + break; + } +} + +void Network::initialize() +{ + if(_hostname == "") + { + _hostname = "nukihub"; + _preferences->putString(preference_hostname, _hostname); + } + + _device->initialize(); + + Serial.print(F("Host name: ")); + Serial.println(_hostname); + + const char* brokerAddr = _preferences->getString(preference_mqtt_broker).c_str(); + strcpy(_mqttBrokerAddr, brokerAddr); + + int port = _preferences->getInt(preference_mqtt_broker_port); + if(port == 0) + { + port = 1883; + _preferences->putInt(preference_mqtt_broker_port, port); + } + + String mqttUser = _preferences->getString(preference_mqtt_user); + if(mqttUser.length() > 0) + { + size_t len = mqttUser.length(); + for(int i=0; i < len; i++) + { + _mqttUser[i] = mqttUser.charAt(i); + } + } + + String mqttPass = _preferences->getString(preference_mqtt_password); + if(mqttPass.length() > 0) + { + size_t len = mqttPass.length(); + for(int i=0; i < len; i++) + { + _mqttPass[i] = mqttPass.charAt(i); + } + } + + Serial.print(F("MQTT Broker: ")); + Serial.print(_mqttBrokerAddr); + Serial.print(F(":")); + Serial.println(port); + + _device->mqttClient()->setServer(_mqttBrokerAddr, port); + _device->mqttClient()->setCallback(Network::onMqttDataReceivedCallback); + + _networkTimeout = _preferences->getInt(preference_network_timeout); + if(_networkTimeout == 0) + { + _networkTimeout = -1; + _preferences->putInt(preference_network_timeout, _networkTimeout); + } +} + +int Network::update() +{ + long ts = millis(); + + _device->update(); + + if(!_device->isConnected()) + { + Serial.println(F("Network not connected. Trying reconnect.")); + bool success = _device->reconnect(); + Serial.println(success ? F("Reconnect successful") : F("Reconnect failed")); + } + + if(!_device->isConnected()) + { + if(_networkTimeout > 0 && (ts - _lastConnectedTs > _networkTimeout * 1000)) + { + Serial.println("Network timeout has been reached, restarting ..."); + delay(200); + ESP.restart(); + } + return 2; + } + + _lastConnectedTs = ts; + + if(!_device->mqttClient()->connected()) + { + bool success = reconnect(); + if(!success) + { + return 1; + } + } + + _device->mqttClient()->loop(); + return 0; +} + +bool Network::reconnect() +{ + _mqttConnected = false; + + while (!_device->mqttClient()->connected() && millis() > _nextReconnect) + { + Serial.println(F("Attempting MQTT connection")); + bool success = false; + + if(strlen(_mqttUser) == 0) + { + Serial.println(F("MQTT: Connecting without credentials")); + success = _device->mqttClient()->connect(_preferences->getString(preference_hostname).c_str()); + } + else + { + Serial.print(F("MQTT: Connecting with user: ")); Serial.println(_mqttUser); + success = _device->mqttClient()->connect(_preferences->getString(preference_hostname).c_str(), _mqttUser, _mqttPass); + } + + if (success) + { + Serial.println(F("MQTT connected")); + _mqttConnected = true; + delay(100); + for(const String& topic : _subscribedTopics) + { + _device->mqttClient()->subscribe(topic.c_str()); + } + } + else + { + Serial.print(F("MQTT connect failed, rc=")); + Serial.println(_device->mqttClient()->state()); + _device->printError(); + _device->mqttClient()->disconnect(); + _mqttConnected = false; + _nextReconnect = millis() + 5000; + } + } + return _mqttConnected; +} + +void Network::subscribe(const char* prefix, const char *path) +{ + char prefixedPath[500]; + buildMqttPath(prefix, path, prefixedPath); + _subscribedTopics.push_back(prefixedPath); +} + +void Network::buildMqttPath(const char* prefix, const char* path, char* outPath) +{ + int offset = 0; + int i=0; + while(prefix[i] != 0x00) + { + outPath[offset] = prefix[i]; + ++offset; + ++i; + } + + i=0; + while(outPath[i] != 0x00) + { + outPath[offset] = path[i]; + ++i; + ++offset; + } + + outPath[i+1] = 0x00; +} + +void Network::registerMqttReceiver(MqttReceiver* receiver) +{ + _mqttReceivers.push_back(receiver); +} + +void Network::onMqttDataReceivedCallback(char *topic, byte *payload, unsigned int length) +{ + _inst->onMqttDataReceived(topic, payload, length); +} + +void Network::onMqttDataReceived(char *&topic, byte *&payload, unsigned int &length) +{ + for(auto receiver : _mqttReceivers) + { + receiver->onMqttDataReceived(topic, payload, length); + } +} + +PubSubClient *Network::mqttClient() +{ + return _device->mqttClient(); +} + +void Network::reconfigureDevice() +{ + _device->reconfigure(); +} + +bool Network::isMqttConnected() +{ + return _mqttConnected; +} + + +void Network::publishFloat(const char* prefix, const char* topic, const float value, const uint8_t precision) +{ + char str[30]; + dtostrf(value, 0, precision, str); + char path[200] = {0}; + buildMqttPath(prefix, topic, path); + _device->mqttClient()->publish(path, str, true); +} + +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); + _device->mqttClient()->publish(path, str, true); +} + +void Network::publishUInt(const char* prefix, const char *topic, const unsigned int value) +{ + char str[30]; + utoa(value, str, 10); + char path[200] = {0}; + buildMqttPath(prefix, topic, path); + _device->mqttClient()->publish(path, str, true); +} + +void Network::publishBool(const char* prefix, const char *topic, const bool value) +{ + char str[2] = {0}; + str[0] = value ? '1' : '0'; + char path[200] = {0}; + buildMqttPath(prefix, topic, path); + _device->mqttClient()->publish(path, str, true); +} + +bool Network::publishString(const char* prefix, const char *topic, const char *value) +{ + char path[200] = {0}; + buildMqttPath(prefix, topic, path); + return _device->mqttClient()->publish(path, value, true); +} + +void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) +{ + String discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery); + + if (discoveryTopic != "") + { + String configJSON = "{\"dev\":{\"ids\":[\"nuki_"; + configJSON.concat(uidString); + configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); + configJSON.concat(deviceType); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\"},\"~\":\""); + configJSON.concat(baseTopic); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\",\"unique_id\":\""); + configJSON.concat(uidString); + configJSON.concat("_lock\",\"cmd_t\":\"~"); + configJSON.concat(mqtt_topic_lock_action); + configJSON.concat("\",\"pl_lock\":\""); + configJSON.concat(lockAction); + configJSON.concat("\",\"pl_unlk\":\""); + configJSON.concat(unlockAction); + configJSON.concat("\",\"pl_open\":\""); + configJSON.concat(openAction); + configJSON.concat("\",\"stat_t\":\"~"); + configJSON.concat(mqtt_topic_lock_state); + configJSON.concat("\",\"stat_locked\":\""); + configJSON.concat(lockedState); + configJSON.concat("\",\"stat_unlocked\":\""); + configJSON.concat(unlockedState); + configJSON.concat("\",\"opt\":\"false\"}"); + + String path = discoveryTopic; + path.concat("/lock/"); + path.concat(uidString); + path.concat("/smartlock/config"); + + Serial.println("HASS Config:"); + Serial.println(configJSON); + + _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); + + configJSON = "{\"dev\":{\"ids\":[\"nuki_"; + configJSON.concat(uidString); + configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); + configJSON.concat(deviceType); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat("\"},\"~\":\""); + configJSON.concat(baseTopic); + configJSON.concat("\",\"name\":\""); + configJSON.concat(name); + configJSON.concat(" battery low\",\"unique_id\":\""); + configJSON.concat(uidString); + configJSON.concat( + "_battery_low\",\"dev_cla\":\"battery\",\"ent_cat\":\"diagnostic\",\"pl_off\":\"0\",\"pl_on\":\"1\",\"stat_t\":\"~"); + configJSON.concat(mqtt_topic_battery_critical); + configJSON.concat("\"}"); + + path = discoveryTopic; + path.concat("/binary_sensor/"); + path.concat(uidString); + path.concat("/battery_low/config"); + + _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); + } +} + +void Network::removeHASSConfig(char* uidString) +{ + String discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery); + + if(discoveryTopic != "") + { + String path = discoveryTopic; + path.concat("/lock/"); + path.concat(uidString); + path.concat("/smartlock/config"); + + _device->mqttClient()->publish(path.c_str(), NULL, 0U, true); + + path = discoveryTopic; + path.concat("/binary_sensor/"); + path.concat(uidString); + path.concat("/battery_low/config"); + + _device->mqttClient()->publish(path.c_str(), NULL, 0U, true); + } +} \ No newline at end of file diff --git a/Network.h b/Network.h new file mode 100644 index 0000000..ee2e093 --- /dev/null +++ b/Network.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include "networkDevices/NetworkDevice.h" +#include "MqttReceiver.h" + +enum class NetworkDeviceType +{ + WiFi, + W5500 +}; + +class Network +{ +public: + explicit Network(const NetworkDeviceType networkDevice, Preferences* preferences); + + void initialize(); + int update(); + void registerMqttReceiver(MqttReceiver* receiver); + void reconfigureDevice(); + + void subscribe(const char* prefix, const char* path); + void publishFloat(const char* prefix, const char* topic, const float value, const uint8_t precision = 2); + void publishInt(const char* prefix, const char* topic, const int value); + void publishUInt(const char* prefix, const char* topic, const unsigned int value); + void publishBool(const char* prefix, const char* topic, const bool value); + bool publishString(const char* prefix, const char* topic, const char* value); + + void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState); + void removeHASSConfig(char* uidString); + + PubSubClient* mqttClient(); + bool isMqttConnected(); + +private: + static void onMqttDataReceivedCallback(char* topic, byte* payload, unsigned int length); + void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length); + void setupDevice(const NetworkDeviceType hardware); + bool reconnect(); + + void buildMqttPath(const char* prefix, const char* path, char* outPath); + + static Network* _inst; + Preferences* _preferences; + String _hostname; + NetworkDevice* _device = nullptr; + bool _mqttConnected = false; + + unsigned long _nextReconnect = 0; + char _mqttBrokerAddr[101] = {0}; + char _mqttUser[31] = {0}; + char _mqttPass[31] = {0}; + std::vector _subscribedTopics; + int _networkTimeout = 0; + std::vector _mqttReceivers; + + unsigned long _lastConnectedTs = 0; +}; diff --git a/NetworkLock.cpp b/NetworkLock.cpp index c367a25..06a4b4f 100644 --- a/NetworkLock.cpp +++ b/NetworkLock.cpp @@ -5,75 +5,26 @@ #include "PreferencesKeys.h" #include "Pins.h" -NetworkLock* nwInst; - -NetworkLock::NetworkLock(const NetworkDeviceType networkDevice, Preferences* preferences) -: _preferences(preferences) +NetworkLock::NetworkLock(Network* network, Preferences* preferences) +: _network(network), + _preferences(preferences) { - nwInst = this; - - _hostname = _preferences->getString(preference_hostname); - setupDevice(networkDevice); - _configTopics.reserve(5); _configTopics.push_back(mqtt_topic_config_button_enabled); _configTopics.push_back(mqtt_topic_config_led_enabled); _configTopics.push_back(mqtt_topic_config_led_brightness); _configTopics.push_back(mqtt_topic_config_auto_unlock); _configTopics.push_back(mqtt_topic_config_auto_lock); + + _network->registerMqttReceiver(this); } NetworkLock::~NetworkLock() { - if(_device != nullptr) - { - delete _device; - _device = nullptr; - } -} - -void NetworkLock::setupDevice(const NetworkDeviceType hardware) -{ - switch(hardware) - { - case NetworkDeviceType::W5500: - Serial.println(F("Network device: W5500")); - _device = new W5500Device(_hostname, _preferences); - break; - case NetworkDeviceType::WiFi: - Serial.println(F("Network device: Builtin WiFi")); - _device = new WifiDevice(_hostname, _preferences); - break; - default: - Serial.println(F("Unknown network device type, defaulting to WiFi")); - _device = new WifiDevice(_hostname, _preferences); - break; - } } void NetworkLock::initialize() { - if(_hostname == "") - { - _hostname = "nukihub"; - _preferences->putString(preference_hostname, _hostname); - } - - _device->initialize(); - - Serial.print(F("Host name: ")); - Serial.println(_hostname); - - const char* brokerAddr = _preferences->getString(preference_mqtt_broker).c_str(); - strcpy(_mqttBrokerAddr, brokerAddr); - - int port = _preferences->getInt(preference_mqtt_broker_port); - if(port == 0) - { - port = 1883; - _preferences->putInt(preference_mqtt_broker_port, port); - } - String mqttPath = _preferences->getString(preference_mqtt_lock_path); if(mqttPath.length() > 0) { @@ -89,122 +40,15 @@ void NetworkLock::initialize() _preferences->putString(preference_mqtt_lock_path, _mqttPath); } - String mqttUser = _preferences->getString(preference_mqtt_user); - if(mqttUser.length() > 0) + _network->subscribe(_mqttPath, mqtt_topic_lock_action); + for(const auto& topic : _configTopics) { - size_t len = mqttUser.length(); - for(int i=0; i < len; i++) - { - _mqttUser[i] = mqttUser.charAt(i); - } + _network->subscribe(_mqttPath, topic); } - - String mqttPass = _preferences->getString(preference_mqtt_password); - if(mqttPass.length() > 0) - { - size_t len = mqttPass.length(); - for(int i=0; i < len; i++) - { - _mqttPass[i] = mqttPass.charAt(i); - } - } - - Serial.print(F("MQTT Broker: ")); - Serial.print(_mqttBrokerAddr); - Serial.print(F(":")); - Serial.println(port); - - _device->mqttClient()->setServer(_mqttBrokerAddr, port); - _device->mqttClient()->setCallback(NetworkLock::onMqttDataReceivedCallback); - - _networkTimeout = _preferences->getInt(preference_network_timeout); - if(_networkTimeout == 0) - { - _networkTimeout = -1; - _preferences->putInt(preference_network_timeout, _networkTimeout); - } -} - -bool NetworkLock::reconnect() -{ - _mqttConnected = false; - - while (!_device->mqttClient()->connected() && millis() > _nextReconnect) - { - Serial.println(F("Attempting MQTT connection")); - bool success = false; - - if(strlen(_mqttUser) == 0) - { - Serial.println(F("MQTT: Connecting without credentials")); - success = _device->mqttClient()->connect(_preferences->getString(preference_hostname).c_str()); - } - else - { - Serial.print(F("MQTT: Connecting with user: ")); Serial.println(_mqttUser); - success = _device->mqttClient()->connect(_preferences->getString(preference_hostname).c_str(), _mqttUser, _mqttPass); - } - - if (success) - { - Serial.println(F("MQTT connected")); - _mqttConnected = true; - delay(100); - subscribe(mqtt_topic_lock_action); - - for(auto topic : _configTopics) - { - subscribe(topic); - } - } - else - { - Serial.print(F("MQTT connect failed, rc=")); - Serial.println(_device->mqttClient()->state()); - _device->printError(); - _device->mqttClient()->disconnect(); - _mqttConnected = false; - _nextReconnect = millis() + 5000; - } - } - return _mqttConnected; } void NetworkLock::update() { - long ts = millis(); - - _device->update(); - - if(!_device->isConnected()) - { - Serial.println(F("Network not connected. Trying reconnect.")); - bool success = _device->reconnect(); - Serial.println(success ? F("Reconnect successful") : F("Reconnect failed")); - } - - if(!_device->isConnected()) - { - if(_networkTimeout > 0 && (ts - _lastConnectedTs > _networkTimeout * 1000)) - { - Serial.println("Network timeout has been reached, restarting ..."); - delay(200); - ESP.restart(); - } - return; - } - - _lastConnectedTs = ts; - - if(!_device->mqttClient()->connected()) - { - bool success = reconnect(); - if(!success) - { - return; - } - } - if(_presenceCsv != nullptr && strlen(_presenceCsv) > 0) { bool success = publishString(mqtt_topic_presence, _presenceCsv); @@ -215,13 +59,6 @@ void NetworkLock::update() } _presenceCsv = nullptr; } - - _device->mqttClient()->loop(); -} - -void NetworkLock::onMqttDataReceivedCallback(char *topic, byte *payload, unsigned int length) -{ - nwInst->onMqttDataReceived(topic, payload, length); } void NetworkLock::onMqttDataReceived(char *&topic, byte *&payload, unsigned int &length) @@ -349,97 +186,6 @@ void NetworkLock::publishPresenceDetection(char *csv) _presenceCsv = csv; } -void NetworkLock::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction, char* lockedState, char* unlockedState) -{ - String discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery); - - if(discoveryTopic != "") - { - String configJSON = "{\"dev\":{\"ids\":[\"nuki_"; - configJSON.concat(uidString); - configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); - configJSON.concat(deviceType); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat("\"},\"~\":\""); - configJSON.concat(baseTopic); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat("\",\"unique_id\":\""); - configJSON.concat(uidString); - configJSON.concat("_lock\",\"cmd_t\":\"~"); - configJSON.concat(mqtt_topic_lock_action); - configJSON.concat("\",\"pl_lock\":\""); - configJSON.concat(lockAction); - configJSON.concat("\",\"pl_unlk\":\""); - configJSON.concat(unlockAction); - configJSON.concat("\",\"pl_open\":\""); - configJSON.concat(openAction); - configJSON.concat("\",\"stat_t\":\"~"); - configJSON.concat(mqtt_topic_lock_state); - configJSON.concat("\",\"stat_locked\":\""); - configJSON.concat(lockedState); - configJSON.concat("\",\"stat_unlocked\":\""); - configJSON.concat(unlockedState); - configJSON.concat("\",\"opt\":\"false\"}"); - - String path = discoveryTopic; - path.concat("/lock/"); - path.concat(uidString); - path.concat("/smartlock/config"); - - Serial.println("HASS Config:"); - Serial.println(configJSON); - - _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); - - configJSON = "{\"dev\":{\"ids\":[\"nuki_"; - configJSON.concat(uidString); - configJSON.concat("\"],\"mf\":\"Nuki\",\"mdl\":\""); - configJSON.concat(deviceType); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat("\"},\"~\":\""); - configJSON.concat(baseTopic); - configJSON.concat("\",\"name\":\""); - configJSON.concat(name); - configJSON.concat(" battery low\",\"unique_id\":\""); - configJSON.concat(uidString); - configJSON.concat("_battery_low\",\"dev_cla\":\"battery\",\"ent_cat\":\"diagnostic\",\"pl_off\":\"0\",\"pl_on\":\"1\",\"stat_t\":\"~"); - configJSON.concat(mqtt_topic_battery_critical); - configJSON.concat("\"}"); - - path = discoveryTopic; - path.concat("/binary_sensor/"); - path.concat(uidString); - path.concat("/battery_low/config"); - - _device->mqttClient()->publish(path.c_str(), configJSON.c_str(), true); - } -} - -void NetworkLock::removeHASSConfig(char* uidString) -{ - String discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery); - - if(discoveryTopic != "") - { - String path = discoveryTopic; - path.concat("/lock/"); - path.concat(uidString); - path.concat("/smartlock/config"); - - _device->mqttClient()->publish(path.c_str(), NULL, 0U, true); - - path = discoveryTopic; - path.concat("/binary_sensor/"); - path.concat(uidString); - path.concat("/battery_low/config"); - - _device->mqttClient()->publish(path.c_str(), NULL, 0U, true); - } -} - void NetworkLock::setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char *)) { _lockActionReceivedCallback = lockActionReceivedCallback; @@ -455,54 +201,6 @@ void NetworkLock::setMqttDataReceivedForwardCallback(void (*callback)(char *, ui _mqttTopicReceivedForwardCallback = callback; } -void NetworkLock::publishFloat(const char* topic, const float value, const uint8_t precision) -{ - char str[30]; - dtostrf(value, 0, precision, str); - char path[200] = {0}; - buildMqttPath(topic, path); - _device->mqttClient()->publish(path, str, true); -} - -void NetworkLock::publishInt(const char *topic, const int value) -{ - char str[30]; - itoa(value, str, 10); - char path[200] = {0}; - buildMqttPath(topic, path); - _device->mqttClient()->publish(path, str, true); -} - -void NetworkLock::publishUInt(const char *topic, const unsigned int value) -{ - char str[30]; - utoa(value, str, 10); - char path[200] = {0}; - buildMqttPath(topic, path); - _device->mqttClient()->publish(path, str, true); -} - -void NetworkLock::publishBool(const char *topic, const bool value) -{ - char str[2] = {0}; - str[0] = value ? '1' : '0'; - char path[200] = {0}; - buildMqttPath(topic, path); - _device->mqttClient()->publish(path, str, true); -} - -bool NetworkLock::publishString(const char *topic, const char *value) -{ - char path[200] = {0}; - buildMqttPath(topic, path); - return _device->mqttClient()->publish(path, value, true); -} - -bool NetworkLock::isMqttConnected() -{ - return _mqttConnected; -} - void NetworkLock::buildMqttPath(const char* path, char* outPath) { int offset = 0; @@ -525,16 +223,10 @@ void NetworkLock::buildMqttPath(const char* path, char* outPath) outPath[i+1] = 0x00; } -void NetworkLock::subscribe(const char *path) -{ - char prefixedPath[500]; - buildMqttPath(path, prefixedPath); - _device->mqttClient()->subscribe(prefixedPath); -} void NetworkLock::restartAndConfigureWifi() { - _device->reconfigure(); + _network->reconfigureDevice(); } bool NetworkLock::comparePrefixedPath(const char *fullPath, const char *subPath) @@ -544,7 +236,39 @@ bool NetworkLock::comparePrefixedPath(const char *fullPath, const char *subPath) return strcmp(fullPath, prefixedPath) == 0; } -NetworkDevice *NetworkLock::device() +void +NetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, char *lockAction, + char *unlockAction, char *openAction, char *lockedState, char *unlockedState) { - return _device; + _network->publishHASSConfig(deviceType, baseTopic, name, uidString, lockAction, unlockAction, openAction, lockedState, unlockedState); +} + +void NetworkLock::removeHASSConfig(char *uidString) +{ + _network->removeHASSConfig(uidString); +} + +void NetworkLock::publishFloat(const char *topic, const float value, const uint8_t precision) +{ + _network->publishFloat(_mqttPath, topic, value, precision); +} + +void NetworkLock::publishInt(const char *topic, const int value) +{ + _network->publishInt(_mqttPath, topic, value); +} + +void NetworkLock::publishUInt(const char *topic, const unsigned int value) +{ + _network->publishUInt(_mqttPath, topic, value); +} + +void NetworkLock::publishBool(const char *topic, const bool value) +{ + _network->publishBool(_mqttPath, topic, value); +} + +bool NetworkLock::publishString(const char *topic, const char *value) +{ + return _network->publishString(_mqttPath, topic, value); } diff --git a/NetworkLock.h b/NetworkLock.h index 2fe4c9c..3558b0b 100644 --- a/NetworkLock.h +++ b/NetworkLock.h @@ -9,24 +9,16 @@ #include "NukiConstants.h" #include "SpiffsCookie.h" #include "NukiLockConstants.h" +#include "Network.h" -enum class NetworkDeviceType -{ - WiFi, - W5500 -}; - -class NetworkLock +class NetworkLock : public MqttReceiver { public: - explicit NetworkLock(const NetworkDeviceType networkDevice, Preferences* preferences); + explicit NetworkLock(Network* network, Preferences* preferences); virtual ~NetworkLock(); void initialize(); void update(); - void setupDevice(const NetworkDeviceType hardware); - - bool isMqttConnected(); void publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurnerState, const NukiLock::KeyTurnerState& lastKeyTurnerState); void publishAuthorizationInfo(const uint32_t authId, const char* authName); @@ -44,42 +36,24 @@ public: void restartAndConfigureWifi(); - NetworkDevice* device(); + void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length) override; + private: - static void onMqttDataReceivedCallback(char* topic, byte* payload, unsigned int length); - void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length); - bool comparePrefixedPath(const char* fullPath, const char* subPath); - void publishFloat(const char* topic, const float value, const uint8_t precision = 2); void publishInt(const char* topic, const int value); void publishUInt(const char* topic, const unsigned int value); void publishBool(const char* topic, const bool value); - bool publishString(const char* topic, const char* value); + bool publishString(const char* topic, const char* value); bool comparePrefixedPath(const char* fullPath, const char* subPath); void buildMqttPath(const char* path, char* outPath); - void subscribe(const char* path); - bool reconnect(); - - NetworkDevice* _device = nullptr; + Network* _network; Preferences* _preferences; - String _hostname; - - bool _mqttConnected = false; - - unsigned long _nextReconnect = 0; - char _mqttBrokerAddr[101] = {0}; - char _mqttPath[181] = {0}; - char _mqttUser[31] = {0}; - char _mqttPass[31] = {0}; - int _networkTimeout = 0; - - unsigned long _lastConnectedTs = 0; char* _presenceCsv = nullptr; - std::vector _configTopics; + char _mqttPath[181] = {0}; bool _firstTunerStatePublish = true; diff --git a/NetworkOpener.cpp b/NetworkOpener.cpp index 848cd83..2c5d88c 100644 --- a/NetworkOpener.cpp +++ b/NetworkOpener.cpp @@ -5,14 +5,10 @@ #include "PreferencesKeys.h" #include "Pins.h" -NetworkOpener* nwInstOpener; - -NetworkOpener::NetworkOpener(NetworkLock* network, Preferences* preferences) +NetworkOpener::NetworkOpener(Network* network, Preferences* preferences) : _preferences(preferences), _network(network) { - nwInstOpener = this; - _configTopics.reserve(5); // _configTopics.push_back(mqtt_topic_config_button_enabled); // _configTopics.push_back(mqtt_topic_config_led_enabled); @@ -38,12 +34,13 @@ void NetworkOpener::initialize() _preferences->putString(preference_mqtt_opener_path, _mqttPath); } - _network->setMqttDataReceivedForwardCallback(nwInstOpener->onMqttDataReceivedCallback); + _network->subscribe(_mqttPath, mqtt_topic_lock_action); + _network->registerMqttReceiver(this); } void NetworkOpener::update() { - bool connected = _network->device()->mqttClient()->connected(); + bool connected = _network->mqttClient()->connected(); if(!_isConnected && connected) { @@ -51,14 +48,6 @@ void NetworkOpener::update() } _isConnected = connected; - -// long ts = millis(); - -} - -void NetworkOpener::onMqttDataReceivedCallback(char *topic, byte *payload, unsigned int length) -{ - nwInstOpener->onMqttDataReceived(topic, payload, length); } void NetworkOpener::onMqttDataReceived(char *&topic, byte *&payload, unsigned int &length) @@ -186,47 +175,29 @@ void NetworkOpener::setConfigUpdateReceivedCallback(void (*configUpdateReceivedC _configUpdateReceivedCallback = configUpdateReceivedCallback; } -void NetworkOpener::publishFloat(const char* topic, const float value, const uint8_t precision) +void NetworkOpener::publishFloat(const char *topic, const float value, const uint8_t precision) { - char str[30]; - dtostrf(value, 0, precision, str); - char path[200] = {0}; - buildMqttPath(topic, path); - _network->device()->mqttClient()->publish(path, str, true); + _network->publishFloat(_mqttPath, topic, value, precision); } void NetworkOpener::publishInt(const char *topic, const int value) { - char str[30]; - itoa(value, str, 10); - char path[200] = {0}; - buildMqttPath(topic, path); - _network->device()->mqttClient()->publish(path, str, true); + _network->publishInt(_mqttPath, topic, value); } void NetworkOpener::publishUInt(const char *topic, const unsigned int value) { - char str[30]; - utoa(value, str, 10); - char path[200] = {0}; - buildMqttPath(topic, path); - _network->device()->mqttClient()->publish(path, str, true); + _network->publishUInt(_mqttPath, topic, value); } void NetworkOpener::publishBool(const char *topic, const bool value) { - char str[2] = {0}; - str[0] = value ? '1' : '0'; - char path[200] = {0}; - buildMqttPath(topic, path); - _network->device()->mqttClient()->publish(path, str, true); + _network->publishBool(_mqttPath, topic, value); } -void NetworkOpener::publishString(const char *topic, const char *value) +void NetworkOpener::publishString(const char* topic, const char* value) { - char path[200] = {0}; - buildMqttPath(topic, path); - _network->device()->mqttClient()->publish(path, value, true); + _network->publishString(_mqttPath, topic, value); } void NetworkOpener::buildMqttPath(const char* path, char* outPath) @@ -255,7 +226,7 @@ void NetworkOpener::subscribe(const char *path) { char prefixedPath[500]; buildMqttPath(path, prefixedPath); - _network->device()->mqttClient()->subscribe(prefixedPath); + _network->mqttClient()->subscribe(prefixedPath); } bool NetworkOpener::comparePrefixedPath(const char *fullPath, const char *subPath) diff --git a/NetworkOpener.h b/NetworkOpener.h index e4e03ba..7dcccb5 100644 --- a/NetworkOpener.h +++ b/NetworkOpener.h @@ -11,10 +11,10 @@ #include "NukiOpenerConstants.h" #include "NetworkLock.h" -class NetworkOpener +class NetworkOpener : public MqttReceiver { public: - explicit NetworkOpener(NetworkLock* network, Preferences* preferences); + explicit NetworkOpener(Network* network, Preferences* preferences); virtual ~NetworkOpener() = default; void initialize(); @@ -32,9 +32,9 @@ public: void setLockActionReceivedCallback(bool (*lockActionReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value)); + void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length) override; + private: - static void onMqttDataReceivedCallback(char* topic, byte* payload, unsigned int length); - void onMqttDataReceived(char*& topic, byte*& payload, unsigned int& length); bool comparePrefixedPath(const char* fullPath, const char* subPath); void publishFloat(const char* topic, const float value, const uint8_t precision = 2); @@ -48,7 +48,7 @@ private: Preferences* _preferences; - NetworkLock* _network = nullptr; + Network* _network = nullptr; char _mqttPath[181] = {0}; bool _isConnected = false; diff --git a/Version.h b/Version.h index bcebc7b..7645307 100644 --- a/Version.h +++ b/Version.h @@ -1,3 +1,3 @@ #pragma once -#define nuki_hub_version "4.7" \ No newline at end of file +#define nuki_hub_version "4.8" \ No newline at end of file diff --git a/WebCfgServer.cpp b/WebCfgServer.cpp index 43736ac..65190cb 100644 --- a/WebCfgServer.cpp +++ b/WebCfgServer.cpp @@ -4,7 +4,7 @@ #include "hardware/WifiEthServer.h" #include -WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NetworkLock* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal) +WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal) : _server(ethServer), _nuki(nuki), _nukiOpener(nukiOpener), @@ -106,7 +106,7 @@ void WebCfgServer::initialize() buildConfirmHtml(response, "Restarting. Connect to ESP access point to reconfigure WiFi.", 0); _server.send(200, "text/html", response); waitAndProcess(true, 2000); - _network->restartAndConfigureWifi(); + _network->reconfigureDevice(); } }); _server.on("/method=get", [&]() { diff --git a/WebCfgServer.h b/WebCfgServer.h index 62861b0..075530f 100644 --- a/WebCfgServer.h +++ b/WebCfgServer.h @@ -22,7 +22,7 @@ enum class TokenType class WebCfgServer { public: - WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NetworkLock* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal); + WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal); ~WebCfgServer() = default; void initialize(); @@ -58,7 +58,7 @@ private: WebServer _server; NukiWrapper* _nuki; NukiOpenerWrapper* _nukiOpener; - NetworkLock* _network; + Network* _network; Preferences* _preferences; Ota _ota; diff --git a/main.cpp b/main.cpp index fd23802..b5fdddd 100644 --- a/main.cpp +++ b/main.cpp @@ -11,7 +11,8 @@ #include "NukiOpenerWrapper.h" #include "Gpio.h" -NetworkLock* network = nullptr; +Network* network = nullptr; +NetworkLock* networkLock = nullptr; NetworkOpener* networkOpener = nullptr; WebCfgServer* webCfgServer = nullptr; BleScanner::Scanner* bleScanner = nullptr; @@ -28,9 +29,29 @@ void networkTask(void *pvParameters) { while(true) { - network->update(); - networkOpener->update(); - webCfgServer->update(); + bool r = network->update(); + + switch(r) + { + // Network Device and MQTT is connected. Process all updates. + case 0: + network->update(); + networkLock->update(); + networkOpener->update(); + webCfgServer->update(); + break; + case 1: + // Network Device is connected, but MQTT isn't. Call network->update() to allow MQTT reconnect and + // keep Webserver alive to allow user to reconfigure network settings + network->update(); + webCfgServer->update(); + break; + // Neither Network Devicce or MQTT is connected + default: + network->update(); + break; + } + delay(200); } } @@ -145,8 +166,10 @@ void setup() // const NetworkDeviceType networkDevice = NetworkDeviceType::WiFi; const NetworkDeviceType networkDevice = digitalRead(NETWORK_SELECT) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500; - network = new NetworkLock(networkDevice, preferences); + network = new Network(networkDevice, preferences); network->initialize(); + networkLock = new NetworkLock(network, preferences); + networkLock->initialize(); networkOpener = new NetworkOpener(network, preferences); networkOpener->initialize(); @@ -167,7 +190,7 @@ void setup() Serial.println(lockEnabled ? F("NUKI Lock enabled") : F("NUKI Lock disabled")); if(lockEnabled) { - nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, network, preferences); + nuki = new NukiWrapper("NukiHub", deviceId, bleScanner, networkLock, preferences); nuki->initialize(); if(preferences->getBool(preference_gpio_locking_enabled)) @@ -187,7 +210,7 @@ void setup() webCfgServer = new WebCfgServer(nuki, nukiOpener, network, ethServer, preferences, networkDevice == NetworkDeviceType::WiFi); webCfgServer->initialize(); - presenceDetection = new PresenceDetection(preferences, bleScanner, network); + presenceDetection = new PresenceDetection(preferences, bleScanner, networkLock); presenceDetection->initialize(); setupTasks();