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

@@ -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)