Official MQTT - Nuki Hub coexistence, keypad/timecontrol sensor per code/entry, option not to publish config, authorization log improvements, various fixes (#389)

* Coexistence with official MQTT over Wifi and Thread

* Coexistence with official MQTT over Wifi and Thread

* Arduino Core 2.0.17 cmake and README

* Coexistence with official MQTT over Wifi and Thread

* Keep updating status until state is known

* Coexistence with official MQTT over Wifi and Thread
This commit is contained in:
iranl
2024-06-08 09:03:35 +02:00
committed by GitHub
parent d2b3509d46
commit 90a8d04b45
19 changed files with 3647 additions and 1876 deletions

View File

@@ -24,6 +24,19 @@
#define mqtt_topic_lock_address "/lock/address"
#define mqtt_topic_lock_retry "/lock/retry"
#define mqtt_topic_official_lock_action "/lockAction"
//#define mqtt_topic_official_mode "/mode"
#define mqtt_topic_official_state "/state"
#define mqtt_topic_official_batteryCritical "/batteryCritical"
#define mqtt_topic_official_batteryChargeState "/batteryChargeState"
#define mqtt_topic_official_batteryCharging "/batteryCharging"
#define mqtt_topic_official_keypadBatteryCritical "/keypadBatteryCritical"
#define mqtt_topic_official_doorsensorState "/doorsensorState"
#define mqtt_topic_official_doorsensorBatteryCritical "/doorsensorBatteryCritical"
#define mqtt_topic_official_connected "/connected"
#define mqtt_topic_official_commandResponse "/commandResponse"
#define mqtt_topic_official_lockActionEvent "/lockActionEvent"
#define mqtt_topic_config_action "/configuration/action"
#define mqtt_topic_config_action_command_result "/configuration/commandResult"
#define mqtt_topic_config_basic_json "/configuration/basicJson"
@@ -50,10 +63,12 @@
#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_battery_doorsensor_critical "/battery/doorSensorCritical"
#define mqtt_topic_battery_basic_json "/battery/basicJson"
#define mqtt_topic_battery_advanced_json "/battery/advancedJson"
#define mqtt_topic_keypad "/keypad"
#define mqtt_topic_keypad_codes "/keypad/codes"
#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"
@@ -64,6 +79,8 @@
#define mqtt_topic_keypad_json_action "/keypad/actionJson"
#define mqtt_topic_keypad_json_command_result "/keypad/commandResultJson"
#define mqtt_topic_timecontrol "/timecontrol"
#define mqtt_topic_timecontrol_entries "/timecontrol/entries"
#define mqtt_topic_timecontrol_json "/timecontrol/json"
#define mqtt_topic_timecontrol_action "/timecontrol/action"
#define mqtt_topic_timecontrol_command_result "/timecontrol/commandResult"

View File

@@ -160,7 +160,7 @@ void Network::setupDevice()
void Network::initialize()
{
_restartOnDisconnect = _preferences->getBool(preference_restart_on_disconnect);
_restartOnDisconnect = _preferences->getBool(preference_restart_on_disconnect, false);
_rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval) * 1000;
_hostname = _preferences->getString(preference_hostname);
@@ -358,6 +358,8 @@ bool Network::update()
if(_lastMaintenanceTs == 0 || (ts - _lastMaintenanceTs) > 30000)
{
publishULong(_maintenancePathPrefix, mqtt_topic_uptime, ts / 1000 / 60);
publishString(_maintenancePathPrefix, mqtt_topic_mqtt_connection_state, "online");
if(_publishDebugInfo)
{
publishUInt(_maintenancePathPrefix, mqtt_topic_freeheap, esp_get_free_heap_size());
@@ -706,56 +708,56 @@ const String Network::networkBSSID() const
return _device->BSSIDstr();
}
void Network::publishFloat(const char* prefix, const char* topic, const float value, const uint8_t precision)
void Network::publishFloat(const char* prefix, const char* topic, const float value, const uint8_t precision, bool retain)
{
char str[30];
dtostrf(value, 0, precision, str);
char path[200] = {0};
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
_device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str);
}
void Network::publishInt(const char* prefix, const char *topic, const int value)
void Network::publishInt(const char* prefix, const char *topic, const int value, bool retain)
{
char str[30];
itoa(value, str, 10);
char path[200] = {0};
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
_device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str);
}
void Network::publishUInt(const char* prefix, const char *topic, const unsigned int value)
void Network::publishUInt(const char* prefix, const char *topic, const unsigned int value, bool retain)
{
char str[30];
utoa(value, str, 10);
char path[200] = {0};
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
_device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str);
}
void Network::publishULong(const char* prefix, const char *topic, const unsigned long value)
void Network::publishULong(const char* prefix, const char *topic, const unsigned long value, bool retain)
{
char str[30];
utoa(value, str, 10);
char path[200] = {0};
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
_device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str);
}
void Network::publishBool(const char* prefix, const char *topic, const bool value)
void Network::publishBool(const char* prefix, const char *topic, const bool value, bool retain)
{
char str[2] = {0};
str[0] = value ? '1' : '0';
char path[200] = {0};
buildMqttPath(path, { prefix, topic });
_device->mqttPublish(path, MQTT_QOS_LEVEL, true, str);
_device->mqttPublish(path, MQTT_QOS_LEVEL, retain, str);
}
bool Network::publishString(const char* prefix, const char *topic, const char *value)
bool Network::publishString(const char* prefix, const char *topic, const char *value, bool retain)
{
char path[200] = {0};
buildMqttPath(path, { prefix, topic });
return _device->mqttPublish(path, MQTT_QOS_LEVEL, true, value) > 0;
return _device->mqttPublish(path, MQTT_QOS_LEVEL, retain, value) > 0;
}
void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction)
@@ -796,6 +798,8 @@ void Network::publishHASSConfig(char* deviceType, const char* baseTopic, char* n
json["stat_locking"] = "locking";
json["stat_unlocked"] = "unlocked";
json["stat_unlocking"] = "unlocking";
json["stat_open"] = "open";
json["stat_opening"] = "opening";
json["opt"] = "false";
serializeJson(json, _buffer, _bufferSize);
@@ -1101,10 +1105,15 @@ void Network::publishHASSConfigAdditionalLockEntities(char *deviceType, const ch
{
uint32_t aclPrefs[17];
_preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs));
uint32_t basicLockConfigAclPrefs[16];
_preferences->getBytes(preference_conf_lock_basic_acl, &basicLockConfigAclPrefs, sizeof(basicLockConfigAclPrefs));
uint32_t advancedLockConfigAclPrefs[22];
_preferences->getBytes(preference_conf_lock_advanced_acl, &advancedLockConfigAclPrefs, sizeof(advancedLockConfigAclPrefs));
uint32_t basicLockConfigAclPrefs[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint32_t advancedLockConfigAclPrefs[22] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if(_preferences->getBool(preference_conf_info_enabled, true))
{
_preferences->getBytes(preference_conf_lock_basic_acl, &basicLockConfigAclPrefs, sizeof(basicLockConfigAclPrefs));
_preferences->getBytes(preference_conf_lock_advanced_acl, &advancedLockConfigAclPrefs, sizeof(advancedLockConfigAclPrefs));
}
if((int)aclPrefs[2])
{
@@ -2166,10 +2175,14 @@ void Network::publishHASSConfigAdditionalOpenerEntities(char *deviceType, const
{
uint32_t aclPrefs[17];
_preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs));
uint32_t basicOpenerConfigAclPrefs[16];
_preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs));
uint32_t advancedOpenerConfigAclPrefs[22];
_preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs));
uint32_t basicOpenerConfigAclPrefs[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint32_t advancedOpenerConfigAclPrefs[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
if(_preferences->getBool(preference_conf_info_enabled, true))
{
_preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs));
_preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs));
}
if((int)aclPrefs[11])
{
@@ -2195,64 +2208,6 @@ void Network::publishHASSConfigAdditionalOpenerEntities(char *deviceType, const
removeHassTopic((char*)"button", (char*)"unlatch", uidString);
}
if((int)basicOpenerConfigAclPrefs[5] == 1)
{
// LED enabled
publishHassTopic("switch",
"led_enabled",
uidString,
"_led_enabled",
"LED enabled",
name,
baseTopic,
String("~") + mqtt_topic_config_basic_json,
deviceType,
"",
"",
"config",
String("~") + mqtt_topic_config_action,
{ { (char*)"en", (char*)"true" },
{ (char*)"ic", (char*)"mdi:led-variant-on" },
{ (char*)"pl_on", (char*)"{ \"ledEnabled\": \"1\"}" },
{ (char*)"pl_off", (char*)"{ \"ledEnabled\": \"0\"}" },
{ (char*)"val_tpl", (char*)"{{value_json.ledEnabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
else
{
removeHassTopic((char*)"switch", (char*)"led_enabled", uidString);
}
if((int)basicOpenerConfigAclPrefs[4] == 1)
{
// Button enabled
publishHassTopic("switch",
"button_enabled",
uidString,
"_button_enabled",
"Button enabled",
name,
baseTopic,
String("~") + mqtt_topic_config_basic_json,
deviceType,
"",
"",
"config",
String("~") + mqtt_topic_config_action,
{ { (char*)"en", (char*)"true" },
{ (char*)"ic", (char*)"mdi:radiobox-marked" },
{ (char*)"pl_on", (char*)"{ \"buttonEnabled\": \"1\"}" },
{ (char*)"pl_off", (char*)"{ \"buttonEnabled\": \"0\"}" },
{ (char*)"val_tpl", (char*)"{{value_json.buttonEnabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
else
{
removeHassTopic((char*)"switch", (char*)"button_enabled", uidString);
}
publishHassTopic("binary_sensor",
"continuous_mode",
uidString,
@@ -2319,6 +2274,64 @@ void Network::publishHASSConfigAdditionalOpenerEntities(char *deviceType, const
String path = createHassTopicPath("event", "ring", uidString);
_device->mqttPublish(path.c_str(), MQTT_QOS_LEVEL, true, _buffer);
if((int)basicOpenerConfigAclPrefs[5] == 1)
{
// LED enabled
publishHassTopic("switch",
"led_enabled",
uidString,
"_led_enabled",
"LED enabled",
name,
baseTopic,
String("~") + mqtt_topic_config_basic_json,
deviceType,
"",
"",
"config",
String("~") + mqtt_topic_config_action,
{ { (char*)"en", (char*)"true" },
{ (char*)"ic", (char*)"mdi:led-variant-on" },
{ (char*)"pl_on", (char*)"{ \"ledEnabled\": \"1\"}" },
{ (char*)"pl_off", (char*)"{ \"ledEnabled\": \"0\"}" },
{ (char*)"val_tpl", (char*)"{{value_json.ledEnabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
else
{
removeHassTopic((char*)"switch", (char*)"led_enabled", uidString);
}
if((int)basicOpenerConfigAclPrefs[4] == 1)
{
// Button enabled
publishHassTopic("switch",
"button_enabled",
uidString,
"_button_enabled",
"Button enabled",
name,
baseTopic,
String("~") + mqtt_topic_config_basic_json,
deviceType,
"",
"",
"config",
String("~") + mqtt_topic_config_action,
{ { (char*)"en", (char*)"true" },
{ (char*)"ic", (char*)"mdi:radiobox-marked" },
{ (char*)"pl_on", (char*)"{ \"buttonEnabled\": \"1\"}" },
{ (char*)"pl_off", (char*)"{ \"buttonEnabled\": \"0\"}" },
{ (char*)"val_tpl", (char*)"{{value_json.buttonEnabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
else
{
removeHassTopic((char*)"switch", (char*)"button_enabled", uidString);
}
if((int)advancedOpenerConfigAclPrefs[15] == 1)
{
publishHassTopic("number",

View File

@@ -40,12 +40,12 @@ public:
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 publishFloat(const char* prefix, const char* topic, const float value, const uint8_t precision = 2, bool retain = false);
void publishInt(const char* prefix, const char* topic, const int value, bool retain = false);
void publishUInt(const char* prefix, const char* topic, const unsigned int value, bool retain = false);
void publishULong(const char* prefix, const char* topic, const unsigned long value, bool retain = false);
void publishBool(const char* prefix, const char* topic, const bool value, bool retain = false);
bool publishString(const char* prefix, const char* topic, const char* value, bool retain = false);
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction);
void publishHASSConfigAdditionalLockEntities(char* deviceType, const char* baseTopic, char* name, char* uidString);
@@ -56,6 +56,22 @@ public:
void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString);
void removeHASSConfig(char* uidString);
void removeHASSConfigTopic(char* deviceType, char* name, char* uidString);
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 = {}
);
void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
void removeTopic(const String& mqttPath, const String& mqttTopic);
void batteryTypeToString(const Nuki::BatteryType battype, char* str);
void advertisingModeToString(const Nuki::AdvertisingMode advmode, char* str);
@@ -87,24 +103,7 @@ private:
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,

View File

@@ -6,6 +6,7 @@
#include "Logger.h"
#include "RestartReason.h"
#include <ArduinoJson.h>
#include <ctype.h>
NetworkLock::NetworkLock(Network* network, Preferences* preferences, char* buffer, size_t bufferSize)
: _network(network),
@@ -16,6 +17,19 @@ NetworkLock::NetworkLock(Network* network, Preferences* preferences, char* buffe
memset(_authName, 0, sizeof(_authName));
_authName[0] = '\0';
_offTopics.reserve(10);
//_offTopics.push_back(mqtt_topic_official_mode);
_offTopics.push_back((char*)mqtt_topic_official_state);
_offTopics.push_back((char*)mqtt_topic_official_batteryCritical);
_offTopics.push_back((char*)mqtt_topic_official_batteryChargeState);
_offTopics.push_back((char*)mqtt_topic_official_batteryCharging);
_offTopics.push_back((char*)mqtt_topic_official_keypadBatteryCritical);
_offTopics.push_back((char*)mqtt_topic_official_doorsensorState);
_offTopics.push_back((char*)mqtt_topic_official_doorsensorBatteryCritical);
_offTopics.push_back((char*)mqtt_topic_official_connected);
_offTopics.push_back((char*)mqtt_topic_official_commandResponse);
_offTopics.push_back((char*)mqtt_topic_official_lockActionEvent);
_network->registerMqttReceiver(this);
}
@@ -88,6 +102,18 @@ void NetworkLock::initialize()
//_network->removeTopic(_mqttPath, mqtt_topic_presence);
}
if(!_preferences->getBool(preference_conf_info_enabled, false))
{
_network->removeTopic(_mqttPath, mqtt_topic_config_basic_json);
_network->removeTopic(_mqttPath, mqtt_topic_config_advanced_json);
_network->removeTopic(_mqttPath, mqtt_topic_config_button_enabled);
_network->removeTopic(_mqttPath, mqtt_topic_config_led_enabled);
_network->removeTopic(_mqttPath, mqtt_topic_config_led_brightness);
_network->removeTopic(_mqttPath, mqtt_topic_config_auto_unlock);
_network->removeTopic(_mqttPath, mqtt_topic_config_auto_lock);
_network->removeTopic(_mqttPath, mqtt_topic_config_single_lock);
}
if(_preferences->getBool(preference_keypad_control_enabled))
{
if(!_preferences->getBool(preference_disable_non_json, false))
@@ -116,6 +142,20 @@ void NetworkLock::initialize()
_network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--");
}
if(_preferences->getBool(preference_official_hybrid, false))
{
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
for(char* c=uidString; *c=toupper(*c); ++c);
strcpy(_offMqttPath, "nuki/");
strcat(_offMqttPath,uidString);
for(const auto& offTopic : _offTopics)
{
_network->subscribe(_offMqttPath, offTopic);
}
}
if(_preferences->getBool(preference_publish_authdata, false))
{
_network->subscribe(_mqttPath, mqtt_topic_lock_log_rolling_last);
@@ -170,6 +210,20 @@ void NetworkLock::onMqttDataReceived(const char* topic, byte* payload, const uns
if(atoi(value) > 0 && atoi(value) > _lastRollingLog) _lastRollingLog = atoi(value);
}
if(_preferences->getBool(preference_official_hybrid, false))
{
for(auto offTopic : _offTopics)
{
if(comparePrefixedPath(topic, offTopic, true))
{
if(_officialUpdateReceivedCallback != nullptr)
{
_officialUpdateReceivedCallback(offTopic, value);
}
}
}
}
if(comparePrefixedPath(topic, mqtt_topic_lock_action))
{
if(strcmp(value, "") == 0 ||
@@ -313,31 +367,49 @@ void NetworkLock::publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurne
JsonDocument json;
JsonDocument jsonBattery;
lockstateToString(keyTurnerState.lockState, str);
if(keyTurnerState.lockState != NukiLock::LockState::Undefined)
if(!_offConnected)
{
lockstateToString(keyTurnerState.lockState, str);
publishString(mqtt_topic_lock_state, str);
if(_haEnabled)
if(keyTurnerState.lockState != NukiLock::LockState::Undefined)
{
publishState(keyTurnerState.lockState);
publishString(mqtt_topic_lock_state, str);
if(_haEnabled)
{
publishState(keyTurnerState.lockState);
}
}
json["lock_state"] = str;
}
else
{
lockstateToString((NukiLock::LockState)_offState, str);
json["lock_state"] = str;
}
json["lock_state"] = str;
json["lockngo_state"] = (keyTurnerState.lockNgoTimer == 0 ? 0 : 1);
memset(&str, 0, sizeof(str));
triggerToString(keyTurnerState.trigger, str);
if(_firstTunerStatePublish || keyTurnerState.trigger != lastKeyTurnerState.trigger)
if(!_offConnected)
{
publishString(mqtt_topic_lock_trigger, str);
}
triggerToString(keyTurnerState.trigger, str);
json["trigger"] = str;
if(_firstTunerStatePublish || keyTurnerState.trigger != lastKeyTurnerState.trigger)
{
publishString(mqtt_topic_lock_trigger, str);
}
json["trigger"] = str;
}
else
{
triggerToString((NukiLock::Trigger)_offTrigger, str);
json["trigger"] = str;
}
char curTime[20];
sprintf(curTime, "%04d-%02d-%02d %02d:%02d:%02d", keyTurnerState.currentTimeYear, keyTurnerState.currentTimeMonth, keyTurnerState.currentTimeDay, keyTurnerState.currentTimeHour, keyTurnerState.currentTimeMinute, keyTurnerState.currentTimeSecond);
@@ -346,14 +418,23 @@ void NetworkLock::publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurne
json["nightModeActive"] = keyTurnerState.nightModeActive;
memset(&str, 0, sizeof(str));
lockactionToString(keyTurnerState.lastLockAction, str);
if(_firstTunerStatePublish || keyTurnerState.lastLockAction != lastKeyTurnerState.lastLockAction)
if(!_offConnected)
{
publishString(mqtt_topic_lock_last_lock_action, str);
}
lockactionToString(keyTurnerState.lastLockAction, str);
json["last_lock_action"] = str;
if(_firstTunerStatePublish || keyTurnerState.lastLockAction != lastKeyTurnerState.lastLockAction)
{
publishString(mqtt_topic_lock_last_lock_action, str);
}
json["last_lock_action"] = str;
}
else
{
lockactionToString((NukiLock::LockAction)_offLockAction, str);
json["last_lock_action"] = str;
}
memset(&str, 0, sizeof(str));
triggerToString(keyTurnerState.lastLockActionTrigger, str);
@@ -368,37 +449,45 @@ void NetworkLock::publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurne
}
json["lock_completion_status"] = str;
memset(&str, 0, sizeof(str));
NukiLock::doorSensorStateToString(keyTurnerState.doorSensorState, str);
if(_firstTunerStatePublish || keyTurnerState.doorSensorState != lastKeyTurnerState.doorSensorState)
if(!_offConnected)
{
publishString(mqtt_topic_lock_door_sensor_state, 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;
bool critical = (keyTurnerState.criticalBatteryState & 0b00000001) > 0;
bool charging = (keyTurnerState.criticalBatteryState & 0b00000010) > 0;
uint8_t level = (keyTurnerState.criticalBatteryState & 0b11111100) >> 1;
bool keypadCritical = (keyTurnerState.accessoryBatteryState & (1 << 7)) != 0 ? (keyTurnerState.accessoryBatteryState & (1 << 6)) != 0 : false;
jsonBattery["critical"] = critical ? "1" : "0";
jsonBattery["charging"] = charging ? "1" : "0";
jsonBattery["level"] = level;
jsonBattery["keypadCritical"] = keypadCritical ? "1" : "0";
if((_firstTunerStatePublish || keyTurnerState.criticalBatteryState != lastKeyTurnerState.criticalBatteryState) && !_preferences->getBool(preference_disable_non_json, false))
{
publishBool(mqtt_topic_battery_critical, critical);
publishBool(mqtt_topic_battery_charging, charging);
publishInt(mqtt_topic_battery_level, level);
}
if((_firstTunerStatePublish || keyTurnerState.accessoryBatteryState != lastKeyTurnerState.accessoryBatteryState) && !_preferences->getBool(preference_disable_non_json, false))
{
publishBool(mqtt_topic_battery_keypad_critical, keypadCritical);
}
}
json["door_sensor_state"] = str;
bool critical = (keyTurnerState.criticalBatteryState & 0b00000001) > 0;
bool charging = (keyTurnerState.criticalBatteryState & 0b00000010) > 0;
uint8_t level = (keyTurnerState.criticalBatteryState & 0b11111100) >> 1;
bool keypadCritical = (keyTurnerState.accessoryBatteryState & (1 << 7)) != 0 ? (keyTurnerState.accessoryBatteryState & (1 << 6)) != 0 : false;
jsonBattery["critical"] = critical ? "1" : "0";
jsonBattery["charging"] = charging ? "1" : "0";
jsonBattery["level"] = level;
jsonBattery["keypadCritical"] = keypadCritical ? "1" : "0";
if((_firstTunerStatePublish || keyTurnerState.criticalBatteryState != lastKeyTurnerState.criticalBatteryState) && !_preferences->getBool(preference_disable_non_json, false))
else
{
publishBool(mqtt_topic_battery_critical, critical);
publishBool(mqtt_topic_battery_charging, charging);
publishInt(mqtt_topic_battery_level, level);
}
if((_firstTunerStatePublish || keyTurnerState.accessoryBatteryState != lastKeyTurnerState.accessoryBatteryState) && !_preferences->getBool(preference_disable_non_json, false))
{
publishBool(mqtt_topic_battery_keypad_critical, keypadCritical);
NukiLock::doorSensorStateToString((NukiLock::DoorSensorState)_offDoorsensorState, str);
json["door_sensor_state"] = str;
}
json["auth_id"] = _authId;
@@ -430,12 +519,18 @@ void NetworkLock::publishState(NukiLock::LockState lockState)
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::Unlatched:
publishString(mqtt_topic_lock_ha_state, "open");
publishString(mqtt_topic_lock_binary_state, "unlocked");
break;
case NukiLock::LockState::Unlatching:
publishString(mqtt_topic_lock_ha_state, "opening");
publishString(mqtt_topic_lock_binary_state, "unlocked");
break;
case NukiLock::LockState::Uncalibrated:
case NukiLock::LockState::Calibration:
case NukiLock::LockState::BootRun:
@@ -506,22 +601,41 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[3], str);
entry["completionStatus"] = str;
entry["completionStatusVal"] = log.data[3];
break;
case NukiLock::LoggingType::KeypadAction:
memset(str, 0, sizeof(str));
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
entry["action"] = str;
switch(log.data[1])
{
case 0:
entry["trigger"] = "arrowkey";
break;
case 1:
entry["trigger"] = "code";
break;
case 2:
entry["trigger"] = "fingerprint";
break;
default:
entry["trigger"] = "Unknown";
break;
}
memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
entry["completionStatus"] = str;
entry["completionStatusVal"] = log.data[2];
if(log.data[2] == 9) entry["completionStatus"] = "notAuthorized";
else if (log.data[2] == 224) entry["completionStatus"] = "invalidCode";
else
{
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
entry["completionStatus"] = str;
}
entry["codeId"] = 256U*log.data[4]+log.data[3];
break;
case NukiLock::LoggingType::DoorSensor:
memset(str, 0, sizeof(str));
NukiLock::lockactionToString((NukiLock::LockAction)log.data[0], str);
switch(log.data[0])
{
case 0:
@@ -537,10 +651,6 @@ void NetworkLock::publishAuthorizationInfo(const std::list<NukiLock::LogEntry>&
entry["action"] = "Unknown";
break;
}
memset(str, 0, sizeof(str));
NukiLock::completionStatusToString((NukiLock::CompletionStatus)log.data[2], str);
entry["completionStatus"] = str;
break;
}
@@ -622,6 +732,9 @@ void NetworkLock::publishConfig(const NukiLock::Config &config)
JsonDocument json;
memset(_nukiName, 0, sizeof(_nukiName));
memcpy(_nukiName, config.name, sizeof(config.name));
json["nukiID"] = uidString;
json["name"] = config.name;
//json["latitude"] = config.latitude;
@@ -742,7 +855,9 @@ void NetworkLock::publishBleAddress(const std::string &address)
void NetworkLock::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount)
{
uint index = 0;
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
JsonDocument json;
for(const auto& entry : entries)
@@ -833,6 +948,51 @@ void NetworkLock::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries,
sprintf(allowedUntilTimeT, "%02d:%02d", entry.allowedUntilTimeHour, entry.allowedUntilTimeMin);
jsonEntry["allowedUntilTime"] = allowedUntilTimeT;
if(_preferences->getBool(preference_keypad_topic_per_entry, false))
{
basePath = mqtt_topic_keypad;
basePath.concat("/codes/");
basePath.concat(std::to_string(index).c_str());
jsonEntry["name_ha"] = entry.name;
jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer);
String basePathPrefix = "~";
basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str();
std::string baseCommand = std::string("{ \"action\": \"update\", \"codeId\": \"") + std::to_string(entry.codeId);
std::string enaCommand = baseCommand + (char*)"\", \"enabled\": \"1\" }";
std::string disCommand = baseCommand + (char*)"\", \"enabled\": \"0\" }";
std::string mqttDeviceName = std::string("keypad_") + std::to_string(index);
std::string uidStringPostfix = std::string("_") + mqttDeviceName;
char codeName[33];
memcpy(codeName, entry.name, sizeof(entry.name));
codeName[sizeof(entry.name)] = '\0';
std::string displayName = std::string("Keypad - ") + std::string((char*)codeName) + " - " + std::to_string(entry.codeId);
_network->publishHassTopic("switch",
mqttDeviceName.c_str(),
uidString,
uidStringPostfix.c_str(),
displayName.c_str(),
_nukiName,
baseTopic.c_str(),
String("~") + basePath.c_str(),
(char*)"SmartLock",
"",
"",
"diagnostic",
String("~") + mqtt_topic_keypad_json_action,
{ { (char*)"json_attr_t", (char*)basePathPrefixChr },
{ (char*)"pl_on", (char*)enaCommand.c_str() },
{ (char*)"pl_off", (char*)disCommand.c_str() },
{ (char*)"val_tpl", (char*)"{{value_json.enabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
++index;
}
@@ -866,7 +1026,7 @@ void NetworkLock::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries,
}
}
}
else if (maxKeypadCodeCount > 0)
else
{
for(int i=0; i<maxKeypadCodeCount; i++)
{
@@ -887,14 +1047,54 @@ void NetworkLock::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries,
_network->removeTopic(codeTopic, "createdSec");
_network->removeTopic(codeTopic, "lockCount");
}
_preferences->putUInt(preference_lock_max_keypad_code_count, 0);
for(int j=entries.size(); j<maxKeypadCodeCount; j++)
{
String codesTopic = _mqttPath;
codesTopic.concat(mqtt_topic_keypad_codes);
codesTopic.concat("/");
String codeTopic = "code_";
codeTopic.concat(std::to_string(j).c_str());
_network->removeTopic(codesTopic, codeTopic);
std::string mqttDeviceName = std::string("keypad_") + std::to_string(j);
_network->removeHassTopic((char*)"switch", (char*)mqttDeviceName.c_str(), uidString);
}
}
}
void NetworkLock::publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries)
void NetworkLock::publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry)
{
if(_preferences->getBool(preference_disable_non_json, false)) return;
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);
if(_preferences->getBool(preference_keypad_publish_code, false))
{
publishInt(concat(topic, "/code").c_str(), entry.code);
}
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::publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount)
{
uint index = 0;
char str[50];
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
JsonDocument json;
for(const auto& entry : timeControlEntries)
@@ -958,10 +1158,63 @@ void NetworkLock::publishTimeControl(const std::list<NukiLock::TimeControlEntry>
memset(str, 0, sizeof(str));
NukiLock::lockactionToString(entry.lockAction, str);
jsonEntry["lockAction"] = str;
if(_preferences->getBool(preference_timecontrol_topic_per_entry, false))
{
String basePath = mqtt_topic_timecontrol;
basePath.concat("/entries/");
basePath.concat(std::to_string(index).c_str());
jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer);
String basePathPrefix = "~";
basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str();
std::string baseCommand = std::string("{ \"action\": \"update\", \"entryId\": \"") + std::to_string(entry.entryId);
std::string enaCommand = baseCommand + (char*)"\", \"enabled\": \"1\" }";
std::string disCommand = baseCommand + (char*)"\", \"enabled\": \"0\" }";
std::string mqttDeviceName = std::string("timecontrol_") + std::to_string(index);
std::string uidStringPostfix = std::string("_") + mqttDeviceName;
std::string displayName = std::string("Timecontrol - ") + std::to_string(entry.entryId);
_network->publishHassTopic("switch",
mqttDeviceName.c_str(),
uidString,
uidStringPostfix.c_str(),
displayName.c_str(),
_nukiName,
baseTopic.c_str(),
String("~") + basePath.c_str(),
(char*)"SmartLock",
"",
"",
"diagnostic",
String("~") + mqtt_topic_timecontrol_action,
{ { (char*)"json_attr_t", (char*)basePathPrefixChr },
{ (char*)"pl_on", (char*)enaCommand.c_str() },
{ (char*)"pl_off", (char*)disCommand.c_str() },
{ (char*)"val_tpl", (char*)"{{value_json.enabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
++index;
}
serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_timecontrol_json, _buffer);
for(int j=timeControlEntries.size(); j<maxTimeControlEntryCount; j++)
{
String entriesTopic = _mqttPath;
entriesTopic.concat(mqtt_topic_timecontrol_entries);
entriesTopic.concat("/");
_network->removeTopic(entriesTopic, (char*)std::to_string(j).c_str());
std::string mqttDeviceName = std::string("timecontrol_") + std::to_string(j);
_network->removeHassTopic((char*)"switch", (char*)mqttDeviceName.c_str(), uidString);
}
}
void NetworkLock::publishConfigCommandResult(const char* result)
@@ -995,6 +1248,11 @@ void NetworkLock::setLockActionReceivedCallback(LockActionResult (*lockActionRec
_lockActionReceivedCallback = lockActionReceivedCallback;
}
void NetworkLock::setOfficialUpdateReceivedCallback(void (*officialUpdateReceivedCallback)(const char *, const char *))
{
_officialUpdateReceivedCallback = officialUpdateReceivedCallback;
}
void NetworkLock::setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char *))
{
_configUpdateReceivedCallback = configUpdateReceivedCallback;
@@ -1016,10 +1274,15 @@ void NetworkLock::setTimeControlCommandReceivedCallback(void (*timeControlComman
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
}
void NetworkLock::buildMqttPath(const char* path, char* outPath)
void NetworkLock::buildMqttPath(const char* path, char* outPath, bool offPath)
{
int offset = 0;
for(const char& c : _mqttPath)
char inPath[181] = {0};
if(offPath) memcpy(inPath, _offMqttPath, sizeof(_offMqttPath));
else memcpy(inPath, _mqttPath, sizeof(_mqttPath));
for(const char& c : inPath)
{
if(c == 0x00)
{
@@ -1038,10 +1301,10 @@ void NetworkLock::buildMqttPath(const char* path, char* outPath)
outPath[i+1] = 0x00;
}
bool NetworkLock::comparePrefixedPath(const char *fullPath, const char *subPath)
bool NetworkLock::comparePrefixedPath(const char *fullPath, const char *subPath, bool offPath)
{
char prefixedPath[500];
buildMqttPath(subPath, prefixedPath);
buildMqttPath(subPath, prefixedPath, offPath);
return strcmp(fullPath, prefixedPath) == 0;
}
@@ -1087,76 +1350,55 @@ void NetworkLock::removeHASSConfig(char *uidString)
_network->removeHASSConfig(uidString);
}
void NetworkLock::publishFloat(const char *topic, const float value, const uint8_t precision)
void NetworkLock::publishOffAction(const int value)
{
_network->publishFloat(_mqttPath, topic, value, precision);
_network->publishInt(_offMqttPath, mqtt_topic_official_lock_action, value, false);
}
void NetworkLock::publishInt(const char *topic, const int value)
void NetworkLock::publishFloat(const char *topic, const float value, const uint8_t precision, bool retain)
{
_network->publishInt(_mqttPath, topic, value);
_network->publishFloat(_mqttPath, topic, value, precision, retain);
}
void NetworkLock::publishUInt(const char *topic, const unsigned int value)
void NetworkLock::publishInt(const char *topic, const int value, bool retain)
{
_network->publishUInt(_mqttPath, topic, value);
_network->publishInt(_mqttPath, topic, value, retain);
}
void NetworkLock::publishBool(const char *topic, const bool value)
void NetworkLock::publishUInt(const char *topic, const unsigned int value, bool retain)
{
_network->publishBool(_mqttPath, topic, value);
_network->publishUInt(_mqttPath, topic, value, retain);
}
bool NetworkLock::publishString(const char *topic, const String &value)
void NetworkLock::publishBool(const char *topic, const bool value, bool retain)
{
_network->publishBool(_mqttPath, topic, value, retain);
}
bool NetworkLock::publishString(const char *topic, const String &value, bool retain)
{
char str[value.length() + 1];
memset(str, 0, sizeof(str));
memcpy(str, value.begin(), value.length());
return publishString(topic, str);
return publishString(topic, str, retain);
}
bool NetworkLock::publishString(const char *topic, const std::string &value)
bool NetworkLock::publishString(const char *topic, const std::string &value, bool retain)
{
char str[value.size() + 1];
memset(str, 0, sizeof(str));
memcpy(str, value.data(), value.length());
return publishString(topic, str);
return publishString(topic, str, retain);
}
bool NetworkLock::publishString(const char *topic, const char *value)
bool NetworkLock::publishString(const char *topic, const char *value, bool retain)
{
return _network->publishString(_mqttPath, topic, value);
return _network->publishString(_mqttPath, topic, value, retain);
}
void NetworkLock::publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry)
void NetworkLock::publishULong(const char *topic, const unsigned long value, bool retain)
{
if(_preferences->getBool(preference_disable_non_json, false)) return;
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);
if(_preferences->getBool(preference_keypad_publish_code, false))
{
publishInt(concat(topic, "/code").c_str(), entry.code);
}
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);
return _network->publishULong(_mqttPath, topic, value, retain);
}
String NetworkLock::concat(String a, String b)

View File

@@ -37,34 +37,59 @@ public:
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, 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 publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount);
void publishStatusUpdated(const bool statusUpdated);
void publishConfigCommandResult(const char* result);
void publishKeypadCommandResult(const char* result);
void publishKeypadJsonCommandResult(const char* result);
void publishTimeControlCommandResult(const char* result);
void publishOffAction(const int value);
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value));
void setOfficialUpdateReceivedCallback(void (*officialUpdateReceivedCallback)(const char* path, const char* value));
void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(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;
void publishFloat(const char* topic, const float value, const uint8_t precision = 2, bool retain = false);
void publishInt(const char* topic, const int value, bool retain = false);
void publishUInt(const char* topic, const unsigned int value, bool retain = false);
void publishULong(const char* topic, const unsigned long value, bool retain = false);
void publishBool(const char* topic, const bool value, bool retain = false);
bool publishString(const char* topic, const String& value, bool retain = false);
bool publishString(const char* topic, const std::string& value, bool retain = false);
bool publishString(const char* topic, const char* value, bool retain = false);
bool reconnected();
uint8_t queryCommands();
//uint8_t _offMode = 0;
uint8_t _offState = 0;
bool _offCritical = false;
uint8_t _offChargeState = 100;
bool _offCharging = false;
bool _offKeypadCritical = false;
uint8_t _offDoorsensorState = 0;
bool _offDoorsensorCritical = false;
bool _offConnected = false;
uint8_t _offCommandResponse = 0;
char* _offLockActionEvent;
uint8_t _offLockAction = 0;
uint8_t _offTrigger = 0;
uint32_t _offAuthId = 0;
uint32_t _offCodeId = 0;
uint8_t _offContext = 0;
uint32_t _authId = 0;
unsigned long _offCommandExecutedTs = 0;
NukiLock::LockAction _offCommand = (NukiLock::LockAction)0xff;
char _nukiName[33];
char _authName[33];
bool _authFound = false;
private:
bool comparePrefixedPath(const char* fullPath, const char* subPath);
bool comparePrefixedPath(const char* fullPath, const char* subPath, bool offPath = false);
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);
void buttonPressActionToString(const NukiLock::ButtonPressAction btnPressAction, char* str);
void homeKitStatusToString(const int hkstatus, char* str);
@@ -72,12 +97,14 @@ private:
String concat(String a, String b);
void buildMqttPath(const char* path, char* outPath);
void buildMqttPath(const char* path, char* outPath, bool offPath = false);
Network* _network;
Preferences* _preferences;
std::vector<char*> _offTopics;
char _mqttPath[181] = {0};
char _offMqttPath[181] = {0};
bool _firstTunerStatePublish = true;
unsigned long _lastMaintenanceTs = 0;
@@ -89,15 +116,13 @@ private:
uint _keypadCommandId = 0;
int _keypadCommandEnabled = 1;
uint8_t _queryCommands = 0;
uint32_t _authId = 0;
char _authName[33];
bool _authFound = false;
uint32_t _lastRollingLog = 0;
char* _buffer;
size_t _bufferSize;
LockActionResult (*_lockActionReceivedCallback)(const char* value) = nullptr;
void (*_officialUpdateReceivedCallback)(const char* path, const char* value) = nullptr;
void (*_configUpdateReceivedCallback)(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;

View File

@@ -68,6 +68,18 @@ void NetworkOpener::initialize()
_network->removeTopic(_mqttPath, mqtt_topic_battery_keypad_critical);
//_network->removeTopic(_mqttPath, mqtt_topic_presence);
}
if(!_preferences->getBool(preference_conf_info_enabled, false))
{
_network->removeTopic(_mqttPath, mqtt_topic_config_basic_json);
_network->removeTopic(_mqttPath, mqtt_topic_config_advanced_json);
_network->removeTopic(_mqttPath, mqtt_topic_config_button_enabled);
_network->removeTopic(_mqttPath, mqtt_topic_config_led_enabled);
_network->removeTopic(_mqttPath, mqtt_topic_config_led_brightness);
_network->removeTopic(_mqttPath, mqtt_topic_config_auto_unlock);
_network->removeTopic(_mqttPath, mqtt_topic_config_auto_lock);
_network->removeTopic(_mqttPath, mqtt_topic_config_single_lock);
}
if(_preferences->getBool(preference_keypad_control_enabled))
{
@@ -397,12 +409,15 @@ void NetworkOpener::publishState(NukiOpener::OpenerState lockState)
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::Open:
publishString(mqtt_topic_lock_ha_state, "open");
publishString(mqtt_topic_lock_binary_state, "unlocked");
break;
case NukiOpener::LockState::Opening:
publishString(mqtt_topic_lock_ha_state, "unlocking");
publishString(mqtt_topic_lock_ha_state, "opening");
publishString(mqtt_topic_lock_binary_state, "unlocked");
break;
case NukiOpener::LockState::Undefined:
@@ -479,10 +494,34 @@ void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntr
memset(str, 0, sizeof(str));
NukiOpener::lockactionToString((NukiOpener::LockAction)log.data[0], str);
entry["action"] = str;
switch(log.data[1])
{
case 0:
entry["trigger"] = "arrowkey";
break;
case 1:
entry["trigger"] = "code";
break;
case 2:
entry["trigger"] = "fingerprint";
break;
default:
entry["trigger"] = "Unknown";
break;
}
memset(str, 0, sizeof(str));
NukiOpener::completionStatusToString((NukiOpener::CompletionStatus)log.data[2], str);
entry["completionStatus"] = str;
if(log.data[2] == 9) entry["completionStatus"] = "notAuthorized";
else if (log.data[2] == 224) entry["completionStatus"] = "invalidCode";
else
{
NukiOpener::completionStatusToString((NukiOpener::CompletionStatus)log.data[2], str);
entry["completionStatus"] = str;
}
entry["codeId"] = 256U*log.data[4]+log.data[3];
break;
case NukiOpener::LoggingType::DoorbellRecognition:
switch(log.data[0] & 3)
@@ -530,8 +569,11 @@ void NetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::LogEntr
entry["geofence"] = log.data[2] == 1 ? "active" : "inactive";
entry["doorbellSuppression"] = log.data[3] == 1 ? "active" : "inactive";
entry["soundId"] = log.data[4];
memset(str, 0, sizeof(str));
NukiOpener::completionStatusToString((NukiOpener::CompletionStatus)log.data[5], str);
entry["completionStatus"] = str;
entry["codeId"] = 256U*log.data[7]+log.data[6];
break;
}
@@ -605,6 +647,9 @@ void NetworkOpener::publishConfig(const NukiOpener::Config &config)
itoa(config.nukiId, uidString, 16);
JsonDocument json;
memset(_nukiName, 0, sizeof(_nukiName));
memcpy(_nukiName, config.name, sizeof(config.name));
json["nukiID"] = uidString;
json["name"] = config.name;
@@ -758,7 +803,9 @@ void NetworkOpener::removeHASSConfig(char* uidString)
void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount)
{
uint index = 0;
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_opener_path);
JsonDocument json;
for(const auto& entry : entries)
@@ -771,14 +818,14 @@ void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entrie
auto jsonEntry = json.add<JsonVariant>();
jsonEntry["codeId"] = entry.codeId;
jsonEntry["enabled"] = entry.enabled;
jsonEntry["name"] = entry.name;
if(_preferences->getBool(preference_keypad_publish_code, false))
{
jsonEntry["code"] = entry.code;
}
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;
@@ -849,6 +896,51 @@ void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entrie
sprintf(allowedUntilTimeT, "%02d:%02d", entry.allowedUntilTimeHour, entry.allowedUntilTimeMin);
jsonEntry["allowedUntilTime"] = allowedUntilTimeT;
if(_preferences->getBool(preference_keypad_topic_per_entry, false))
{
basePath = mqtt_topic_keypad;
basePath.concat("/codes/");
basePath.concat(std::to_string(index).c_str());
jsonEntry["name_ha"] = entry.name;
jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer);
String basePathPrefix = "~";
basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str();
std::string baseCommand = std::string("{ \"action\": \"update\", \"codeId\": \"") + std::to_string(entry.codeId);
std::string enaCommand = baseCommand + (char*)"\", \"enabled\": \"1\" }";
std::string disCommand = baseCommand + (char*)"\", \"enabled\": \"0\" }";
std::string mqttDeviceName = std::string("keypad_") + std::to_string(index);
std::string uidStringPostfix = std::string("_") + mqttDeviceName;
char codeName[33];
memcpy(codeName, entry.name, sizeof(entry.name));
codeName[sizeof(entry.name)] = '\0';
std::string displayName = std::string("Keypad - ") + std::string((char*)codeName) + " - " + std::to_string(entry.codeId);
_network->publishHassTopic("switch",
mqttDeviceName.c_str(),
uidString,
uidStringPostfix.c_str(),
displayName.c_str(),
_nukiName,
baseTopic.c_str(),
String("~") + basePath.c_str(),
(char*)"SmartLock",
"",
"",
"diagnostic",
String("~") + mqtt_topic_keypad_json_action,
{ { (char*)"json_attr_t", (char*)basePathPrefixChr },
{ (char*)"pl_on", (char*)enaCommand.c_str() },
{ (char*)"pl_off", (char*)disCommand.c_str() },
{ (char*)"val_tpl", (char*)"{{value_json.enabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
++index;
}
@@ -868,7 +960,7 @@ void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entrie
++index;
}
if(!_preferences->getBool(preference_keypad_publish_code, false))
{
for(int i=0; i<maxKeypadCodeCount; i++)
@@ -882,7 +974,7 @@ void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entrie
}
}
}
else if (maxKeypadCodeCount > 0)
else
{
for(int i=0; i<maxKeypadCodeCount; i++)
{
@@ -903,14 +995,26 @@ void NetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entrie
_network->removeTopic(codeTopic, "createdSec");
_network->removeTopic(codeTopic, "lockCount");
}
_preferences->putUInt(preference_lock_max_keypad_code_count, 0);
for(int j=entries.size(); j<maxKeypadCodeCount; j++)
{
String codesTopic = _mqttPath;
codesTopic.concat(mqtt_topic_keypad_codes);
codesTopic.concat("/");
_network->removeTopic(codesTopic, (char*)std::to_string(j).c_str());
std::string mqttDeviceName = std::string("keypad_") + std::to_string(j);
_network->removeHassTopic((char*)"switch", (char*)mqttDeviceName.c_str(), uidString);
}
}
}
void NetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries)
void NetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount)
{
uint index = 0;
char str[50];
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_opener_path);
JsonDocument json;
for(const auto& entry : timeControlEntries)
@@ -974,10 +1078,62 @@ void NetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeControlEn
memset(str, 0, sizeof(str));
NukiOpener::lockactionToString(entry.lockAction, str);
jsonEntry["lockAction"] = str;
if(_preferences->getBool(preference_timecontrol_topic_per_entry, false))
{
String basePath = mqtt_topic_timecontrol;
basePath.concat("/entries/");
basePath.concat(std::to_string(index).c_str());
jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer);
String basePathPrefix = "~";
basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str();
std::string baseCommand = std::string("{ \"action\": \"update\", \"entryId\": \"") + std::to_string(entry.entryId);
std::string enaCommand = baseCommand + (char*)"\", \"enabled\": \"1\" }";
std::string disCommand = baseCommand + (char*)"\", \"enabled\": \"0\" }";
std::string mqttDeviceName = std::string("timecontrol_") + std::to_string(index);
std::string uidStringPostfix = std::string("_") + mqttDeviceName;
std::string displayName = std::string("Timecontrol - ") + std::to_string(entry.entryId);
_network->publishHassTopic("switch",
mqttDeviceName.c_str(),
uidString,
uidStringPostfix.c_str(),
displayName.c_str(),
_nukiName,
baseTopic.c_str(),
String("~") + basePath.c_str(),
(char*)"Opener",
"",
"",
"diagnostic",
String("~") + mqtt_topic_timecontrol_action,
{ { (char*)"json_attr_t", (char*)basePathPrefixChr },
{ (char*)"pl_on", (char*)enaCommand.c_str() },
{ (char*)"pl_off", (char*)disCommand.c_str() },
{ (char*)"val_tpl", (char*)"{{value_json.enabled}}" },
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
++index;
}
serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_timecontrol_json, _buffer);
for(int j=timeControlEntries.size(); j<maxTimeControlEntryCount; j++)
{
String entriesTopic = _mqttPath;
entriesTopic.concat(mqtt_topic_timecontrol_entries);
entriesTopic.concat("/");
_network->removeTopic(entriesTopic, (char*)std::to_string(j).c_str());
std::string mqttDeviceName = std::string("timecontrol_") + std::to_string(j);
_network->removeHassTopic((char*)"switch", (char*)mqttDeviceName.c_str(), uidString);
}
}
void NetworkOpener::publishConfigCommandResult(const char* result)
@@ -1032,45 +1188,45 @@ void NetworkOpener::setTimeControlCommandReceivedCallback(void (*timeControlComm
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
}
void NetworkOpener::publishFloat(const char *topic, const float value, const uint8_t precision)
void NetworkOpener::publishFloat(const char *topic, const float value, const uint8_t precision, bool retain)
{
_network->publishFloat(_mqttPath, topic, value, precision);
_network->publishFloat(_mqttPath, topic, value, precision, retain);
}
void NetworkOpener::publishInt(const char *topic, const int value)
void NetworkOpener::publishInt(const char *topic, const int value, bool retain)
{
_network->publishInt(_mqttPath, topic, value);
_network->publishInt(_mqttPath, topic, value, retain);
}
void NetworkOpener::publishUInt(const char *topic, const unsigned int value)
void NetworkOpener::publishUInt(const char *topic, const unsigned int value, bool retain)
{
_network->publishUInt(_mqttPath, topic, value);
_network->publishUInt(_mqttPath, topic, value, retain);
}
void NetworkOpener::publishBool(const char *topic, const bool value)
void NetworkOpener::publishBool(const char *topic, const bool value, bool retain)
{
_network->publishBool(_mqttPath, topic, value);
_network->publishBool(_mqttPath, topic, value, retain);
}
void NetworkOpener::publishString(const char *topic, const String &value)
void NetworkOpener::publishString(const char *topic, const String &value, bool retain)
{
char str[value.length() + 1];
memset(str, 0, sizeof(str));
memcpy(str, value.begin(), value.length());
publishString(topic, str);
publishString(topic, str, retain);
}
void NetworkOpener::publishString(const char *topic, const std::string &value)
void NetworkOpener::publishString(const char *topic, const std::string &value, bool retain)
{
char str[value.size() + 1];
memset(str, 0, sizeof(str));
memcpy(str, value.data(), value.length());
publishString(topic, str);
publishString(topic, str, retain);
}
void NetworkOpener::publishString(const char* topic, const char* value)
void NetworkOpener::publishString(const char* topic, const char* value, bool retain)
{
_network->publishString(_mqttPath, topic, value);
_network->publishString(_mqttPath, topic, value, retain);
}
void NetworkOpener::publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry)

View File

@@ -34,7 +34,7 @@ public:
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, 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 publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount);
void publishStatusUpdated(const bool statusUpdated);
void publishConfigCommandResult(const char* result);
void publishKeypadCommandResult(const char* result);
@@ -50,17 +50,18 @@ public:
bool reconnected();
uint8_t queryCommands();
char _nukiName[33];
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 publishFloat(const char* topic, const float value, const uint8_t precision = 2, bool retain = false);
void publishInt(const char* topic, const int value, bool retain = false);
void publishUInt(const char* topic, const unsigned int value, bool retain = false);
void publishBool(const char* topic, const bool value, bool retain = false);
void publishString(const char* topic, const String& value, bool retain = false);
void publishString(const char* topic, const std::string& value, bool retain = false);
void publishString(const char* topic, const char* value, bool retain = false);
void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry);
void buildMqttPath(const char* path, char* outPath);

File diff suppressed because it is too large Load Diff

View File

@@ -100,6 +100,7 @@ private:
int _restartBeaconTimeout = 0; // seconds
bool _publishAuthData = false;
bool _clearAuthData = false;
bool _taskRunning = false;
int _nrOfRetries = 0;
int _retryDelay = 0;
int _retryCount = 0;
@@ -107,7 +108,6 @@ private:
int _retryLockstateCount = 0;
unsigned long _nextRetryTs = 0;
std::vector<uint16_t> _keypadCodeIds;
std::vector<uint32_t> _keypadCodes;
std::vector<uint8_t> _timeControlIds;
NukiOpener::OpenerState _lastKeyTurnerState;
@@ -128,6 +128,7 @@ private:
bool _hasKeypad = false;
bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0;
uint _maxTimeControlEntryCount = 0;
bool _configRead = false;
long _rssiPublishInterval = 0;
unsigned long _nextLockStateUpdateTs = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -46,12 +46,14 @@ public:
private:
static LockActionResult onLockActionReceivedCallback(const char* value);
static void onOfficialUpdateReceivedCallback(const char* topic, const char* value);
static void onConfigUpdateReceivedCallback(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 onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
void onOfficialUpdateReceived(const char* topic, const char* value);
void onConfigUpdateReceived(const char* value);
void onKeypadJsonCommandReceived(const char* value);
void onTimeControlCommandReceived(const char* value);
@@ -88,14 +90,15 @@ private:
Gpio* _gpio = nullptr;
Preferences* _preferences;
int _intervalLockstate = 0; // seconds
int _intervalHybridLockstate = 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;
bool _taskRunning = false;
std::vector<uint16_t> _keypadCodeIds;
std::vector<uint32_t> _keypadCodes;
std::vector<uint8_t> _timeControlIds;
NukiLock::KeyTurnerState _lastKeyTurnerState;
@@ -116,6 +119,7 @@ private:
bool _hasKeypad = false;
bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0;
uint _maxTimeControlEntryCount = 0;
bool _configRead = false;
int _nrOfRetries = 0;
int _retryDelay = 0;
@@ -123,8 +127,9 @@ private:
int _retryConfigCount = 0;
int _retryLockstateCount = 0;
long _rssiPublishInterval = 0;
unsigned long _nextRetryTs = 0;
unsigned long _nextRetryTs = 0;
unsigned long _nextLockStateUpdateTs = 0;
unsigned long _nextHybridLockStateUpdateTs = 0;
unsigned long _nextBatteryReportTs = 0;
unsigned long _nextConfigUpdateTs = 0;
unsigned long _waitAuthLogUpdateTs = 0;

View File

@@ -24,6 +24,8 @@
#define preference_check_updates (char*)"checkupdates"
#define preference_lock_max_keypad_code_count (char*)"maxkpad"
#define preference_opener_max_keypad_code_count (char*)"opmaxkpad"
#define preference_lock_max_timecontrol_entry_count (char*)"maxtc"
#define preference_opener_max_timecontrol_entry_count (char*)"opmaxtc"
#define preference_mqtt_ca (char*)"mqttca"
#define preference_mqtt_crt (char*)"mqttcrt"
#define preference_mqtt_key (char*)"mqttkey"
@@ -50,17 +52,21 @@
#define preference_query_interval_keypad (char*)"kpInterval"
#define preference_access_level (char*)"accLvl"
#define preference_keypad_info_enabled (char*)"kpInfoEnabled"
#define preference_keypad_topic_per_entry (char*)"kpPerEntry"
#define preference_keypad_control_enabled (char*)"kpCntrlEnabled"
#define preference_keypad_publish_code (char*)"kpPubCode"
#define preference_timecontrol_control_enabled (char*)"tcCntrlEnabled"
#define preference_timecontrol_topic_per_entry (char*)"tcPerEntry"
#define preference_timecontrol_info_enabled (char*)"tcInfoEnabled"
#define preference_publish_authdata (char*)"pubAuth"
#define preference_acl (char*)"aclLckOpn"
#define preference_conf_info_enabled (char*)"cnfInfoEnabled"
#define preference_conf_lock_basic_acl (char*)"confLckBasAcl"
#define preference_conf_lock_advanced_acl (char*)"confLckAdvAcl"
#define preference_conf_opener_basic_acl (char*)"confOpnBasAcl"
#define preference_conf_opener_advanced_acl (char*)"confOpnAdvAcl"
#define preference_register_as_app (char*)"regAsApp" // true = register as hub; false = register as app
#define preference_register_opener_as_app (char*)"regOpnAsApp"
#define preference_command_nr_of_retries (char*)"nrRetry"
#define preference_command_retry_delay (char*)"rtryDelay"
#define preference_cred_user (char*)"crdusr"
@@ -84,6 +90,10 @@
#define preference_enable_bootloop_reset (char*)"enabtlprst"
#define preference_buffer_size (char*)"buffsize"
#define preference_disable_non_json (char*)"disnonjson"
#define preference_official_hybrid (char*)"offHybrid"
#define preference_official_hybrid_actions (char*)"hybridAct"
#define preference_official_hybrid_retry (char*)"hybridRtry"
#define preference_query_interval_hybrid_lockstate (char*)"hybridTimer"
class DebugPreferences
{
@@ -92,18 +102,18 @@ private:
{
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_webserver_enabled,
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_enable_bootloop_reset, preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url,
preference_buffer_size, 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_hostname, preference_find_best_rssi,
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_keypad_info_enabled, preference_keypad_publish_code, preference_acl, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled,
preference_conf_lock_basic_acl, preference_conf_lock_advanced_acl, preference_conf_opener_basic_acl, preference_conf_opener_advanced_acl,
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_disable_non_json,
preference_has_mac_saved, preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2, preference_latest_version,
preference_task_size_network, preference_task_size_nuki, preference_task_size_pd, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries
preference_opener_continuous_mode, preference_mqtt_opener_path, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_lock_max_timecontrol_entry_count,
preference_opener_max_timecontrol_entry_count, preference_enable_bootloop_reset, preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_mqtt_hass_discovery,
preference_mqtt_hass_cu_url, preference_buffer_size, 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_hostname, preference_find_best_rssi,
preference_network_timeout, preference_restart_on_disconnect, preference_restart_ble_beacon_lost, preference_query_interval_lockstate, preference_timecontrol_topic_per_entry,
preference_keypad_topic_per_entry, preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_keypad_control_enabled,
preference_keypad_info_enabled, preference_keypad_publish_code, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_conf_info_enabled,
preference_register_as_app, preference_register_opener_as_app, preference_command_nr_of_retries, preference_command_retry_delay, preference_cred_user, preference_cred_password,
preference_disable_non_json, preference_publish_authdata, preference_publish_debug_info, preference_presence_detection_timeout, preference_official_hybrid, preference_query_interval_hybrid_lockstate,
preference_official_hybrid_actions, preference_official_hybrid_retry, preference_has_mac_saved, preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2,
preference_latest_version, preference_task_size_network, preference_task_size_nuki, preference_task_size_pd, preference_authlog_max_entries, preference_keypad_max_entries,
preference_timecontrol_max_entries
};
std::vector<char*> _redact =
{
@@ -115,9 +125,10 @@ private:
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_enable_bootloop_reset, preference_webserver_enabled, preference_find_best_rssi, preference_restart_on_disconnect, preference_keypad_control_enabled,
preference_keypad_info_enabled, preference_keypad_publish_code, 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, preference_disable_non_json
preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled, preference_find_best_rssi, preference_restart_on_disconnect,
preference_keypad_control_enabled, preference_keypad_info_enabled, preference_keypad_publish_code, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled,
preference_register_as_app, preference_register_opener_as_app, preference_ip_dhcp_enabled, preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info,
preference_network_wifi_fallback_disabled, preference_official_hybrid, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json
};
const bool isRedacted(const char* key) const

View File

@@ -430,6 +430,28 @@ bool WebCfgServer::processArgs(String& message)
_preferences->putBool(preference_check_updates, (value == "1"));
configChanged = true;
}
else if(key == "OFFHYBRID")
{
_preferences->putBool(preference_official_hybrid, (value == "1"));
if((value == "1")) _preferences->putBool(preference_register_as_app, true);
configChanged = true;
}
else if(key == "HYBRIDACT")
{
_preferences->putBool(preference_official_hybrid_actions, (value == "1"));
if(value == "1") _preferences->putBool(preference_register_as_app, true);
configChanged = true;
}
else if(key == "HYBRIDTIMER")
{
_preferences->putInt(preference_query_interval_hybrid_lockstate, value.toInt());
configChanged = true;
}
else if(key == "HYBRIDRETRY")
{
_preferences->putBool(preference_official_hybrid_retry, (value == "1"));
configChanged = true;
}
else if(key == "DISNONJSON")
{
_preferences->putBool(preference_disable_non_json, (value == "1"));
@@ -565,6 +587,11 @@ bool WebCfgServer::processArgs(String& message)
{
aclLvlChanged = true;
}
else if(key == "CONFPUB")
{
_preferences->putBool(preference_conf_info_enabled, (value == "1"));
configChanged = true;
}
else if(key == "KPPUB")
{
_preferences->putBool(preference_keypad_info_enabled, (value == "1"));
@@ -585,6 +612,16 @@ bool WebCfgServer::processArgs(String& message)
_preferences->putBool(preference_timecontrol_info_enabled, (value == "1"));
configChanged = true;
}
else if(key == "KPPER")
{
_preferences->putBool(preference_keypad_topic_per_entry, (value == "1"));
configChanged = true;
}
else if(key == "TCPER")
{
_preferences->putBool(preference_timecontrol_topic_per_entry, (value == "1"));
configChanged = true;
}
else if(key == "TCENA")
{
_preferences->putBool(preference_timecontrol_control_enabled, (value == "1"));
@@ -956,6 +993,11 @@ bool WebCfgServer::processArgs(String& message)
_preferences->putBool(preference_register_as_app, (value == "1"));
configChanged = true;
}
else if(key == "REGAPPOPN")
{
_preferences->putBool(preference_register_opener_as_app, (value == "1"));
configChanged = true;
}
else if(key == "LOCKENA")
{
_preferences->putBool(preference_lock_enabled, (value == "1"));
@@ -1177,9 +1219,9 @@ void WebCfgServer::buildCredHtml(String &response)
response.concat("<form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Credentials</h3>");
response.concat("<table>");
printInputField(response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, false, true);
printInputField(response, "CREDPASS", "Password", "*", 30, true, true);
printInputField(response, "CREDPASSRE", "Retype password", "*", 30, true);
printInputField(response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "", false, true);
printInputField(response, "CREDPASS", "Password", "*", 30, "", true, true);
printInputField(response, "CREDPASSRE", "Retype password", "*", 30, "", true);
response.concat("</table>");
response.concat("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.concat("</form>");
@@ -1189,7 +1231,7 @@ void WebCfgServer::buildCredHtml(String &response)
response.concat("<br><br><form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Nuki Lock PIN</h3>");
response.concat("<table>");
printInputField(response, "NUKIPIN", "PIN Code (# to clear)", "*", 20, true);
printInputField(response, "NUKIPIN", "PIN Code (# to clear)", "*", 20, "", true);
response.concat("</table>");
response.concat("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.concat("</form>");
@@ -1200,7 +1242,7 @@ void WebCfgServer::buildCredHtml(String &response)
response.concat("<br><br><form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Nuki Opener PIN</h3>");
response.concat("<table>");
printInputField(response, "NUKIOPPIN", "PIN Code (# to clear)", "*", 20, true);
printInputField(response, "NUKIOPPIN", "PIN Code (# to clear)", "*", 20, "", true);
response.concat("</table>");
response.concat("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.concat("</form>");
@@ -1215,7 +1257,7 @@ void WebCfgServer::buildCredHtml(String &response)
String message = "Type ";
message.concat(_confirmCode);
message.concat(" to confirm unpair");
printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10);
printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10, "");
response.concat("</table>");
response.concat("<br><button type=\"submit\">OK</button></form>");
}
@@ -1228,7 +1270,7 @@ void WebCfgServer::buildCredHtml(String &response)
String message = "Type ";
message.concat(_confirmCode);
message.concat(" to confirm unpair");
printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10);
printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10, "");
response.concat("</table>");
response.concat("<br><button type=\"submit\">OK</button></form>");
}
@@ -1240,7 +1282,7 @@ void WebCfgServer::buildCredHtml(String &response)
String message = "Type ";
message.concat(_confirmCode);
message.concat(" to confirm factory reset");
printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10);
printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10, "");
printCheckBox(response, "WIFI", "Also reset WiFi settings", false, "");
response.concat("</table>");
response.concat("<br><button type=\"submit\">OK</button></form>");
@@ -1304,17 +1346,17 @@ void WebCfgServer::buildMqttConfigHtml(String &response)
response.concat("<form class=\"adapt\" method=\"post\" action=\"savecfg\">");
response.concat("<h3>Basic MQTT and Network Configuration</h3>");
response.concat("<table>");
printInputField(response, "HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100);
printInputField(response, "MQTTSERVER", "MQTT Broker", _preferences->getString(preference_mqtt_broker).c_str(), 100);
printInputField(response, "MQTTPORT", "MQTT Broker port", _preferences->getInt(preference_mqtt_broker_port), 5);
printInputField(response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, false, true);
printInputField(response, "MQTTPASS", "MQTT Password", "*", 30, true, true);
printInputField(response, "HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100, "");
printInputField(response, "MQTTSERVER", "MQTT Broker", _preferences->getString(preference_mqtt_broker).c_str(), 100, "");
printInputField(response, "MQTTPORT", "MQTT Broker port", _preferences->getInt(preference_mqtt_broker_port), 5, "");
printInputField(response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true);
printInputField(response, "MQTTPASS", "MQTT Password", "*", 30, "", true, true);
response.concat("</table><br>");
response.concat("<h3>Advanced MQTT and Network Configuration</h3>");
response.concat("<table>");
printInputField(response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30);
printInputField(response, "HASSCUURL", "Home Assistant device configuration URL (empty to use http://LOCALIP; fill when using a reverse proxy for example)", _preferences->getString(preference_mqtt_hass_cu_url).c_str(), 261);
printInputField(response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, "");
printInputField(response, "HASSCUURL", "Home Assistant device configuration URL (empty to use http://LOCALIP; fill when using a reverse proxy for example)", _preferences->getString(preference_mqtt_hass_cu_url).c_str(), 261, "");
if(_nukiOpener != nullptr) printCheckBox(response, "OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), "");
printTextarea(response, "MQTTCA", "MQTT SSL CA Certificate (*, optional)", _preferences->getString(preference_mqtt_ca).c_str(), TLS_CA_MAX_SIZE, _network->encryptionSupported(), true);
printTextarea(response, "MQTTCRT", "MQTT SSL Client Certificate (*, optional)", _preferences->getString(preference_mqtt_crt).c_str(), TLS_CERT_MAX_SIZE, _network->encryptionSupported(), true);
@@ -1322,22 +1364,26 @@ void WebCfgServer::buildMqttConfigHtml(String &response)
printDropDown(response, "NWHW", "Network hardware", String(_preferences->getInt(preference_network_hardware)), getNetworkDetectionOptions());
printCheckBox(response, "NWHWWIFIFB", "Disable fallback to Wi-Fi / Wi-Fi config portal", _preferences->getBool(preference_network_wifi_fallback_disabled), "");
printCheckBox(response, "BESTRSSI", "Connect to AP with the best signal in an environment with multiple APs with the same SSID", _preferences->getBool(preference_find_best_rssi), "");
printInputField(response, "RSSI", "RSSI Publish interval (seconds; -1 to disable)", _preferences->getInt(preference_rssi_publish_interval), 6);
printInputField(response, "NETTIMEOUT", "Network Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5);
printInputField(response, "RSSI", "RSSI Publish interval (seconds; -1 to disable)", _preferences->getInt(preference_rssi_publish_interval), 6, "");
printInputField(response, "NETTIMEOUT", "Network Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5, "");
printCheckBox(response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect), "");
printCheckBox(response, "MQTTLOG", "Enable MQTT logging", _preferences->getBool(preference_mqtt_log_enabled), "");
printCheckBox(response, "CHECKUPDATE", "Check for Firmware Updates every 24h", _preferences->getBool(preference_check_updates), "");
printCheckBox(response, "DISNONJSON", "Disable some extraneous non-JSON topics", _preferences->getBool(preference_disable_non_json), "");
printCheckBox(response, "OFFHYBRID", "Enable hybrid official MQTT and Nuki Hub setup", _preferences->getBool(preference_official_hybrid), "");
printCheckBox(response, "HYBRIDACT", "Enable sending actions through official MQTT", _preferences->getBool(preference_official_hybrid_actions), "");
printInputField(response, "HYBRIDTIMER", "Time between status updates when official MQTT is offline (seconds)", _preferences->getInt(preference_query_interval_hybrid_lockstate), 5, "");
printCheckBox(response, "HYBRIDRETRY", "Retry command sent using official MQTT over BLE if failed", _preferences->getBool(preference_official_hybrid_retry), "");
response.concat("</table>");
response.concat("* If no encryption is configured for the MQTT broker, leave empty. Only supported for Wi-Fi connections.<br><br>");
response.concat("<h3>IP Address assignment</h3>");
response.concat("<table>");
printCheckBox(response, "DHCPENA", "Enable DHCP", _preferences->getBool(preference_ip_dhcp_enabled), "");
printInputField(response, "IPADDR", "Static IP address", _preferences->getString(preference_ip_address).c_str(), 15);
printInputField(response, "IPSUB", "Subnet", _preferences->getString(preference_ip_subnet).c_str(), 15);
printInputField(response, "IPGTW", "Default gateway", _preferences->getString(preference_ip_gateway).c_str(), 15);
printInputField(response, "DNSSRV", "DNS Server", _preferences->getString(preference_ip_dns_server).c_str(), 15);
printInputField(response, "IPADDR", "Static IP address", _preferences->getString(preference_ip_address).c_str(), 15, "");
printInputField(response, "IPSUB", "Subnet", _preferences->getString(preference_ip_subnet).c_str(), 15, "");
printInputField(response, "IPGTW", "Default gateway", _preferences->getString(preference_ip_gateway).c_str(), 15, "");
printInputField(response, "DNSSRV", "DNS Server", _preferences->getString(preference_ip_dns_server).c_str(), 15, "");
response.concat("</table>");
response.concat("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
@@ -1356,18 +1402,20 @@ void WebCfgServer::buildAdvancedConfigHtml(String &response)
response.concat(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Enabled" : "Disabled");
response.concat("</td></tr>");
printCheckBox(response, "BTLPRST", "Enable Bootloop prevention (Try to reset these settings to default on bootloop)", true, "");
printInputField(response, "BUFFSIZE", "Char buffer size (min 4096, max 32768)", _preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE), 6);
printInputField(response, "TSKNTWK", "Task size Network (min 12288, max 32768)", _preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), 6);
printInputField(response, "TSKNUKI", "Task size Nuki (min 8192, max 32768)", _preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), 6);
printInputField(response, "TSKPD", "Task size Presence Detection (min 1024, max 4048)", _preferences->getInt(preference_task_size_pd, PD_TASK_SIZE), 6);
printInputField(response, "ALMAX", "Max auth log entries (min 1, max 50)", _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 3);
printInputField(response, "KPMAX", "Max keypad entries (min 1, max 100)", _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD), 3);
printInputField(response, "TCMAX", "Max timecontrol entries (min 1, max 50)", _preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL), 3);
printInputField(response, "BUFFSIZE", "Char buffer size (min 4096, max 32768)", _preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE), 6, "");
printInputField(response, "TSKNTWK", "Task size Network (min 12288, max 32768)", _preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), 6, "");
printInputField(response, "TSKNUKI", "Task size Nuki (min 8192, max 32768)", _preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), 6, "");
printInputField(response, "TSKPD", "Task size Presence Detection (min 1024, max 4048)", _preferences->getInt(preference_task_size_pd, PD_TASK_SIZE), 6, "");
printInputField(response, "ALMAX", "Max auth log entries (min 1, max 50)", _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 3, "inputmaxauthlog");
printInputField(response, "KPMAX", "Max keypad entries (min 1, max 100)", _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD), 3, "inputmaxkeypad");
printInputField(response, "TCMAX", "Max timecontrol entries (min 1, max 50)", _preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL), 3, "inputmaxtimecontrol");
response.concat("<tr><td>Advised minimum char buffer size based on current settings</td><td id=\"mincharbuffer\"></td>");
response.concat("<tr><td>Advised minimum network task size based on current settings</td><td id=\"minnetworktask\"></td>");
response.concat("</table>");
response.concat("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.concat("</form>");
response.concat("</body></html>");
response.concat("</body><script>window.onload=function(){ document.getElementById(\"inputmaxauthlog\").addEventListener(\"keyup\", calculate);document.getElementById(\"inputmaxkeypad\").addEventListener(\"keyup\", calculate);document.getElementById(\"inputmaxtimecontrol\").addEventListener(\"keyup\", calculate); calculate(); }; function calculate() { var authlog = document.getElementById(\"inputmaxauthlog\").value; var keypad = document.getElementById(\"inputmaxkeypad\").value; var timecontrol = document.getElementById(\"inputmaxtimecontrol\").value; var charbuf = 0; var networktask = 0; var sizeauthlog = 0; var sizekeypad = 0; var sizetimecontrol = 0; if(authlog > 0) { sizeauthlog = 280 * authlog; } if(keypad > 0) { sizekeypad = 350 * keypad; } if(timecontrol > 0) { sizetimecontrol = 120 * timecontrol; } charbuf = sizetimecontrol; networktask = 10240 + sizetimecontrol; if(sizeauthlog>sizekeypad && sizeauthlog>sizetimecontrol) { charbuf = sizeauthlog; networktask = 10240 + sizeauthlog;} else if(sizekeypad>sizeauthlog && sizekeypad>sizetimecontrol) { charbuf = sizekeypad; networktask = 10240 + sizekeypad;} if(charbuf<4096) { charbuf = 4096; } else if (charbuf>32768) { charbuf = 32768; } if(networktask<12288) { networktask = 12288; } else if (networktask>32768) { networktask = 32768; } document.getElementById(\"mincharbuffer\").innerHTML = charbuf; document.getElementById(\"minnetworktask\").innerHTML = networktask; }</script></html>");
}
void WebCfgServer::buildStatusHtml(String &response)
@@ -1400,7 +1448,7 @@ void WebCfgServer::buildStatusHtml(String &response)
if(_nuki->isPaired())
{
json["lockPin"] = pinStateToString(_preferences->getInt(preference_lock_pin_status, 4));
lockDone = true;
if(strcmp(lockStateArr, "undefined") != 0) lockDone = true;
}
else json["lockPin"] = "Not Paired";
}
@@ -1419,7 +1467,7 @@ void WebCfgServer::buildStatusHtml(String &response)
if(_nukiOpener->isPaired())
{
json["openerPin"] = pinStateToString(_preferences->getInt(preference_opener_pin_status, 4));
openerDone = true;
if(strcmp(openerStateArr, "undefined") != 0) openerDone = true;
}
else json["openerPin"] = "Not Paired";
}
@@ -1462,14 +1510,17 @@ void WebCfgServer::buildAccLvlHtml(String &response)
response.concat("<input type=\"hidden\" name=\"ACLLVLCHANGED\" value=\"1\">");
response.concat("<h3>Nuki General Access Control</h3>");
response.concat("<table><tr><th>Setting</th><th>Enabled</th></tr>");
printCheckBox(response, "CONFPUB", "Publish Nuki configuration information", _preferences->getBool(preference_conf_info_enabled, true), "");
if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad()))
{
printCheckBox(response, "KPPUB", "Publish keypad entries information", _preferences->getBool(preference_keypad_info_enabled), "");
printCheckBox(response, "KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), "");
printCheckBox(response, "KPCODE", "Also publish keypad codes (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_publish_code, false), "");
printCheckBox(response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), "");
}
printCheckBox(response, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), "");
printCheckBox(response, "TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), "");
printCheckBox(response, "TCENA", "Add, modify and delete time control entries", _preferences->getBool(preference_timecontrol_control_enabled), "");
printCheckBox(response, "PUBAUTH", "Publish authorization log (may reduce battery life)", _preferences->getBool(preference_publish_authdata), "");
response.concat("</table><br>");
@@ -1548,9 +1599,9 @@ void WebCfgServer::buildAccLvlHtml(String &response)
}
if(_nukiOpener != nullptr)
{
uint32_t basicOpenerConfigAclPrefs[16];
uint32_t basicOpenerConfigAclPrefs[14];
_preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs));
uint32_t advancedOpenerConfigAclPrefs[22];
uint32_t advancedOpenerConfigAclPrefs[20];
_preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs));
response.concat("<h3>Nuki Opener Access Control</h3>");
@@ -1630,32 +1681,33 @@ void WebCfgServer::buildNukiConfigHtml(String &response)
if(_preferences->getBool(preference_lock_enabled))
{
printInputField(response, "MQTTPATH", "MQTT Nuki Smartlock Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180);
printInputField(response, "MQTTPATH", "MQTT Nuki Smartlock Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, "");
}
printCheckBox(response, "OPENA", "Nuki Opener enabled", _preferences->getBool(preference_opener_enabled), "");
if(_preferences->getBool(preference_opener_enabled))
{
printInputField(response, "MQTTOPPATH", "MQTT Nuki Opener Path", _preferences->getString(preference_mqtt_opener_path).c_str(), 180);
printInputField(response, "MQTTOPPATH", "MQTT Nuki Opener Path", _preferences->getString(preference_mqtt_opener_path).c_str(), 180, "");
}
response.concat("</table><br>");
response.concat("<h3>Advanced Nuki Configuration</h3>");
response.concat("<table>");
printInputField(response, "LSTINT", "Query interval lock state (seconds)", _preferences->getInt(preference_query_interval_lockstate), 10);
printInputField(response, "CFGINT", "Query interval configuration (seconds)", _preferences->getInt(preference_query_interval_configuration), 10);
printInputField(response, "BATINT", "Query interval battery (seconds)", _preferences->getInt(preference_query_interval_battery), 10);
printInputField(response, "LSTINT", "Query interval lock state (seconds)", _preferences->getInt(preference_query_interval_lockstate), 10, "");
printInputField(response, "CFGINT", "Query interval configuration (seconds)", _preferences->getInt(preference_query_interval_configuration), 10, "");
printInputField(response, "BATINT", "Query interval battery (seconds)", _preferences->getInt(preference_query_interval_battery), 10, "");
if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad()))
{
printInputField(response, "KPINT", "Query interval keypad (seconds)", _preferences->getInt(preference_query_interval_keypad), 10);
printInputField(response, "KPINT", "Query interval keypad (seconds)", _preferences->getInt(preference_query_interval_keypad), 10, "");
}
printInputField(response, "NRTRY", "Number of retries if command failed", _preferences->getInt(preference_command_nr_of_retries), 10);
printInputField(response, "TRYDLY", "Delay between retries (milliseconds)", _preferences->getInt(preference_command_retry_delay), 10);
printCheckBox(response, "REGAPP", "Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_as_app), "");
printInputField(response, "PRDTMO", "Presence detection timeout (seconds; -1 to disable)", _preferences->getInt(preference_presence_detection_timeout), 10);
printInputField(response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10);
printInputField(response, "NRTRY", "Number of retries if command failed", _preferences->getInt(preference_command_nr_of_retries), 10, "");
printInputField(response, "TRYDLY", "Delay between retries (milliseconds)", _preferences->getInt(preference_command_retry_delay), 10, "");
if(_nuki != nullptr) printCheckBox(response, "REGAPP", "Lock: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_as_app), "");
if(_nukiOpener != nullptr) printCheckBox(response, "REGAPPOPN", "Opener: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_opener_as_app), "");
printInputField(response, "PRDTMO", "Presence detection timeout (seconds; -1 to disable)", _preferences->getInt(preference_presence_detection_timeout), 10, "");
printInputField(response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10, "");
response.concat("</table>");
response.concat("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.concat("</form>");
@@ -1845,9 +1897,9 @@ void WebCfgServer::buildInfoHtml(String &response)
if(_nukiOpener != nullptr)
{
uint32_t basicOpenerConfigAclPrefs[16];
uint32_t basicOpenerConfigAclPrefs[14];
_preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs));
uint32_t advancedOpenerConfigAclPrefs[22];
uint32_t advancedOpenerConfigAclPrefs[20];
_preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs));
response.concat("Opener firmware version: ");
response.concat(_nukiOpener->firmwareVersion().c_str());
@@ -2105,6 +2157,7 @@ void WebCfgServer::printInputField(String& response,
const char *description,
const char *value,
const size_t& maxLength,
const char *id,
const bool& isPassword,
const bool& showLengthRestriction)
{
@@ -2124,7 +2177,13 @@ void WebCfgServer::printInputField(String& response,
response.concat("</td><td>");
response.concat("<input type=");
response.concat(isPassword ? "password" : "text");
response.concat(isPassword ? "\"password\"" : "\"text\"");
if(id)
{
response.concat(" id=\"");
response.concat(id);
response.concat("\"");
}
response.concat(" value=\"");
response.concat(value);
response.concat("\" name=\"");
@@ -2139,11 +2198,12 @@ void WebCfgServer::printInputField(String& response,
const char *token,
const char *description,
const int value,
size_t maxLength)
size_t maxLength,
const char *id)
{
char valueStr[20];
itoa(value, valueStr, 10);
printInputField(response, token, description, valueStr, maxLength);
printInputField(response, token, description, valueStr, maxLength, id);
}
void WebCfgServer::printCheckBox(String &response, const char *token, const char *description, const bool value, const char *htmlClass)

View File

@@ -53,10 +53,9 @@ private:
void sendFavicon();
void processUnpair(bool opener);
void processFactoryReset();
void buildHtmlHeader(String& response, String additionalHeader = "");
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 printInputField(String& response, const char* token, const char* description, const char* value, const size_t& maxLength, const char* id, const bool& isPassword = false, const bool& showLengthRestriction = false);
void printInputField(String& response, const char* token, const char* description, const int value, size_t maxLength, const char* id);
void printCheckBox(String& response, const char* token, const char* description, const bool value, const char* htmlClass);
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);

View File

@@ -154,6 +154,7 @@ bool initPreferences()
{
preferences->putBool(preference_started_before, true);
preferences->putBool(preference_lock_enabled, true);
preferences->putBool(preference_conf_info_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));
uint32_t basicLockConfigAclPrefs[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

View File

@@ -31,7 +31,7 @@ public:
virtual bool isConnected() = 0;
virtual int8_t signalStrength() = 0;
virtual String localIP() = 0;
virtual String BSSIDstr() = 0;