PR343
This commit is contained in:
13
src/CharBuffer.cpp
Normal file
13
src/CharBuffer.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "CharBuffer.h"
|
||||
|
||||
void CharBuffer::initialize()
|
||||
{
|
||||
_buffer = new char[CHAR_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
char *CharBuffer::get()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
char* CharBuffer::_buffer;
|
||||
13
src/CharBuffer.h
Normal file
13
src/CharBuffer.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#define CHAR_BUFFER_SIZE 4096
|
||||
|
||||
class CharBuffer
|
||||
{
|
||||
public:
|
||||
static void initialize();
|
||||
static char* get();
|
||||
|
||||
private:
|
||||
static char* _buffer;
|
||||
};
|
||||
12
src/Config.h
Normal file
12
src/Config.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#define NUKI_HUB_VERSION "8.33"
|
||||
|
||||
#define GITHUB_LATEST_RELEASE_URL "https://github.com/technyon/nuki_hub/releases/latest"
|
||||
#define GITHUB_LATEST_RELEASE_API_URL "https://api.github.com/repos/technyon/nuki_hub/releases/latest"
|
||||
#define GITHUB_LATEST_RELEASE_BINARY_URL "https://github.com/technyon/nuki_hub/raw/master/webflash/nuki_hub.bin"
|
||||
|
||||
#define MQTT_QOS_LEVEL 1
|
||||
#define MQTT_CLEAN_SESSIONS false
|
||||
|
||||
#define GPIO_DEBOUNCE_TIME 200
|
||||
388
src/Gpio.cpp
Normal file
388
src/Gpio.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#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 = GPIO_DEBOUNCE_TIME;
|
||||
|
||||
Gpio::Gpio(Preferences* preferences)
|
||||
: _preferences(preferences)
|
||||
{
|
||||
_inst = this;
|
||||
loadPinConfiguration();
|
||||
|
||||
if(_preferences->getBool(preference_gpio_locking_enabled))
|
||||
{
|
||||
migrateObsoleteSetting();
|
||||
}
|
||||
|
||||
_inst->init();
|
||||
}
|
||||
|
||||
void Gpio::init()
|
||||
{
|
||||
for(const auto& entry : _inst->_pinConfiguration)
|
||||
{
|
||||
const auto it = std::find(_inst->availablePins().begin(), _inst->availablePins().end(), entry.pin);
|
||||
|
||||
if(it == _inst->availablePins().end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(entry.role)
|
||||
{
|
||||
case PinRole::InputLock:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrLock, FALLING);
|
||||
break;
|
||||
case PinRole::InputUnlock:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrUnlock, FALLING);
|
||||
break;
|
||||
case PinRole::InputUnlatch:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrUnlatch, FALLING);
|
||||
break;
|
||||
case PinRole::InputLockNgo:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrLockNgo, FALLING);
|
||||
break;
|
||||
case PinRole::InputLockNgoUnlatch:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrLockNgoUnlatch, FALLING);
|
||||
break;
|
||||
case PinRole::InputElectricStrikeActuation:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrElectricStrikeActuation, FALLING);
|
||||
break;
|
||||
case PinRole::InputActivateRTO:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrActivateRTO, FALLING);
|
||||
break;
|
||||
case PinRole::InputActivateCM:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrActivateCM, FALLING);
|
||||
break;
|
||||
case PinRole::InputDeactivateRtoCm:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrDeactivateRtoCm, FALLING);
|
||||
break;
|
||||
case PinRole::InputDeactivateRTO:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrDeactivateRTO, FALLING);
|
||||
break;
|
||||
case PinRole::InputDeactivateCM:
|
||||
pinMode(entry.pin, INPUT_PULLUP);
|
||||
attachInterrupt(entry.pin, isrDeactivateCM, 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);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& Gpio::availablePins() const
|
||||
{
|
||||
return _availablePins;
|
||||
}
|
||||
|
||||
void Gpio::loadPinConfiguration()
|
||||
{
|
||||
size_t storedLength = _preferences->getBytesLength(preference_gpio_configuration);
|
||||
if(storedLength == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t serialized[storedLength];
|
||||
memset(serialized, 0, sizeof(serialized));
|
||||
|
||||
size_t size = _preferences->getBytes(preference_gpio_configuration, serialized, sizeof(serialized));
|
||||
|
||||
if(size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
size_t numEntries = size / 2;
|
||||
|
||||
_pinConfiguration.clear();
|
||||
_pinConfiguration.reserve(numEntries);
|
||||
|
||||
for(int i=0; i < numEntries; i++)
|
||||
{
|
||||
PinEntry entry;
|
||||
entry.pin = serialized[i * 2];
|
||||
entry.role = (PinRole) serialized[(i * 2 + 1)];
|
||||
if(entry.role != PinRole::Disabled)
|
||||
{
|
||||
_pinConfiguration.push_back(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Gpio::savePinConfiguration(const std::vector<PinEntry> &pinConfiguration)
|
||||
{
|
||||
int8_t serialized[std::max(pinConfiguration.size() * 2, _preferences->getBytesLength(preference_gpio_configuration))];
|
||||
memset(serialized, 0, sizeof(serialized));
|
||||
|
||||
int len = pinConfiguration.size();
|
||||
for(int i=0; i < len; i++)
|
||||
{
|
||||
const auto& entry = pinConfiguration[i];
|
||||
|
||||
if(entry.role != PinRole::Disabled)
|
||||
{
|
||||
serialized[i * 2] = entry.pin;
|
||||
serialized[i * 2 + 1] = (int8_t) entry.role;
|
||||
}
|
||||
}
|
||||
|
||||
_preferences->putBytes(preference_gpio_configuration, serialized, sizeof(serialized));
|
||||
}
|
||||
|
||||
const std::vector<PinEntry> &Gpio::pinConfiguration() const
|
||||
{
|
||||
return _pinConfiguration;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
case PinRole::Disabled:
|
||||
return "Disabled";
|
||||
case PinRole::InputLock:
|
||||
return "Input: Lock";
|
||||
case PinRole::InputUnlock:
|
||||
return "Input: Unlock";
|
||||
case PinRole::InputUnlatch:
|
||||
return "Input: Unlatch";
|
||||
case PinRole::InputLockNgo:
|
||||
return "Input: Lock n Go";
|
||||
case PinRole::InputLockNgoUnlatch:
|
||||
return "Input: Lock n Go and unlatch";
|
||||
case PinRole::InputElectricStrikeActuation:
|
||||
return "Input: Electric strike actuation";
|
||||
case PinRole::InputActivateRTO:
|
||||
return "Input: Activate RTO";
|
||||
case PinRole::InputActivateCM:
|
||||
return "Input: Activate CM";
|
||||
case PinRole::InputDeactivateRtoCm:
|
||||
return "Input: Deactivate RTO/CM";
|
||||
case PinRole::InputDeactivateRTO:
|
||||
return "Input: Deactivate RTO";
|
||||
case PinRole::InputDeactivateCM:
|
||||
return "Input: Deactivate CM";
|
||||
case PinRole::OutputHighLocked:
|
||||
return "Output: High when locked";
|
||||
case PinRole::OutputHighUnlocked:
|
||||
return "Output: High when unlocked";
|
||||
case PinRole::OutputHighMotorBlocked:
|
||||
return "Output: High when motor blocked";
|
||||
case PinRole::OutputHighRtoActive:
|
||||
return "Output: High when RTO active";
|
||||
case PinRole::OutputHighCmActive:
|
||||
return "Output: High when CM active";
|
||||
case PinRole::OutputHighRtoOrCmActive:
|
||||
return "Output: High when RTO or CM active";
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
void Gpio::getConfigurationText(String& text, const std::vector<PinEntry>& pinConfiguration, const String& linebreak) const
|
||||
{
|
||||
for(const auto& entry : pinConfiguration)
|
||||
{
|
||||
if(entry.role != PinRole::Disabled)
|
||||
{
|
||||
text.concat("GPIO ");
|
||||
text.concat(entry.pin);
|
||||
if(entry.pin < 10)
|
||||
{
|
||||
text.concat(' ');
|
||||
}
|
||||
text.concat(": ");
|
||||
text.concat(getRoleDescription(entry.role));
|
||||
text.concat(linebreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<PinRole>& Gpio::getAllRoles() const
|
||||
{
|
||||
return _allRoles;
|
||||
}
|
||||
|
||||
void Gpio::notify(const GpioAction &action, const int& pin)
|
||||
{
|
||||
for(auto& callback : _callbacks)
|
||||
{
|
||||
callback(action, pin);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Gpio::isrLock()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::Lock, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrUnlock()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::Unlock, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrUnlatch()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::Unlatch, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrLockNgo()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::LockNgo, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrLockNgoUnlatch()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::LockNgoUnlatch, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrElectricStrikeActuation()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::ElectricStrikeActuation, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrActivateRTO()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::ActivateRTO, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrActivateCM()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::ActivateCM, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrDeactivateRtoCm()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::DeactivateRtoCm, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrDeactivateRTO()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::DeactivateRTO, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::isrDeactivateCM()
|
||||
{
|
||||
if(millis() < _debounceTs) return;
|
||||
_inst->notify(GpioAction::DeactivateCM, -1);
|
||||
_debounceTs = millis() + _debounceTime;
|
||||
}
|
||||
|
||||
void Gpio::setPinOutput(const uint8_t& pin, const uint8_t& state)
|
||||
{
|
||||
digitalWrite(pin, state);
|
||||
}
|
||||
|
||||
void Gpio::migrateObsoleteSetting()
|
||||
{
|
||||
_pinConfiguration.clear();
|
||||
|
||||
PinEntry entry1;
|
||||
entry1.pin = 27;
|
||||
entry1.role = PinRole::InputUnlatch;
|
||||
|
||||
PinEntry entry2;
|
||||
entry2.pin = 32;
|
||||
entry2.role = PinRole::InputLock;
|
||||
|
||||
PinEntry entry3;
|
||||
entry3.pin = 33;
|
||||
entry3.role = PinRole::InputUnlock;
|
||||
|
||||
_pinConfiguration.push_back(entry1);
|
||||
_pinConfiguration.push_back(entry2);
|
||||
_pinConfiguration.push_back(entry3);
|
||||
|
||||
savePinConfiguration(_pinConfiguration);
|
||||
|
||||
_preferences->remove(preference_gpio_locking_enabled);
|
||||
Log->println("Migrated gpio control setting");
|
||||
delay(200);
|
||||
restartEsp(RestartReason::GpioConfigurationUpdated);
|
||||
}
|
||||
|
||||
128
src/Gpio.h
Normal file
128
src/Gpio.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <Preferences.h>
|
||||
#include <vector>
|
||||
|
||||
enum class PinRole
|
||||
{
|
||||
Disabled,
|
||||
InputLock,
|
||||
InputUnlock,
|
||||
InputUnlatch,
|
||||
InputLockNgo,
|
||||
InputLockNgoUnlatch,
|
||||
InputElectricStrikeActuation,
|
||||
InputActivateRTO,
|
||||
InputActivateCM,
|
||||
InputDeactivateRtoCm,
|
||||
InputDeactivateRTO,
|
||||
InputDeactivateCM,
|
||||
OutputHighLocked,
|
||||
OutputHighUnlocked,
|
||||
OutputHighMotorBlocked,
|
||||
OutputHighRtoActive,
|
||||
OutputHighCmActive,
|
||||
OutputHighRtoOrCmActive,
|
||||
GeneralOutput,
|
||||
GeneralInputPullDown,
|
||||
GeneralInputPullUp
|
||||
};
|
||||
|
||||
enum class GpioAction
|
||||
{
|
||||
Lock,
|
||||
Unlock,
|
||||
Unlatch,
|
||||
LockNgo,
|
||||
LockNgoUnlatch,
|
||||
ElectricStrikeActuation,
|
||||
ActivateRTO,
|
||||
ActivateCM,
|
||||
DeactivateRtoCm,
|
||||
DeactivateRTO,
|
||||
DeactivateCM,
|
||||
GeneralInput
|
||||
};
|
||||
|
||||
struct PinEntry
|
||||
{
|
||||
uint8_t pin = 0;
|
||||
PinRole role = PinRole::Disabled;
|
||||
};
|
||||
|
||||
class Gpio
|
||||
{
|
||||
public:
|
||||
Gpio(Preferences* preferences);
|
||||
static void init();
|
||||
|
||||
void migrateObsoleteSetting();
|
||||
|
||||
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;
|
||||
|
||||
const std::vector<PinRole>& getAllRoles() const;
|
||||
|
||||
void setPinOutput(const uint8_t& pin, const uint8_t& state);
|
||||
|
||||
private:
|
||||
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 =
|
||||
{
|
||||
PinRole::Disabled,
|
||||
PinRole::InputLock,
|
||||
PinRole::InputUnlock,
|
||||
PinRole::InputUnlatch,
|
||||
PinRole::InputLockNgo,
|
||||
PinRole::InputLockNgoUnlatch,
|
||||
PinRole::InputElectricStrikeActuation,
|
||||
PinRole::InputActivateRTO,
|
||||
PinRole::InputActivateCM,
|
||||
PinRole::InputDeactivateRtoCm,
|
||||
PinRole::InputDeactivateRTO,
|
||||
PinRole::InputDeactivateCM,
|
||||
PinRole::OutputHighLocked,
|
||||
PinRole::OutputHighUnlocked,
|
||||
PinRole::OutputHighRtoActive,
|
||||
PinRole::OutputHighCmActive,
|
||||
PinRole::OutputHighRtoOrCmActive,
|
||||
PinRole::GeneralInputPullDown,
|
||||
PinRole::GeneralInputPullUp,
|
||||
PinRole::GeneralOutput
|
||||
};
|
||||
|
||||
std::vector<PinEntry> _pinConfiguration;
|
||||
static const uint _debounceTime;
|
||||
|
||||
static void IRAM_ATTR isrLock();
|
||||
static void IRAM_ATTR isrUnlock();
|
||||
static void IRAM_ATTR isrUnlatch();
|
||||
static void IRAM_ATTR isrLockNgo();
|
||||
static void IRAM_ATTR isrLockNgoUnlatch();
|
||||
static void IRAM_ATTR isrElectricStrikeActuation();
|
||||
static void IRAM_ATTR isrActivateRTO();
|
||||
static void IRAM_ATTR isrActivateCM();
|
||||
static void IRAM_ATTR isrDeactivateRtoCm();
|
||||
static void IRAM_ATTR isrDeactivateRTO();
|
||||
static void IRAM_ATTR isrDeactivateCM();
|
||||
|
||||
std::vector<std::function<void(const GpioAction&, const int&)>> _callbacks;
|
||||
|
||||
static Gpio* _inst;
|
||||
static unsigned long _debounceTs;
|
||||
|
||||
Preferences* _preferences = nullptr;
|
||||
};
|
||||
9
src/LockActionResult.h
Normal file
9
src/LockActionResult.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
enum class LockActionResult
|
||||
{
|
||||
Success,
|
||||
UnknownAction,
|
||||
AccessDenied,
|
||||
Failed
|
||||
};
|
||||
3
src/Logger.cpp
Normal file
3
src/Logger.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "Logger.h"
|
||||
|
||||
Print* Log = nullptr;
|
||||
7
src/Logger.h
Normal file
7
src/Logger.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef MQTT_LOGGER_GLOBAL
|
||||
#define MQTT_LOGGER_GLOBAL
|
||||
|
||||
#include "MqttLogger.h"
|
||||
extern Print* Log;
|
||||
|
||||
#endif
|
||||
9
src/MqttReceiver.h
Normal file
9
src/MqttReceiver.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class MqttReceiver
|
||||
{
|
||||
public:
|
||||
virtual void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) = 0;
|
||||
};
|
||||
82
src/MqttTopics.h
Normal file
82
src/MqttTopics.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#define mqtt_topic_lock_action "/lock/action"
|
||||
#define mqtt_topic_lock_state "/lock/state"
|
||||
#define mqtt_topic_lock_ha_state "/lock/hastate"
|
||||
#define mqtt_topic_lock_json "/lock/json"
|
||||
#define mqtt_topic_lock_binary_state "/lock/binaryState"
|
||||
#define mqtt_topic_lock_continuous_mode "/lock/continuousMode"
|
||||
#define mqtt_topic_lock_ring "/lock/ring"
|
||||
#define mqtt_topic_lock_binary_ring "/lock/binaryRing"
|
||||
#define mqtt_topic_lock_trigger "/lock/trigger"
|
||||
#define mqtt_topic_lock_last_lock_action "/lock/lastLockAction"
|
||||
#define mqtt_topic_lock_log "/lock/log"
|
||||
#define mqtt_topic_lock_auth_id "/lock/authorizationId"
|
||||
#define mqtt_topic_lock_auth_name "/lock/authorizationName"
|
||||
#define mqtt_topic_lock_completionStatus "/lock/completionStatus"
|
||||
#define mqtt_topic_lock_action_command_result "/lock/commandResult"
|
||||
#define mqtt_topic_lock_door_sensor_state "/lock/doorSensorState"
|
||||
#define mqtt_topic_lock_rssi "/lock/rssi"
|
||||
#define mqtt_topic_lock_address "/lock/address"
|
||||
#define mqtt_topic_lock_retry "/lock/retry"
|
||||
|
||||
#define mqtt_topic_config_button_enabled "/configuration/buttonEnabled"
|
||||
#define mqtt_topic_config_led_enabled "/configuration/ledEnabled"
|
||||
#define mqtt_topic_config_led_brightness "/configuration/ledBrightness"
|
||||
#define mqtt_topic_config_auto_unlock "/configuration/autoUnlock"
|
||||
#define mqtt_topic_config_auto_lock "/configuration/autoLock"
|
||||
#define mqtt_topic_config_single_lock "/configuration/singleLock"
|
||||
#define mqtt_topic_config_sound_level "/configuration/soundLevel"
|
||||
|
||||
#define mqtt_topic_query_config "/lock/query/config"
|
||||
#define mqtt_topic_query_lockstate "/lock/query/lockstate"
|
||||
#define mqtt_topic_query_keypad "/lock/query/keypad"
|
||||
#define mqtt_topic_query_battery "/lock/query/battery"
|
||||
#define mqtt_topic_query_lockstate_command_result "/lock/query/lockstateCommandResult"
|
||||
|
||||
#define mqtt_topic_battery_level "/battery/level"
|
||||
#define mqtt_topic_battery_critical "/battery/critical"
|
||||
#define mqtt_topic_battery_charging "/battery/charging"
|
||||
#define mqtt_topic_battery_voltage "/battery/voltage"
|
||||
#define mqtt_topic_battery_drain "/battery/drain"
|
||||
#define mqtt_topic_battery_max_turn_current "/battery/maxTurnCurrent"
|
||||
#define mqtt_topic_battery_lock_distance "/battery/lockDistance"
|
||||
#define mqtt_topic_battery_keypad_critical "/battery/keypadCritical"
|
||||
|
||||
#define mqtt_topic_keypad "/keypad"
|
||||
#define mqtt_topic_keypad_command_action "/keypad/command/action"
|
||||
#define mqtt_topic_keypad_command_id "/keypad/command/id"
|
||||
#define mqtt_topic_keypad_command_name "/keypad/command/name"
|
||||
#define mqtt_topic_keypad_command_code "/keypad/command/code"
|
||||
#define mqtt_topic_keypad_command_enabled "/keypad/command/enabled"
|
||||
#define mqtt_topic_keypad_command_result "/keypad/command/commandResult"
|
||||
#define mqtt_topic_keypad_json "/keypad/json"
|
||||
#define mqtt_topic_keypad_json_action "/keypad/actionJson"
|
||||
#define mqtt_topic_keypad_json_command_result "/keypad/commandResultJson"
|
||||
|
||||
#define mqtt_topic_timecontrol_json "/timecontrol/json"
|
||||
#define mqtt_topic_timecontrol_action "/timecontrol/action"
|
||||
#define mqtt_topic_timecontrol_command_result "/timecontrol/commandResult"
|
||||
|
||||
#define mqtt_topic_info_hardware_version "/info/hardwareVersion"
|
||||
#define mqtt_topic_info_firmware_version "/info/firmwareVersion"
|
||||
#define mqtt_topic_info_nuki_hub_version "/info/nukiHubVersion"
|
||||
#define mqtt_topic_info_nuki_hub_latest "/info/nukiHubLatest"
|
||||
#define mqtt_topic_info_nuki_hub_ip "/info/nukiHubIp"
|
||||
|
||||
#define mqtt_topic_reset "/maintenance/reset"
|
||||
#define mqtt_topic_uptime "/maintenance/uptime"
|
||||
#define mqtt_topic_wifi_rssi "/maintenance/wifiRssi"
|
||||
#define mqtt_topic_log "/maintenance/log"
|
||||
#define mqtt_topic_freeheap "/maintenance/freeHeap"
|
||||
#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_presence "/presence/devices"
|
||||
|
||||
#define mqtt_topic_gpio_prefix "/gpio"
|
||||
#define mqtt_topic_gpio_pin "/pin_"
|
||||
#define mqtt_topic_gpio_role "/role"
|
||||
#define mqtt_topic_gpio_state "/state"
|
||||
1749
src/Network.cpp
Normal file
1749
src/Network.cpp
Normal file
File diff suppressed because it is too large
Load Diff
177
src/Network.h
Normal file
177
src/Network.h
Normal file
@@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include <Preferences.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "networkDevices/NetworkDevice.h"
|
||||
#include "MqttReceiver.h"
|
||||
#include "networkDevices/IPConfiguration.h"
|
||||
#include "MqttTopics.h"
|
||||
#include "Gpio.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <HTTPClient.h>
|
||||
|
||||
enum class NetworkDeviceType
|
||||
{
|
||||
WiFi,
|
||||
W5500,
|
||||
Olimex_LAN8720,
|
||||
WT32_LAN8720,
|
||||
M5STACK_PoESP32_Unit,
|
||||
LilyGO_T_ETH_POE
|
||||
};
|
||||
|
||||
#define JSON_BUFFER_SIZE 1024
|
||||
|
||||
class Network
|
||||
{
|
||||
public:
|
||||
explicit Network(Preferences* preferences, Gpio* gpio, const String& maintenancePathPrefix, char* buffer, size_t bufferSize);
|
||||
|
||||
void initialize();
|
||||
bool update();
|
||||
void registerMqttReceiver(MqttReceiver* receiver);
|
||||
void reconfigureDevice();
|
||||
void setMqttPresencePath(char* path);
|
||||
void disableAutoRestarts(); // disable on OTA start
|
||||
void disableMqtt();
|
||||
|
||||
void subscribe(const char* prefix, const char* path);
|
||||
void initTopic(const char* prefix, const char* path, const char* value);
|
||||
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 publishULong(const char* prefix, const char* topic, const unsigned long 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, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction);
|
||||
void publishHASSConfigAdditionalButtons(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigBatLevel(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigRingDetect(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigContinuousMode(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigLedBrightness(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigSoundLevel(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSConfigKeypadAttemptInfo(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void publishHASSBleRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString);
|
||||
void removeHASSConfig(char* uidString);
|
||||
void removeHASSConfigTopic(char* deviceType, char* name, char* uidString);
|
||||
|
||||
void clearWifiFallback();
|
||||
|
||||
void publishPresenceDetection(char* csv);
|
||||
|
||||
int mqttConnectionState(); // 0 = not connected; 1 = connected; 2 = connected and mqtt processed
|
||||
bool encryptionSupported();
|
||||
const String networkDeviceName() const;
|
||||
const String networkBSSID() const;
|
||||
|
||||
const NetworkDeviceType networkDeviceType();
|
||||
|
||||
uint16_t subscribe(const char* topic, uint8_t qos);
|
||||
|
||||
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 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();
|
||||
|
||||
void publishHassTopic(const String& mqttDeviceType,
|
||||
const String& mqttDeviceName,
|
||||
const String& uidString,
|
||||
const String& uidStringPostfix,
|
||||
const String& displayName,
|
||||
const String& name,
|
||||
const String& baseTopic,
|
||||
const String& stateTopic,
|
||||
const String& deviceType,
|
||||
const String& deviceClass,
|
||||
const String& stateClass = "",
|
||||
const String& entityCat = "",
|
||||
const String& commandTopic = "",
|
||||
std::vector<std::pair<char*, char*>> additionalEntries = {}
|
||||
);
|
||||
|
||||
String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
|
||||
void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
|
||||
JsonDocument createHassJson(const String& uidString,
|
||||
const String& uidStringPostfix,
|
||||
const String& displayName,
|
||||
const String& name,
|
||||
const String& baseTopic,
|
||||
const String& stateTopic,
|
||||
const String& deviceType,
|
||||
const String& deviceClass,
|
||||
const String& stateClass = "",
|
||||
const String& entityCat = "",
|
||||
const String& commandTopic = "",
|
||||
std::vector<std::pair<char*, char*>> additionalEntries = {}
|
||||
);
|
||||
|
||||
void onMqttConnect(const bool& sessionPresent);
|
||||
void onMqttDisconnect(const espMqttClientTypes::DisconnectReason& reason);
|
||||
|
||||
void buildMqttPath(char* outPath, std::initializer_list<const char*> paths);
|
||||
|
||||
static Network* _inst;
|
||||
|
||||
const char* _lastWillPayload = "offline";
|
||||
char _mqttConnectionStateTopic[211] = {0};
|
||||
String _lockPath;
|
||||
|
||||
const char* _latestVersion;
|
||||
HTTPClient https;
|
||||
|
||||
Preferences* _preferences;
|
||||
Gpio* _gpio;
|
||||
IPConfiguration* _ipConfiguration = nullptr;
|
||||
String _hostname;
|
||||
char _hostnameArr[101] = {0};
|
||||
NetworkDevice* _device = nullptr;
|
||||
int _mqttConnectionState = 0;
|
||||
bool _connectReplyReceived = false;
|
||||
|
||||
unsigned long _nextReconnect = 0;
|
||||
char _mqttBrokerAddr[101] = {0};
|
||||
char _mqttUser[31] = {0};
|
||||
char _mqttPass[31] = {0};
|
||||
char _mqttPresencePrefix[181] = {0};
|
||||
char _maintenancePathPrefix[181] = {0};
|
||||
int _networkTimeout = 0;
|
||||
std::vector<MqttReceiver*> _mqttReceivers;
|
||||
char* _presenceCsv = nullptr;
|
||||
bool _restartOnDisconnect = false;
|
||||
bool _firstConnect = true;
|
||||
bool _publishDebugInfo = false;
|
||||
std::vector<String> _subscribedTopics;
|
||||
std::map<String, String> _initTopics;
|
||||
|
||||
unsigned long _lastConnectedTs = 0;
|
||||
unsigned long _lastMaintenanceTs = 0;
|
||||
unsigned long _lastUpdateCheckTs = 0;
|
||||
unsigned long _lastRssiTs = 0;
|
||||
bool _mqttEnabled = true;
|
||||
static unsigned long _ignoreSubscriptionsTs;
|
||||
long _rssiPublishInterval = 0;
|
||||
std::map<uint8_t, unsigned long> _gpioTs;
|
||||
|
||||
char* _buffer;
|
||||
const size_t _bufferSize;
|
||||
|
||||
std::function<void()> _keepAliveCallback = nullptr;
|
||||
std::vector<std::function<void()>> _reconnectedCallbacks;
|
||||
|
||||
NetworkDeviceType _networkDeviceType = (NetworkDeviceType)-1;
|
||||
|
||||
int8_t _lastRssi = 127;
|
||||
};
|
||||
905
src/NetworkLock.cpp
Normal file
905
src/NetworkLock.cpp
Normal file
@@ -0,0 +1,905 @@
|
||||
#include "NetworkLock.h"
|
||||
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
|
||||
#include "Arduino.h"
|
||||
#include "MqttTopics.h"
|
||||
#include "PreferencesKeys.h"
|
||||
#include "Logger.h"
|
||||
#include "RestartReason.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
NetworkLock::NetworkLock(Network* network, Preferences* preferences, char* buffer, size_t bufferSize)
|
||||
: _network(network),
|
||||
_preferences(preferences),
|
||||
_buffer(buffer),
|
||||
_bufferSize(bufferSize)
|
||||
{
|
||||
_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);
|
||||
_configTopics.push_back(mqtt_topic_config_single_lock);
|
||||
|
||||
memset(_authName, 0, sizeof(_authName));
|
||||
_authName[0] = '\0';
|
||||
|
||||
_network->registerMqttReceiver(this);
|
||||
}
|
||||
|
||||
NetworkLock::~NetworkLock()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkLock::initialize()
|
||||
{
|
||||
String mqttPath = _preferences->getString(preference_mqtt_lock_path);
|
||||
if(mqttPath.length() > 0)
|
||||
{
|
||||
size_t len = mqttPath.length();
|
||||
for(int i=0; i < len; i++)
|
||||
{
|
||||
_mqttPath[i] = mqttPath.charAt(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(_mqttPath, "nuki");
|
||||
_preferences->putString(preference_mqtt_lock_path, _mqttPath);
|
||||
}
|
||||
|
||||
_network->setMqttPresencePath(_mqttPath);
|
||||
|
||||
_haEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
|
||||
|
||||
_network->initTopic(_mqttPath, mqtt_topic_lock_action, "--");
|
||||
_network->subscribe(_mqttPath, mqtt_topic_lock_action);
|
||||
for(const auto& topic : _configTopics)
|
||||
{
|
||||
_network->subscribe(_mqttPath, topic);
|
||||
}
|
||||
|
||||
_network->subscribe(_mqttPath, mqtt_topic_reset);
|
||||
_network->initTopic(_mqttPath, mqtt_topic_reset, "0");
|
||||
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_config, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_lockstate, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_battery, "0");
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_config);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_lockstate);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_battery);
|
||||
|
||||
if(_preferences->getBool(preference_keypad_control_enabled))
|
||||
{
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_action);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_id);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_name);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_code);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_enabled);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_keypad);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_json_action);
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_action, "--");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_id, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_name, "--");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_code, "000000");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_enabled, "1");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_keypad, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_json_action, "--");
|
||||
}
|
||||
|
||||
if(_preferences->getBool(preference_timecontrol_control_enabled))
|
||||
{
|
||||
_network->subscribe(_mqttPath, mqtt_topic_timecontrol_action);
|
||||
_network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--");
|
||||
}
|
||||
|
||||
_network->addReconnectedCallback([&]()
|
||||
{
|
||||
_reconnected = true;
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkLock::onMqttDataReceived(const char* topic, byte* payload, const unsigned int length)
|
||||
{
|
||||
char* value = (char*)payload;
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_reset) && strcmp(value, "1") == 0)
|
||||
{
|
||||
Log->println(F("Restart requested via MQTT."));
|
||||
_network->clearWifiFallback();
|
||||
delay(200);
|
||||
restartEsp(RestartReason::RequestedViaMqtt);
|
||||
}
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_lock_action))
|
||||
{
|
||||
if(strcmp(value, "") == 0 ||
|
||||
strcmp(value, "--") == 0 ||
|
||||
strcmp(value, "ack") == 0 ||
|
||||
strcmp(value, "unknown_action") == 0 ||
|
||||
strcmp(value, "denied") == 0 ||
|
||||
strcmp(value, "error") == 0) return;
|
||||
|
||||
Log->print(F("Lock action received: "));
|
||||
Log->println(value);
|
||||
LockActionResult lockActionResult = LockActionResult::Failed;
|
||||
if(_lockActionReceivedCallback != NULL)
|
||||
{
|
||||
lockActionResult = _lockActionReceivedCallback(value);
|
||||
}
|
||||
|
||||
switch(lockActionResult)
|
||||
{
|
||||
case LockActionResult::Success:
|
||||
publishString(mqtt_topic_lock_action, "ack");
|
||||
break;
|
||||
case LockActionResult::UnknownAction:
|
||||
publishString(mqtt_topic_lock_action, "unknown_action");
|
||||
break;
|
||||
case LockActionResult::AccessDenied:
|
||||
publishString(mqtt_topic_lock_action, "denied");
|
||||
break;
|
||||
case LockActionResult::Failed:
|
||||
publishString(mqtt_topic_lock_action, "error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_keypad_command_action))
|
||||
{
|
||||
if(_keypadCommandReceivedReceivedCallback != nullptr)
|
||||
{
|
||||
if(strcmp(value, "--") == 0) return;
|
||||
|
||||
_keypadCommandReceivedReceivedCallback(value, _keypadCommandId, _keypadCommandName, _keypadCommandCode, _keypadCommandEnabled);
|
||||
|
||||
_keypadCommandId = 0;
|
||||
_keypadCommandName = "--";
|
||||
_keypadCommandCode = "000000";
|
||||
_keypadCommandEnabled = 1;
|
||||
|
||||
if(strcmp(value, "--") != 0)
|
||||
{
|
||||
publishString(mqtt_topic_keypad_command_action, "--");
|
||||
}
|
||||
publishInt(mqtt_topic_keypad_command_id, _keypadCommandId);
|
||||
publishString(mqtt_topic_keypad_command_name, _keypadCommandName);
|
||||
publishString(mqtt_topic_keypad_command_code, _keypadCommandCode);
|
||||
publishInt(mqtt_topic_keypad_command_enabled, _keypadCommandEnabled);
|
||||
}
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_id))
|
||||
{
|
||||
_keypadCommandId = atoi(value);
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_name))
|
||||
{
|
||||
_keypadCommandName = value;
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_code))
|
||||
{
|
||||
_keypadCommandCode = value;
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_enabled))
|
||||
{
|
||||
_keypadCommandEnabled = atoi(value);
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_config) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_CONFIG;
|
||||
publishString(mqtt_topic_query_config, "0");
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_lockstate) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_LOCKSTATE;
|
||||
publishString(mqtt_topic_query_lockstate, "0");
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_keypad) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_KEYPAD;
|
||||
publishString(mqtt_topic_query_keypad, "0");
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_battery) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_BATTERY;
|
||||
publishString(mqtt_topic_query_battery, "0");
|
||||
}
|
||||
|
||||
for(auto configTopic : _configTopics)
|
||||
{
|
||||
if(comparePrefixedPath(topic, configTopic))
|
||||
{
|
||||
if(_configUpdateReceivedCallback != nullptr)
|
||||
{
|
||||
_configUpdateReceivedCallback(configTopic, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_keypad_json_action))
|
||||
{
|
||||
if(strcmp(value, "") == 0 || strcmp(value, "--") == 0) return;
|
||||
|
||||
if(_keypadJsonCommandReceivedReceivedCallback != NULL)
|
||||
{
|
||||
_keypadJsonCommandReceivedReceivedCallback(value);
|
||||
}
|
||||
|
||||
publishString(mqtt_topic_keypad_json_action, "--");
|
||||
}
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_timecontrol_action))
|
||||
{
|
||||
if(strcmp(value, "") == 0 || strcmp(value, "--") == 0) return;
|
||||
|
||||
if(_timeControlCommandReceivedReceivedCallback != NULL)
|
||||
{
|
||||
_timeControlCommandReceivedReceivedCallback(value);
|
||||
}
|
||||
|
||||
publishString(mqtt_topic_timecontrol_action, "--");
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkLock::publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurnerState, const NukiLock::KeyTurnerState& lastKeyTurnerState)
|
||||
{
|
||||
char str[50];
|
||||
memset(&str, 0, sizeof(str));
|
||||
|
||||
JsonDocument json;
|
||||
|
||||
lockstateToString(keyTurnerState.lockState, str);
|
||||
|
||||
if((_firstTunerStatePublish || keyTurnerState.lockState != lastKeyTurnerState.lockState) && keyTurnerState.lockState != NukiLock::LockState::Undefined)
|
||||
{
|
||||
|
||||
publishString(mqtt_topic_lock_state, str);
|
||||
|
||||
if(_haEnabled)
|
||||
{
|
||||
publishState(keyTurnerState.lockState);
|
||||
}
|
||||
}
|
||||
|
||||
json["lock_state"] = str;
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
triggerToString(keyTurnerState.trigger, str);
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.trigger != lastKeyTurnerState.trigger)
|
||||
{
|
||||
publishString(mqtt_topic_lock_trigger, str);
|
||||
}
|
||||
|
||||
json["trigger"] = str;
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
lockactionToString(keyTurnerState.lastLockAction, str);
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.lastLockAction != lastKeyTurnerState.lastLockAction)
|
||||
{
|
||||
publishString(mqtt_topic_lock_last_lock_action, str);
|
||||
}
|
||||
|
||||
json["last_lock_action"] = str;
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
NukiLock::completionStatusToString(keyTurnerState.lastLockActionCompletionStatus, str);
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.lastLockActionCompletionStatus != lastKeyTurnerState.lastLockActionCompletionStatus)
|
||||
{
|
||||
publishString(mqtt_topic_lock_completionStatus, str);
|
||||
}
|
||||
|
||||
json["lock_completion_status"] = str;
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
NukiLock::doorSensorStateToString(keyTurnerState.doorSensorState, str);
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.doorSensorState != lastKeyTurnerState.doorSensorState)
|
||||
{
|
||||
publishString(mqtt_topic_lock_door_sensor_state, str);
|
||||
}
|
||||
|
||||
json["door_sensor_state"] = str;
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.criticalBatteryState != lastKeyTurnerState.criticalBatteryState)
|
||||
{
|
||||
bool critical = (keyTurnerState.criticalBatteryState & 0b00000001) > 0;
|
||||
publishBool(mqtt_topic_battery_critical, critical);
|
||||
|
||||
bool charging = (keyTurnerState.criticalBatteryState & 0b00000010) > 0;
|
||||
publishBool(mqtt_topic_battery_charging, charging);
|
||||
|
||||
uint8_t level = (keyTurnerState.criticalBatteryState & 0b11111100) >> 1;
|
||||
publishInt(mqtt_topic_battery_level, level);
|
||||
}
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.accessoryBatteryState != lastKeyTurnerState.accessoryBatteryState)
|
||||
{
|
||||
if ((keyTurnerState.accessoryBatteryState & (1 << 7)) != 0) {
|
||||
publishBool(mqtt_topic_battery_keypad_critical, (keyTurnerState.accessoryBatteryState & (1 << 6)) != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
publishBool(mqtt_topic_battery_keypad_critical, false);
|
||||
}
|
||||
}
|
||||
|
||||
json["auth_id"] = _authId;
|
||||
json["auth_name"] = _authName;
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_lock_json, _buffer);
|
||||
|
||||
_firstTunerStatePublish = false;
|
||||
}
|
||||
|
||||
void NetworkLock::publishState(NukiLock::LockState lockState)
|
||||
{
|
||||
switch(lockState)
|
||||
{
|
||||
case NukiLock::LockState::Locked:
|
||||
publishString(mqtt_topic_lock_ha_state, "locked");
|
||||
publishString(mqtt_topic_lock_binary_state, "locked");
|
||||
break;
|
||||
case NukiLock::LockState::Locking:
|
||||
publishString(mqtt_topic_lock_ha_state, "locking");
|
||||
publishString(mqtt_topic_lock_binary_state, "locked");
|
||||
break;
|
||||
case NukiLock::LockState::Unlocking:
|
||||
publishString(mqtt_topic_lock_ha_state, "unlocking");
|
||||
publishString(mqtt_topic_lock_binary_state, "unlocked");
|
||||
break;
|
||||
case NukiLock::LockState::Unlocked:
|
||||
case NukiLock::LockState::Unlatched:
|
||||
case NukiLock::LockState::Unlatching:
|
||||
case NukiLock::LockState::UnlockedLnga:
|
||||
publishString(mqtt_topic_lock_ha_state, "unlocked");
|
||||
publishString(mqtt_topic_lock_binary_state, "unlocked");
|
||||
break;
|
||||
case NukiLock::LockState::Uncalibrated:
|
||||
case NukiLock::LockState::Calibration:
|
||||
case NukiLock::LockState::BootRun:
|
||||
case NukiLock::LockState::MotorBlocked:
|
||||
publishString(mqtt_topic_lock_ha_state, "jammed");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries)
|
||||
{
|
||||
char str[50];
|
||||
|
||||
_authId = 0;
|
||||
memset(_authName, 0, sizeof(_authName));
|
||||
_authName[0] = '\0';
|
||||
_authFound = false;
|
||||
|
||||
JsonDocument json;
|
||||
|
||||
int i = 5;
|
||||
for(const auto& log : logEntries)
|
||||
{
|
||||
if(i <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
--i;
|
||||
if((log.loggingType == NukiLock::LoggingType::LockAction || log.loggingType == NukiLock::LoggingType::KeypadAction) && ! _authFound)
|
||||
{
|
||||
_authFound = true;
|
||||
_authId = log.authId;
|
||||
int sizeName = sizeof(log.name);
|
||||
memcpy(_authName, log.name, sizeName);
|
||||
if(_authName[sizeName - 1] != '\0') _authName[sizeName] = '\0';
|
||||
}
|
||||
|
||||
auto entry = json.add();
|
||||
|
||||
entry["index"] = log.index;
|
||||
entry["authorizationId"] = log.authId;
|
||||
entry["authorizationName"] = _authName;
|
||||
entry["timeYear"] = log.timeStampYear;
|
||||
entry["timeMonth"] = log.timeStampMonth;
|
||||
entry["timeDay"] = log.timeStampDay;
|
||||
entry["timeHour"] = log.timeStampHour;
|
||||
entry["timeMinute"] = log.timeStampMinute;
|
||||
entry["timeSecond"] = log.timeStampSecond;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
loggingTypeToString(log.loggingType, str);
|
||||
entry["type"] = str;
|
||||
|
||||
switch(log.loggingType)
|
||||
{
|
||||
case NukiLock::LoggingType::LockAction:
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
|
||||
entry["action"] = str;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::triggerToString((NukiLock::Trigger)log.data[1], str);
|
||||
entry["trigger"] = str;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[3], str);
|
||||
entry["completionStatus"] = str;
|
||||
break;
|
||||
case NukiLock::LoggingType::KeypadAction:
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
|
||||
entry["action"] = str;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
|
||||
entry["completionStatus"] = str;
|
||||
break;
|
||||
case NukiLock::LoggingType::DoorSensor:
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
|
||||
|
||||
switch(log.data[0])
|
||||
{
|
||||
case 0:
|
||||
entry["action"] = "DoorOpened";
|
||||
break;
|
||||
case 1:
|
||||
entry["action"] = "DoorClosed";
|
||||
break;
|
||||
case 2:
|
||||
entry["action"] = "SensorJammed";
|
||||
break;
|
||||
default:
|
||||
entry["action"] = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
|
||||
entry["completionStatus"] = str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_lock_log, _buffer);
|
||||
|
||||
if(_authFound)
|
||||
{
|
||||
publishUInt(mqtt_topic_lock_auth_id, _authId);
|
||||
publishString(mqtt_topic_lock_auth_name, _authName);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkLock::clearAuthorizationInfo()
|
||||
{
|
||||
publishString(mqtt_topic_lock_log, "--");
|
||||
publishUInt(mqtt_topic_lock_auth_id, 0);
|
||||
publishString(mqtt_topic_lock_auth_name, "--");}
|
||||
|
||||
void NetworkLock::publishCommandResult(const char *resultStr)
|
||||
{
|
||||
publishString(mqtt_topic_lock_action_command_result, resultStr);
|
||||
}
|
||||
|
||||
void NetworkLock::publishLockstateCommandResult(const char *resultStr)
|
||||
{
|
||||
publishString(mqtt_topic_query_lockstate_command_result, resultStr);
|
||||
}
|
||||
|
||||
void NetworkLock::publishBatteryReport(const NukiLock::BatteryReport& batteryReport)
|
||||
{
|
||||
publishFloat(mqtt_topic_battery_voltage, (float)batteryReport.batteryVoltage / 1000.0);
|
||||
publishInt(mqtt_topic_battery_drain, batteryReport.batteryDrain); // milliwatt seconds
|
||||
publishFloat(mqtt_topic_battery_max_turn_current, (float)batteryReport.maxTurnCurrent / 1000.0);
|
||||
publishInt(mqtt_topic_battery_lock_distance, batteryReport.lockDistance); // degrees
|
||||
}
|
||||
|
||||
void NetworkLock::publishConfig(const NukiLock::Config &config)
|
||||
{
|
||||
publishBool(mqtt_topic_config_button_enabled, config.buttonEnabled == 1);
|
||||
publishBool(mqtt_topic_config_led_enabled, config.ledEnabled == 1);
|
||||
publishInt(mqtt_topic_config_led_brightness, config.ledBrightness);
|
||||
publishBool(mqtt_topic_config_single_lock, config.singleLock == 1);
|
||||
publishString(mqtt_topic_info_firmware_version, std::to_string(config.firmwareVersion[0]) + "." + std::to_string(config.firmwareVersion[1]) + "." + std::to_string(config.firmwareVersion[2]));
|
||||
publishString(mqtt_topic_info_hardware_version, std::to_string(config.hardwareRevision[0]) + "." + std::to_string(config.hardwareRevision[1]));
|
||||
}
|
||||
|
||||
void NetworkLock::publishAdvancedConfig(const NukiLock::AdvancedConfig &config)
|
||||
{
|
||||
publishBool(mqtt_topic_config_auto_unlock, config.autoUnLockDisabled == 0);
|
||||
publishBool(mqtt_topic_config_auto_lock, config.autoLockEnabled == 1);
|
||||
}
|
||||
|
||||
void NetworkLock::publishRssi(const int& rssi)
|
||||
{
|
||||
publishInt(mqtt_topic_lock_rssi, rssi);
|
||||
}
|
||||
|
||||
void NetworkLock::publishRetry(const std::string& message)
|
||||
{
|
||||
publishString(mqtt_topic_lock_retry, message);
|
||||
}
|
||||
|
||||
void NetworkLock::publishBleAddress(const std::string &address)
|
||||
{
|
||||
publishString(mqtt_topic_lock_address, address);
|
||||
}
|
||||
|
||||
void NetworkLock::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount)
|
||||
{
|
||||
uint index = 0;
|
||||
|
||||
JsonDocument json;
|
||||
|
||||
for(const auto& entry : entries)
|
||||
{
|
||||
String basePath = mqtt_topic_keypad;
|
||||
basePath.concat("/code_");
|
||||
basePath.concat(std::to_string(index).c_str());
|
||||
publishKeypadEntry(basePath, entry);
|
||||
|
||||
auto jsonEntry = json.add();
|
||||
|
||||
jsonEntry["codeId"] = entry.codeId;
|
||||
jsonEntry["enabled"] = entry.enabled;
|
||||
jsonEntry["name"] = entry.name;
|
||||
char createdDT[20];
|
||||
sprintf(createdDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.dateCreatedYear, entry.dateCreatedMonth, entry.dateCreatedDay, entry.dateCreatedHour, entry.dateCreatedMin, entry.dateCreatedSec);
|
||||
jsonEntry["dateCreated"] = createdDT;
|
||||
jsonEntry["lockCount"] = entry.lockCount;
|
||||
char lastActiveDT[20];
|
||||
sprintf(lastActiveDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.dateLastActiveYear, entry.dateLastActiveMonth, entry.dateLastActiveDay, entry.dateLastActiveHour, entry.dateLastActiveMin, entry.dateLastActiveSec);
|
||||
jsonEntry["dateLastActive"] = lastActiveDT;
|
||||
jsonEntry["timeLimited"] = entry.timeLimited;
|
||||
char allowedFromDT[20];
|
||||
sprintf(allowedFromDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedFromYear, entry.allowedFromMonth, entry.allowedFromDay, entry.allowedFromHour, entry.allowedFromMin, entry.allowedFromSec);
|
||||
jsonEntry["allowedFrom"] = allowedFromDT;
|
||||
char allowedUntilDT[20];
|
||||
sprintf(allowedUntilDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedUntilYear, entry.allowedUntilMonth, entry.allowedUntilDay, entry.allowedUntilHour, entry.allowedUntilMin, entry.allowedUntilSec);
|
||||
jsonEntry["allowedUntil"] = allowedUntilDT;
|
||||
|
||||
uint8_t allowedWeekdaysInt = entry.allowedWeekdays;
|
||||
JsonArray weekdays = jsonEntry["allowedWeekdays"].to<JsonArray>();
|
||||
|
||||
while(allowedWeekdaysInt > 0) {
|
||||
if(allowedWeekdaysInt >= 64)
|
||||
{
|
||||
weekdays.add("mon");
|
||||
allowedWeekdaysInt -= 64;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 32)
|
||||
{
|
||||
weekdays.add("tue");
|
||||
allowedWeekdaysInt -= 32;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 16)
|
||||
{
|
||||
weekdays.add("wed");
|
||||
allowedWeekdaysInt -= 16;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 8)
|
||||
{
|
||||
weekdays.add("thu");
|
||||
allowedWeekdaysInt -= 8;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 4)
|
||||
{
|
||||
weekdays.add("fri");
|
||||
allowedWeekdaysInt -= 4;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 2)
|
||||
{
|
||||
weekdays.add("sat");
|
||||
allowedWeekdaysInt -= 2;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 1)
|
||||
{
|
||||
weekdays.add("sun");
|
||||
allowedWeekdaysInt -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
char allowedFromTimeT[5];
|
||||
sprintf(allowedFromTimeT, "%02d:%02d", entry.allowedFromTimeHour, entry.allowedFromTimeMin);
|
||||
jsonEntry["allowedFromTime"] = allowedFromTimeT;
|
||||
char allowedUntilTimeT[5];
|
||||
sprintf(allowedUntilTimeT, "%02d:%02d", entry.allowedUntilTimeHour, entry.allowedUntilTimeMin);
|
||||
jsonEntry["allowedUntilTime"] = allowedUntilTimeT;
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_keypad_json, _buffer);
|
||||
|
||||
while(index < maxKeypadCodeCount)
|
||||
{
|
||||
NukiLock::KeypadEntry entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
String basePath = mqtt_topic_keypad;
|
||||
basePath.concat("/code_");
|
||||
basePath.concat(std::to_string(index).c_str());
|
||||
publishKeypadEntry(basePath, entry);
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkLock::publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries)
|
||||
{
|
||||
char str[50];
|
||||
JsonDocument json;
|
||||
|
||||
for(const auto& entry : timeControlEntries)
|
||||
{
|
||||
auto jsonEntry = json.add();
|
||||
|
||||
jsonEntry["entryId"] = entry.entryId;
|
||||
jsonEntry["enabled"] = entry.enabled;
|
||||
uint8_t weekdaysInt = entry.weekdays;
|
||||
JsonArray weekdays = jsonEntry["weekdays"].to<JsonArray>();
|
||||
|
||||
while(weekdaysInt > 0) {
|
||||
if(weekdaysInt >= 64)
|
||||
{
|
||||
weekdays.add("mon");
|
||||
weekdaysInt -= 64;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 32)
|
||||
{
|
||||
weekdays.add("tue");
|
||||
weekdaysInt -= 32;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 16)
|
||||
{
|
||||
weekdays.add("wed");
|
||||
weekdaysInt -= 16;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 8)
|
||||
{
|
||||
weekdays.add("thu");
|
||||
weekdaysInt -= 8;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 4)
|
||||
{
|
||||
weekdays.add("fri");
|
||||
weekdaysInt -= 4;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 2)
|
||||
{
|
||||
weekdays.add("sat");
|
||||
weekdaysInt -= 2;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 1)
|
||||
{
|
||||
weekdays.add("sun");
|
||||
weekdaysInt -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
char timeT[5];
|
||||
sprintf(timeT, "%02d:%02d", entry.timeHour, entry.timeMin);
|
||||
jsonEntry["time"] = timeT;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiLock::lockactionToString(entry.lockAction, str);
|
||||
jsonEntry["lockAction"] = str;
|
||||
}
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_timecontrol_json, _buffer);
|
||||
}
|
||||
|
||||
void NetworkLock::publishKeypadCommandResult(const char* result)
|
||||
{
|
||||
publishString(mqtt_topic_keypad_command_result, result);
|
||||
}
|
||||
|
||||
void NetworkLock::publishKeypadJsonCommandResult(const char* result)
|
||||
{
|
||||
publishString(mqtt_topic_keypad_json_command_result, result);
|
||||
}
|
||||
|
||||
void NetworkLock::publishTimeControlCommandResult(const char* result)
|
||||
{
|
||||
publishString(mqtt_topic_timecontrol_command_result, result);
|
||||
}
|
||||
|
||||
void NetworkLock::setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char *))
|
||||
{
|
||||
_lockActionReceivedCallback = lockActionReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkLock::setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char *, const char *))
|
||||
{
|
||||
_configUpdateReceivedCallback = configUpdateReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled))
|
||||
{
|
||||
_keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkLock::setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char *))
|
||||
{
|
||||
_keypadJsonCommandReceivedReceivedCallback = keypadJsonCommandReceivedReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkLock::setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char *))
|
||||
{
|
||||
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkLock::buildMqttPath(const char* path, char* outPath)
|
||||
{
|
||||
int offset = 0;
|
||||
for(const char& c : _mqttPath)
|
||||
{
|
||||
if(c == 0x00)
|
||||
{
|
||||
break;
|
||||
}
|
||||
outPath[offset] = c;
|
||||
++offset;
|
||||
}
|
||||
int i=0;
|
||||
while(outPath[i] != 0x00)
|
||||
{
|
||||
outPath[offset] = path[i];
|
||||
++i;
|
||||
++offset;
|
||||
}
|
||||
outPath[i+1] = 0x00;
|
||||
}
|
||||
|
||||
bool NetworkLock::comparePrefixedPath(const char *fullPath, const char *subPath)
|
||||
{
|
||||
char prefixedPath[500];
|
||||
buildMqttPath(subPath, prefixedPath);
|
||||
return strcmp(fullPath, prefixedPath) == 0;
|
||||
}
|
||||
|
||||
void NetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction,
|
||||
char *unlockAction, char *openAction)
|
||||
{
|
||||
_network->publishHASSConfig(deviceType, baseTopic, name, uidString, "~/maintenance/mqttConnectionState", hasKeypad, lockAction, unlockAction, openAction);
|
||||
_network->publishHASSConfigAdditionalButtons(deviceType, baseTopic, name, uidString);
|
||||
_network->publishHASSConfigBatLevel(deviceType, baseTopic, name, uidString);
|
||||
_network->publishHASSConfigLedBrightness(deviceType, baseTopic, name, uidString);
|
||||
if(hasDoorSensor)
|
||||
{
|
||||
_network->publishHASSConfigDoorSensor(deviceType, baseTopic, name, uidString);
|
||||
}
|
||||
else
|
||||
{
|
||||
_network->removeHASSConfigTopic("binary_sensor", "door_sensor", uidString);
|
||||
}
|
||||
_network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString);
|
||||
_network->publishHASSBleRssiConfig(deviceType, baseTopic, name, uidString);
|
||||
|
||||
if(publishAuthData)
|
||||
{
|
||||
_network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString);
|
||||
}
|
||||
else
|
||||
{
|
||||
_network->removeHASSConfigTopic("sensor", "last_action_authorization", uidString);
|
||||
}
|
||||
|
||||
if(hasKeypad)
|
||||
{
|
||||
_network->publishHASSConfigKeypadAttemptInfo(deviceType, baseTopic, name, uidString);
|
||||
}
|
||||
else
|
||||
{
|
||||
_network->removeHASSConfigTopic("sensor", "keypad_status", uidString);
|
||||
}
|
||||
}
|
||||
|
||||
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 String &value)
|
||||
{
|
||||
char str[value.length() + 1];
|
||||
memset(str, 0, sizeof(str));
|
||||
memcpy(str, value.begin(), value.length());
|
||||
return publishString(topic, str);
|
||||
}
|
||||
|
||||
bool NetworkLock::publishString(const char *topic, const std::string &value)
|
||||
{
|
||||
char str[value.size() + 1];
|
||||
memset(str, 0, sizeof(str));
|
||||
memcpy(str, value.data(), value.length());
|
||||
return publishString(topic, str);
|
||||
}
|
||||
|
||||
bool NetworkLock::publishString(const char *topic, const char *value)
|
||||
{
|
||||
return _network->publishString(_mqttPath, topic, value);
|
||||
}
|
||||
|
||||
void NetworkLock::publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry)
|
||||
{
|
||||
char codeName[sizeof(entry.name) + 1];
|
||||
memset(codeName, 0, sizeof(codeName));
|
||||
memcpy(codeName, entry.name, sizeof(entry.name));
|
||||
|
||||
publishInt(concat(topic, "/id").c_str(), entry.codeId);
|
||||
publishBool(concat(topic, "/enabled").c_str(), entry.enabled);
|
||||
publishString(concat(topic, "/name").c_str(), codeName);
|
||||
publishInt(concat(topic, "/createdYear").c_str(), entry.dateCreatedYear);
|
||||
publishInt(concat(topic, "/createdMonth").c_str(), entry.dateCreatedMonth);
|
||||
publishInt(concat(topic, "/createdDay").c_str(), entry.dateCreatedDay);
|
||||
publishInt(concat(topic, "/createdHour").c_str(), entry.dateCreatedHour);
|
||||
publishInt(concat(topic, "/createdMin").c_str(), entry.dateCreatedMin);
|
||||
publishInt(concat(topic, "/createdSec").c_str(), entry.dateCreatedSec);
|
||||
publishInt(concat(topic, "/lockCount").c_str(), entry.lockCount);
|
||||
}
|
||||
|
||||
void NetworkLock::publishULong(const char *topic, const unsigned long value)
|
||||
{
|
||||
return _network->publishULong(_mqttPath, topic, value);
|
||||
}
|
||||
|
||||
String NetworkLock::concat(String a, String b)
|
||||
{
|
||||
String c = a;
|
||||
c.concat(b);
|
||||
return c;
|
||||
}
|
||||
|
||||
bool NetworkLock::reconnected()
|
||||
{
|
||||
bool r = _reconnected;
|
||||
_reconnected = false;
|
||||
return r;
|
||||
}
|
||||
|
||||
uint8_t NetworkLock::queryCommands()
|
||||
{
|
||||
uint8_t qc = _queryCommands;
|
||||
_queryCommands = 0;
|
||||
return qc;
|
||||
}
|
||||
100
src/NetworkLock.h
Normal file
100
src/NetworkLock.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include "networkDevices/NetworkDevice.h"
|
||||
#include "networkDevices/WifiDevice.h"
|
||||
#include "networkDevices/W5500Device.h"
|
||||
#include <Preferences.h>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include "NukiConstants.h"
|
||||
#include "NukiLockConstants.h"
|
||||
#include "Network.h"
|
||||
#include "QueryCommand.h"
|
||||
#include "LockActionResult.h"
|
||||
|
||||
#define LOCK_LOG_JSON_BUFFER_SIZE 2048
|
||||
|
||||
class NetworkLock : public MqttReceiver
|
||||
{
|
||||
public:
|
||||
explicit NetworkLock(Network* network, Preferences* preferences, char* buffer, size_t bufferSize);
|
||||
virtual ~NetworkLock();
|
||||
|
||||
void initialize();
|
||||
|
||||
void publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurnerState, const NukiLock::KeyTurnerState& lastKeyTurnerState);
|
||||
void publishState(NukiLock::LockState lockState);
|
||||
void publishAuthorizationInfo(const std::list<NukiLock::LogEntry>& logEntries);
|
||||
void clearAuthorizationInfo();
|
||||
void publishCommandResult(const char* resultStr);
|
||||
void publishLockstateCommandResult(const char* resultStr);
|
||||
void publishBatteryReport(const NukiLock::BatteryReport& batteryReport);
|
||||
void publishConfig(const NukiLock::Config& config);
|
||||
void publishAdvancedConfig(const NukiLock::AdvancedConfig& config);
|
||||
void publishRssi(const int& rssi);
|
||||
void publishRetry(const std::string& message);
|
||||
void publishBleAddress(const std::string& address);
|
||||
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char* lockAction, char* unlockAction, char* openAction);
|
||||
void removeHASSConfig(char* uidString);
|
||||
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
|
||||
void publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries);
|
||||
void publishKeypadCommandResult(const char* result);
|
||||
void publishKeypadJsonCommandResult(const char* result);
|
||||
void publishTimeControlCommandResult(const char* result);
|
||||
|
||||
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value));
|
||||
void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value));
|
||||
void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled));
|
||||
void setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char* value));
|
||||
void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char* value));
|
||||
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
|
||||
|
||||
bool reconnected();
|
||||
uint8_t queryCommands();
|
||||
|
||||
private:
|
||||
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 publishULong(const char* topic, const unsigned long value);
|
||||
void publishBool(const char* topic, const bool value);
|
||||
bool publishString(const char* topic, const String& value);
|
||||
bool publishString(const char* topic, const std::string& value);
|
||||
bool publishString(const char* topic, const char* value);
|
||||
void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry);
|
||||
|
||||
String concat(String a, String b);
|
||||
|
||||
void buildMqttPath(const char* path, char* outPath);
|
||||
|
||||
Network* _network;
|
||||
Preferences* _preferences;
|
||||
|
||||
std::vector<char*> _configTopics;
|
||||
char _mqttPath[181] = {0};
|
||||
|
||||
bool _firstTunerStatePublish = true;
|
||||
unsigned long _lastMaintenanceTs = 0;
|
||||
bool _haEnabled = false;
|
||||
bool _reconnected = false;
|
||||
|
||||
String _keypadCommandName = "";
|
||||
String _keypadCommandCode = "";
|
||||
uint _keypadCommandId = 0;
|
||||
int _keypadCommandEnabled = 1;
|
||||
uint8_t _queryCommands = 0;
|
||||
uint32_t _authId = 0;
|
||||
char _authName[33];
|
||||
bool _authFound = false;
|
||||
|
||||
char* _buffer;
|
||||
size_t _bufferSize;
|
||||
|
||||
LockActionResult (*_lockActionReceivedCallback)(const char* value) = nullptr;
|
||||
void (*_configUpdateReceivedCallback)(const char* path, const char* value) = nullptr;
|
||||
void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
|
||||
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr;
|
||||
void (*_timeControlCommandReceivedReceivedCallback)(const char* value) = nullptr;
|
||||
};
|
||||
922
src/NetworkOpener.cpp
Normal file
922
src/NetworkOpener.cpp
Normal file
@@ -0,0 +1,922 @@
|
||||
#include "NetworkOpener.h"
|
||||
#include "Arduino.h"
|
||||
#include "MqttTopics.h"
|
||||
#include "PreferencesKeys.h"
|
||||
#include "Logger.h"
|
||||
#include "Config.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
NetworkOpener::NetworkOpener(Network* network, Preferences* preferences, char* buffer, size_t bufferSize)
|
||||
: _preferences(preferences),
|
||||
_network(network),
|
||||
_buffer(buffer),
|
||||
_bufferSize(bufferSize)
|
||||
{
|
||||
_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_sound_level);
|
||||
|
||||
memset(_authName, 0, sizeof(_authName));
|
||||
_authName[0] = '\0';
|
||||
|
||||
_network->registerMqttReceiver(this);
|
||||
}
|
||||
|
||||
void NetworkOpener::initialize()
|
||||
{
|
||||
String mqttPath = _preferences->getString(preference_mqtt_opener_path);
|
||||
if(mqttPath.length() > 0)
|
||||
{
|
||||
size_t len = mqttPath.length();
|
||||
for(int i=0; i < len; i++)
|
||||
{
|
||||
_mqttPath[i] = mqttPath.charAt(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(_mqttPath, "nukiopener");
|
||||
_preferences->putString(preference_mqtt_opener_path, _mqttPath);
|
||||
}
|
||||
|
||||
_haEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
|
||||
|
||||
_network->initTopic(_mqttPath, mqtt_topic_lock_action, "--");
|
||||
_network->subscribe(_mqttPath, mqtt_topic_lock_action);
|
||||
for(const auto& topic : _configTopics)
|
||||
{
|
||||
_network->subscribe(_mqttPath, topic);
|
||||
}
|
||||
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_config, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_lockstate, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_battery, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_lock_binary_ring, "standby");
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_config);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_lockstate);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_battery);
|
||||
|
||||
if(_preferences->getBool(preference_keypad_control_enabled))
|
||||
{
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_action);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_id);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_name);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_code);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_command_enabled);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_query_keypad);
|
||||
_network->subscribe(_mqttPath, mqtt_topic_keypad_json_action);
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_action, "--");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_id, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_name, "--");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_code, "000000");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_command_enabled, "1");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_query_keypad, "0");
|
||||
_network->initTopic(_mqttPath, mqtt_topic_keypad_json_action, "--");
|
||||
}
|
||||
|
||||
if(_preferences->getBool(preference_timecontrol_control_enabled))
|
||||
{
|
||||
_network->subscribe(_mqttPath, mqtt_topic_timecontrol_action);
|
||||
_network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--");
|
||||
}
|
||||
|
||||
_network->addReconnectedCallback([&]()
|
||||
{
|
||||
_reconnected = true;
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkOpener::update()
|
||||
{
|
||||
if(_resetRingStateTs != 0 && millis() >= _resetRingStateTs)
|
||||
{
|
||||
_resetRingStateTs = 0;
|
||||
publishString(mqtt_topic_lock_binary_ring, "standby");
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOpener::onMqttDataReceived(const char* topic, byte* payload, const unsigned int length)
|
||||
{
|
||||
char* value = (char*)payload;
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_lock_action))
|
||||
{
|
||||
if(strcmp(value, "") == 0 ||
|
||||
strcmp(value, "--") == 0 ||
|
||||
strcmp(value, "ack") == 0 ||
|
||||
strcmp(value, "unknown_action") == 0 ||
|
||||
strcmp(value, "denied") == 0 ||
|
||||
strcmp(value, "error") == 0) return;
|
||||
|
||||
Log->print(F("Lock action received: "));
|
||||
Log->println(value);
|
||||
LockActionResult lockActionResult = LockActionResult::Failed;
|
||||
if(_lockActionReceivedCallback != NULL)
|
||||
{
|
||||
lockActionResult = _lockActionReceivedCallback(value);
|
||||
}
|
||||
|
||||
switch(lockActionResult)
|
||||
{
|
||||
case LockActionResult::Success:
|
||||
publishString(mqtt_topic_lock_action, "ack");
|
||||
break;
|
||||
case LockActionResult::UnknownAction:
|
||||
publishString(mqtt_topic_lock_action, "unknown_action");
|
||||
break;
|
||||
case LockActionResult::AccessDenied:
|
||||
publishString(mqtt_topic_lock_action, "denied");
|
||||
break;
|
||||
case LockActionResult::Failed:
|
||||
publishString(mqtt_topic_lock_action, "error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_keypad_command_action))
|
||||
{
|
||||
if(_keypadCommandReceivedReceivedCallback != nullptr)
|
||||
{
|
||||
if(strcmp(value, "--") == 0) return;
|
||||
|
||||
_keypadCommandReceivedReceivedCallback(value, _keypadCommandId, _keypadCommandName, _keypadCommandCode, _keypadCommandEnabled);
|
||||
|
||||
_keypadCommandId = 0;
|
||||
_keypadCommandName = "--";
|
||||
_keypadCommandCode = "000000";
|
||||
_keypadCommandEnabled = 1;
|
||||
|
||||
if(strcmp(value, "--") != 0)
|
||||
{
|
||||
publishString(mqtt_topic_keypad_command_action, "--");
|
||||
}
|
||||
publishInt(mqtt_topic_keypad_command_id, _keypadCommandId);
|
||||
publishString(mqtt_topic_keypad_command_name, _keypadCommandName);
|
||||
publishString(mqtt_topic_keypad_command_code, _keypadCommandCode);
|
||||
publishInt(mqtt_topic_keypad_command_enabled, _keypadCommandEnabled);
|
||||
}
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_id))
|
||||
{
|
||||
_keypadCommandId = atoi(value);
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_name))
|
||||
{
|
||||
_keypadCommandName = value;
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_code))
|
||||
{
|
||||
_keypadCommandCode = value;
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_keypad_command_enabled))
|
||||
{
|
||||
_keypadCommandEnabled = atoi(value);
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_config) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_CONFIG;
|
||||
publishString(mqtt_topic_query_config, "0");
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_lockstate) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_LOCKSTATE;
|
||||
publishString(mqtt_topic_query_lockstate, "0");
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_keypad) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_KEYPAD;
|
||||
publishString(mqtt_topic_query_keypad, "0");
|
||||
}
|
||||
else if(comparePrefixedPath(topic, mqtt_topic_query_battery) && strcmp(value, "1") == 0)
|
||||
{
|
||||
_queryCommands = _queryCommands | QUERY_COMMAND_BATTERY;
|
||||
publishString(mqtt_topic_query_battery, "0");
|
||||
}
|
||||
|
||||
for(auto configTopic : _configTopics)
|
||||
{
|
||||
if(comparePrefixedPath(topic, configTopic))
|
||||
{
|
||||
if(_configUpdateReceivedCallback != nullptr)
|
||||
{
|
||||
_configUpdateReceivedCallback(configTopic, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_keypad_json_action))
|
||||
{
|
||||
if(strcmp(value, "") == 0 || strcmp(value, "--") == 0) return;
|
||||
|
||||
if(_keypadJsonCommandReceivedReceivedCallback != NULL)
|
||||
{
|
||||
_keypadJsonCommandReceivedReceivedCallback(value);
|
||||
}
|
||||
|
||||
publishString(mqtt_topic_keypad_json_action, "--");
|
||||
}
|
||||
|
||||
if(comparePrefixedPath(topic, mqtt_topic_timecontrol_action))
|
||||
{
|
||||
if(strcmp(value, "") == 0 || strcmp(value, "--") == 0) return;
|
||||
|
||||
if(_timeControlCommandReceivedReceivedCallback != NULL)
|
||||
{
|
||||
_timeControlCommandReceivedReceivedCallback(value);
|
||||
}
|
||||
|
||||
publishString(mqtt_topic_timecontrol_action, "--");
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOpener::publishKeyTurnerState(const NukiOpener::OpenerState& keyTurnerState, const NukiOpener::OpenerState& lastKeyTurnerState)
|
||||
{
|
||||
_currentLockState = keyTurnerState.lockState;
|
||||
|
||||
char str[50];
|
||||
memset(&str, 0, sizeof(str));
|
||||
|
||||
JsonDocument json;
|
||||
|
||||
lockstateToString(keyTurnerState.lockState, str);
|
||||
|
||||
if((_firstTunerStatePublish || keyTurnerState.lockState != lastKeyTurnerState.lockState || keyTurnerState.nukiState != lastKeyTurnerState.nukiState) && keyTurnerState.lockState != NukiOpener::LockState::Undefined)
|
||||
{
|
||||
publishString(mqtt_topic_lock_state, str);
|
||||
|
||||
if(_haEnabled)
|
||||
{
|
||||
publishState(keyTurnerState);
|
||||
}
|
||||
}
|
||||
|
||||
json["lock_state"] = str;
|
||||
|
||||
if(keyTurnerState.nukiState == NukiOpener::State::ContinuousMode)
|
||||
{
|
||||
publishString(mqtt_topic_lock_continuous_mode, "on");
|
||||
json["continuous_mode"] = 1;
|
||||
} else {
|
||||
publishString(mqtt_topic_lock_continuous_mode, "off");
|
||||
json["continuous_mode"] = 0;
|
||||
}
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
triggerToString(keyTurnerState.trigger, str);
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.trigger != lastKeyTurnerState.trigger)
|
||||
{
|
||||
publishString(mqtt_topic_lock_trigger, str);
|
||||
}
|
||||
|
||||
json["trigger"] = str;
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
completionStatusToString(keyTurnerState.lastLockActionCompletionStatus, str);
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.lastLockActionCompletionStatus != lastKeyTurnerState.lastLockActionCompletionStatus)
|
||||
{
|
||||
publishString(mqtt_topic_lock_completionStatus, str);
|
||||
}
|
||||
|
||||
json["lock_completion_status"] = str;
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
NukiOpener::doorSensorStateToString(keyTurnerState.doorSensorState, str);
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.doorSensorState != lastKeyTurnerState.doorSensorState)
|
||||
{
|
||||
publishString(mqtt_topic_lock_door_sensor_state, str);
|
||||
}
|
||||
|
||||
json["door_sensor_state"] = str;
|
||||
|
||||
if(_firstTunerStatePublish || keyTurnerState.criticalBatteryState != lastKeyTurnerState.criticalBatteryState)
|
||||
{
|
||||
bool critical = (keyTurnerState.criticalBatteryState & 0b00000001) > 0;
|
||||
publishBool(mqtt_topic_battery_critical, critical);
|
||||
}
|
||||
|
||||
json["auth_id"] = _authId;
|
||||
json["auth_name"] = _authName;
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_lock_json, _buffer);
|
||||
|
||||
_firstTunerStatePublish = false;
|
||||
}
|
||||
|
||||
void NetworkOpener::publishRing(const bool locked)
|
||||
{
|
||||
if (locked)
|
||||
{
|
||||
publishString(mqtt_topic_lock_ring, "ringlocked");
|
||||
}
|
||||
else
|
||||
{
|
||||
publishString(mqtt_topic_lock_ring, "ring");
|
||||
}
|
||||
|
||||
publishString(mqtt_topic_lock_binary_ring, "ring");
|
||||
_resetRingStateTs = millis() + 2000;
|
||||
}
|
||||
|
||||
void NetworkOpener::publishState(NukiOpener::OpenerState lockState)
|
||||
{
|
||||
if(lockState.nukiState == NukiOpener::State::ContinuousMode)
|
||||
{
|
||||
publishString(mqtt_topic_lock_ha_state, "unlocked");
|
||||
publishString(mqtt_topic_lock_binary_state, "unlocked");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (lockState.lockState)
|
||||
{
|
||||
case NukiOpener::LockState::Locked:
|
||||
publishString(mqtt_topic_lock_ha_state, "locked");
|
||||
publishString(mqtt_topic_lock_binary_state, "locked");
|
||||
break;
|
||||
case NukiOpener::LockState::RTOactive:
|
||||
case NukiOpener::LockState::Open:
|
||||
publishString(mqtt_topic_lock_ha_state, "unlocked");
|
||||
publishString(mqtt_topic_lock_binary_state, "unlocked");
|
||||
break;
|
||||
case NukiOpener::LockState::Opening:
|
||||
publishString(mqtt_topic_lock_ha_state, "unlocking");
|
||||
publishString(mqtt_topic_lock_binary_state, "unlocked");
|
||||
break;
|
||||
case NukiOpener::LockState::Undefined:
|
||||
case NukiOpener::LockState::Uncalibrated:
|
||||
publishString(mqtt_topic_lock_ha_state, "jammed");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntry>& logEntries)
|
||||
{
|
||||
char str[50];
|
||||
|
||||
_authId = 0;
|
||||
memset(_authName, 0, sizeof(_authName));
|
||||
_authName[0] = '\0';
|
||||
_authFound = false;
|
||||
|
||||
JsonDocument json;
|
||||
|
||||
int i = 5;
|
||||
for(const auto& log : logEntries)
|
||||
{
|
||||
if(i <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
--i;
|
||||
|
||||
if((log.loggingType == NukiOpener::LoggingType::LockAction || log.loggingType == NukiOpener::LoggingType::KeypadAction) && ! _authFound)
|
||||
{
|
||||
_authFound = true;
|
||||
_authId = log.authId;
|
||||
int sizeName = sizeof(log.name);
|
||||
memcpy(_authName, log.name, sizeName);
|
||||
if(_authName[sizeName - 1] != '\0') _authName[sizeName] = '\0';
|
||||
}
|
||||
|
||||
auto entry = json.add();
|
||||
|
||||
entry["index"] = log.index;
|
||||
entry["authorizationId"] = log.authId;
|
||||
entry["authorizationName"] = _authName;
|
||||
entry["timeYear"] = log.timeStampYear;
|
||||
entry["timeMonth"] = log.timeStampMonth;
|
||||
entry["timeDay"] = log.timeStampDay;
|
||||
entry["timeHour"] = log.timeStampHour;
|
||||
entry["timeMinute"] = log.timeStampMinute;
|
||||
entry["timeSecond"] = log.timeStampSecond;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
loggingTypeToString(log.loggingType, str);
|
||||
entry["type"] = str;
|
||||
|
||||
switch(log.loggingType)
|
||||
{
|
||||
case NukiOpener::LoggingType::LockAction:
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiOpener::lockactionToString((NukiOpener::LockAction)log.data[0], str);
|
||||
entry["action"] = str;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiOpener::triggerToString((NukiOpener::Trigger)log.data[1], str);
|
||||
entry["trigger"] = str;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiOpener::completionStatusToString((NukiOpener::CompletionStatus)log.data[3], str);
|
||||
entry["completionStatus"] = str;
|
||||
break;
|
||||
case NukiOpener::LoggingType::KeypadAction:
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiOpener::lockactionToString((NukiOpener::LockAction)log.data[0], str);
|
||||
entry["action"] = str;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiOpener::completionStatusToString((NukiOpener::CompletionStatus)log.data[2], str);
|
||||
entry["completionStatus"] = str;
|
||||
break;
|
||||
case NukiOpener::LoggingType::DoorbellRecognition:
|
||||
switch(log.data[0] & 3)
|
||||
{
|
||||
case 0:
|
||||
entry["mode"] = "None";
|
||||
break;
|
||||
case 1:
|
||||
entry["mode"] = "RTO";
|
||||
break;
|
||||
case 2:
|
||||
entry["mode"] = "CM";
|
||||
break;
|
||||
default:
|
||||
entry["mode"] = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
switch(log.data[1])
|
||||
{
|
||||
case 0:
|
||||
entry["source"] = "Doorbell";
|
||||
break;
|
||||
case 1:
|
||||
entry["source"] = "Timecontrol";
|
||||
break;
|
||||
case 2:
|
||||
entry["source"] = "App";
|
||||
break;
|
||||
case 3:
|
||||
entry["source"] = "Button";
|
||||
break;
|
||||
case 4:
|
||||
entry["source"] = "Fob";
|
||||
break;
|
||||
case 5:
|
||||
entry["source"] = "Bridge";
|
||||
break;
|
||||
case 6:
|
||||
entry["source"] = "Keypad";
|
||||
break;
|
||||
default:
|
||||
entry["source"] = "Unknown";
|
||||
break; }
|
||||
|
||||
entry["geofence"] = log.data[2] == 1 ? "active" : "inactive";
|
||||
entry["doorbellSuppression"] = log.data[3] == 1 ? "active" : "inactive";
|
||||
entry["completionStatus"] = str;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_lock_log, _buffer);
|
||||
|
||||
if(_authFound)
|
||||
{
|
||||
publishUInt(mqtt_topic_lock_auth_id, _authId);
|
||||
publishString(mqtt_topic_lock_auth_name, _authName);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOpener::logactionCompletionStatusToString(uint8_t value, char* out)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0x00:
|
||||
strcpy(out, "success");
|
||||
break;
|
||||
case 0x02:
|
||||
strcpy(out, "cancelled");
|
||||
break;
|
||||
case 0x03:
|
||||
strcpy(out, "tooRecent");
|
||||
break;
|
||||
case 0x04:
|
||||
strcpy(out, "busy");
|
||||
break;
|
||||
case 0x08:
|
||||
strcpy(out, "incomplete");
|
||||
break;
|
||||
case 0xfe:
|
||||
strcpy(out, "otherError");
|
||||
break;
|
||||
case 0xff:
|
||||
strcpy(out, "unknown");
|
||||
break;
|
||||
default:
|
||||
strcpy(out, "undefined");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOpener::clearAuthorizationInfo()
|
||||
{
|
||||
publishString(mqtt_topic_lock_log, "--");
|
||||
publishUInt(mqtt_topic_lock_auth_id, 0);
|
||||
publishString(mqtt_topic_lock_auth_name, "--");
|
||||
}
|
||||
|
||||
void NetworkOpener::publishCommandResult(const char *resultStr)
|
||||
{
|
||||
publishString(mqtt_topic_lock_action_command_result, resultStr);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishLockstateCommandResult(const char *resultStr)
|
||||
{
|
||||
publishString(mqtt_topic_query_lockstate_command_result, resultStr);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishBatteryReport(const NukiOpener::BatteryReport& batteryReport)
|
||||
{
|
||||
publishFloat(mqtt_topic_battery_voltage, (float)batteryReport.batteryVoltage / 1000.0);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishConfig(const NukiOpener::Config &config)
|
||||
{
|
||||
publishBool(mqtt_topic_config_button_enabled, config.buttonEnabled == 1);
|
||||
publishBool(mqtt_topic_config_led_enabled, config.ledFlashEnabled == 1);
|
||||
publishString(mqtt_topic_info_firmware_version, std::to_string(config.firmwareVersion[0]) + "." + std::to_string(config.firmwareVersion[1]) + "." + std::to_string(config.firmwareVersion[2]));
|
||||
publishString(mqtt_topic_info_hardware_version, std::to_string(config.hardwareRevision[0]) + "." + std::to_string(config.hardwareRevision[1]));
|
||||
}
|
||||
|
||||
void NetworkOpener::publishAdvancedConfig(const NukiOpener::AdvancedConfig &config)
|
||||
{
|
||||
publishUInt(mqtt_topic_config_sound_level, config.soundLevel);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishRssi(const int &rssi)
|
||||
{
|
||||
publishInt(mqtt_topic_lock_rssi, rssi);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishRetry(const std::string& message)
|
||||
{
|
||||
publishString(mqtt_topic_lock_retry, message);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishBleAddress(const std::string &address)
|
||||
{
|
||||
publishString(mqtt_topic_lock_address, address);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction)
|
||||
{
|
||||
String availabilityTopic = _preferences->getString("mqttpath");
|
||||
availabilityTopic.concat("/maintenance/mqttConnectionState");
|
||||
|
||||
_network->publishHASSConfig(deviceType, baseTopic, name, uidString, availabilityTopic.c_str(), false, lockAction, unlockAction, openAction);
|
||||
_network->publishHASSConfigRingDetect(deviceType, baseTopic, name, uidString);
|
||||
_network->publishHASSConfigContinuousMode(deviceType, baseTopic, name, uidString);
|
||||
_network->publishHASSConfigSoundLevel(deviceType, baseTopic, name, uidString);
|
||||
_network->publishHASSBleRssiConfig(deviceType, baseTopic, name, uidString);
|
||||
}
|
||||
|
||||
void NetworkOpener::removeHASSConfig(char* uidString)
|
||||
{
|
||||
_network->removeHASSConfig(uidString);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount)
|
||||
{
|
||||
uint index = 0;
|
||||
|
||||
JsonDocument json;
|
||||
|
||||
for(const auto& entry : entries)
|
||||
{
|
||||
String basePath = mqtt_topic_keypad;
|
||||
basePath.concat("/code_");
|
||||
basePath.concat(std::to_string(index).c_str());
|
||||
publishKeypadEntry(basePath, entry);
|
||||
|
||||
auto jsonEntry = json.add();
|
||||
|
||||
jsonEntry["codeId"] = entry.codeId;
|
||||
jsonEntry["enabled"] = entry.enabled;
|
||||
jsonEntry["name"] = entry.name;
|
||||
char createdDT[20];
|
||||
sprintf(createdDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.dateCreatedYear, entry.dateCreatedMonth, entry.dateCreatedDay, entry.dateCreatedHour, entry.dateCreatedMin, entry.dateCreatedSec);
|
||||
jsonEntry["dateCreated"] = createdDT;
|
||||
jsonEntry["lockCount"] = entry.lockCount;
|
||||
char lastActiveDT[20];
|
||||
sprintf(lastActiveDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.dateLastActiveYear, entry.dateLastActiveMonth, entry.dateLastActiveDay, entry.dateLastActiveHour, entry.dateLastActiveMin, entry.dateLastActiveSec);
|
||||
jsonEntry["dateLastActive"] = lastActiveDT;
|
||||
jsonEntry["timeLimited"] = entry.timeLimited;
|
||||
char allowedFromDT[20];
|
||||
sprintf(allowedFromDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedFromYear, entry.allowedFromMonth, entry.allowedFromDay, entry.allowedFromHour, entry.allowedFromMin, entry.allowedFromSec);
|
||||
jsonEntry["allowedFrom"] = allowedFromDT;
|
||||
char allowedUntilDT[20];
|
||||
sprintf(allowedUntilDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedUntilYear, entry.allowedUntilMonth, entry.allowedUntilDay, entry.allowedUntilHour, entry.allowedUntilMin, entry.allowedUntilSec);
|
||||
jsonEntry["allowedUntil"] = allowedUntilDT;
|
||||
|
||||
uint8_t allowedWeekdaysInt = entry.allowedWeekdays;
|
||||
JsonArray weekdays = jsonEntry["allowedWeekdays"].to<JsonArray>();
|
||||
|
||||
while(allowedWeekdaysInt > 0) {
|
||||
if(allowedWeekdaysInt >= 64)
|
||||
{
|
||||
weekdays.add("mon");
|
||||
allowedWeekdaysInt -= 64;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 32)
|
||||
{
|
||||
weekdays.add("tue");
|
||||
allowedWeekdaysInt -= 32;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 16)
|
||||
{
|
||||
weekdays.add("wed");
|
||||
allowedWeekdaysInt -= 16;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 8)
|
||||
{
|
||||
weekdays.add("thu");
|
||||
allowedWeekdaysInt -= 8;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 4)
|
||||
{
|
||||
weekdays.add("fri");
|
||||
allowedWeekdaysInt -= 4;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 2)
|
||||
{
|
||||
weekdays.add("sat");
|
||||
allowedWeekdaysInt -= 2;
|
||||
continue;
|
||||
}
|
||||
if(allowedWeekdaysInt >= 1)
|
||||
{
|
||||
weekdays.add("sun");
|
||||
allowedWeekdaysInt -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
char allowedFromTimeT[5];
|
||||
sprintf(allowedFromTimeT, "%02d:%02d", entry.allowedFromTimeHour, entry.allowedFromTimeMin);
|
||||
jsonEntry["allowedFromTime"] = allowedFromTimeT;
|
||||
char allowedUntilTimeT[5];
|
||||
sprintf(allowedUntilTimeT, "%02d:%02d", entry.allowedUntilTimeHour, entry.allowedUntilTimeMin);
|
||||
jsonEntry["allowedUntilTime"] = allowedUntilTimeT;
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_keypad_json, _buffer);
|
||||
|
||||
while(index < maxKeypadCodeCount)
|
||||
{
|
||||
NukiLock::KeypadEntry entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
String basePath = mqtt_topic_keypad;
|
||||
basePath.concat("/code_");
|
||||
basePath.concat(std::to_string(index).c_str());
|
||||
publishKeypadEntry(basePath, entry);
|
||||
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries)
|
||||
{
|
||||
char str[50];
|
||||
JsonDocument json;
|
||||
|
||||
for(const auto& entry : timeControlEntries)
|
||||
{
|
||||
auto jsonEntry = json.add();
|
||||
|
||||
jsonEntry["entryId"] = entry.entryId;
|
||||
jsonEntry["enabled"] = entry.enabled;
|
||||
uint8_t weekdaysInt = entry.weekdays;
|
||||
JsonArray weekdays = jsonEntry["weekdays"].to<JsonArray>();
|
||||
|
||||
while(weekdaysInt > 0) {
|
||||
if(weekdaysInt >= 64)
|
||||
{
|
||||
weekdays.add("mon");
|
||||
weekdaysInt -= 64;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 32)
|
||||
{
|
||||
weekdays.add("tue");
|
||||
weekdaysInt -= 32;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 16)
|
||||
{
|
||||
weekdays.add("wed");
|
||||
weekdaysInt -= 16;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 8)
|
||||
{
|
||||
weekdays.add("thu");
|
||||
weekdaysInt -= 8;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 4)
|
||||
{
|
||||
weekdays.add("fri");
|
||||
weekdaysInt -= 4;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 2)
|
||||
{
|
||||
weekdays.add("sat");
|
||||
weekdaysInt -= 2;
|
||||
continue;
|
||||
}
|
||||
if(weekdaysInt >= 1)
|
||||
{
|
||||
weekdays.add("sun");
|
||||
weekdaysInt -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
char timeT[5];
|
||||
sprintf(timeT, "%02d:%02d", entry.timeHour, entry.timeMin);
|
||||
jsonEntry["time"] = timeT;
|
||||
|
||||
memset(str, 0, sizeof(str));
|
||||
NukiOpener::lockactionToString(entry.lockAction, str);
|
||||
jsonEntry["lockAction"] = str;
|
||||
}
|
||||
|
||||
serializeJson(json, _buffer, _bufferSize);
|
||||
publishString(mqtt_topic_timecontrol_json, _buffer);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishKeypadCommandResult(const char* result)
|
||||
{
|
||||
publishString(mqtt_topic_keypad_command_result, result);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishKeypadJsonCommandResult(const char* result)
|
||||
{
|
||||
publishString(mqtt_topic_keypad_json_command_result, result);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishTimeControlCommandResult(const char* result)
|
||||
{
|
||||
publishString(mqtt_topic_timecontrol_command_result, result);
|
||||
}
|
||||
|
||||
void NetworkOpener::setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char *))
|
||||
{
|
||||
_lockActionReceivedCallback = lockActionReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkOpener::setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char *, const char *))
|
||||
{
|
||||
_configUpdateReceivedCallback = configUpdateReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkOpener::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled))
|
||||
{
|
||||
_keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkOpener::setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char *))
|
||||
{
|
||||
_keypadJsonCommandReceivedReceivedCallback = keypadJsonCommandReceivedReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkOpener::setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char *))
|
||||
{
|
||||
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
|
||||
}
|
||||
|
||||
void NetworkOpener::publishFloat(const char *topic, const float value, const uint8_t precision)
|
||||
{
|
||||
_network->publishFloat(_mqttPath, topic, value, precision);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishInt(const char *topic, const int value)
|
||||
{
|
||||
_network->publishInt(_mqttPath, topic, value);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishUInt(const char *topic, const unsigned int value)
|
||||
{
|
||||
_network->publishUInt(_mqttPath, topic, value);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishBool(const char *topic, const bool value)
|
||||
{
|
||||
_network->publishBool(_mqttPath, topic, value);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishString(const char *topic, const String &value)
|
||||
{
|
||||
char str[value.length() + 1];
|
||||
memset(str, 0, sizeof(str));
|
||||
memcpy(str, value.begin(), value.length());
|
||||
publishString(topic, str);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishString(const char *topic, const std::string &value)
|
||||
{
|
||||
char str[value.size() + 1];
|
||||
memset(str, 0, sizeof(str));
|
||||
memcpy(str, value.data(), value.length());
|
||||
publishString(topic, str);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishString(const char* topic, const char* value)
|
||||
{
|
||||
_network->publishString(_mqttPath, topic, value);
|
||||
}
|
||||
|
||||
void NetworkOpener::publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry)
|
||||
{
|
||||
char codeName[sizeof(entry.name) + 1];
|
||||
memset(codeName, 0, sizeof(codeName));
|
||||
memcpy(codeName, entry.name, sizeof(entry.name));
|
||||
|
||||
publishInt(concat(topic, "/id").c_str(), entry.codeId);
|
||||
publishBool(concat(topic, "/enabled").c_str(), entry.enabled);
|
||||
publishString(concat(topic, "/name").c_str(), codeName);
|
||||
publishInt(concat(topic, "/createdYear").c_str(), entry.dateCreatedYear);
|
||||
publishInt(concat(topic, "/createdMonth").c_str(), entry.dateCreatedMonth);
|
||||
publishInt(concat(topic, "/createdDay").c_str(), entry.dateCreatedDay);
|
||||
publishInt(concat(topic, "/createdHour").c_str(), entry.dateCreatedHour);
|
||||
publishInt(concat(topic, "/createdMin").c_str(), entry.dateCreatedMin);
|
||||
publishInt(concat(topic, "/createdSec").c_str(), entry.dateCreatedSec);
|
||||
publishInt(concat(topic, "/lockCount").c_str(), entry.lockCount);
|
||||
}
|
||||
|
||||
void NetworkOpener::buildMqttPath(const char* path, char* outPath)
|
||||
{
|
||||
int offset = 0;
|
||||
for(const char& c : _mqttPath)
|
||||
{
|
||||
if(c == 0x00)
|
||||
{
|
||||
break;
|
||||
}
|
||||
outPath[offset] = c;
|
||||
++offset;
|
||||
}
|
||||
int i=0;
|
||||
while(path[i] != 0x00)
|
||||
{
|
||||
outPath[offset] = path[i];
|
||||
++i;
|
||||
++offset;
|
||||
}
|
||||
outPath[offset] = 0x00;
|
||||
}
|
||||
|
||||
void NetworkOpener::subscribe(const char *path)
|
||||
{
|
||||
char prefixedPath[500];
|
||||
buildMqttPath(path, prefixedPath);
|
||||
_network->subscribe(prefixedPath, MQTT_QOS_LEVEL);
|
||||
}
|
||||
|
||||
bool NetworkOpener::comparePrefixedPath(const char *fullPath, const char *subPath)
|
||||
{
|
||||
char prefixedPath[500];
|
||||
buildMqttPath(subPath, prefixedPath);
|
||||
|
||||
return strcmp(fullPath, prefixedPath) == 0;
|
||||
}
|
||||
|
||||
String NetworkOpener::concat(String a, String b)
|
||||
{
|
||||
String c = a;
|
||||
c.concat(b);
|
||||
return c;
|
||||
}
|
||||
|
||||
bool NetworkOpener::reconnected()
|
||||
{
|
||||
bool r = _reconnected;
|
||||
_reconnected = false;
|
||||
return r;
|
||||
}
|
||||
|
||||
uint8_t NetworkOpener::queryCommands()
|
||||
{
|
||||
uint8_t qc = _queryCommands;
|
||||
_queryCommands = 0;
|
||||
return qc;
|
||||
}
|
||||
103
src/NetworkOpener.h
Normal file
103
src/NetworkOpener.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include "networkDevices/NetworkDevice.h"
|
||||
#include "networkDevices/WifiDevice.h"
|
||||
#include "networkDevices/W5500Device.h"
|
||||
#include <Preferences.h>
|
||||
#include <vector>
|
||||
#include "NukiConstants.h"
|
||||
#include "NukiOpenerConstants.h"
|
||||
#include "NetworkLock.h"
|
||||
|
||||
class NetworkOpener : public MqttReceiver
|
||||
{
|
||||
public:
|
||||
explicit NetworkOpener(Network* network, Preferences* preferences, char* buffer, size_t bufferSize);
|
||||
virtual ~NetworkOpener() = default;
|
||||
|
||||
void initialize();
|
||||
void update();
|
||||
|
||||
void publishKeyTurnerState(const NukiOpener::OpenerState& keyTurnerState, const NukiOpener::OpenerState& lastKeyTurnerState);
|
||||
void publishRing(const bool locked);
|
||||
void publishState(NukiOpener::OpenerState lockState);
|
||||
void publishAuthorizationInfo(const std::list<NukiOpener::LogEntry>& logEntries);
|
||||
void clearAuthorizationInfo();
|
||||
void publishCommandResult(const char* resultStr);
|
||||
void publishLockstateCommandResult(const char* resultStr);
|
||||
void publishBatteryReport(const NukiOpener::BatteryReport& batteryReport);
|
||||
void publishConfig(const NukiOpener::Config& config);
|
||||
void publishAdvancedConfig(const NukiOpener::AdvancedConfig& config);
|
||||
void publishRssi(const int& rssi);
|
||||
void publishRetry(const std::string& message);
|
||||
void publishBleAddress(const std::string& address);
|
||||
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction);
|
||||
void removeHASSConfig(char* uidString);
|
||||
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
|
||||
void publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries);
|
||||
void publishKeypadCommandResult(const char* result);
|
||||
void publishKeypadJsonCommandResult(const char* result);
|
||||
void publishTimeControlCommandResult(const char* result);
|
||||
|
||||
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value));
|
||||
void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* path, const char* value));
|
||||
void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled));
|
||||
void setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char* value));
|
||||
void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char* value));
|
||||
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
|
||||
|
||||
bool reconnected();
|
||||
uint8_t queryCommands();
|
||||
|
||||
private:
|
||||
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);
|
||||
void publishString(const char* topic, const String& value);
|
||||
void publishString(const char* topic, const std::string& value);
|
||||
void publishString(const char* topic, const char* value);
|
||||
void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry);
|
||||
|
||||
void buildMqttPath(const char* path, char* outPath);
|
||||
void subscribe(const char* path);
|
||||
void logactionCompletionStatusToString(uint8_t value, char* out);
|
||||
|
||||
String concat(String a, String b);
|
||||
|
||||
Preferences* _preferences;
|
||||
|
||||
Network* _network = nullptr;
|
||||
|
||||
char _mqttPath[181] = {0};
|
||||
bool _isConnected = false;
|
||||
|
||||
std::vector<char*> _configTopics;
|
||||
|
||||
bool _firstTunerStatePublish = true;
|
||||
bool _haEnabled= false;
|
||||
bool _reconnected = false;
|
||||
|
||||
String _keypadCommandName = "";
|
||||
String _keypadCommandCode = "";
|
||||
uint _keypadCommandId = 0;
|
||||
int _keypadCommandEnabled = 1;
|
||||
unsigned long _resetRingStateTs = 0;
|
||||
uint8_t _queryCommands = 0;
|
||||
uint32_t _authId = 0;
|
||||
char _authName[33];
|
||||
bool _authFound = false;
|
||||
|
||||
NukiOpener::LockState _currentLockState = NukiOpener::LockState::Undefined;
|
||||
|
||||
char* _buffer;
|
||||
const size_t _bufferSize;
|
||||
|
||||
LockActionResult (*_lockActionReceivedCallback)(const char* value) = nullptr;
|
||||
void (*_configUpdateReceivedCallback)(const char* path, const char* value) = nullptr;
|
||||
void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
|
||||
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr;
|
||||
void (*_timeControlCommandReceivedReceivedCallback)(const char* value) = nullptr;
|
||||
};
|
||||
44
src/NukiDeviceId.cpp
Normal file
44
src/NukiDeviceId.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <cstring>
|
||||
#include <Arduino.h>
|
||||
#include "NukiDeviceId.h"
|
||||
#include "PreferencesKeys.h"
|
||||
|
||||
NukiDeviceId::NukiDeviceId(Preferences* preferences, const std::string& preferencesId)
|
||||
: _preferences(preferences),
|
||||
_preferencesId(preferencesId)
|
||||
{
|
||||
_deviceId = _preferences->getUInt(_preferencesId.c_str());
|
||||
|
||||
if(_deviceId == 0)
|
||||
{
|
||||
assignNewId();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t NukiDeviceId::get()
|
||||
{
|
||||
return _deviceId;
|
||||
}
|
||||
|
||||
void NukiDeviceId::assignId(const uint32_t& id)
|
||||
{
|
||||
_deviceId = id;
|
||||
_preferences->putUInt(_preferencesId.c_str(), id);
|
||||
}
|
||||
|
||||
void NukiDeviceId::assignNewId()
|
||||
{
|
||||
assignId(getRandomId());
|
||||
}
|
||||
|
||||
uint32_t NukiDeviceId::getRandomId()
|
||||
{
|
||||
uint8_t rnd[4];
|
||||
for(int i=0; i<4; i++)
|
||||
{
|
||||
rnd[i] = random(255);
|
||||
}
|
||||
uint32_t deviceId;
|
||||
memcpy(&deviceId, &rnd, sizeof(deviceId));
|
||||
return deviceId;
|
||||
}
|
||||
22
src/NukiDeviceId.h
Normal file
22
src/NukiDeviceId.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <Preferences.h>
|
||||
|
||||
class NukiDeviceId
|
||||
{
|
||||
public:
|
||||
NukiDeviceId(Preferences* preferences, const std::string& preferencesId);
|
||||
|
||||
uint32_t get();
|
||||
|
||||
void assignId(const uint32_t& id);
|
||||
void assignNewId();
|
||||
|
||||
private:
|
||||
uint32_t getRandomId();
|
||||
|
||||
Preferences* _preferences;
|
||||
const std::string _preferencesId;
|
||||
uint32_t _deviceId = 0;
|
||||
};
|
||||
1471
src/NukiOpenerWrapper.cpp
Normal file
1471
src/NukiOpenerWrapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
134
src/NukiOpenerWrapper.h
Normal file
134
src/NukiOpenerWrapper.h
Normal file
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include "NukiOpener.h"
|
||||
#include "NetworkOpener.h"
|
||||
#include "NukiOpenerConstants.h"
|
||||
#include "NukiDataTypes.h"
|
||||
#include "BleScanner.h"
|
||||
#include "Gpio.h"
|
||||
#include "NukiDeviceId.h"
|
||||
|
||||
class NukiOpenerWrapper : public NukiOpener::SmartlockEventHandler
|
||||
{
|
||||
public:
|
||||
NukiOpenerWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkOpener* network, Gpio* gpio, Preferences* preferences);
|
||||
virtual ~NukiOpenerWrapper();
|
||||
|
||||
void initialize();
|
||||
void update();
|
||||
|
||||
void electricStrikeActuation();
|
||||
void activateRTO();
|
||||
void activateCM();
|
||||
void deactivateRtoCm();
|
||||
void deactivateRTO();
|
||||
void deactivateCM();
|
||||
|
||||
bool isPinSet();
|
||||
void setPin(const uint16_t pin);
|
||||
|
||||
void unpair();
|
||||
|
||||
void disableHASS();
|
||||
|
||||
void disableWatchdog();
|
||||
|
||||
const NukiOpener::OpenerState& keyTurnerState();
|
||||
const bool isPaired() const;
|
||||
const bool hasKeypad() const;
|
||||
const BLEAddress getBleAddress() const;
|
||||
|
||||
std::string firmwareVersion() const;
|
||||
std::string hardwareVersion() const;
|
||||
|
||||
BleScanner::Scanner* bleScanner();
|
||||
|
||||
void notify(NukiOpener::EventType eventType) override;
|
||||
|
||||
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 onKeypadJsonCommandReceivedCallback(const char* value);
|
||||
static void onTimeControlCommandReceivedCallback(const char* value);
|
||||
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);
|
||||
void onKeypadJsonCommandReceived(const char* value);
|
||||
void onTimeControlCommandReceived(const char* value);
|
||||
|
||||
void updateKeyTurnerState();
|
||||
void updateBatteryState();
|
||||
void updateConfig();
|
||||
void updateAuthData();
|
||||
void updateKeypad();
|
||||
void updateTimeControl(bool retrieved);
|
||||
void postponeBleWatchdog();
|
||||
|
||||
void updateGpioOutputs();
|
||||
|
||||
void readConfig();
|
||||
void readAdvancedConfig();
|
||||
|
||||
void setupHASS();
|
||||
|
||||
void printCommandResult(Nuki::CmdResult result);
|
||||
|
||||
NukiOpener::LockAction lockActionToEnum(const char* str); // char array at least 14 characters
|
||||
|
||||
std::string _deviceName;
|
||||
NukiDeviceId* _deviceId = nullptr;
|
||||
NukiOpener::NukiOpener _nukiOpener;
|
||||
BleScanner::Scanner* _bleScanner = nullptr;
|
||||
NetworkOpener* _network = nullptr;
|
||||
Gpio* _gpio = nullptr;
|
||||
Preferences* _preferences = nullptr;
|
||||
int _intervalLockstate = 0; // seconds
|
||||
int _intervalBattery = 0; // seconds
|
||||
int _intervalConfig = 60 * 60; // seconds
|
||||
int _intervalKeypad = 0; // seconds
|
||||
int _restartBeaconTimeout = 0; // seconds
|
||||
bool _publishAuthData = false;
|
||||
bool _clearAuthData = false;
|
||||
int _nrOfRetries = 0;
|
||||
int _retryDelay = 0;
|
||||
int _retryCount = 0;
|
||||
int _retryConfigCount = 0;
|
||||
int _retryLockstateCount = 0;
|
||||
unsigned long _nextRetryTs = 0;
|
||||
std::vector<uint16_t> _keypadCodeIds;
|
||||
std::vector<uint8_t> _timeControlIds;
|
||||
|
||||
NukiOpener::OpenerState _lastKeyTurnerState;
|
||||
NukiOpener::OpenerState _keyTurnerState;
|
||||
|
||||
NukiOpener::BatteryReport _batteryReport;
|
||||
NukiOpener::BatteryReport _lastBatteryReport;
|
||||
|
||||
NukiOpener::Config _nukiConfig = {0};
|
||||
NukiOpener::AdvancedConfig _nukiAdvancedConfig = {0};
|
||||
bool _nukiConfigValid = false;
|
||||
bool _nukiAdvancedConfigValid = false;
|
||||
bool _hassEnabled = false;
|
||||
bool _hassSetupCompleted = false;
|
||||
|
||||
bool _paired = false;
|
||||
bool _statusUpdated = false;
|
||||
bool _hasKeypad = false;
|
||||
bool _keypadEnabled = false;
|
||||
uint _maxKeypadCodeCount = 0;
|
||||
bool _configRead = false;
|
||||
long _rssiPublishInterval = 0;
|
||||
unsigned long _nextLockStateUpdateTs = 0;
|
||||
unsigned long _nextBatteryReportTs = 0;
|
||||
unsigned long _nextConfigUpdateTs = 0;
|
||||
unsigned long _nextTimeControlUpdateTs = 0;
|
||||
unsigned long _nextKeypadUpdateTs = 0;
|
||||
unsigned long _nextPairTs = 0;
|
||||
long _nextRssiTs = 0;
|
||||
unsigned long _lastRssi = 0;
|
||||
unsigned long _disableBleWatchdogTs = 0;
|
||||
std::string _firmwareVersion = "";
|
||||
std::string _hardwareVersion = "";
|
||||
NukiOpener::LockAction _nextLockAction = (NukiOpener::LockAction)0xff;
|
||||
};
|
||||
1459
src/NukiWrapper.cpp
Normal file
1459
src/NukiWrapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
132
src/NukiWrapper.h
Normal file
132
src/NukiWrapper.h
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include "NetworkLock.h"
|
||||
#include "NukiConstants.h"
|
||||
#include "NukiDataTypes.h"
|
||||
#include "BleScanner.h"
|
||||
#include "NukiLock.h"
|
||||
#include "Gpio.h"
|
||||
#include "LockActionResult.h"
|
||||
#include "NukiDeviceId.h"
|
||||
|
||||
class NukiWrapper : public Nuki::SmartlockEventHandler
|
||||
{
|
||||
public:
|
||||
NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NetworkLock* network, Gpio* gpio, Preferences* preferences);
|
||||
virtual ~NukiWrapper();
|
||||
|
||||
void initialize(const bool& firstStart);
|
||||
void update();
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
void unlatch();
|
||||
void lockngo();
|
||||
void lockngounlatch();
|
||||
|
||||
bool isPinSet();
|
||||
void setPin(const uint16_t pin);
|
||||
void unpair();
|
||||
|
||||
void disableHASS();
|
||||
|
||||
void disableWatchdog();
|
||||
|
||||
const NukiLock::KeyTurnerState& keyTurnerState();
|
||||
const bool isPaired() const;
|
||||
const bool hasKeypad() const;
|
||||
bool hasDoorSensor() const;
|
||||
const BLEAddress getBleAddress() const;
|
||||
|
||||
std::string firmwareVersion() const;
|
||||
std::string hardwareVersion() const;
|
||||
|
||||
void notify(Nuki::EventType eventType) override;
|
||||
|
||||
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 onKeypadJsonCommandReceivedCallback(const char* value);
|
||||
static void onTimeControlCommandReceivedCallback(const char* value);
|
||||
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);
|
||||
void onKeypadJsonCommandReceived(const char* value);
|
||||
void onTimeControlCommandReceived(const char* value);
|
||||
|
||||
void updateKeyTurnerState();
|
||||
void updateBatteryState();
|
||||
void updateConfig();
|
||||
void updateAuthData();
|
||||
void updateKeypad();
|
||||
void updateTimeControl(bool retrieved);
|
||||
void postponeBleWatchdog();
|
||||
|
||||
void updateGpioOutputs();
|
||||
|
||||
void readConfig();
|
||||
void readAdvancedConfig();
|
||||
|
||||
void setupHASS();
|
||||
|
||||
void printCommandResult(Nuki::CmdResult result);
|
||||
|
||||
NukiLock::LockAction lockActionToEnum(const char* str); // char array at least 14 characters
|
||||
|
||||
std::string _deviceName;
|
||||
NukiDeviceId* _deviceId = nullptr;
|
||||
NukiLock::NukiLock _nukiLock;
|
||||
BleScanner::Scanner* _bleScanner = nullptr;
|
||||
NetworkLock* _network = nullptr;
|
||||
Gpio* _gpio = nullptr;
|
||||
Preferences* _preferences;
|
||||
int _intervalLockstate = 0; // seconds
|
||||
int _intervalBattery = 0; // seconds
|
||||
int _intervalConfig = 60 * 60; // seconds
|
||||
int _intervalKeypad = 0; // seconds
|
||||
int _restartBeaconTimeout = 0; // seconds
|
||||
bool _publishAuthData = false;
|
||||
bool _clearAuthData = false;
|
||||
std::vector<uint16_t> _keypadCodeIds;
|
||||
std::vector<uint8_t> _timeControlIds;
|
||||
|
||||
NukiLock::KeyTurnerState _lastKeyTurnerState;
|
||||
NukiLock::KeyTurnerState _keyTurnerState;
|
||||
|
||||
NukiLock::BatteryReport _batteryReport;
|
||||
NukiLock::BatteryReport _lastBatteryReport;
|
||||
|
||||
NukiLock::Config _nukiConfig = {0};
|
||||
NukiLock::AdvancedConfig _nukiAdvancedConfig = {0};
|
||||
bool _nukiConfigValid = false;
|
||||
bool _nukiAdvancedConfigValid = false;
|
||||
bool _hassEnabled = false;
|
||||
bool _hassSetupCompleted = false;
|
||||
|
||||
bool _paired = false;
|
||||
bool _statusUpdated = false;
|
||||
bool _hasKeypad = false;
|
||||
bool _keypadEnabled = false;
|
||||
uint _maxKeypadCodeCount = 0;
|
||||
bool _configRead = false;
|
||||
int _nrOfRetries = 0;
|
||||
int _retryDelay = 0;
|
||||
int _retryCount = 0;
|
||||
int _retryConfigCount = 0;
|
||||
int _retryLockstateCount = 0;
|
||||
long _rssiPublishInterval = 0;
|
||||
unsigned long _nextRetryTs = 0;
|
||||
unsigned long _nextLockStateUpdateTs = 0;
|
||||
unsigned long _nextBatteryReportTs = 0;
|
||||
unsigned long _nextConfigUpdateTs = 0;
|
||||
unsigned long _nextTimeControlUpdateTs = 0;
|
||||
unsigned long _nextKeypadUpdateTs = 0;
|
||||
unsigned long _nextRssiTs = 0;
|
||||
unsigned long _lastRssi = 0;
|
||||
unsigned long _disableBleWatchdogTs = 0;
|
||||
std::string _firmwareVersion = "";
|
||||
std::string _hardwareVersion = "";
|
||||
volatile NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff;
|
||||
};
|
||||
52
src/Ota.cpp
Normal file
52
src/Ota.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <Arduino.h>
|
||||
#include "Ota.h"
|
||||
#include "Logger.h"
|
||||
#include "RestartReason.h"
|
||||
|
||||
#define FULL_PACKET 1436 // HTTP_UPLOAD_BUFLEN in WebServer,h
|
||||
|
||||
void Ota::updateFirmware(uint8_t* buf, size_t size)
|
||||
{
|
||||
if(!_updateStarted && size == 0)
|
||||
{
|
||||
Log->println("OTA upload cancelled, size is 0.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_updateStarted)
|
||||
{ //If it's the first packet of OTA since bootup, begin OTA
|
||||
Log->println("BeginOTA");
|
||||
esp_ota_begin(esp_ota_get_next_update_partition(NULL), OTA_SIZE_UNKNOWN, &otaHandler);
|
||||
_updateStarted = true;
|
||||
}
|
||||
esp_ota_write(otaHandler, buf, size);
|
||||
if (size != FULL_PACKET)
|
||||
{
|
||||
esp_ota_end(otaHandler);
|
||||
Log->println("EndOTA");
|
||||
if (ESP_OK == esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)))
|
||||
{
|
||||
_updateCompleted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log->println("Upload Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Ota::updateStarted()
|
||||
{
|
||||
return _updateStarted;
|
||||
}
|
||||
|
||||
bool Ota::updateCompleted()
|
||||
{
|
||||
return _updateCompleted;
|
||||
}
|
||||
|
||||
void Ota::restart()
|
||||
{
|
||||
_updateCompleted = false;
|
||||
_updateStarted = false;
|
||||
}
|
||||
20
src/Ota.h
Normal file
20
src/Ota.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstddef>
|
||||
#include "esp_ota_ops.h"
|
||||
|
||||
class Ota
|
||||
{
|
||||
public:
|
||||
void updateFirmware(uint8_t* buf, size_t size);
|
||||
|
||||
bool updateStarted();
|
||||
bool updateCompleted();
|
||||
void restart();
|
||||
|
||||
private:
|
||||
bool _updateStarted = false;
|
||||
bool _updateCompleted = false;
|
||||
esp_ota_handle_t otaHandler = 0;
|
||||
};
|
||||
261
src/PreferencesKeys.h
Normal file
261
src/PreferencesKeys.h
Normal file
@@ -0,0 +1,261 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define preference_started_before "run"
|
||||
#define preference_config_version "confVersion"
|
||||
#define preference_device_id_lock "deviceId"
|
||||
#define preference_device_id_opener "deviceIdOp"
|
||||
#define preference_nuki_id_lock "nukiId"
|
||||
#define preference_nuki_id_opener "nukidOp"
|
||||
#define preference_mqtt_broker "mqttbroker"
|
||||
#define preference_mqtt_broker_port "mqttport"
|
||||
#define preference_mqtt_user "mqttuser"
|
||||
#define preference_mqtt_password "mqttpass"
|
||||
#define preference_mqtt_log_enabled "mqttlog"
|
||||
#define preference_lock_enabled "lockena"
|
||||
#define preference_lock_pin_status "lockpin"
|
||||
#define preference_mqtt_lock_path "mqttpath"
|
||||
#define preference_opener_enabled "openerena"
|
||||
#define preference_opener_pin_status "openerpin"
|
||||
#define preference_opener_continuous_mode "openercont"
|
||||
#define preference_mqtt_opener_path "mqttoppath"
|
||||
#define preference_check_updates "checkupdates"
|
||||
#define preference_lock_max_keypad_code_count "maxkpad"
|
||||
#define preference_opener_max_keypad_code_count "opmaxkpad"
|
||||
#define preference_mqtt_ca "mqttca"
|
||||
#define preference_mqtt_crt "mqttcrt"
|
||||
#define preference_mqtt_key "mqttkey"
|
||||
#define preference_mqtt_hass_discovery "hassdiscovery"
|
||||
#define preference_mqtt_hass_cu_url "hassConfigUrl"
|
||||
#define preference_ip_dhcp_enabled "dhcpena"
|
||||
#define preference_ip_address "ipaddr"
|
||||
#define preference_ip_subnet "ipsub"
|
||||
#define preference_ip_gateway "ipgtw"
|
||||
#define preference_ip_dns_server "dnssrv"
|
||||
#define preference_network_hardware "nwhw"
|
||||
#define preference_network_hardware_gpio "nwhwdt" // obsolete
|
||||
#define preference_network_wifi_fallback_disabled "nwwififb"
|
||||
#define preference_find_best_rssi "nwbestrssi"
|
||||
#define preference_rssi_publish_interval "rssipb"
|
||||
#define preference_hostname "hostname"
|
||||
#define preference_network_timeout "nettmout"
|
||||
#define preference_restart_on_disconnect "restdisc"
|
||||
#define preference_restart_timer "resttmr"
|
||||
#define preference_restart_ble_beacon_lost "rstbcn"
|
||||
#define preference_query_interval_lockstate "lockStInterval"
|
||||
#define preference_query_interval_configuration "configInterval"
|
||||
#define preference_query_interval_battery "batInterval"
|
||||
#define preference_query_interval_keypad "kpInterval"
|
||||
#define preference_access_level "accLvl"
|
||||
#define preference_admin_enabled "aclConfig"
|
||||
#define preference_keypad_info_enabled "kpInfoEnabled"
|
||||
#define preference_keypad_control_enabled "kpCntrlEnabled"
|
||||
#define preference_timecontrol_control_enabled "tcCntrlEnabled"
|
||||
#define preference_timecontrol_info_enabled "tcInfoEnabled"
|
||||
#define preference_publish_authdata "pubAuth"
|
||||
#define preference_acl "aclLckOpn"
|
||||
#define preference_register_as_app "regAsApp" // true = register as hub; false = register as app
|
||||
#define preference_command_nr_of_retries "nrRetry"
|
||||
#define preference_command_retry_delay "rtryDelay"
|
||||
#define preference_cred_user "crdusr"
|
||||
#define preference_cred_password "crdpass"
|
||||
#define preference_gpio_locking_enabled "gpiolck" // obsolete
|
||||
#define preference_gpio_configuration "gpiocfg"
|
||||
#define preference_publish_debug_info "pubdbg"
|
||||
#define preference_presence_detection_timeout "prdtimeout"
|
||||
#define preference_has_mac_saved "hasmac"
|
||||
#define preference_has_mac_byte_0 "macb0"
|
||||
#define preference_has_mac_byte_1 "macb1"
|
||||
#define preference_has_mac_byte_2 "macb2"
|
||||
#define preference_latest_version "latest"
|
||||
|
||||
class DebugPreferences
|
||||
{
|
||||
private:
|
||||
std::vector<char*> _keys =
|
||||
{
|
||||
preference_started_before, preference_config_version, preference_device_id_lock, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_opener, preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_check_updates,
|
||||
preference_lock_enabled, preference_lock_pin_status, preference_mqtt_lock_path, preference_opener_enabled, preference_opener_pin_status,
|
||||
preference_opener_continuous_mode, preference_mqtt_opener_path, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count,
|
||||
preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url,
|
||||
preference_ip_dhcp_enabled, preference_ip_address, preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server,
|
||||
preference_network_hardware, preference_network_wifi_fallback_disabled, preference_rssi_publish_interval,
|
||||
preference_find_best_rssi, preference_hostname, preference_network_timeout, preference_restart_on_disconnect,
|
||||
preference_restart_ble_beacon_lost, preference_query_interval_lockstate,
|
||||
preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad,
|
||||
preference_keypad_control_enabled, preference_admin_enabled, preference_keypad_info_enabled, preference_acl,
|
||||
preference_timecontrol_control_enabled, preference_timecontrol_info_enabled,
|
||||
preference_access_level, preference_register_as_app, preference_command_nr_of_retries,
|
||||
preference_command_retry_delay, preference_cred_user, preference_cred_password, preference_publish_authdata,
|
||||
preference_publish_debug_info, preference_presence_detection_timeout,
|
||||
preference_has_mac_saved, preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2, preference_latest_version,
|
||||
};
|
||||
std::vector<char*> _redact =
|
||||
{
|
||||
preference_mqtt_user, preference_mqtt_password,
|
||||
preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key,
|
||||
preference_cred_user, preference_cred_password,
|
||||
preference_nuki_id_lock, preference_nuki_id_opener,
|
||||
};
|
||||
std::vector<char*> _boolPrefs =
|
||||
{
|
||||
preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_opener_enabled, preference_opener_continuous_mode,
|
||||
preference_find_best_rssi, preference_restart_on_disconnect, preference_keypad_control_enabled, preference_admin_enabled, preference_keypad_info_enabled,
|
||||
preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_ip_dhcp_enabled,
|
||||
preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info, preference_network_wifi_fallback_disabled
|
||||
};
|
||||
|
||||
const bool isRedacted(const char* key) const
|
||||
{
|
||||
return std::find(_redact.begin(), _redact.end(), key) != _redact.end();
|
||||
}
|
||||
const String redact(const String s) const
|
||||
{
|
||||
return s == "" ? "" : "***";
|
||||
}
|
||||
const String redact(const int32_t i) const
|
||||
{
|
||||
return i == 0 ? "" : "***";
|
||||
}
|
||||
const String redact(const uint32_t i) const
|
||||
{
|
||||
return i == 0 ? "" : "***";
|
||||
}
|
||||
const String redact(const int64_t i) const
|
||||
{
|
||||
return i == 0 ? "" : "***";
|
||||
}
|
||||
const String redact(const uint64_t i) const
|
||||
{
|
||||
return i == 0 ? "" : "***";
|
||||
}
|
||||
|
||||
const void appendPreferenceInt8(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const int32_t)preferences->getChar(key)) : String(preferences->getChar(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceUInt8(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const uint32_t)preferences->getUChar(key)) : String(preferences->getUChar(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceInt16(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const int32_t)preferences->getShort(key)) : String(preferences->getShort(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceUInt16(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const uint32_t)preferences->getUShort(key)) : String(preferences->getUShort(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceInt32(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const int32_t)preferences->getInt(key)) : String(preferences->getInt(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceUInt32(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const uint32_t)preferences->getUInt(key)) : String(preferences->getUInt(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceInt64(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const int64_t)preferences->getLong64(key)) : String(preferences->getLong64(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceUInt64(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const uint64_t)preferences->getULong64(key)) : String(preferences->getULong64(key)));
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceBool(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(preferences->getBool(key) ? "true" : "false");
|
||||
s.concat("\n");
|
||||
}
|
||||
const void appendPreferenceString(Preferences *preferences, String& s, const char* description, const char* key)
|
||||
{
|
||||
s.concat(description);
|
||||
s.concat(": ");
|
||||
s.concat(isRedacted(key) ? redact((const String)preferences->getString(key)) : preferences->getString(key));
|
||||
s.concat("\n");
|
||||
}
|
||||
|
||||
const void appendPreference(Preferences *preferences, String& s, const char* key)
|
||||
{
|
||||
if(std::find(_boolPrefs.begin(), _boolPrefs.end(), key) != _boolPrefs.end())
|
||||
{
|
||||
appendPreferenceBool(preferences, s, key, key);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(preferences->getType(key))
|
||||
{
|
||||
case PT_I8:
|
||||
appendPreferenceInt8(preferences, s, key, key);
|
||||
break;
|
||||
case PT_I16:
|
||||
appendPreferenceInt16(preferences, s, key, key);
|
||||
break;
|
||||
case PT_I32:
|
||||
appendPreferenceInt32(preferences, s, key, key);
|
||||
break;
|
||||
case PT_I64:
|
||||
appendPreferenceInt64(preferences, s, key, key);
|
||||
break;
|
||||
case PT_U8:
|
||||
appendPreferenceUInt8(preferences, s, key, key);
|
||||
break;
|
||||
case PT_U16:
|
||||
appendPreferenceUInt16(preferences, s, key, key);
|
||||
break;
|
||||
case PT_U32:
|
||||
appendPreferenceUInt32(preferences, s, key, key);
|
||||
break;
|
||||
case PT_U64:
|
||||
appendPreferenceUInt64(preferences, s, key, key);
|
||||
break;
|
||||
case PT_STR:
|
||||
appendPreferenceString(preferences, s, key, key);
|
||||
break;
|
||||
default:
|
||||
appendPreferenceString(preferences, s, key, key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
const String preferencesToString(Preferences *preferences)
|
||||
{
|
||||
String s = "";
|
||||
|
||||
for(const auto& key : _keys)
|
||||
{
|
||||
appendPreference(preferences, s, key);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
};
|
||||
223
src/PresenceDetection.cpp
Normal file
223
src/PresenceDetection.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "PresenceDetection.h"
|
||||
#include "PreferencesKeys.h"
|
||||
#include "Logger.h"
|
||||
#include "CharBuffer.h"
|
||||
#include <NimBLEDevice.h>
|
||||
#include <NimBLEAdvertisedDevice.h>
|
||||
#include "NimBLEBeacon.h"
|
||||
#include "NukiUtils.h"
|
||||
|
||||
PresenceDetection::PresenceDetection(Preferences* preferences, BleScanner::Scanner *bleScanner, Network* network, char* buffer, size_t bufferSize)
|
||||
: _preferences(preferences),
|
||||
_bleScanner(bleScanner),
|
||||
_network(network),
|
||||
_csv(buffer),
|
||||
_bufferSize(bufferSize)
|
||||
{
|
||||
_timeout = _preferences->getInt(preference_presence_detection_timeout) * 1000;
|
||||
if(_timeout == 0)
|
||||
{
|
||||
_timeout = 60000;
|
||||
_preferences->putInt(preference_presence_detection_timeout, 60);
|
||||
}
|
||||
|
||||
Log->print(F("Presence detection timeout (ms): "));
|
||||
Log->println(_timeout);
|
||||
}
|
||||
|
||||
PresenceDetection::~PresenceDetection()
|
||||
{
|
||||
_bleScanner->unsubscribe(this);
|
||||
_bleScanner = nullptr;
|
||||
|
||||
_network = nullptr;
|
||||
|
||||
delete _csv;
|
||||
_csv = nullptr;
|
||||
}
|
||||
|
||||
void PresenceDetection::initialize()
|
||||
{
|
||||
_bleScanner->subscribe(this);
|
||||
}
|
||||
|
||||
void PresenceDetection::update()
|
||||
{
|
||||
delay(3000);
|
||||
|
||||
if(_timeout < 0) return;
|
||||
memset(_csv, 0, _bufferSize);
|
||||
|
||||
if(_devices.size() == 0)
|
||||
{
|
||||
strcpy(_csv, ";;");
|
||||
_network->publishPresenceDetection(_csv);
|
||||
return;
|
||||
}
|
||||
|
||||
_csvIndex = 0;
|
||||
long ts = millis();
|
||||
for(auto it : _devices)
|
||||
{
|
||||
if(ts - _timeout < it.second.timestamp)
|
||||
{
|
||||
buildCsv(it.second);
|
||||
}
|
||||
|
||||
// Prevent csv buffer overflow
|
||||
if(_csvIndex > _bufferSize - (sizeof(it.second.name) + sizeof(it.second.address) + 10))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_csv[_csvIndex-1] = 0x00;
|
||||
|
||||
// Log->print("Devices found: ");
|
||||
// Log->println(_devices.size());
|
||||
_network->publishPresenceDetection(_csv);
|
||||
}
|
||||
|
||||
|
||||
void PresenceDetection::buildCsv(const PdDevice &device)
|
||||
{
|
||||
for(int i = 0; i < 17; i++)
|
||||
{
|
||||
_csv[_csvIndex] = device.address[i];
|
||||
++_csvIndex;
|
||||
}
|
||||
_csv[_csvIndex] = ';';
|
||||
++_csvIndex;
|
||||
|
||||
int i=0;
|
||||
while(device.name[i] != 0x00 && i < sizeof(device.name))
|
||||
{
|
||||
_csv[_csvIndex] = device.name[i];
|
||||
++_csvIndex;
|
||||
++i;
|
||||
}
|
||||
|
||||
_csv[_csvIndex] = ';';
|
||||
++_csvIndex;
|
||||
|
||||
if(device.hasRssi)
|
||||
{
|
||||
char rssiStr[20] = {0};
|
||||
itoa(device.rssi, rssiStr, 10);
|
||||
|
||||
int i=0;
|
||||
while(rssiStr[i] != 0x00 && i < 20)
|
||||
{
|
||||
_csv[_csvIndex] = rssiStr[i];
|
||||
++_csvIndex;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
_csv[_csvIndex] = '\n';
|
||||
_csvIndex++;
|
||||
}
|
||||
|
||||
void PresenceDetection::onResult(NimBLEAdvertisedDevice *device)
|
||||
{
|
||||
std::string addressStr = device->getAddress().toString();
|
||||
char addrArrComp[13] = {0};
|
||||
|
||||
// Log->println(addressStr.c_str());
|
||||
|
||||
addrArrComp[0] = addressStr.at(0);
|
||||
addrArrComp[1] = addressStr.at(1);
|
||||
addrArrComp[2] = addressStr.at(3);
|
||||
addrArrComp[3] = addressStr.at(4);
|
||||
addrArrComp[4] = addressStr.at(6);
|
||||
addrArrComp[5] = addressStr.at(7);
|
||||
addrArrComp[6] = addressStr.at(9);
|
||||
addrArrComp[7] = addressStr.at(10);
|
||||
addrArrComp[8] = addressStr.at(12);
|
||||
addrArrComp[9] = addressStr.at(13);
|
||||
addrArrComp[10] = addressStr.at(15);
|
||||
addrArrComp[11] = addressStr.at(16);
|
||||
|
||||
long long addr = strtoll(addrArrComp, nullptr, 16);
|
||||
|
||||
auto it = _devices.find(addr);
|
||||
if(it == _devices.end())
|
||||
{
|
||||
|
||||
PdDevice pdDevice;
|
||||
|
||||
int i=0;
|
||||
size_t len = addressStr.length();
|
||||
while(i < len)
|
||||
{
|
||||
pdDevice.address[i] = addressStr.at(i);
|
||||
++i;
|
||||
}
|
||||
|
||||
if(device->haveRSSI())
|
||||
{
|
||||
pdDevice.hasRssi = true;
|
||||
pdDevice.rssi = device->getRSSI();
|
||||
}
|
||||
|
||||
std::string nameStr = "-";
|
||||
if(device->haveName())
|
||||
{
|
||||
std::string nameStr = device->getName();
|
||||
|
||||
i=0;
|
||||
len = nameStr.length();
|
||||
while(i < len && i < sizeof(pdDevice.name)-1)
|
||||
{
|
||||
pdDevice.name[i] = nameStr.at(i);
|
||||
++i;
|
||||
}
|
||||
|
||||
pdDevice.timestamp = millis();
|
||||
|
||||
_devices[addr] = pdDevice;
|
||||
}
|
||||
else if (device->haveManufacturerData())
|
||||
{
|
||||
std::string strManufacturerData = device->getManufacturerData();
|
||||
|
||||
uint8_t cManufacturerData[100];
|
||||
strManufacturerData.copy((char *)cManufacturerData, std::min(strManufacturerData.length(), sizeof(cManufacturerData)), 0);
|
||||
|
||||
if (strManufacturerData.length() == 25 && cManufacturerData[0] == 0x4C && cManufacturerData[1] == 0x00)
|
||||
{
|
||||
BLEBeacon oBeacon = BLEBeacon();
|
||||
oBeacon.setData(strManufacturerData);
|
||||
|
||||
if(ENDIAN_CHANGE_U16(oBeacon.getMinor()) == 40004)
|
||||
{
|
||||
pdDevice.timestamp = millis();
|
||||
strcpy(pdDevice.name, oBeacon.getProximityUUID().toString().c_str());
|
||||
_devices[addr] = pdDevice;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second.timestamp = millis();
|
||||
if(device->haveRSSI())
|
||||
{
|
||||
it->second.hasRssi = true;
|
||||
it->second.rssi = device->getRSSI();
|
||||
}
|
||||
}
|
||||
|
||||
// if(device->haveName())
|
||||
// {
|
||||
// Log->print(" | ");
|
||||
// Log->print(device->getName().c_str());
|
||||
// if(device->haveRSSI())
|
||||
// {
|
||||
// Log->print(" | ");
|
||||
// Log->print(device->getRSSI());
|
||||
// }
|
||||
// }
|
||||
// Log->println();
|
||||
|
||||
}
|
||||
38
src/PresenceDetection.h
Normal file
38
src/PresenceDetection.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "BleScanner.h"
|
||||
#include "BleInterfaces.h"
|
||||
#include "Network.h"
|
||||
|
||||
struct PdDevice
|
||||
{
|
||||
char address[18] = {0};
|
||||
char name[37] = {0};
|
||||
unsigned long timestamp = 0;
|
||||
int rssi = 0;
|
||||
bool hasRssi = false;
|
||||
};
|
||||
|
||||
class PresenceDetection : public BleScanner::Subscriber
|
||||
{
|
||||
public:
|
||||
PresenceDetection(Preferences* preferences, BleScanner::Scanner* bleScanner, Network* network, char* buffer, size_t bufferSize);
|
||||
virtual ~PresenceDetection();
|
||||
|
||||
void initialize();
|
||||
void update();
|
||||
|
||||
void onResult(NimBLEAdvertisedDevice* advertisedDevice) override;
|
||||
|
||||
private:
|
||||
void buildCsv(const PdDevice& device);
|
||||
|
||||
Preferences* _preferences;
|
||||
BleScanner::Scanner* _bleScanner;
|
||||
Network* _network;
|
||||
char* _csv = {0};
|
||||
size_t _bufferSize = 0;
|
||||
std::map<long long, PdDevice> _devices;
|
||||
int _timeout = 20000;
|
||||
int _csvIndex = 0;
|
||||
};
|
||||
6
src/QueryCommand.h
Normal file
6
src/QueryCommand.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#define QUERY_COMMAND_LOCKSTATE 1
|
||||
#define QUERY_COMMAND_CONFIG 2
|
||||
#define QUERY_COMMAND_KEYPAD 4
|
||||
#define QUERY_COMMAND_BATTERY 8
|
||||
147
src/RestartReason.h
Normal file
147
src/RestartReason.h
Normal file
@@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
enum class RestartReason
|
||||
{
|
||||
RequestedViaMqtt,
|
||||
BLEBeaconWatchdog,
|
||||
RestartOnDisconnectWatchdog,
|
||||
RestartIntervalWatchdog,
|
||||
NetworkTimeoutWatchdog,
|
||||
WifiInitFailed,
|
||||
ReconfigureWifi,
|
||||
ReconfigureLAN8720,
|
||||
NetworkDeviceCriticalFailure,
|
||||
NetworkDeviceCriticalFailureNoWifiFallback,
|
||||
ConfigurationUpdated,
|
||||
GpioConfigurationUpdated,
|
||||
RestartTimer,
|
||||
OTACompleted,
|
||||
OTATimeout,
|
||||
OTAAborted,
|
||||
OTAUnknownState,
|
||||
DeviceUnpaired,
|
||||
NotApplicable
|
||||
};
|
||||
|
||||
#define RESTART_REASON_VALID_DETECT 0xa00ab00bc00bd00d;
|
||||
|
||||
extern int restartReason;
|
||||
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;
|
||||
restartReasonValidDetect = RESTART_REASON_VALID_DETECT;
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
inline static void initializeRestartReason()
|
||||
{
|
||||
uint64_t cmp = RESTART_REASON_VALID_DETECT;
|
||||
restartReason_isValid = (restartReasonValidDetect == cmp);
|
||||
if(restartReason_isValid)
|
||||
{
|
||||
currentRestartReason = (RestartReason)restartReason;
|
||||
memset(&restartReasonValidDetect, 0, sizeof(restartReasonValidDetect));
|
||||
}
|
||||
else
|
||||
{
|
||||
rebuildGpioRequested = false;
|
||||
}
|
||||
}
|
||||
|
||||
inline static String getRestartReason()
|
||||
{
|
||||
switch(currentRestartReason)
|
||||
{
|
||||
case RestartReason::RequestedViaMqtt:
|
||||
return "RequestedViaMqtt";
|
||||
case RestartReason::BLEBeaconWatchdog:
|
||||
return "BLEBeaconWatchdog";
|
||||
case RestartReason::RestartOnDisconnectWatchdog:
|
||||
return "RestartOnDisconnectWatchdog";
|
||||
case RestartReason::RestartIntervalWatchdog:
|
||||
return "RestartIntervalWatchdog";
|
||||
case RestartReason::NetworkTimeoutWatchdog:
|
||||
return "NetworkTimeoutWatchdog";
|
||||
case RestartReason::WifiInitFailed:
|
||||
return "WifiInitFailed";
|
||||
case RestartReason::ReconfigureWifi:
|
||||
return "ReconfigureWifi";
|
||||
case RestartReason::ReconfigureLAN8720:
|
||||
return "ReconfigureLAN8720";
|
||||
case RestartReason::NetworkDeviceCriticalFailure:
|
||||
return "NetworkDeviceCriticalFailure";
|
||||
case RestartReason::NetworkDeviceCriticalFailureNoWifiFallback:
|
||||
return "NetworkDeviceCriticalFailureNoWifiFallback";
|
||||
case RestartReason::ConfigurationUpdated:
|
||||
return "ConfigurationUpdated";
|
||||
case RestartReason::GpioConfigurationUpdated:
|
||||
return "GpioConfigurationUpdated";
|
||||
case RestartReason::RestartTimer:
|
||||
return "RestartTimer";
|
||||
case RestartReason::OTACompleted:
|
||||
return "OTACompleted";
|
||||
case RestartReason::OTATimeout:
|
||||
return "OTATimeout";
|
||||
case RestartReason::OTAAborted:
|
||||
return "OTAAborted";
|
||||
case RestartReason::OTAUnknownState:
|
||||
return "OTAUnknownState";
|
||||
case RestartReason::DeviceUnpaired:
|
||||
return "DeviceUnpaired";
|
||||
case RestartReason::NotApplicable:
|
||||
return "NotApplicable";
|
||||
default:
|
||||
return "Unknown: " + restartReason;
|
||||
}
|
||||
}
|
||||
|
||||
inline static String getEspRestartReason()
|
||||
{
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
switch(reason)
|
||||
{
|
||||
case esp_reset_reason_t::ESP_RST_UNKNOWN:
|
||||
return "ESP_RST_UNKNOWN: Reset reason can not be determined.";
|
||||
case esp_reset_reason_t::ESP_RST_POWERON:
|
||||
return "ESP_RST_POWERON: Reset due to power-on event.";
|
||||
case esp_reset_reason_t::ESP_RST_EXT:
|
||||
return "ESP_RST_EXT: Reset by external pin";
|
||||
case esp_reset_reason_t::ESP_RST_SW:
|
||||
return "ESP_RST_SW: Software reset via esp_restart.";
|
||||
case esp_reset_reason_t::ESP_RST_PANIC:
|
||||
return "ESP_RST_PANIC: Software reset due to exception/panic.";
|
||||
case esp_reset_reason_t::ESP_RST_INT_WDT:
|
||||
return "ESP_RST_INT_WDT: Reset (software or hardware) due to interrupt watchdog";
|
||||
case esp_reset_reason_t::ESP_RST_TASK_WDT:
|
||||
return "ESP_RST_TASK_WDT: Reset due to task watchdog.";
|
||||
case esp_reset_reason_t::ESP_RST_WDT:
|
||||
return "ESP_RST_WDT: Reset due to other watchdogs.";
|
||||
case esp_reset_reason_t::ESP_RST_DEEPSLEEP:
|
||||
return "ESP_RST_DEEPSLEEP: Reset after exiting deep sleep mode.";
|
||||
case esp_reset_reason_t::ESP_RST_BROWNOUT:
|
||||
return "ESP_RST_BROWNOUT: Brownout reset (software or hardware)";
|
||||
case esp_reset_reason_t::ESP_RST_SDIO:
|
||||
return "ESP_RST_SDIO: Reset over SDIO.";
|
||||
default:
|
||||
return "Unknown: " + (int)reason;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool rebuildGpio()
|
||||
{
|
||||
bool rebGpio = rebuildGpioRequested;
|
||||
rebuildGpioRequested = false;
|
||||
return restartReason_isValid && rebGpio;
|
||||
}
|
||||
1615
src/WebCfgServer.cpp
Normal file
1615
src/WebCfgServer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
93
src/WebCfgServer.h
Normal file
93
src/WebCfgServer.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <Preferences.h>
|
||||
#include <WebServer.h>
|
||||
#include "NukiWrapper.h"
|
||||
#include "NetworkLock.h"
|
||||
#include "NukiOpenerWrapper.h"
|
||||
#include "Ota.h"
|
||||
#include "Gpio.h"
|
||||
|
||||
extern TaskHandle_t networkTaskHandle;
|
||||
extern TaskHandle_t nukiTaskHandle;
|
||||
extern TaskHandle_t presenceDetectionTaskHandle;
|
||||
|
||||
enum class TokenType
|
||||
{
|
||||
None,
|
||||
MqttServer,
|
||||
MqttPort,
|
||||
MqttUser,
|
||||
MqttPass,
|
||||
MqttPath,
|
||||
QueryIntervalLockstate,
|
||||
QueryIntervalBattery,
|
||||
};
|
||||
|
||||
class WebCfgServer
|
||||
{
|
||||
public:
|
||||
WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, Network* network, Gpio* gpio, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal);
|
||||
~WebCfgServer() = default;
|
||||
|
||||
void initialize();
|
||||
void update();
|
||||
|
||||
private:
|
||||
bool processArgs(String& message);
|
||||
void processGpioArgs();
|
||||
void buildHtml(String& response);
|
||||
void buildAccLvlHtml(String& response);
|
||||
void buildCredHtml(String& response);
|
||||
void buildOtaHtml(String& response, bool errored);
|
||||
void buildOtaCompletedHtml(String& response);
|
||||
void buildMqttConfigHtml(String& response);
|
||||
void buildNukiConfigHtml(String& response);
|
||||
void buildGpioConfigHtml(String& response);
|
||||
void buildConfirmHtml(String& response, const String &message, uint32_t redirectDelay = 5);
|
||||
void buildConfigureWifiHtml(String& response);
|
||||
void buildInfoHtml(String& response);
|
||||
void sendCss();
|
||||
void sendFavicon();
|
||||
void processUnpair(bool opener);
|
||||
|
||||
void buildHtmlHeader(String& response);
|
||||
void printInputField(String& response, const char* token, const char* description, const char* value, const size_t& maxLength, const bool& isPassword = false, const bool& showLengthRestriction = false);
|
||||
void printInputField(String& response, const char* token, const char* description, const int value, size_t maxLength);
|
||||
void printCheckBox(String& response, const char* token, const char* description, const bool value);
|
||||
void printTextarea(String& response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false);
|
||||
void printDropDown(String &response, const char *token, const char *description, const String preselectedValue, std::vector<std::pair<String, String>> options);
|
||||
void buildNavigationButton(String& response, const char* caption, const char* targetPath, const char* labelText = "");
|
||||
|
||||
const std::vector<std::pair<String, String>> getNetworkDetectionOptions() const;
|
||||
const std::vector<std::pair<String, String>> getGpioOptions() const;
|
||||
String getPreselectionForGpio(const uint8_t& pin);
|
||||
|
||||
void printParameter(String& response, const char* description, const char* value, const char *link = "");
|
||||
|
||||
String generateConfirmCode();
|
||||
void waitAndProcess(const bool blocking, const uint32_t duration);
|
||||
void handleOtaUpload();
|
||||
|
||||
WebServer _server;
|
||||
NukiWrapper* _nuki = nullptr;
|
||||
NukiOpenerWrapper* _nukiOpener = nullptr;
|
||||
Network* _network = nullptr;
|
||||
Gpio* _gpio = nullptr;
|
||||
Preferences* _preferences = nullptr;
|
||||
Ota _ota;
|
||||
|
||||
bool _hasCredentials = false;
|
||||
char _credUser[31] = {0};
|
||||
char _credPassword[31] = {0};
|
||||
bool _allowRestartToPortal = false;
|
||||
bool _pinsConfigured = false;
|
||||
bool _brokerConfigured = false;
|
||||
uint32_t _transferredSize = 0;
|
||||
unsigned long _otaStartTs = 0;
|
||||
String _hostname;
|
||||
|
||||
String _confirmCode = "----";
|
||||
|
||||
bool _enabled = true;
|
||||
};
|
||||
62
src/WebCfgServerConstants.h
Normal file
62
src/WebCfgServerConstants.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
// escaped by https://www.cescaper.com/
|
||||
// source: https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css
|
||||
|
||||
const char stylecss[] = ":root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047}@media (prefers-color-scheme:dark){:root{--nc-tx-1:#ffffff;--nc-tx-2:#eeeeee;--nc-bg-1:#000000;--nc-bg-2:#111111;--nc-bg-3:#222222;--nc-lk-1:#3291FF;--nc-lk-2:#0070F3;--nc-lk-tx:#FFFFFF;--nc-ac-1:#7928CA;--nc-ac-tx:#FFFFFF}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:normal;overflow-wrap:anywhere;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr:hover{cursor:help}blockquote{padding:1.5rem;background:var(--nc-bg-2);border-left:5px solid var(--nc-bg-3)}abbr{cursor:help}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{background:var(--nc-bg-2);border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(0px - (50vw - 50%)) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}header>:last-child{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5;cursor:not-allowed}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background:var(--nc-lk-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono)}code,kbd,pre,samp{background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9rem}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto}pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}code pre{display:inline;background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details{padding:.6rem 1rem;background:var(--nc-bg-2);border:1px solid var(--nc-bg-3);border-radius:4px}summary{cursor:pointer;font-weight:700}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}details[open]>:last-child{margin-bottom:0}dt{font-weight:700}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border:1px solid var(--nc-bg-3);border-radius:4px}legend{padding:0.5rem}table{border-collapse:collapse;width:100%}td,th{border:1px solid var(--nc-bg-3);text-align:left;padding:.5rem}th{background:var(--nc-bg-2)}tr:nth-child(even){background:var(--nc-bg-2)}table caption{font-weight:700;margin-bottom:.5rem}textarea{max-width:100%}ol,ul{padding-left:2rem}li{margin-top:.4rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;background:var(--nc-bg-2);color:var(--nc-tx-2);border:1px solid var(--nc-bg-3);border-radius:4px;box-shadow:none;box-sizing:border-box}img{max-width:100%}td>input{margin-top:0px;margin-bottom:0px}td>textarea{margin-top:0px;margin-bottom:0px}td>select{margin-top:0px;margin-bottom:0px}#tblnav td,th{border:0;border-bottom:1px solid;}.tdbtn{text-align:center;vertical-align: middle;}";
|
||||
|
||||
// converted to char array by https://notisrac.github.io/FileToCArray/
|
||||
const unsigned char favicon_32x32[] = {
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7a, 0x7a,
|
||||
0xf4, 0x00, 0x00, 0x02, 0xfb, 0x49, 0x44, 0x41, 0x54, 0x58, 0x47, 0xcd, 0x97, 0x4b, 0x4c, 0x53,
|
||||
0x41, 0x14, 0x86, 0xff, 0x69, 0xa5, 0x3c, 0x8a, 0x12, 0x14, 0x84, 0xa6, 0x96, 0x60, 0x6d, 0xd0,
|
||||
0x50, 0x14, 0x51, 0xd1, 0x6a, 0x4c, 0x51, 0x63, 0x88, 0x8a, 0x06, 0x63, 0x42, 0xc0, 0x85, 0x01,
|
||||
0xc3, 0xae, 0xe8, 0x0a, 0x0d, 0x4a, 0x30, 0x31, 0x18, 0xd1, 0x84, 0x18, 0x83, 0x46, 0x5d, 0xa9,
|
||||
0x09, 0xb8, 0x01, 0xa2, 0x31, 0x82, 0x31, 0x82, 0x6e, 0x48, 0x78, 0xb6, 0x74, 0xe1, 0x8b, 0x47,
|
||||
0xb0, 0x3e, 0x0a, 0xb4, 0x05, 0xa1, 0xe1, 0x25, 0x14, 0x68, 0xeb, 0x9d, 0x22, 0xb7, 0x40, 0x1f,
|
||||
0x1b, 0xef, 0xa5, 0xce, 0xea, 0xde, 0x99, 0xce, 0x39, 0xdf, 0xfc, 0x7f, 0x67, 0xee, 0x1c, 0xe2,
|
||||
0xbc, 0x0e, 0x81, 0xe3, 0x84, 0xba, 0x58, 0x40, 0x88, 0xc6, 0x09, 0x48, 0xb0, 0x0a, 0x8d, 0x00,
|
||||
0x26, 0x07, 0x71, 0x3e, 0x14, 0xd4, 0x37, 0x95, 0x11, 0x7b, 0x87, 0xba, 0x84, 0x10, 0x72, 0x63,
|
||||
0x15, 0xf2, 0x7a, 0xa4, 0x70, 0x12, 0xe7, 0x35, 0xe2, 0xe8, 0x48, 0x33, 0x81, 0x20, 0x36, 0x10,
|
||||
0x00, 0x4c, 0x4e, 0x33, 0x71, 0x68, 0xd3, 0x18, 0xe5, 0x03, 0xd7, 0xfe, 0x6f, 0x80, 0x86, 0xb6,
|
||||
0x51, 0xd4, 0xbe, 0x1b, 0x66, 0xe5, 0xd1, 0x64, 0x49, 0x91, 0xb2, 0x35, 0xdc, 0xaf, 0x5c, 0x25,
|
||||
0x8f, 0xbe, 0xc1, 0x36, 0xeb, 0x60, 0x7f, 0x93, 0x79, 0x28, 0x0a, 0x07, 0x93, 0x23, 0x7c, 0xce,
|
||||
0xf1, 0xab, 0xc0, 0x9d, 0x67, 0x46, 0x5c, 0xae, 0x30, 0xb0, 0x93, 0x93, 0xb6, 0x88, 0xa1, 0xad,
|
||||
0xdc, 0x85, 0x60, 0x91, 0xc0, 0x67, 0xc0, 0xc8, 0xc3, 0xcd, 0x18, 0x9b, 0x9c, 0x67, 0xc7, 0x2b,
|
||||
0x2e, 0x29, 0x70, 0x31, 0x5b, 0xca, 0x0d, 0x00, 0x8d, 0x72, 0xf5, 0x7c, 0x1c, 0x6e, 0x6a, 0x36,
|
||||
0xfb, 0x0c, 0xb8, 0xe1, 0x48, 0x33, 0xac, 0x13, 0x3c, 0x02, 0xac, 0x11, 0x12, 0xb4, 0x3c, 0x49,
|
||||
0xc1, 0x9e, 0xc4, 0xb5, 0x5e, 0x21, 0xa2, 0x8f, 0xb6, 0x60, 0x64, 0x6c, 0x8e, 0x3f, 0x05, 0x68,
|
||||
0x64, 0xa5, 0x5c, 0x0c, 0x5d, 0x95, 0x77, 0x2b, 0x62, 0xd2, 0x5b, 0x30, 0x6c, 0xe5, 0x19, 0xc0,
|
||||
0x9f, 0x15, 0x92, 0x63, 0xad, 0xb0, 0x8c, 0xcc, 0xf2, 0xab, 0x00, 0x8d, 0x4e, 0xad, 0x68, 0x66,
|
||||
0xac, 0x48, 0x5d, 0x61, 0xc5, 0xa6, 0xe3, 0xad, 0x18, 0xfc, 0xc5, 0x13, 0xc0, 0x3a, 0xb1, 0x10,
|
||||
0xe3, 0x53, 0x76, 0x76, 0x75, 0x4a, 0x79, 0x18, 0x63, 0xc5, 0xee, 0x65, 0xbb, 0x22, 0x2e, 0xa3,
|
||||
0x0d, 0xfd, 0x43, 0x36, 0x7e, 0x14, 0x50, 0xa7, 0x44, 0xb8, 0x92, 0x35, 0xb6, 0x5b, 0xd9, 0x04,
|
||||
0x57, 0xf2, 0x64, 0x28, 0x2b, 0x90, 0xb3, 0xef, 0xf1, 0xa7, 0xda, 0xf0, 0xd3, 0xcc, 0x13, 0x00,
|
||||
0x3d, 0x84, 0x6a, 0x6e, 0x27, 0x62, 0x47, 0x8e, 0x0e, 0xd3, 0xb6, 0x85, 0xc3, 0xc6, 0x65, 0xc5,
|
||||
0x63, 0xc6, 0x0a, 0xe5, 0xc2, 0xae, 0x90, 0x67, 0xb6, 0xe3, 0xfb, 0xe0, 0x0c, 0x3f, 0x0a, 0x50,
|
||||
0xc9, 0x3f, 0x56, 0xa7, 0xa2, 0xbc, 0xd2, 0x88, 0xa2, 0xfb, 0xee, 0x03, 0x2a, 0xf1, 0xaf, 0x15,
|
||||
0x21, 0x8c, 0x3a, 0x8a, 0xd3, 0xed, 0x30, 0x0c, 0xf0, 0x04, 0xb0, 0x2d, 0x3e, 0x0c, 0x5f, 0x6a,
|
||||
0x53, 0x31, 0x6f, 0x77, 0x42, 0x95, 0xab, 0x87, 0xbe, 0x67, 0x92, 0x5d, 0x69, 0x51, 0xae, 0x0c,
|
||||
0xb7, 0x2e, 0xc8, 0x91, 0x70, 0xa6, 0x03, 0x7d, 0xc6, 0x69, 0x7e, 0x14, 0x48, 0x88, 0x0b, 0x45,
|
||||
0xf7, 0xf3, 0xbd, 0xae, 0xe0, 0xfa, 0xee, 0x49, 0xa8, 0xf2, 0xf4, 0x2e, 0x18, 0xda, 0x84, 0x02,
|
||||
0xba, 0x2b, 0x76, 0x22, 0xbf, 0xb4, 0x17, 0x9f, 0x0d, 0x53, 0xfc, 0x00, 0x28, 0x64, 0xa1, 0xe8,
|
||||
0x7d, 0xb1, 0x00, 0x40, 0x5b, 0xd1, 0x3d, 0x03, 0xca, 0xab, 0x8c, 0xec, 0xfb, 0x76, 0x85, 0x18,
|
||||
0xa2, 0x20, 0x01, 0x3a, 0xbb, 0x26, 0xf8, 0x01, 0x90, 0x4b, 0x43, 0xd0, 0xf7, 0x72, 0x1f, 0x1b,
|
||||
0xfc, 0xf7, 0x8c, 0x1d, 0xc9, 0x67, 0x3b, 0xf1, 0xb5, 0xdf, 0x2d, 0x39, 0x23, 0x04, 0x1c, 0x4b,
|
||||
0x6e, 0x18, 0x9c, 0x7e, 0x8c, 0xe2, 0x25, 0x21, 0x30, 0xbc, 0x72, 0x03, 0x50, 0x92, 0xf7, 0x5a,
|
||||
0x2b, 0xd2, 0x35, 0x1f, 0xe0, 0xeb, 0x56, 0xc3, 0x29, 0x80, 0x2c, 0x36, 0x18, 0x3f, 0xea, 0x54,
|
||||
0xac, 0x02, 0x8b, 0x0f, 0xf9, 0xa5, 0x3d, 0x78, 0x5a, 0x67, 0xf6, 0xe8, 0xa7, 0x1d, 0x9c, 0x02,
|
||||
0x48, 0x37, 0x06, 0xc3, 0xf8, 0xda, 0x13, 0x60, 0x74, 0x7c, 0x0e, 0xca, 0x2c, 0x1d, 0x2c, 0xa3,
|
||||
0xee, 0x23, 0x78, 0x91, 0x86, 0x53, 0x00, 0x49, 0x94, 0x08, 0x03, 0x6f, 0xf6, 0x7b, 0x5d, 0x69,
|
||||
0x4d, 0xe3, 0x10, 0x72, 0x8a, 0xbb, 0x3c, 0xc6, 0x38, 0x05, 0x88, 0x59, 0x2f, 0x82, 0xe9, 0xad,
|
||||
0x77, 0x00, 0x9a, 0x39, 0xb3, 0xf0, 0x13, 0xea, 0x9a, 0x46, 0x96, 0x41, 0x70, 0x0a, 0x10, 0x1d,
|
||||
0x19, 0x04, 0x4b, 0xc3, 0x01, 0xaf, 0x0a, 0xd0, 0xce, 0x7e, 0x8b, 0x0d, 0x49, 0xd9, 0xda, 0x65,
|
||||
0x1f, 0xac, 0x7f, 0x02, 0x58, 0x79, 0x29, 0x0d, 0x0f, 0x15, 0xe2, 0x6e, 0xa1, 0xc2, 0x27, 0x00,
|
||||
0x1d, 0xa8, 0x66, 0xac, 0xa8, 0x5f, 0xa2, 0xc2, 0xb9, 0x8c, 0x58, 0xa4, 0xab, 0x22, 0x7d, 0xce,
|
||||
0xf9, 0xbf, 0xaf, 0xe5, 0x7e, 0x97, 0xca, 0xd1, 0x20, 0x55, 0xc0, 0xc4, 0xc4, 0x0a, 0x5c, 0x69,
|
||||
0x66, 0xd7, 0x31, 0xc5, 0xa9, 0x33, 0x30, 0xc5, 0x29, 0xa1, 0xc5, 0xa9, 0xab, 0x3c, 0x3f, 0xa9,
|
||||
0x2e, 0x66, 0x20, 0x0a, 0x56, 0x51, 0x09, 0x33, 0x93, 0xfc, 0x01, 0xf3, 0x6f, 0x2d, 0xfb, 0x03,
|
||||
0xed, 0x06, 0xb0, 0xce, 0xb5, 0xc4, 0xb4, 0x59, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
|
||||
0xae, 0x42, 0x60, 0x82
|
||||
};
|
||||
301
src/main.cpp
Normal file
301
src/main.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
#include "Arduino.h"
|
||||
#include "NukiWrapper.h"
|
||||
#include "NetworkLock.h"
|
||||
#include "WebCfgServer.h"
|
||||
#include <RTOS.h>
|
||||
#include "PreferencesKeys.h"
|
||||
#include "PresenceDetection.h"
|
||||
#include "hardware/W5500EthServer.h"
|
||||
#include "hardware/WifiEthServer.h"
|
||||
#include "NukiOpenerWrapper.h"
|
||||
#include "Gpio.h"
|
||||
#include "Logger.h"
|
||||
#include "Config.h"
|
||||
#include "RestartReason.h"
|
||||
#include "CharBuffer.h"
|
||||
#include "NukiDeviceId.h"
|
||||
|
||||
Network* network = nullptr;
|
||||
NetworkLock* networkLock = nullptr;
|
||||
NetworkOpener* networkOpener = nullptr;
|
||||
WebCfgServer* webCfgServer = nullptr;
|
||||
BleScanner::Scanner* bleScanner = nullptr;
|
||||
NukiWrapper* nuki = nullptr;
|
||||
NukiOpenerWrapper* nukiOpener = nullptr;
|
||||
PresenceDetection* presenceDetection = nullptr;
|
||||
NukiDeviceId* deviceIdLock = nullptr;
|
||||
NukiDeviceId* deviceIdOpener = nullptr;
|
||||
Preferences* preferences = nullptr;
|
||||
EthServer* ethServer = nullptr;
|
||||
Gpio* gpio = nullptr;
|
||||
|
||||
bool lockEnabled = false;
|
||||
bool openerEnabled = false;
|
||||
unsigned long restartTs = (2^32) - 5 * 60000;
|
||||
|
||||
RTC_NOINIT_ATTR int restartReason;
|
||||
RTC_NOINIT_ATTR uint64_t restartReasonValidDetect;
|
||||
RTC_NOINIT_ATTR bool rebuildGpioRequested;
|
||||
bool restartReason_isValid;
|
||||
RestartReason currentRestartReason = RestartReason::NotApplicable;
|
||||
|
||||
TaskHandle_t networkTaskHandle = nullptr;
|
||||
TaskHandle_t nukiTaskHandle = nullptr;
|
||||
TaskHandle_t presenceDetectionTaskHandle = nullptr;
|
||||
|
||||
void networkTask(void *pvParameters)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
bool connected = network->update();
|
||||
if(connected && openerEnabled)
|
||||
{
|
||||
networkOpener->update();
|
||||
}
|
||||
webCfgServer->update();
|
||||
|
||||
// millis() is about to overflow. Restart device to prevent problems with overflow
|
||||
if(millis() > restartTs)
|
||||
{
|
||||
Log->println(F("Restart timer expired, restarting device."));
|
||||
delay(200);
|
||||
restartEsp(RestartReason::RestartTimer);
|
||||
}
|
||||
|
||||
delay(100);
|
||||
|
||||
// if(wmts < millis())
|
||||
// {
|
||||
// Serial.print("# ");
|
||||
// Serial.println(uxTaskGetStackHighWaterMark(NULL));
|
||||
// wmts = millis() + 60000;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void nukiTask(void *pvParameters)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
bleScanner->update();
|
||||
delay(20);
|
||||
|
||||
bool needsPairing = (lockEnabled && !nuki->isPaired()) || (openerEnabled && !nukiOpener->isPaired());
|
||||
|
||||
if (needsPairing)
|
||||
{
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
if(lockEnabled)
|
||||
{
|
||||
nuki->update();
|
||||
}
|
||||
if(openerEnabled)
|
||||
{
|
||||
nukiOpener->update();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void presenceDetectionTask(void *pvParameters)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
presenceDetection->update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void setupTasks()
|
||||
{
|
||||
// configMAX_PRIORITIES is 25
|
||||
|
||||
xTaskCreatePinnedToCore(networkTask, "ntw", 8192, NULL, 3, &networkTaskHandle, 1);
|
||||
xTaskCreatePinnedToCore(nukiTask, "nuki", 3328, NULL, 2, &nukiTaskHandle, 1);
|
||||
xTaskCreatePinnedToCore(presenceDetectionTask, "prdet", 896, NULL, 5, &presenceDetectionTaskHandle, 1);
|
||||
}
|
||||
|
||||
void initEthServer(const NetworkDeviceType device)
|
||||
{
|
||||
switch (device)
|
||||
{
|
||||
case NetworkDeviceType::W5500:
|
||||
ethServer = new W5500EthServer(80);
|
||||
break;
|
||||
case NetworkDeviceType::WiFi:
|
||||
ethServer = new WifiEthServer(80);
|
||||
break;
|
||||
default:
|
||||
ethServer = new WifiEthServer(80);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool initPreferences()
|
||||
{
|
||||
preferences = new Preferences();
|
||||
preferences->begin("nukihub", false);
|
||||
|
||||
// preferences->putBool(preference_network_wifi_fallback_disabled, false);
|
||||
|
||||
bool firstStart = !preferences->getBool(preference_started_before);
|
||||
|
||||
if(firstStart)
|
||||
{
|
||||
preferences->putBool(preference_started_before, true);
|
||||
preferences->putBool(preference_lock_enabled, true);
|
||||
preferences->putBool(preference_admin_enabled, true);
|
||||
|
||||
uint32_t aclPrefs[17] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs));
|
||||
}
|
||||
else
|
||||
{
|
||||
int configVer = preferences->getInt(preference_config_version);
|
||||
|
||||
if(configVer < (atof(NUKI_HUB_VERSION) * 100))
|
||||
{
|
||||
if (configVer < 834)
|
||||
{
|
||||
if(preferences->getInt(preference_keypad_control_enabled))
|
||||
{
|
||||
preferences->putBool(preference_keypad_info_enabled, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
preferences->putBool(preference_keypad_info_enabled, false);
|
||||
}
|
||||
|
||||
switch(preferences->getInt(preference_access_level))
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
preferences->putBool(preference_keypad_control_enabled, true);
|
||||
preferences->putBool(preference_admin_enabled, true);
|
||||
|
||||
uint32_t aclPrefs[17] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs));
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
preferences->putBool(preference_keypad_control_enabled, false);
|
||||
preferences->putBool(preference_admin_enabled, false);
|
||||
|
||||
uint32_t aclPrefs[17] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0};
|
||||
preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs));
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
preferences->putBool(preference_keypad_control_enabled, false);
|
||||
preferences->putBool(preference_admin_enabled, false);
|
||||
|
||||
uint32_t aclPrefs[17] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs));
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
preferences->putBool(preference_keypad_control_enabled, false);
|
||||
preferences->putBool(preference_admin_enabled, false);
|
||||
|
||||
uint32_t aclPrefs[17] = {1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0};
|
||||
preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preferences->putInt(preference_config_version, atof(NUKI_HUB_VERSION) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
return firstStart;
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Log = &Serial;
|
||||
|
||||
Log->print(F("Nuki Hub version ")); Log->println(NUKI_HUB_VERSION);
|
||||
|
||||
bool firstStart = initPreferences();
|
||||
|
||||
initializeRestartReason();
|
||||
|
||||
|
||||
uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener);
|
||||
|
||||
deviceIdLock = new NukiDeviceId(preferences, preference_device_id_lock);
|
||||
deviceIdOpener = new NukiDeviceId(preferences, preference_device_id_opener);
|
||||
|
||||
if(deviceIdLock->get() != 0 && devIdOpener == 0)
|
||||
{
|
||||
deviceIdOpener->assignId(deviceIdLock->get());
|
||||
}
|
||||
|
||||
CharBuffer::initialize();
|
||||
|
||||
if(preferences->getInt(preference_restart_timer) != 0)
|
||||
{
|
||||
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, gpio, mqttLockPath, CharBuffer::get(), CHAR_BUFFER_SIZE);
|
||||
network->initialize();
|
||||
|
||||
networkLock = new NetworkLock(network, preferences, CharBuffer::get(), CHAR_BUFFER_SIZE);
|
||||
networkLock->initialize();
|
||||
|
||||
if(openerEnabled)
|
||||
{
|
||||
networkOpener = new NetworkOpener(network, preferences, CharBuffer::get(), CHAR_BUFFER_SIZE);
|
||||
networkOpener->initialize();
|
||||
}
|
||||
|
||||
initEthServer(network->networkDeviceType());
|
||||
|
||||
bleScanner = new BleScanner::Scanner();
|
||||
bleScanner->initialize("NukiHub");
|
||||
bleScanner->setScanDuration(10);
|
||||
|
||||
Log->println(lockEnabled ? F("Nuki Lock enabled") : F("Nuki Lock disabled"));
|
||||
if(lockEnabled)
|
||||
{
|
||||
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, gpio, preferences);
|
||||
nuki->initialize(firstStart);
|
||||
}
|
||||
|
||||
Log->println(openerEnabled ? F("Nuki Opener enabled") : F("Nuki Opener disabled"));
|
||||
if(openerEnabled)
|
||||
{
|
||||
nukiOpener = new NukiOpenerWrapper("NukiHub", deviceIdOpener, bleScanner, networkOpener, gpio, preferences);
|
||||
nukiOpener->initialize();
|
||||
}
|
||||
|
||||
webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi);
|
||||
webCfgServer->initialize();
|
||||
|
||||
presenceDetection = new PresenceDetection(preferences, bleScanner, network, CharBuffer::get(), CHAR_BUFFER_SIZE);
|
||||
presenceDetection->initialize();
|
||||
|
||||
setupTasks();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
delay(60000);
|
||||
}
|
||||
75
src/networkDevices/ClientSyncW5500.cpp
Normal file
75
src/networkDevices/ClientSyncW5500.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright (c) 2022 Bert Melis. All rights reserved.
|
||||
|
||||
This work is licensed under the terms of the MIT license.
|
||||
For a copy, see <https://opensource.org/licenses/MIT> or
|
||||
the LICENSE file.
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "ClientSyncW5500.h"
|
||||
#include <lwip/sockets.h> // socket options
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
ClientSyncW5500::ClientSyncW5500()
|
||||
: client() {
|
||||
// empty
|
||||
}
|
||||
|
||||
bool ClientSyncW5500::connect(IPAddress ip, uint16_t port) {
|
||||
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
|
||||
if (ret) {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
client.setNoDelay(true);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here)
|
||||
int val = true;
|
||||
|
||||
// TODO
|
||||
// client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ClientSyncW5500::connect(const char* host, uint16_t port) {
|
||||
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
|
||||
if (ret) {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
client.setNoDelay(true);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here)
|
||||
int val = true;
|
||||
|
||||
// TODO
|
||||
// client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ClientSyncW5500::write(const uint8_t* buf, size_t size) {
|
||||
return client.write(buf, size);
|
||||
}
|
||||
|
||||
int ClientSyncW5500::read(uint8_t* buf, size_t size) {
|
||||
return client.read(buf, size);
|
||||
}
|
||||
|
||||
void ClientSyncW5500::stop() {
|
||||
client.stop();
|
||||
}
|
||||
|
||||
bool ClientSyncW5500::connected() {
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
bool ClientSyncW5500::disconnected() {
|
||||
return !client.connected();
|
||||
}
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
25
src/networkDevices/ClientSyncW5500.h
Normal file
25
src/networkDevices/ClientSyncW5500.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "Transport/Transport.h"
|
||||
#include "EthernetClient.h"
|
||||
|
||||
namespace espMqttClientInternals {
|
||||
|
||||
class ClientSyncW5500 : public Transport {
|
||||
public:
|
||||
ClientSyncW5500();
|
||||
bool connect(IPAddress ip, uint16_t port) override;
|
||||
bool connect(const char* host, uint16_t port) override;
|
||||
size_t write(const uint8_t* buf, size_t size) override;
|
||||
int read(uint8_t* buf, size_t size) override;
|
||||
void stop() override;
|
||||
bool connected() override;
|
||||
bool disconnected() override;
|
||||
EthernetClient client;
|
||||
};
|
||||
|
||||
} // namespace espMqttClientInternals
|
||||
|
||||
#endif
|
||||
150
src/networkDevices/EthLan8720Device.cpp
Normal file
150
src/networkDevices/EthLan8720Device.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
//#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT
|
||||
//#define ETH_PHY_POWER 12
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <ETH.h>
|
||||
#include "EthLan8720Device.h"
|
||||
#include "../PreferencesKeys.h"
|
||||
#include "../Logger.h"
|
||||
#include "../MqttTopics.h"
|
||||
#include "espMqttClient.h"
|
||||
#include "../RestartReason.h"
|
||||
|
||||
EthLan8720Device::EthLan8720Device(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration, const std::string& deviceName, uint8_t phy_addr, int power, int mdc, int mdio, eth_phy_type_t ethtype, eth_clock_mode_t clock_mode, bool use_mac_from_efuse)
|
||||
: NetworkDevice(hostname, ipConfiguration),
|
||||
_deviceName(deviceName),
|
||||
_phy_addr(phy_addr),
|
||||
_power(power),
|
||||
_mdc(mdc),
|
||||
_mdio(mdio),
|
||||
_type(ethtype),
|
||||
_clock_mode(clock_mode),
|
||||
_use_mac_from_efuse(use_mac_from_efuse)
|
||||
{
|
||||
_restartOnDisconnect = preferences->getBool(preference_restart_on_disconnect);
|
||||
|
||||
size_t caLength = preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE);
|
||||
size_t crtLength = preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE);
|
||||
size_t keyLength = preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE);
|
||||
|
||||
_useEncryption = caLength > 1; // length is 1 when empty
|
||||
|
||||
if(_useEncryption)
|
||||
{
|
||||
Log->println(F("MQTT over TLS."));
|
||||
Log->println(_ca);
|
||||
_mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
|
||||
_mqttClientSecure->setCACert(_ca);
|
||||
if(crtLength > 1 && keyLength > 1) // length is 1 when empty
|
||||
{
|
||||
Log->println(F("MQTT with client certificate."));
|
||||
Log->println(_cert);
|
||||
Log->println(_key);
|
||||
_mqttClientSecure->setCertificate(_cert);
|
||||
_mqttClientSecure->setPrivateKey(_key);
|
||||
}
|
||||
} else
|
||||
{
|
||||
Log->println(F("MQTT without TLS."));
|
||||
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
|
||||
}
|
||||
|
||||
if(preferences->getBool(preference_mqtt_log_enabled))
|
||||
{
|
||||
_path = new char[200];
|
||||
memset(_path, 0, sizeof(_path));
|
||||
|
||||
String pathStr = preferences->getString(preference_mqtt_lock_path);
|
||||
pathStr.concat(mqtt_topic_log);
|
||||
strcpy(_path, pathStr.c_str());
|
||||
Log = new MqttLogger(*getMqttClient(), _path, MqttLoggerMode::MqttAndSerial);
|
||||
}
|
||||
}
|
||||
|
||||
const String EthLan8720Device::deviceName() const
|
||||
{
|
||||
return _deviceName.c_str();
|
||||
}
|
||||
|
||||
void EthLan8720Device::initialize()
|
||||
{
|
||||
delay(250);
|
||||
|
||||
WiFi.setHostname(_hostname.c_str());
|
||||
_hardwareInitialized = ETH.begin(_phy_addr, _power, _mdc, _mdio, _type, _clock_mode, _use_mac_from_efuse);
|
||||
ETH.setHostname(_hostname.c_str());
|
||||
if(!_ipConfiguration->dhcpEnabled())
|
||||
{
|
||||
ETH.config(_ipConfiguration->ipAddress(), _ipConfiguration->defaultGateway(), _ipConfiguration->subnet(), _ipConfiguration->dnsServer());
|
||||
}
|
||||
|
||||
if(_restartOnDisconnect)
|
||||
{
|
||||
WiFi.onEvent([&](WiFiEvent_t event, WiFiEventInfo_t info)
|
||||
{
|
||||
if(event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED)
|
||||
{
|
||||
onDisconnected();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EthLan8720Device::reconfigure()
|
||||
{
|
||||
delay(200);
|
||||
restartEsp(RestartReason::ReconfigureLAN8720);
|
||||
}
|
||||
|
||||
bool EthLan8720Device::supportsEncryption()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EthLan8720Device::isConnected()
|
||||
{
|
||||
bool connected = ETH.linkUp();
|
||||
|
||||
if(_lastConnected == false && connected == true)
|
||||
{
|
||||
Serial.print(F("Ethernet connected. IP address: "));
|
||||
Serial.println(ETH.localIP().toString());
|
||||
}
|
||||
|
||||
_lastConnected = connected;
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
ReconnectStatus EthLan8720Device::reconnect()
|
||||
{
|
||||
if(!_hardwareInitialized)
|
||||
{
|
||||
return ReconnectStatus::CriticalFailure;
|
||||
}
|
||||
delay(200);
|
||||
return isConnected() ? ReconnectStatus::Success : ReconnectStatus::Failure;
|
||||
}
|
||||
|
||||
void EthLan8720Device::onDisconnected()
|
||||
{
|
||||
if(millis() > 60000)
|
||||
{
|
||||
restartEsp(RestartReason::RestartOnDisconnectWatchdog);
|
||||
}
|
||||
}
|
||||
|
||||
int8_t EthLan8720Device::signalStrength()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
String EthLan8720Device::localIP()
|
||||
{
|
||||
return ETH.localIP().toString();
|
||||
}
|
||||
|
||||
String EthLan8720Device::BSSIDstr()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
61
src/networkDevices/EthLan8720Device.h
Normal file
61
src/networkDevices/EthLan8720Device.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <Preferences.h>
|
||||
#include "NetworkDevice.h"
|
||||
#include "espMqttClient.h"
|
||||
#include <ETH.h>
|
||||
|
||||
class EthLan8720Device : public NetworkDevice
|
||||
{
|
||||
|
||||
public:
|
||||
EthLan8720Device(const String& hostname,
|
||||
Preferences* preferences,
|
||||
const IPConfiguration* ipConfiguration,
|
||||
const std::string& deviceName,
|
||||
uint8_t phy_addr = ETH_PHY_ADDR,
|
||||
int power = ETH_PHY_POWER,
|
||||
int mdc = ETH_PHY_MDC,
|
||||
int mdio = ETH_PHY_MDIO,
|
||||
eth_phy_type_t ethtype = ETH_PHY_TYPE,
|
||||
eth_clock_mode_t clock_mode = ETH_CLK_MODE,
|
||||
bool use_mac_from_efuse = false);
|
||||
|
||||
const String deviceName() const override;
|
||||
|
||||
virtual void initialize();
|
||||
virtual void reconfigure();
|
||||
virtual ReconnectStatus reconnect();
|
||||
bool supportsEncryption() override;
|
||||
|
||||
virtual bool isConnected();
|
||||
|
||||
int8_t signalStrength() override;
|
||||
|
||||
String localIP() override;
|
||||
String BSSIDstr() override;
|
||||
|
||||
private:
|
||||
void onDisconnected();
|
||||
|
||||
bool _restartOnDisconnect = false;
|
||||
bool _startAp = false;
|
||||
char* _path;
|
||||
bool _hardwareInitialized = false;
|
||||
bool _lastConnected = false;
|
||||
|
||||
const std::string _deviceName;
|
||||
uint8_t _phy_addr;
|
||||
int _power;
|
||||
int _mdc;
|
||||
int _mdio;
|
||||
eth_phy_type_t _type;
|
||||
eth_clock_mode_t _clock_mode;
|
||||
bool _use_mac_from_efuse;
|
||||
|
||||
char _ca[TLS_CA_MAX_SIZE] = {0};
|
||||
char _cert[TLS_CERT_MAX_SIZE] = {0};
|
||||
char _key[TLS_KEY_MAX_SIZE] = {0};
|
||||
};
|
||||
56
src/networkDevices/IPConfiguration.cpp
Normal file
56
src/networkDevices/IPConfiguration.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "IPConfiguration.h"
|
||||
#include "../PreferencesKeys.h"
|
||||
#include "../Logger.h"
|
||||
|
||||
IPConfiguration::IPConfiguration(Preferences *preferences)
|
||||
: _preferences(preferences)
|
||||
{
|
||||
if(_preferences->getString(preference_ip_address).length() <= 0)
|
||||
{
|
||||
Log->println("IP address empty, falling back to DHCP.");
|
||||
_preferences->putBool(preference_ip_dhcp_enabled, true);
|
||||
}
|
||||
|
||||
_ipAddress.fromString(_preferences->getString(preference_ip_address));
|
||||
_subnet.fromString(_preferences->getString(preference_ip_subnet));
|
||||
_gateway.fromString(_preferences->getString(preference_ip_gateway));
|
||||
_dnsServer.fromString(_preferences->getString(preference_ip_dns_server));
|
||||
|
||||
Log->print(F("IP configuration: "));
|
||||
if(dhcpEnabled())
|
||||
{
|
||||
Log->println(F("DHCP"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log->print(F("IP address: ")); Log->print(ipAddress());
|
||||
Log->print(F(", Subnet: ")); Log->print(subnet());
|
||||
Log->print(F(", Gateway: ")); Log->print(defaultGateway());
|
||||
Log->print(F(", DNS: ")); Log->println(dnsServer());
|
||||
}
|
||||
}
|
||||
|
||||
bool IPConfiguration::dhcpEnabled() const
|
||||
{
|
||||
return _preferences->getBool(preference_ip_dhcp_enabled);
|
||||
}
|
||||
|
||||
const IPAddress IPConfiguration::ipAddress() const
|
||||
{
|
||||
return _ipAddress;
|
||||
}
|
||||
|
||||
const IPAddress IPConfiguration::subnet() const
|
||||
{
|
||||
return _subnet;
|
||||
}
|
||||
|
||||
const IPAddress IPConfiguration::defaultGateway() const
|
||||
{
|
||||
return _gateway;
|
||||
}
|
||||
|
||||
const IPAddress IPConfiguration::dnsServer() const
|
||||
{
|
||||
return _dnsServer;
|
||||
}
|
||||
24
src/networkDevices/IPConfiguration.h
Normal file
24
src/networkDevices/IPConfiguration.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <Preferences.h>
|
||||
|
||||
class IPConfiguration
|
||||
{
|
||||
public:
|
||||
explicit IPConfiguration(Preferences* preferences);
|
||||
|
||||
bool dhcpEnabled() const;
|
||||
const IPAddress ipAddress() const;
|
||||
const IPAddress subnet() const;
|
||||
const IPAddress defaultGateway() const;
|
||||
const IPAddress dnsServer() const;
|
||||
|
||||
private:
|
||||
Preferences* _preferences = nullptr;
|
||||
|
||||
IPAddress _ipAddress;
|
||||
IPAddress _subnet;
|
||||
IPAddress _gateway;
|
||||
IPAddress _dnsServer;
|
||||
};
|
||||
|
||||
161
src/networkDevices/NetworkDevice.cpp
Normal file
161
src/networkDevices/NetworkDevice.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include <Arduino.h>
|
||||
#include "NetworkDevice.h"
|
||||
#include "../Logger.h"
|
||||
|
||||
void NetworkDevice::printError()
|
||||
{
|
||||
Log->print(F("Free Heap: "));
|
||||
Log->println(ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
void NetworkDevice::update()
|
||||
{
|
||||
if (_mqttEnabled)
|
||||
{
|
||||
getMqttClient()->loop();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkDevice::mqttSetClientId(const char *clientId)
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->setClientId(clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->setClientId(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkDevice::mqttSetCleanSession(bool cleanSession)
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->setCleanSession(cleanSession);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->setCleanSession(cleanSession);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t NetworkDevice::mqttPublish(const char *topic, uint8_t qos, bool retain, const char *payload)
|
||||
{
|
||||
return getMqttClient()->publish(topic, qos, retain, payload);
|
||||
}
|
||||
|
||||
uint16_t NetworkDevice::mqttPublish(const char *topic, uint8_t qos, bool retain, const uint8_t *payload, size_t length)
|
||||
{
|
||||
return getMqttClient()->publish(topic, qos, retain, payload, length);
|
||||
}
|
||||
|
||||
bool NetworkDevice::mqttConnected() const
|
||||
{
|
||||
return getMqttClient()->connected();
|
||||
}
|
||||
|
||||
void NetworkDevice::mqttSetServer(const char *host, uint16_t port)
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->setServer(host, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->setServer(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkDevice::mqttConnect()
|
||||
{
|
||||
return getMqttClient()->connect();
|
||||
}
|
||||
|
||||
bool NetworkDevice::mqttDisconnect(bool force)
|
||||
{
|
||||
return getMqttClient()->disconnect(force);
|
||||
}
|
||||
|
||||
void NetworkDevice::setWill(const char *topic, uint8_t qos, bool retain, const char *payload)
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->setWill(topic, qos, retain, payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->setWill(topic, qos, retain, payload);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkDevice::mqttSetCredentials(const char *username, const char *password)
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->setCredentials(username, password);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->setCredentials(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkDevice::mqttOnMessage(espMqttClientTypes::OnMessageCallback callback)
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->onMessage(callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->onMessage(callback);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkDevice::mqttOnConnect(espMqttClientTypes::OnConnectCallback callback)
|
||||
{
|
||||
if(_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->onConnect(callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->onConnect(callback);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkDevice::mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback)
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
_mqttClientSecure->onDisconnect(callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mqttClient->onDisconnect(callback);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t NetworkDevice::mqttSubscribe(const char *topic, uint8_t qos)
|
||||
{
|
||||
return getMqttClient()->subscribe(topic, qos);
|
||||
}
|
||||
|
||||
void NetworkDevice::disableMqtt()
|
||||
{
|
||||
getMqttClient()->disconnect();
|
||||
_mqttEnabled = false;
|
||||
}
|
||||
|
||||
MqttClient *NetworkDevice::getMqttClient() const
|
||||
{
|
||||
if (_useEncryption)
|
||||
{
|
||||
return _mqttClientSecure;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _mqttClient;
|
||||
}
|
||||
}
|
||||
66
src/networkDevices/NetworkDevice.h
Normal file
66
src/networkDevices/NetworkDevice.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "espMqttClient.h"
|
||||
#include "MqttClientSetup.h"
|
||||
#include "IPConfiguration.h"
|
||||
|
||||
enum class ReconnectStatus
|
||||
{
|
||||
Failure = 0,
|
||||
Success = 1,
|
||||
CriticalFailure = 2
|
||||
};
|
||||
|
||||
class NetworkDevice
|
||||
{
|
||||
public:
|
||||
explicit NetworkDevice(const String& hostname, const IPConfiguration* ipConfiguration)
|
||||
: _hostname(hostname),
|
||||
_ipConfiguration(ipConfiguration)
|
||||
{}
|
||||
|
||||
virtual const String deviceName() const = 0;
|
||||
|
||||
virtual void initialize() = 0;
|
||||
virtual ReconnectStatus reconnect() = 0;
|
||||
virtual void reconfigure() = 0;
|
||||
virtual void printError();
|
||||
virtual bool supportsEncryption() = 0;
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isConnected() = 0;
|
||||
virtual int8_t signalStrength() = 0;
|
||||
|
||||
virtual String localIP() = 0;
|
||||
virtual String BSSIDstr() = 0;
|
||||
|
||||
virtual void mqttSetClientId(const char* clientId);
|
||||
virtual void mqttSetCleanSession(bool cleanSession);
|
||||
virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const char* payload);
|
||||
virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length);
|
||||
virtual bool mqttConnected() const;
|
||||
virtual void mqttSetServer(const char* host, uint16_t port);
|
||||
virtual bool mqttConnect();
|
||||
virtual bool mqttDisconnect(bool force);
|
||||
virtual void setWill(const char* topic, uint8_t qos, bool retain, const char* payload);
|
||||
virtual void mqttSetCredentials(const char* username, const char* password);
|
||||
virtual void mqttOnMessage(espMqttClientTypes::OnMessageCallback callback);
|
||||
virtual void mqttOnConnect(espMqttClientTypes::OnConnectCallback callback);
|
||||
virtual void mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback);
|
||||
virtual void disableMqtt();
|
||||
|
||||
virtual uint16_t mqttSubscribe(const char* topic, uint8_t qos);
|
||||
|
||||
protected:
|
||||
espMqttClient *_mqttClient = nullptr;
|
||||
espMqttClientSecure *_mqttClientSecure = nullptr;
|
||||
|
||||
bool _useEncryption = false;
|
||||
bool _mqttEnabled = true;
|
||||
|
||||
const String _hostname;
|
||||
const IPConfiguration* _ipConfiguration = nullptr;
|
||||
|
||||
MqttClient *getMqttClient() const;
|
||||
};
|
||||
232
src/networkDevices/W5500Device.cpp
Normal file
232
src/networkDevices/W5500Device.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include "W5500Device.h"
|
||||
#include "../PreferencesKeys.h"
|
||||
#include "../Logger.h"
|
||||
#include "../MqttTopics.h"
|
||||
|
||||
W5500Device::W5500Device(const String &hostname, Preferences* preferences, const IPConfiguration* ipConfiguration, int variant)
|
||||
: NetworkDevice(hostname, ipConfiguration),
|
||||
_preferences(preferences),
|
||||
_variant((W5500Variant)variant)
|
||||
{
|
||||
initializeMacAddress(_mac);
|
||||
|
||||
Log->print("MAC Adress: ");
|
||||
for(int i=0; i < 6; i++)
|
||||
{
|
||||
if(_mac[i] < 10)
|
||||
{
|
||||
Log->print(F("0"));
|
||||
}
|
||||
Log->print(_mac[i], 16);
|
||||
if(i < 5)
|
||||
{
|
||||
Log->print(F(":"));
|
||||
}
|
||||
}
|
||||
Log->println();
|
||||
|
||||
_mqttClient = new espMqttClientW5500();
|
||||
}
|
||||
|
||||
W5500Device::~W5500Device()
|
||||
{}
|
||||
|
||||
const String W5500Device::deviceName() const
|
||||
{
|
||||
return "Wiznet W5500";
|
||||
}
|
||||
|
||||
void W5500Device::initialize()
|
||||
{
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
resetDevice();
|
||||
|
||||
switch(_variant)
|
||||
{
|
||||
case W5500Variant::M5StackAtomPoe:
|
||||
_resetPin = -1;
|
||||
Ethernet.init(19, 22, 23, 33);
|
||||
break;
|
||||
default:
|
||||
_resetPin = -1;
|
||||
Ethernet.init(5);
|
||||
break;
|
||||
}
|
||||
|
||||
if(_preferences->getBool(preference_mqtt_log_enabled))
|
||||
{
|
||||
String pathStr = _preferences->getString(preference_mqtt_lock_path);
|
||||
pathStr.concat(mqtt_topic_log);
|
||||
_path = new char[pathStr.length() + 1];
|
||||
memset(_path, 0, sizeof(_path));
|
||||
strcpy(_path, pathStr.c_str());
|
||||
Log = new MqttLogger(*getMqttClient(), _path, MqttLoggerMode::MqttAndSerial);
|
||||
}
|
||||
|
||||
reconnect();
|
||||
}
|
||||
|
||||
ReconnectStatus W5500Device::reconnect()
|
||||
{
|
||||
_hasDHCPAddress = false;
|
||||
|
||||
// start the Ethernet connection:
|
||||
Log->println(F("Initialize Ethernet with DHCP:"));
|
||||
|
||||
int dhcpRetryCnt = 0;
|
||||
bool hardwareFound = false;
|
||||
|
||||
while(dhcpRetryCnt < 3)
|
||||
{
|
||||
Log->print(F("DHCP connect try #"));
|
||||
Log->print(dhcpRetryCnt);
|
||||
Log->println();
|
||||
dhcpRetryCnt++;
|
||||
|
||||
if (Ethernet.begin(_mac, 1000, 1000) == 0)
|
||||
{
|
||||
Log->println(F("Failed to configure Ethernet using DHCP"));
|
||||
// Check for Ethernet hardware present
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware)
|
||||
{
|
||||
Log->println(F("Ethernet module not found"));
|
||||
continue;
|
||||
}
|
||||
if (Ethernet.linkStatus() == LinkOFF)
|
||||
{
|
||||
Log->println(F("Ethernet cable is not connected."));
|
||||
}
|
||||
|
||||
hardwareFound = true;
|
||||
|
||||
IPAddress ip;
|
||||
ip.fromString("192.168.4.1");
|
||||
|
||||
IPAddress subnet;
|
||||
subnet.fromString("255.255.255.0");
|
||||
|
||||
// try to congifure using IP address instead of DHCP:
|
||||
Ethernet.begin(_mac, ip);
|
||||
Ethernet.setSubnetMask(subnet);
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
hardwareFound = true;
|
||||
_hasDHCPAddress = true;
|
||||
dhcpRetryCnt = 1000;
|
||||
if(_ipConfiguration->dhcpEnabled())
|
||||
{
|
||||
Log->print(F(" DHCP assigned IP "));
|
||||
Log->println(Ethernet.localIP());
|
||||
}
|
||||
}
|
||||
|
||||
if(!_ipConfiguration->dhcpEnabled())
|
||||
{
|
||||
Ethernet.setLocalIP(_ipConfiguration->ipAddress());
|
||||
Ethernet.setSubnetMask(_ipConfiguration->subnet());
|
||||
Ethernet.setGatewayIP(_ipConfiguration->defaultGateway());
|
||||
Ethernet.setDnsServerIP(_ipConfiguration->dnsServer());
|
||||
}
|
||||
}
|
||||
|
||||
if(!hardwareFound)
|
||||
{
|
||||
return ReconnectStatus::CriticalFailure;
|
||||
}
|
||||
|
||||
return _hasDHCPAddress ? ReconnectStatus::Success : ReconnectStatus::Failure;
|
||||
}
|
||||
|
||||
|
||||
void W5500Device::reconfigure()
|
||||
{
|
||||
Log->println(F("Reconfigure W5500 not implemented."));
|
||||
}
|
||||
|
||||
void W5500Device::resetDevice()
|
||||
{
|
||||
if(_resetPin == -1) return;
|
||||
|
||||
Log->println(F("Resetting network hardware."));
|
||||
pinMode(_resetPin, OUTPUT);
|
||||
digitalWrite(_resetPin, HIGH);
|
||||
delay(50);
|
||||
digitalWrite(_resetPin, LOW);
|
||||
delay(50);
|
||||
digitalWrite(_resetPin, HIGH);
|
||||
delay(50);
|
||||
}
|
||||
|
||||
bool W5500Device::supportsEncryption()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool W5500Device::isConnected()
|
||||
{
|
||||
bool connected = (Ethernet.linkStatus() == EthernetLinkStatus::LinkON && _maintainResult == 0 && _hasDHCPAddress);
|
||||
|
||||
if(_lastConnected == false && connected == true)
|
||||
{
|
||||
Serial.print(F("Ethernet connected. IP address: "));
|
||||
Serial.println(Ethernet.localIP().toString());
|
||||
}
|
||||
|
||||
_lastConnected = connected;
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
void W5500Device::initializeMacAddress(byte *mac)
|
||||
{
|
||||
memset(mac, 0, 6);
|
||||
|
||||
mac[0] = 0x00; // wiznet prefix
|
||||
mac[1] = 0x08; // wiznet prefix
|
||||
mac[2] = 0xDC; // wiznet prefix
|
||||
|
||||
if(_preferences->getBool(preference_has_mac_saved))
|
||||
{
|
||||
mac[3] = _preferences->getChar(preference_has_mac_byte_0);
|
||||
mac[4] = _preferences->getChar(preference_has_mac_byte_1);
|
||||
mac[5] = _preferences->getChar(preference_has_mac_byte_2);
|
||||
}
|
||||
else
|
||||
{
|
||||
mac[3] = random(0,255);
|
||||
mac[4] = random(0,255);
|
||||
mac[5] = random(0,255);
|
||||
|
||||
_preferences->putChar(preference_has_mac_byte_0, mac[3]);
|
||||
_preferences->putChar(preference_has_mac_byte_1, mac[4]);
|
||||
_preferences->putChar(preference_has_mac_byte_2, mac[5]);
|
||||
_preferences->putBool(preference_has_mac_saved, true);
|
||||
}
|
||||
}
|
||||
|
||||
void W5500Device::update()
|
||||
{
|
||||
_maintainResult = Ethernet.maintain();
|
||||
NetworkDevice::update();
|
||||
}
|
||||
|
||||
int8_t W5500Device::signalStrength()
|
||||
{
|
||||
return 127;
|
||||
}
|
||||
|
||||
String W5500Device::localIP()
|
||||
{
|
||||
return Ethernet.localIP().toString();
|
||||
}
|
||||
|
||||
String W5500Device::BSSIDstr()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
52
src/networkDevices/W5500Device.h
Normal file
52
src/networkDevices/W5500Device.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "NetworkDevice.h"
|
||||
#include "espMqttClient.h"
|
||||
#include "espMqttClientW5500.h"
|
||||
#include <Ethernet.h>
|
||||
#include <Preferences.h>
|
||||
|
||||
enum class W5500Variant
|
||||
{
|
||||
Generic = 2,
|
||||
M5StackAtomPoe = 3
|
||||
};
|
||||
|
||||
class W5500Device : public NetworkDevice
|
||||
{
|
||||
public:
|
||||
explicit W5500Device(const String& hostname, Preferences* _preferences, const IPConfiguration* ipConfiguration, int variant);
|
||||
~W5500Device();
|
||||
|
||||
const String deviceName() const override;
|
||||
|
||||
virtual void initialize();
|
||||
virtual ReconnectStatus reconnect();
|
||||
virtual void reconfigure();
|
||||
|
||||
bool supportsEncryption() override;
|
||||
|
||||
virtual void update() override;
|
||||
|
||||
virtual bool isConnected();
|
||||
|
||||
int8_t signalStrength() override;
|
||||
|
||||
String localIP() override;
|
||||
String BSSIDstr() override;
|
||||
|
||||
private:
|
||||
void resetDevice();
|
||||
void initializeMacAddress(byte* mac);
|
||||
|
||||
Preferences* _preferences = nullptr;
|
||||
|
||||
int _maintainResult = 0;
|
||||
int _resetPin = -1;
|
||||
bool _hasDHCPAddress = false;
|
||||
char* _path;
|
||||
W5500Variant _variant;
|
||||
bool _lastConnected = false;
|
||||
|
||||
byte _mac[6];
|
||||
};
|
||||
171
src/networkDevices/WifiDevice.cpp
Normal file
171
src/networkDevices/WifiDevice.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include <WiFi.h>
|
||||
#include "WifiDevice.h"
|
||||
#include "../PreferencesKeys.h"
|
||||
#include "../Logger.h"
|
||||
#include "../MqttTopics.h"
|
||||
#include "espMqttClient.h"
|
||||
#include "../RestartReason.h"
|
||||
|
||||
RTC_NOINIT_ATTR char WiFiDevice_reconfdetect[17];
|
||||
|
||||
WifiDevice::WifiDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration)
|
||||
: NetworkDevice(hostname, ipConfiguration),
|
||||
_preferences(preferences),
|
||||
_wm(preferences->getString(preference_cred_user).c_str(), preferences->getString(preference_cred_password).c_str())
|
||||
{
|
||||
_startAp = strcmp(WiFiDevice_reconfdetect, "reconfigure_wifi") == 0;
|
||||
|
||||
_restartOnDisconnect = preferences->getBool(preference_restart_on_disconnect);
|
||||
|
||||
size_t caLength = preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE);
|
||||
size_t crtLength = preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE);
|
||||
size_t keyLength = preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE);
|
||||
|
||||
_useEncryption = caLength > 1; // length is 1 when empty
|
||||
|
||||
if(_useEncryption)
|
||||
{
|
||||
Log->println(F("MQTT over TLS."));
|
||||
Log->println(_ca);
|
||||
_mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
|
||||
_mqttClientSecure->setCACert(_ca);
|
||||
if(crtLength > 1 && keyLength > 1) // length is 1 when empty
|
||||
{
|
||||
Log->println(F("MQTT with client certificate."));
|
||||
Log->println(_cert);
|
||||
Log->println(_key);
|
||||
_mqttClientSecure->setCertificate(_cert);
|
||||
_mqttClientSecure->setPrivateKey(_key);
|
||||
}
|
||||
} else
|
||||
{
|
||||
Log->println(F("MQTT without TLS."));
|
||||
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
|
||||
}
|
||||
|
||||
if(preferences->getBool(preference_mqtt_log_enabled))
|
||||
{
|
||||
_path = new char[200];
|
||||
memset(_path, 0, sizeof(_path));
|
||||
|
||||
String pathStr = preferences->getString(preference_mqtt_lock_path);
|
||||
pathStr.concat(mqtt_topic_log);
|
||||
strcpy(_path, pathStr.c_str());
|
||||
Log = new MqttLogger(*getMqttClient(), _path, MqttLoggerMode::MqttAndSerial);
|
||||
}
|
||||
}
|
||||
|
||||
const String WifiDevice::deviceName() const
|
||||
{
|
||||
return "Built-in Wi-Fi";
|
||||
}
|
||||
|
||||
void WifiDevice::initialize()
|
||||
{
|
||||
std::vector<const char *> wm_menu;
|
||||
wm_menu.push_back("wifi");
|
||||
wm_menu.push_back("exit");
|
||||
_wm.setEnableConfigPortal(_startAp || !_preferences->getBool(preference_network_wifi_fallback_disabled));
|
||||
// reduced tieout if ESP is set to restart on disconnect
|
||||
_wm.setFindBestRSSI(_preferences->getBool(preference_find_best_rssi));
|
||||
_wm.setConfigPortalTimeout(_restartOnDisconnect ? 60 * 3 : 60 * 30);
|
||||
_wm.setShowInfoUpdate(false);
|
||||
_wm.setMenu(wm_menu);
|
||||
_wm.setHostname(_hostname);
|
||||
|
||||
if(!_ipConfiguration->dhcpEnabled())
|
||||
{
|
||||
_wm.setSTAStaticIPConfig(_ipConfiguration->ipAddress(), _ipConfiguration->defaultGateway(), _ipConfiguration->subnet(), _ipConfiguration->dnsServer());
|
||||
}
|
||||
|
||||
_wm.setAPCallback(clearRtcInitVar);
|
||||
|
||||
bool res = false;
|
||||
|
||||
if(_startAp)
|
||||
{
|
||||
Log->println(F("Opening Wi-Fi configuration portal."));
|
||||
res = _wm.startConfigPortal();
|
||||
}
|
||||
else
|
||||
{
|
||||
res = _wm.autoConnect(); // password protected ap
|
||||
}
|
||||
|
||||
if(!res)
|
||||
{
|
||||
esp_wifi_disconnect ();
|
||||
esp_wifi_stop ();
|
||||
esp_wifi_deinit ();
|
||||
|
||||
Log->println(F("Failed to connect. Wait for ESP restart."));
|
||||
delay(1000);
|
||||
restartEsp(RestartReason::WifiInitFailed);
|
||||
}
|
||||
else {
|
||||
Log->print(F("Wi-Fi connected: "));
|
||||
Log->println(WiFi.localIP().toString());
|
||||
}
|
||||
|
||||
if(_restartOnDisconnect)
|
||||
{
|
||||
WiFi.onEvent([&](WiFiEvent_t event, WiFiEventInfo_t info)
|
||||
{
|
||||
if(event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED)
|
||||
{
|
||||
onDisconnected();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void WifiDevice::reconfigure()
|
||||
{
|
||||
strcpy(WiFiDevice_reconfdetect, "reconfigure_wifi");
|
||||
delay(200);
|
||||
restartEsp(RestartReason::ReconfigureWifi);
|
||||
}
|
||||
|
||||
bool WifiDevice::supportsEncryption()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WifiDevice::isConnected()
|
||||
{
|
||||
return WiFi.isConnected();
|
||||
}
|
||||
|
||||
ReconnectStatus WifiDevice::reconnect()
|
||||
{
|
||||
delay(3000);
|
||||
return isConnected() ? ReconnectStatus::Success : ReconnectStatus::Failure;
|
||||
}
|
||||
|
||||
void WifiDevice::onDisconnected()
|
||||
{
|
||||
if(millis() > 60000)
|
||||
{
|
||||
restartEsp(RestartReason::RestartOnDisconnectWatchdog);
|
||||
}
|
||||
}
|
||||
|
||||
int8_t WifiDevice::signalStrength()
|
||||
{
|
||||
return WiFi.RSSI();
|
||||
}
|
||||
|
||||
String WifiDevice::localIP()
|
||||
{
|
||||
return WiFi.localIP().toString();
|
||||
}
|
||||
|
||||
String WifiDevice::BSSIDstr()
|
||||
{
|
||||
return WiFi.BSSIDstr();
|
||||
}
|
||||
|
||||
void WifiDevice::clearRtcInitVar(WiFiManager *)
|
||||
{
|
||||
memset(WiFiDevice_reconfdetect, 0, sizeof WiFiDevice_reconfdetect);
|
||||
}
|
||||
45
src/networkDevices/WifiDevice.h
Normal file
45
src/networkDevices/WifiDevice.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <Preferences.h>
|
||||
#include "NetworkDevice.h"
|
||||
#include "WiFiManager.h"
|
||||
#include "espMqttClient.h"
|
||||
#include "IPConfiguration.h"
|
||||
|
||||
class WifiDevice : public NetworkDevice
|
||||
{
|
||||
public:
|
||||
WifiDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration);
|
||||
|
||||
const String deviceName() const override;
|
||||
|
||||
virtual void initialize();
|
||||
virtual void reconfigure();
|
||||
virtual ReconnectStatus reconnect();
|
||||
bool supportsEncryption() override;
|
||||
|
||||
virtual bool isConnected();
|
||||
|
||||
int8_t signalStrength() override;
|
||||
|
||||
String localIP() override;
|
||||
String BSSIDstr() override;
|
||||
|
||||
private:
|
||||
static void clearRtcInitVar(WiFiManager*);
|
||||
|
||||
void onDisconnected();
|
||||
|
||||
WiFiManager _wm;
|
||||
Preferences* _preferences = nullptr;
|
||||
|
||||
bool _restartOnDisconnect = false;
|
||||
bool _startAp = false;
|
||||
char* _path;
|
||||
|
||||
char _ca[TLS_CA_MAX_SIZE] = {0};
|
||||
char _cert[TLS_CERT_MAX_SIZE] = {0};
|
||||
char _key[TLS_KEY_MAX_SIZE] = {0};
|
||||
};
|
||||
13
src/networkDevices/espMqttClientW5500.cpp
Normal file
13
src/networkDevices/espMqttClientW5500.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "espMqttClientW5500.h"
|
||||
|
||||
espMqttClientW5500::espMqttClientW5500()
|
||||
: espMqttClient(espMqttClientTypes::UseInternalTask::NO),
|
||||
_client()
|
||||
{
|
||||
_transport = &_client;
|
||||
}
|
||||
|
||||
void espMqttClientW5500::update()
|
||||
{
|
||||
loop();
|
||||
}
|
||||
22
src/networkDevices/espMqttClientW5500.h
Normal file
22
src/networkDevices/espMqttClientW5500.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "espMqttClient.h"
|
||||
#include "ClientSyncW5500.h"
|
||||
|
||||
class espMqttClientW5500 : public espMqttClient {
|
||||
public:
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
explicit espMqttClientW5500();
|
||||
#else
|
||||
espMqttClient();
|
||||
#endif
|
||||
|
||||
void update();
|
||||
|
||||
protected:
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
espMqttClientInternals::ClientSyncW5500 _client;
|
||||
#elif defined(__linux__)
|
||||
espMqttClientInternals::ClientPosix _client;
|
||||
#endif
|
||||
};
|
||||
Reference in New Issue
Block a user