Add Authorization entries (#456)

* Add and remove libs and components for Arduino Core 3

* Arduino Core 3

* Add back Solo1

* Change ESP32-S3 to 4MB build

* Add Authorization info and control

* Use esp_crt_bundle for HTTPS requests

* Remove Solo1 support

* Improve Nuki device config read functions

* Webserial

* OTA Improvements

* Authorization Entries

* Authorization entries

* Authorization
This commit is contained in:
iranl
2024-08-17 05:21:22 +02:00
committed by GitHub
parent 00a9b30a39
commit b298d410eb
20 changed files with 1728 additions and 155 deletions

View File

@@ -102,6 +102,7 @@
#define MAX_AUTHLOG 5
#define MAX_KEYPAD 10
#define MAX_TIMECONTROL 10
#define MAX_AUTH 10
#endif
#define NETWORK_TASK_SIZE 12288

View File

@@ -85,6 +85,12 @@
#define mqtt_topic_timecontrol_action "/timecontrol/action"
#define mqtt_topic_timecontrol_command_result "/timecontrol/commandResult"
#define mqtt_topic_auth "/authorization"
#define mqtt_topic_auth_entries "/authorization/entries"
#define mqtt_topic_auth_json "/authorization/json"
#define mqtt_topic_auth_action "/authorization/action"
#define mqtt_topic_auth_command_result "/authorization/commandResult"
#define mqtt_topic_info_hardware_version "/info/hardwareVersion"
#define mqtt_topic_info_firmware_version "/info/firmwareVersion"
#define mqtt_topic_info_nuki_hub_version "/info/nukiHubVersion"

View File

@@ -107,7 +107,7 @@ void NukiNetwork::setupDevice()
_networkDeviceType = NetworkDeviceType::WiFi;
#else
int custEth = _preferences->getInt(preference_network_custom_phy, 0);
if(custEth<3) custEth++;
else custEth = 0;
_preferences->putInt(preference_network_custom_phy, custEth);
@@ -377,7 +377,7 @@ void NukiNetwork::setupDevice()
_preferences->getInt(preference_network_custom_mosi, -1),
ETH_PHY_SPI_FREQ_MHZ,
ETH_PHY_W5500);
break;
break;
#endif
}
@@ -570,7 +570,6 @@ void NukiNetwork::initialize()
bool NukiNetwork::update()
{
int64_t ts = (esp_timer_get_time() / 1000);
_device->update();
if(!_mqttEnabled)
@@ -630,9 +629,9 @@ bool NukiNetwork::update()
return false;
}
_mqttConnectCounter = 0;
if(forceEnableWebServer && !_webEnabled)
if(forceEnableWebServer && !_webEnabled)
{
forceEnableWebServer = false;
forceEnableWebServer = false;
delay(200);
restartEsp(RestartReason::ReconfigureWebServer);
}
@@ -654,12 +653,13 @@ bool NukiNetwork::update()
}
_lastConnectedTs = ts;
#if PRESENCE_DETECTION_ENABLED
if(_presenceDetection != nullptr && (_lastPresenceTs == 0 || (ts - _lastPresenceTs) > 3000))
{
char* presenceCsv = _presenceDetection->generateCsv();
bool success = publishString(_mqttPresencePrefix, mqtt_topic_presence, presenceCsv, true);
if(!success)
{
Log->println(F("Failed to publish presence CSV data."));
@@ -706,11 +706,12 @@ bool NukiNetwork::update()
if(_lastUpdateCheckTs == 0 || (ts - _lastUpdateCheckTs) > 86400000)
{
_lastUpdateCheckTs = ts;
bool otaManifestSuccess = false;
JsonDocument doc;
NetworkClientSecure *client = new NetworkClientSecure;
if (client) {
//client->setDefaultCACertBundle();
client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start);
client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start);
{
HTTPClient https;
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
@@ -721,28 +722,29 @@ bool NukiNetwork::update()
if (httpResponseCode == HTTP_CODE_OK || httpResponseCode == HTTP_CODE_MOVED_PERMANENTLY)
{
JsonDocument doc;
DeserializationError jsonError = deserializeJson(doc, https.getStream());
if (!jsonError)
{
String currentVersion = NUKI_HUB_VERSION;
if(atof(doc["release"]["version"]) >= atof(currentVersion.c_str())) _latestVersion = doc["release"]["fullversion"];
else if(currentVersion.indexOf("beta") > 0) _latestVersion = doc["beta"]["fullversion"];
else if(currentVersion.indexOf("master") > 0) _latestVersion = doc["master"]["fullversion"];
else _latestVersion = doc["release"]["fullversion"];
publishString(_maintenancePathPrefix, mqtt_topic_info_nuki_hub_latest, _latestVersion, true);
if(strcmp(_latestVersion, _preferences->getString(preference_latest_version).c_str()) != 0) _preferences->putString(preference_latest_version, _latestVersion);
}
if (!jsonError) { otaManifestSuccess = true; }
}
}
https.end();
}
delete client;
}
if (otaManifestSuccess)
{
String currentVersion = NUKI_HUB_VERSION;
if(atof(doc["release"]["version"]) >= atof(currentVersion.c_str())) _latestVersion = doc["release"]["fullversion"];
else if(currentVersion.indexOf("beta") > 0) _latestVersion = doc["beta"]["fullversion"];
else if(currentVersion.indexOf("master") > 0) _latestVersion = doc["master"]["fullversion"];
else _latestVersion = doc["release"]["fullversion"];
publishString(_maintenancePathPrefix, mqtt_topic_info_nuki_hub_latest, _latestVersion, true);
if(strcmp(_latestVersion, _preferences->getString(preference_latest_version).c_str()) != 0) _preferences->putString(preference_latest_version, _latestVersion);
}
}
}
@@ -861,7 +863,6 @@ bool NukiNetwork::reconnect()
_mqttConnectedTs = millis();
_mqttConnectionState = 1;
delay(100);
_device->mqttOnMessage(NukiNetwork::onMqttDataReceivedCallback);
for(const String& topic : _subscribedTopics)
{

View File

@@ -53,9 +53,9 @@ public:
explicit NukiNetwork(Preferences* preferences, PresenceDetection* presenceDetection, Gpio* gpio, const String& maintenancePathPrefix, char* buffer, size_t bufferSize);
void registerMqttReceiver(MqttReceiver* receiver);
#if PRESENCE_DETECTION_ENABLED
#if PRESENCE_DETECTION_ENABLED
void setMqttPresencePath(char* path);
#endif
#endif
void disableAutoRestarts(); // disable on OTA start
void disableMqtt();
String localIP();
@@ -105,7 +105,6 @@ public:
bool encryptionSupported();
bool mqttRecentlyConnected();
bool pathEquals(const char* prefix, const char* path, const char* referencePath);
uint16_t subscribe(const char* topic, uint8_t qos);
void addReconnectedCallback(std::function<void()> reconnectedCallback);

View File

@@ -68,7 +68,6 @@ void NukiNetworkLock::initialize()
_network->subscribe(_mqttPath, mqtt_topic_lock_action);
_network->initTopic(_mqttPath, mqtt_topic_config_action, "--");
_network->subscribe(_mqttPath, mqtt_topic_config_action);
_network->subscribe(_mqttPath, mqtt_topic_reset);
_network->initTopic(_mqttPath, mqtt_topic_reset, "0");
@@ -154,6 +153,12 @@ void NukiNetworkLock::initialize()
_network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--");
}
if(_preferences->getBool(preference_auth_control_enabled))
{
_network->subscribe(_mqttPath, mqtt_topic_auth_action);
_network->initTopic(_mqttPath, mqtt_topic_auth_action, "--");
}
if(_offEnabled)
{
char uidString[20];
@@ -200,7 +205,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
{
Log->println(F("Update requested via MQTT."));
String currentVersion = NUKI_HUB_VERSION;
if(atof(_preferences->getString(preference_latest_version).c_str()) >= atof(currentVersion.c_str()))
{
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL);
@@ -403,6 +408,18 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
publishString(mqtt_topic_timecontrol_action, "--", true);
}
if(comparePrefixedPath(topic, mqtt_topic_auth_action))
{
if(strcmp(value, "") == 0 || strcmp(value, "--") == 0) return;
if(_authCommandReceivedReceivedCallback != NULL)
{
_authCommandReceivedReceivedCallback(value);
}
publishString(mqtt_topic_auth_action, "--", true);
}
}
void NukiNetworkLock::publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurnerState, const NukiLock::KeyTurnerState& lastKeyTurnerState)
@@ -1262,6 +1279,152 @@ void NukiNetworkLock::publishTimeControl(const std::list<NukiLock::TimeControlEn
}
}
void NukiNetworkLock::publishAuth(const std::list<NukiLock::AuthorizationEntry>& authEntries, uint maxAuthEntryCount)
{
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 : authEntries)
{
auto jsonEntry = json.add<JsonVariant>();
jsonEntry["authId"] = entry.authId;
jsonEntry["idType"] = entry.idType; //CONSIDER INT TO STRING
jsonEntry["enabled"] = entry.enabled;
jsonEntry["name"] = entry.name;
jsonEntry["remoteAllowed"] = entry.remoteAllowed;
char createdDT[20];
sprintf(createdDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.createdYear, entry.createdMonth, entry.createdDay, entry.createdHour, entry.createdMinute, entry.createdSecond);
jsonEntry["dateCreated"] = createdDT;
jsonEntry["lockCount"] = entry.lockCount;
char lastActiveDT[20];
sprintf(lastActiveDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.lastActYear, entry.lastActMonth, entry.lastActDay, entry.lastActHour, entry.lastActMinute, entry.lastActSecond);
jsonEntry["dateLastActive"] = lastActiveDT;
jsonEntry["timeLimited"] = entry.timeLimited;
char allowedFromDT[20];
sprintf(allowedFromDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedFromYear, entry.allowedFromMonth, entry.allowedFromDay, entry.allowedFromHour, entry.allowedFromMinute, entry.allowedFromSecond);
jsonEntry["allowedFrom"] = allowedFromDT;
char allowedUntilDT[20];
sprintf(allowedUntilDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedUntilYear, entry.allowedUntilMonth, entry.allowedUntilDay, entry.allowedUntilHour, entry.allowedUntilMinute, entry.allowedUntilSecond);
jsonEntry["allowedUntil"] = allowedUntilDT;
uint8_t allowedWeekdaysInt = entry.allowedWeekdays;
JsonArray weekdays = jsonEntry["allowedWeekdays"].to<JsonArray>();
while(allowedWeekdaysInt > 0) {
if(allowedWeekdaysInt >= 64)
{
weekdays.add("mon");
allowedWeekdaysInt -= 64;
continue;
}
if(allowedWeekdaysInt >= 32)
{
weekdays.add("tue");
allowedWeekdaysInt -= 32;
continue;
}
if(allowedWeekdaysInt >= 16)
{
weekdays.add("wed");
allowedWeekdaysInt -= 16;
continue;
}
if(allowedWeekdaysInt >= 8)
{
weekdays.add("thu");
allowedWeekdaysInt -= 8;
continue;
}
if(allowedWeekdaysInt >= 4)
{
weekdays.add("fri");
allowedWeekdaysInt -= 4;
continue;
}
if(allowedWeekdaysInt >= 2)
{
weekdays.add("sat");
allowedWeekdaysInt -= 2;
continue;
}
if(allowedWeekdaysInt >= 1)
{
weekdays.add("sun");
allowedWeekdaysInt -= 1;
continue;
}
}
char allowedFromTimeT[5];
sprintf(allowedFromTimeT, "%02d:%02d", entry.allowedFromTimeHour, entry.allowedFromTimeMin);
jsonEntry["allowedFromTime"] = allowedFromTimeT;
char allowedUntilTimeT[5];
sprintf(allowedUntilTimeT, "%02d:%02d", entry.allowedUntilTimeHour, entry.allowedUntilTimeMin);
jsonEntry["allowedUntilTime"] = allowedUntilTimeT;
if(_preferences->getBool(preference_auth_topic_per_entry, false))
{
String basePath = mqtt_topic_auth;
basePath.concat("/entries/");
basePath.concat(std::to_string(index).c_str());
jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer, true);
String basePathPrefix = "~";
basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str();
std::string baseCommand = std::string("{ \"action\": \"update\", \"authId\": \"") + std::to_string(entry.authId);
std::string enaCommand = baseCommand + (char*)"\", \"enabled\": \"1\" }";
std::string disCommand = baseCommand + (char*)"\", \"enabled\": \"0\" }";
std::string mqttDeviceName = std::string("auth_") + std::to_string(index);
std::string uidStringPostfix = std::string("_") + mqttDeviceName;
std::string displayName = std::string("Authorization - ") + std::to_string(entry.authId);
_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_auth_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_auth_json, _buffer, true);
for(int j=authEntries.size(); j<maxAuthEntryCount; j++)
{
String entriesTopic = _mqttPath;
entriesTopic.concat(mqtt_topic_auth_entries);
entriesTopic.concat("/");
_network->removeTopic(entriesTopic, (char*)std::to_string(j).c_str());
std::string mqttDeviceName = std::string("auth_") + std::to_string(j);
_network->removeHassTopic((char*)"switch", (char*)mqttDeviceName.c_str(), uidString);
}
}
void NukiNetworkLock::publishConfigCommandResult(const char* result)
{
publishString(mqtt_topic_config_action_command_result, result, true);
@@ -1283,6 +1446,11 @@ void NukiNetworkLock::publishTimeControlCommandResult(const char* result)
publishString(mqtt_topic_timecontrol_command_result, result, true);
}
void NukiNetworkLock::publishAuthCommandResult(const char* result)
{
publishString(mqtt_topic_auth_command_result, result, true);
}
void NukiNetworkLock::publishStatusUpdated(const bool statusUpdated)
{
publishBool(mqtt_topic_lock_status_updated, statusUpdated, true);
@@ -1319,6 +1487,11 @@ void NukiNetworkLock::setTimeControlCommandReceivedCallback(void (*timeControlCo
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
}
void NukiNetworkLock::setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char *))
{
_authCommandReceivedReceivedCallback = authCommandReceivedReceivedCallback;
}
void NukiNetworkLock::buildMqttPath(const char* path, char* outPath, bool offPath)
{
int offset = 0;

View File

@@ -39,11 +39,13 @@ public:
void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount);
void publishAuth(const std::list<NukiLock::AuthorizationEntry>& authEntries, uint maxAuthEntryCount);
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 publishAuthCommandResult(const char* result);
void publishOffAction(const int value);
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value));
@@ -52,6 +54,7 @@ public:
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 setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char* value));
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
void publishFloat(const char* topic, const float value, bool retain, const uint8_t precision = 2);
@@ -132,4 +135,5 @@ private:
void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_timeControlCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_authCommandReceivedReceivedCallback)(const char* value) = nullptr;
};

View File

@@ -69,7 +69,7 @@ void NukiNetworkOpener::initialize()
_network->removeTopic(_mqttPath, mqtt_topic_battery_keypad_critical);
//_network->removeTopic(_mqttPath, mqtt_topic_presence);
}
if(!_preferences->getBool(preference_conf_info_enabled, true))
{
_network->removeTopic(_mqttPath, mqtt_topic_config_basic_json);
@@ -110,6 +110,12 @@ void NukiNetworkOpener::initialize()
_network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--");
}
if(_preferences->getBool(preference_auth_control_enabled))
{
_network->subscribe(_mqttPath, mqtt_topic_auth_action);
_network->initTopic(_mqttPath, mqtt_topic_auth_action, "--");
}
if(_preferences->getBool(preference_publish_authdata, false))
{
_network->subscribe(_mqttPath, mqtt_topic_lock_log_rolling_last);
@@ -281,6 +287,18 @@ void NukiNetworkOpener::onMqttDataReceived(const char* topic, byte* payload, con
publishString(mqtt_topic_timecontrol_action, "--", true);
}
if(comparePrefixedPath(topic, mqtt_topic_auth_action))
{
if(strcmp(value, "") == 0 || strcmp(value, "--") == 0) return;
if(_authCommandReceivedReceivedCallback != NULL)
{
_authCommandReceivedReceivedCallback(value);
}
publishString(mqtt_topic_auth_action, "--", true);
}
}
void NukiNetworkOpener::publishKeyTurnerState(const NukiOpener::OpenerState& keyTurnerState, const NukiOpener::OpenerState& lastKeyTurnerState)
@@ -501,7 +519,7 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::Log
memset(str, 0, sizeof(str));
NukiOpener::lockactionToString((NukiOpener::LockAction)log.data[0], str);
entry["action"] = str;
switch(log.data[1])
{
case 0:
@@ -527,7 +545,7 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::Log
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:
@@ -583,7 +601,7 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::Log
entry["codeId"] = 256U*log.data[7]+log.data[6];
break;
}
if(log.index > _lastRollingLog)
{
_lastRollingLog = log.index;
@@ -654,7 +672,7 @@ void NukiNetworkOpener::publishConfig(const NukiOpener::Config &config)
itoa(config.nukiId, uidString, 16);
JsonDocument json;
memset(_nukiName, 0, sizeof(_nukiName));
memcpy(_nukiName, config.name, sizeof(config.name));
@@ -1000,7 +1018,7 @@ void NukiNetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& en
_network->removeTopic(codeTopic, "createdSec");
_network->removeTopic(codeTopic, "lockCount");
}
for(int j=entries.size(); j<maxKeypadCodeCount; j++)
{
String codesTopic = _mqttPath;
@@ -1084,7 +1102,7 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
memset(str, 0, sizeof(str));
NukiOpener::lockactionToString(entry.lockAction, str);
jsonEntry["lockAction"] = str;
if(topicPerEntry)
{
String basePath = mqtt_topic_timecontrol;
@@ -1093,7 +1111,6 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer, true);
String basePathPrefix = "~";
basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str();
@@ -1103,7 +1120,7 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
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,
@@ -1124,13 +1141,13 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
{ (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }});
}
++index;
}
serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_timecontrol_json, _buffer, true);
for(int j=timeControlEntries.size(); j<maxTimeControlEntryCount; j++)
{
String entriesTopic = _mqttPath;
@@ -1142,6 +1159,152 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
}
}
void NukiNetworkOpener::publishAuth(const std::list<NukiOpener::AuthorizationEntry>& authEntries, uint maxAuthEntryCount)
{
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 : authEntries)
{
auto jsonEntry = json.add<JsonVariant>();
jsonEntry["authId"] = entry.authId;
jsonEntry["idType"] = entry.idType; //CONSIDER INT TO STRING
jsonEntry["enabled"] = entry.enabled;
jsonEntry["name"] = entry.name;
jsonEntry["remoteAllowed"] = entry.remoteAllowed;
char createdDT[20];
sprintf(createdDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.createdYear, entry.createdMonth, entry.createdDay, entry.createdHour, entry.createdMinute, entry.createdSecond);
jsonEntry["dateCreated"] = createdDT;
jsonEntry["lockCount"] = entry.lockCount;
char lastActiveDT[20];
sprintf(lastActiveDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.lastActYear, entry.lastActMonth, entry.lastActDay, entry.lastActHour, entry.lastActMinute, entry.lastActSecond);
jsonEntry["dateLastActive"] = lastActiveDT;
jsonEntry["timeLimited"] = entry.timeLimited;
char allowedFromDT[20];
sprintf(allowedFromDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedFromYear, entry.allowedFromMonth, entry.allowedFromDay, entry.allowedFromHour, entry.allowedFromMinute, entry.allowedFromSecond);
jsonEntry["allowedFrom"] = allowedFromDT;
char allowedUntilDT[20];
sprintf(allowedUntilDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.allowedUntilYear, entry.allowedUntilMonth, entry.allowedUntilDay, entry.allowedUntilHour, entry.allowedUntilMinute, entry.allowedUntilSecond);
jsonEntry["allowedUntil"] = allowedUntilDT;
uint8_t allowedWeekdaysInt = entry.allowedWeekdays;
JsonArray weekdays = jsonEntry["allowedWeekdays"].to<JsonArray>();
while(allowedWeekdaysInt > 0) {
if(allowedWeekdaysInt >= 64)
{
weekdays.add("mon");
allowedWeekdaysInt -= 64;
continue;
}
if(allowedWeekdaysInt >= 32)
{
weekdays.add("tue");
allowedWeekdaysInt -= 32;
continue;
}
if(allowedWeekdaysInt >= 16)
{
weekdays.add("wed");
allowedWeekdaysInt -= 16;
continue;
}
if(allowedWeekdaysInt >= 8)
{
weekdays.add("thu");
allowedWeekdaysInt -= 8;
continue;
}
if(allowedWeekdaysInt >= 4)
{
weekdays.add("fri");
allowedWeekdaysInt -= 4;
continue;
}
if(allowedWeekdaysInt >= 2)
{
weekdays.add("sat");
allowedWeekdaysInt -= 2;
continue;
}
if(allowedWeekdaysInt >= 1)
{
weekdays.add("sun");
allowedWeekdaysInt -= 1;
continue;
}
}
char allowedFromTimeT[5];
sprintf(allowedFromTimeT, "%02d:%02d", entry.allowedFromTimeHour, entry.allowedFromTimeMin);
jsonEntry["allowedFromTime"] = allowedFromTimeT;
char allowedUntilTimeT[5];
sprintf(allowedUntilTimeT, "%02d:%02d", entry.allowedUntilTimeHour, entry.allowedUntilTimeMin);
jsonEntry["allowedUntilTime"] = allowedUntilTimeT;
if(_preferences->getBool(preference_auth_topic_per_entry, false))
{
String basePath = mqtt_topic_auth;
basePath.concat("/entries/");
basePath.concat(std::to_string(index).c_str());
jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer, true);
String basePathPrefix = "~";
basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str();
std::string baseCommand = std::string("{ \"action\": \"update\", \"authId\": \"") + std::to_string(entry.authId);
std::string enaCommand = baseCommand + (char*)"\", \"enabled\": \"1\" }";
std::string disCommand = baseCommand + (char*)"\", \"enabled\": \"0\" }";
std::string mqttDeviceName = std::string("auth_") + std::to_string(index);
std::string uidStringPostfix = std::string("_") + mqttDeviceName;
std::string displayName = std::string("Authorization - ") + std::to_string(entry.authId);
_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_auth_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_auth_json, _buffer, true);
for(int j=authEntries.size(); j<maxAuthEntryCount; j++)
{
String entriesTopic = _mqttPath;
entriesTopic.concat(mqtt_topic_auth_entries);
entriesTopic.concat("/");
_network->removeTopic(entriesTopic, (char*)std::to_string(j).c_str());
std::string mqttDeviceName = std::string("auth_") + std::to_string(j);
_network->removeHassTopic((char*)"switch", (char*)mqttDeviceName.c_str(), uidString);
}
}
void NukiNetworkOpener::publishConfigCommandResult(const char* result)
{
publishString(mqtt_topic_config_action_command_result, result, true);
@@ -1163,6 +1326,11 @@ void NukiNetworkOpener::publishTimeControlCommandResult(const char* result)
publishString(mqtt_topic_timecontrol_command_result, result, true);
}
void NukiNetworkOpener::publishAuthCommandResult(const char* result)
{
publishString(mqtt_topic_auth_command_result, result, true);
}
void NukiNetworkOpener::publishStatusUpdated(const bool statusUpdated)
{
publishBool(mqtt_topic_lock_status_updated, statusUpdated, true);
@@ -1194,6 +1362,11 @@ void NukiNetworkOpener::setTimeControlCommandReceivedCallback(void (*timeControl
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
}
void NukiNetworkOpener::setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char *))
{
_authCommandReceivedReceivedCallback = authCommandReceivedReceivedCallback;
}
void NukiNetworkOpener::publishFloat(const char *topic, const float value, bool retain, const uint8_t precision)
{
_network->publishFloat(_mqttPath, topic, value, retain, precision);

View File

@@ -33,17 +33,20 @@ public:
void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount);
void publishAuth(const std::list<NukiLock::AuthorizationEntry>& authEntries, uint maxAuthEntryCount);
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 publishAuthCommandResult(const char* result);
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(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 setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char* value));
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
bool reconnected();
@@ -105,4 +108,5 @@ private:
void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_timeControlCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_authCommandReceivedReceivedCallback)(const char* value) = nullptr;
};

View File

@@ -33,6 +33,8 @@ NukiOpenerWrapper::NukiOpenerWrapper(const std::string& deviceName, NukiDeviceId
network->setConfigUpdateReceivedCallback(nukiOpenerInst->onConfigUpdateReceivedCallback);
if(_preferences->getBool(preference_disable_non_json, false)) network->setKeypadCommandReceivedCallback(nukiOpenerInst->onKeypadCommandReceivedCallback);
network->setKeypadJsonCommandReceivedCallback(nukiOpenerInst->onKeypadJsonCommandReceivedCallback);
network->setTimeControlCommandReceivedCallback(nukiOpenerInst->onTimeControlCommandReceivedCallback);
network->setAuthCommandReceivedCallback(nukiOpenerInst->onAuthCommandReceivedCallback);
_gpio->addCallback(NukiOpenerWrapper::gpioActionCallback);
}
@@ -72,6 +74,7 @@ void NukiOpenerWrapper::initialize()
_publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_opener_max_keypad_code_count);
_maxTimeControlEntryCount = _preferences->getUInt(preference_opener_max_timecontrol_entry_count);
_maxAuthEntryCount = _preferences->getUInt(preference_opener_max_auth_entry_count);
_restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200);
@@ -219,6 +222,11 @@ void NukiOpenerWrapper::update()
_waitTimeControlUpdateTs = 0;
updateTimeControl(true);
}
if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs)
{
_waitAuthUpdateTs = 0;
updateAuth(true);
}
if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && _network->reconnected())
{
setupHASS();
@@ -492,6 +500,7 @@ void NukiOpenerWrapper::updateConfig()
if(_preferences->getBool(preference_conf_info_enabled, true)) _network->publishConfig(_nukiConfig);
_retryConfigCount = 0;
if(_preferences->getBool(preference_timecontrol_info_enabled, false)) updateTimeControl(false);
if(_preferences->getBool(preference_auth_info_enabled)) updateAuth(false);
const int pinStatus = _preferences->getInt(preference_opener_pin_status, 4);
@@ -729,7 +738,7 @@ void NukiOpenerWrapper::updateTimeControl(bool retrieved)
while(_retryCount < _nrOfRetries + 1)
{
Log->print(F("Querying opener time control: "));
Log->print(F("Querying opener timecontrol: "));
result = _nukiOpener.retrieveTimeControlEntries();
if(result != Nuki::CmdResult::Success) {
@@ -749,7 +758,7 @@ void NukiOpenerWrapper::updateTimeControl(bool retrieved)
std::list<NukiOpener::TimeControlEntry> timeControlEntries;
_nukiOpener.getTimeControlEntries(&timeControlEntries);
Log->print(F("Opener time control entries: "));
Log->print(F("Opener timecontrol entries: "));
Log->println(timeControlEntries.size());
timeControlEntries.sort([](const NukiOpener::TimeControlEntry& a, const NukiOpener::TimeControlEntry& b) { return a.entryId < b.entryId; });
@@ -779,6 +788,67 @@ void NukiOpenerWrapper::updateTimeControl(bool retrieved)
postponeBleWatchdog();
}
void NukiOpenerWrapper::updateAuth(bool retrieved)
{
if(!_preferences->getBool(preference_auth_info_enabled)) return;
if(!retrieved)
{
Nuki::CmdResult result = (Nuki::CmdResult)-1;
_retryCount = 0;
while(_retryCount < _nrOfRetries)
{
Log->print(F("Querying opener authorization: "));
result = _nukiOpener.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH));
delay(250);
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
else break;
}
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
_waitAuthUpdateTs = millis() + 5000;
}
}
else
{
std::list<NukiOpener::AuthorizationEntry> authEntries;
_nukiOpener.getAuthorizationEntries(&authEntries);
Log->print(F("Opener authorization entries: "));
Log->println(authEntries.size());
authEntries.sort([](const NukiOpener::AuthorizationEntry& a, const NukiOpener::AuthorizationEntry& b) { return a.authId < b.authId; });
if(authEntries.size() > _preferences->getInt(preference_auth_max_entries, MAX_AUTH))
{
authEntries.resize(_preferences->getInt(preference_auth_max_entries, MAX_AUTH));
}
uint authCount = authEntries.size();
if(authCount > _maxAuthEntryCount)
{
_maxAuthEntryCount = authCount;
_preferences->putUInt(preference_opener_max_auth_entry_count, _maxAuthEntryCount);
}
_network->publishAuth(authEntries, _maxAuthEntryCount);
_authIds.clear();
_authIds.reserve(authEntries.size());
for(const auto& entry : authEntries)
{
_authIds.push_back(entry.authId);
}
}
postponeBleWatchdog();
}
void NukiOpenerWrapper::postponeBleWatchdog()
{
_disableBleWatchdogTs = (esp_timer_get_time() / 1000) + 15000;
@@ -1477,6 +1547,16 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceivedCallback(const char *value)
nukiOpenerInst->onKeypadJsonCommandReceived(value);
}
void NukiOpenerWrapper::onTimeControlCommandReceivedCallback(const char *value)
{
nukiOpenerInst->onTimeControlCommandReceived(value);
}
void NukiOpenerWrapper::onAuthCommandReceivedCallback(const char *value)
{
nukiOpenerInst->onAuthCommandReceived(value);
}
void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{
switch(action)
@@ -2161,7 +2241,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
if(idExists)
{
result = _nukiOpener.removeTimeControlEntry(entryId);
Log->print(F("Delete time control: "));
Log->print(F("Delete timecontrol: "));
Log->println((int)result);
}
else
@@ -2219,7 +2299,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction;
result = _nukiOpener.addTimeControlEntry(entry);
Log->print(F("Add time control: "));
Log->print(F("Add timecontrol: "));
Log->println((int)result);
}
else if (strcmp(action, "update") == 0)
@@ -2257,13 +2337,13 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
if(!foundExisting)
{
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry");
_network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return;
}
}
else
{
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry");
_network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return;
}
@@ -2281,7 +2361,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction;
result = _nukiOpener.updateTimeControlEntry(entry);
Log->print(F("Update time control: "));
Log->print(F("Update timecontrol: "));
Log->println((int)result);
}
}
@@ -2314,6 +2394,464 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
}
}
void NukiOpenerWrapper::onAuthCommandReceived(const char *value)
{
if(!_nukiConfigValid)
{
_network->publishAuthCommandResult("configNotReady");
return;
}
if(!isPinValid())
{
_network->publishAuthCommandResult("noValidPinSet");
return;
}
if(!_preferences->getBool(preference_auth_control_enabled))
{
_network->publishAuthCommandResult("keypadControlDisabled");
return;
}
JsonDocument json;
DeserializationError jsonError = deserializeJson(json, value);
if(jsonError)
{
_network->publishAuthCommandResult("invalidJson");
return;
}
char oldName[33];
const char *action = json["action"].as<const char*>();
uint16_t authId = json["authId"].as<unsigned int>();
//uint8_t idType = json["idType"].as<unsigned int>();
//unsigned char secretKeyK[32] = {0x00};
uint8_t remoteAllowed;
uint8_t enabled;
uint8_t timeLimited;
String name;
//String sharedKey;
String allowedFrom;
String allowedUntil;
String allowedWeekdays;
String allowedFromTime;
String allowedUntilTime;
if(json.containsKey("remoteAllowed")) remoteAllowed = json["remoteAllowed"].as<unsigned int>();
else remoteAllowed = 2;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("timeLimited")) timeLimited = json["timeLimited"].as<unsigned int>();
else timeLimited = 2;
if(json.containsKey("name")) name = json["name"].as<String>();
//if(json.containsKey("sharedKey")) sharedKey = json["sharedKey"].as<String>();
if(json.containsKey("allowedFrom")) allowedFrom = json["allowedFrom"].as<String>();
if(json.containsKey("allowedUntil")) allowedUntil = json["allowedUntil"].as<String>();
if(json.containsKey("allowedWeekdays")) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json.containsKey("allowedFromTime")) allowedFromTime = json["allowedFromTime"].as<String>();
if(json.containsKey("allowedUntilTime")) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(action)
{
bool idExists = false;
if(authId)
{
idExists = std::find(_authIds.begin(), _authIds.end(), authId) != _authIds.end();
}
Nuki::CmdResult result = (Nuki::CmdResult)-1;
_retryCount = 0;
while(_retryCount < _nrOfRetries)
{
if(strcmp(action, "delete") == 0) {
if(idExists)
{
result = _nukiOpener.deleteAuthorizationEntry(authId);
Log->print(F("Delete authorization: "));
Log->println((int)result);
}
else
{
_network->publishAuthCommandResult("noExistingAuthIdSet");
return;
}
}
else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0)
{
if(name.length() < 1)
{
if (strcmp(action, "update") != 0)
{
_network->publishAuthCommandResult("noNameSet");
return;
}
}
/*
if(sharedKey.length() != 64)
{
if (strcmp(action, "update") != 0)
{
_network->publishAuthCommandResult("noSharedKeySet");
return;
}
}
else
{
for(int i=0; i<sharedKey.length();i+=2) secretKeyK[(i/2)] = std::stoi(sharedKey.substring(i, i+2).c_str(), nullptr, 16);
}
*/
unsigned int allowedFromAr[6];
unsigned int allowedUntilAr[6];
unsigned int allowedFromTimeAr[2];
unsigned int allowedUntilTimeAr[2];
uint8_t allowedWeekdaysInt = 0;
if(timeLimited == 1)
{
if(allowedFrom.length() > 0)
{
if(allowedFrom.length() == 19)
{
allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt();
allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt();
allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt();
allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt();
allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt();
allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt();
if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59)
{
_network->publishAuthCommandResult("invalidAllowedFrom");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedFrom");
return;
}
}
if(allowedUntil.length() > 0)
{
if(allowedUntil.length() > 0 == 19)
{
allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt();
allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt();
allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt();
allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt();
allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt();
allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt();
if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59)
{
_network->publishAuthCommandResult("invalidAllowedUntil");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedUntil");
return;
}
}
if(allowedFromTime.length() > 0)
{
if(allowedFromTime.length() == 5)
{
allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt();
allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt();
if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59)
{
_network->publishAuthCommandResult("invalidAllowedFromTime");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedFromTime");
return;
}
}
if(allowedUntilTime.length() > 0)
{
if(allowedUntilTime.length() == 5)
{
allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt();
allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt();
if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59)
{
_network->publishAuthCommandResult("invalidAllowedUntilTime");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedUntilTime");
return;
}
}
if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64;
if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32;
if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16;
if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8;
if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4;
if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2;
if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1;
}
if(strcmp(action, "add") == 0)
{
_network->publishAuthCommandResult("addActionNotSupported");
return;
NukiOpener::NewAuthorizationEntry entry;
memset(&entry, 0, sizeof(entry));
size_t nameLen = name.length();
memcpy(&entry.name, name.c_str(), nameLen > 32 ? 32 : nameLen);
/*
memcpy(&entry.sharedKey, secretKeyK, 32);
if(idType != 1)
{
_network->publishAuthCommandResult("invalidIdType");
return;
}
entry.idType = idType;
*/
entry.remoteAllowed = remoteAllowed == 1 ? 1 : 0;
entry.timeLimited = timeLimited == 1 ? 1 : 0;
if(allowedFrom.length() > 0)
{
entry.allowedFromYear = allowedFromAr[0];
entry.allowedFromMonth = allowedFromAr[1];
entry.allowedFromDay = allowedFromAr[2];
entry.allowedFromHour = allowedFromAr[3];
entry.allowedFromMinute = allowedFromAr[4];
entry.allowedFromSecond = allowedFromAr[5];
}
if(allowedUntil.length() > 0)
{
entry.allowedUntilYear = allowedUntilAr[0];
entry.allowedUntilMonth = allowedUntilAr[1];
entry.allowedUntilDay = allowedUntilAr[2];
entry.allowedUntilHour = allowedUntilAr[3];
entry.allowedUntilMinute = allowedUntilAr[4];
entry.allowedUntilSecond = allowedUntilAr[5];
}
entry.allowedWeekdays = allowedWeekdaysInt;
if(allowedFromTime.length() > 0)
{
entry.allowedFromTimeHour = allowedFromTimeAr[0];
entry.allowedFromTimeMin = allowedFromTimeAr[1];
}
if(allowedUntilTime.length() > 0)
{
entry.allowedUntilTimeHour = allowedUntilTimeAr[0];
entry.allowedUntilTimeMin = allowedUntilTimeAr[1];
}
result = _nukiOpener.addAuthorizationEntry(entry);
Log->print(F("Add authorization: "));
Log->println((int)result);
}
else if (strcmp(action, "update") == 0)
{
if(!authId)
{
_network->publishAuthCommandResult("noAuthIdSet");
return;
}
if(!idExists)
{
_network->publishAuthCommandResult("noExistingAuthIdSet");
return;
}
Nuki::CmdResult resultAuth = _nukiOpener.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH));
bool foundExisting = false;
if(resultAuth == Nuki::CmdResult::Success)
{
delay(250);
std::list<NukiOpener::AuthorizationEntry> entries;
_nukiOpener.getAuthorizationEntries(&entries);
for(const auto& entry : entries)
{
if (authId != entry.authId) continue;
else foundExisting = true;
if(name.length() < 1)
{
memset(oldName, 0, sizeof(oldName));
memcpy(oldName, entry.name, sizeof(entry.name));
}
if(remoteAllowed == 2) remoteAllowed = entry.remoteAllowed;
if(enabled == 2) enabled = entry.enabled;
if(timeLimited == 2) timeLimited = entry.timeLimited;
if(allowedFrom.length() < 1)
{
allowedFrom = "old";
allowedFromAr[0] = entry.allowedFromYear;
allowedFromAr[1] = entry.allowedFromMonth;
allowedFromAr[2] = entry.allowedFromDay;
allowedFromAr[3] = entry.allowedFromHour;
allowedFromAr[4] = entry.allowedFromMinute;
allowedFromAr[5] = entry.allowedFromSecond;
}
if(allowedUntil.length() < 1)
{
allowedUntil = "old";
allowedUntilAr[0] = entry.allowedUntilYear;
allowedUntilAr[1] = entry.allowedUntilMonth;
allowedUntilAr[2] = entry.allowedUntilDay;
allowedUntilAr[3] = entry.allowedUntilHour;
allowedUntilAr[4] = entry.allowedUntilMinute;
allowedUntilAr[5] = entry.allowedUntilSecond;
}
if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays;
if(allowedFromTime.length() < 1)
{
allowedFromTime = "old";
allowedFromTimeAr[0] = entry.allowedFromTimeHour;
allowedFromTimeAr[1] = entry.allowedFromTimeMin;
}
if(allowedUntilTime.length() < 1)
{
allowedUntilTime = "old";
allowedUntilTimeAr[0] = entry.allowedUntilTimeHour;
allowedUntilTimeAr[1] = entry.allowedUntilTimeMin;
}
}
if(!foundExisting)
{
_network->publishAuthCommandResult("failedToRetrieveExistingAuthorizationEntry");
return;
}
}
else
{
_network->publishAuthCommandResult("failedToRetrieveExistingAuthorizationEntry");
return;
}
NukiOpener::UpdatedAuthorizationEntry entry;
memset(&entry, 0, sizeof(entry));
entry.authId = authId;
if(name.length() < 1)
{
size_t nameLen = strlen(oldName);
memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen);
}
else
{
size_t nameLen = name.length();
memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen);
}
entry.remoteAllowed = remoteAllowed;
entry.enabled = enabled;
entry.timeLimited = timeLimited;
if(enabled == 1)
{
if(timeLimited == 1)
{
if(allowedFrom.length() > 0)
{
entry.allowedFromYear = allowedFromAr[0];
entry.allowedFromMonth = allowedFromAr[1];
entry.allowedFromDay = allowedFromAr[2];
entry.allowedFromHour = allowedFromAr[3];
entry.allowedFromMinute = allowedFromAr[4];
entry.allowedFromSecond = allowedFromAr[5];
}
if(allowedUntil.length() > 0)
{
entry.allowedUntilYear = allowedUntilAr[0];
entry.allowedUntilMonth = allowedUntilAr[1];
entry.allowedUntilDay = allowedUntilAr[2];
entry.allowedUntilHour = allowedUntilAr[3];
entry.allowedUntilMinute = allowedUntilAr[4];
entry.allowedUntilSecond = allowedUntilAr[5];
}
entry.allowedWeekdays = allowedWeekdaysInt;
if(allowedFromTime.length() > 0)
{
entry.allowedFromTimeHour = allowedFromTimeAr[0];
entry.allowedFromTimeMin = allowedFromTimeAr[1];
}
if(allowedUntilTime.length() > 0)
{
entry.allowedUntilTimeHour = allowedUntilTimeAr[0];
entry.allowedUntilTimeMin = allowedUntilTimeAr[1];
}
}
}
result = _nukiOpener.updateAuthorizationEntry(entry);
Log->print(F("Update authorization: "));
Log->println((int)result);
}
}
else
{
_network->publishAuthCommandResult("invalidAction");
return;
}
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
else break;
}
updateAuth(false);
if((int)result != -1)
{
char resultStr[15];
memset(&resultStr, 0, sizeof(resultStr));
NukiOpener::cmdResultToString(result, resultStr);
_network->publishAuthCommandResult(resultStr);
}
}
else
{
_network->publishAuthCommandResult("noActionSet");
return;
}
}
const NukiOpener::OpenerState &NukiOpenerWrapper::keyTurnerState()
{
return _keyTurnerState;

View File

@@ -53,12 +53,14 @@ private:
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 onAuthCommandReceivedCallback(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 onConfigUpdateReceived(const char* value);
void onKeypadJsonCommandReceived(const char* value);
void onTimeControlCommandReceived(const char* value);
void onAuthCommandReceived(const char* value);
void updateKeyTurnerState();
void updateBatteryState();
@@ -66,6 +68,7 @@ private:
void updateAuthData(bool retrieved);
void updateKeypad(bool retrieved);
void updateTimeControl(bool retrieved);
void updateAuth(bool retrieved);
void postponeBleWatchdog();
void updateGpioOutputs();
@@ -109,6 +112,7 @@ private:
int64_t _nextRetryTs = 0;
std::vector<uint16_t> _keypadCodeIds;
std::vector<uint8_t> _timeControlIds;
std::vector<uint32_t> _authIds;
NukiOpener::OpenerState _lastKeyTurnerState;
NukiOpener::OpenerState _keyTurnerState;
@@ -129,6 +133,7 @@ private:
bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0;
uint _maxTimeControlEntryCount = 0;
uint _maxAuthEntryCount = 0;
int _rssiPublishInterval = 0;
int64_t _nextLockStateUpdateTs = 0;
int64_t _nextBatteryReportTs = 0;
@@ -136,6 +141,7 @@ private:
int64_t _waitAuthLogUpdateTs = 0;
int64_t _waitKeypadUpdateTs = 0;
int64_t _waitTimeControlUpdateTs = 0;
int64_t _waitAuthUpdateTs = 0;
int64_t _nextKeypadUpdateTs = 0;
int64_t _nextPairTs = 0;
int64_t _nextRssiTs = 0;

View File

@@ -37,6 +37,7 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId,
if(_disableNonJSON) network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback);
network->setKeypadJsonCommandReceivedCallback(nukiInst->onKeypadJsonCommandReceivedCallback);
network->setTimeControlCommandReceivedCallback(nukiInst->onTimeControlCommandReceivedCallback);
network->setAuthCommandReceivedCallback(nukiInst->onAuthCommandReceivedCallback);
_gpio->addCallback(NukiWrapper::gpioActionCallback);
}
@@ -76,6 +77,7 @@ void NukiWrapper::initialize(const bool& firstStart)
_publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_lock_max_keypad_code_count);
_maxTimeControlEntryCount = _preferences->getUInt(preference_lock_max_timecontrol_entry_count);
_maxAuthEntryCount = _preferences->getUInt(preference_lock_max_auth_entry_count);
_restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200);
@@ -262,7 +264,6 @@ void NukiWrapper::update()
cmdResult = _nukiLock.lockAction(_nextLockAction, 0, 0);
char resultStr[15] = {0};
NukiLock::cmdResultToString(cmdResult, resultStr);
_network->publishCommandResult(resultStr);
Log->print(F("Lock action result: "));
@@ -344,6 +345,11 @@ void NukiWrapper::update()
_waitTimeControlUpdateTs = 0;
updateTimeControl(true);
}
if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs)
{
_waitAuthUpdateTs = 0;
updateAuth(true);
}
if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && _network->reconnected())
{
setupHASS();
@@ -446,7 +452,6 @@ void NukiWrapper::updateKeyTurnerState()
Log->print(_retryCount + 1);
Log->print("): ");
result =_nukiLock.requestKeyTurnerState(&_keyTurnerState);
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
@@ -566,6 +571,7 @@ void NukiWrapper::updateConfig()
_hardwareVersion = std::to_string(_nukiConfig.hardwareRevision[0]) + "." + std::to_string(_nukiConfig.hardwareRevision[1]);
if(_preferences->getBool(preference_conf_info_enabled, true)) _network->publishConfig(_nukiConfig);
if(_preferences->getBool(preference_timecontrol_info_enabled)) updateTimeControl(false);
if(_preferences->getBool(preference_auth_info_enabled)) updateAuth(false);
const int pinStatus = _preferences->getInt(preference_lock_pin_status, 4);
@@ -577,7 +583,6 @@ void NukiWrapper::updateConfig()
while(_retryCount < _nrOfRetries + 1)
{
result = _nukiLock.verifySecurityPin();
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
@@ -665,7 +670,6 @@ void NukiWrapper::updateAuthData(bool retrieved)
{
Log->print(F("Retrieve log entries: "));
result = _nukiLock.retrieveLogEntries(0, _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 1, false);
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
@@ -737,7 +741,6 @@ void NukiWrapper::updateKeypad(bool retrieved)
{
Log->print(F("Querying lock keypad: "));
result = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
@@ -802,9 +805,8 @@ void NukiWrapper::updateTimeControl(bool retrieved)
while(_retryCount < _nrOfRetries + 1)
{
Log->print(F("Querying lock time control: "));
Log->print(F("Querying lock timecontrol: "));
result = _nukiLock.retrieveTimeControlEntries();
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
@@ -822,7 +824,7 @@ void NukiWrapper::updateTimeControl(bool retrieved)
std::list<NukiLock::TimeControlEntry> timeControlEntries;
_nukiLock.getTimeControlEntries(&timeControlEntries);
Log->print(F("Lock time control entries: "));
Log->print(F("Lock timecontrol entries: "));
Log->println(timeControlEntries.size());
timeControlEntries.sort([](const NukiLock::TimeControlEntry& a, const NukiLock::TimeControlEntry& b) { return a.entryId < b.entryId; });
@@ -852,6 +854,67 @@ void NukiWrapper::updateTimeControl(bool retrieved)
postponeBleWatchdog();
}
void NukiWrapper::updateAuth(bool retrieved)
{
if(!_preferences->getBool(preference_auth_info_enabled)) return;
if(!retrieved)
{
Nuki::CmdResult result = (Nuki::CmdResult)-1;
_retryCount = 0;
while(_retryCount < _nrOfRetries)
{
Log->print(F("Querying lock authorization: "));
result = _nukiLock.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH));
delay(250);
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
else break;
}
printCommandResult(result);
if(result == Nuki::CmdResult::Success)
{
_waitAuthUpdateTs = millis() + 5000;
}
}
else
{
std::list<NukiLock::AuthorizationEntry> authEntries;
_nukiLock.getAuthorizationEntries(&authEntries);
Log->print(F("Lock authorization entries: "));
Log->println(authEntries.size());
authEntries.sort([](const NukiLock::AuthorizationEntry& a, const NukiLock::AuthorizationEntry& b) { return a.authId < b.authId; });
if(authEntries.size() > _preferences->getInt(preference_auth_max_entries, MAX_AUTH))
{
authEntries.resize(_preferences->getInt(preference_auth_max_entries, MAX_AUTH));
}
uint authCount = authEntries.size();
if(authCount > _maxAuthEntryCount)
{
_maxAuthEntryCount = authCount;
_preferences->putUInt(preference_lock_max_auth_entry_count, _maxAuthEntryCount);
}
_network->publishAuth(authEntries, _maxAuthEntryCount);
_authIds.clear();
_authIds.reserve(authEntries.size());
for(const auto& entry : authEntries)
{
_authIds.push_back(entry.authId);
}
}
postponeBleWatchdog();
}
void NukiWrapper::postponeBleWatchdog()
{
_disableBleWatchdogTs = (esp_timer_get_time() / 1000) + 15000;
@@ -1725,6 +1788,11 @@ void NukiWrapper::onTimeControlCommandReceivedCallback(const char *value)
nukiInst->onTimeControlCommandReceived(value);
}
void NukiWrapper::onAuthCommandReceivedCallback(const char *value)
{
nukiInst->onAuthCommandReceived(value);
}
void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{
switch(action)
@@ -2436,7 +2504,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
if(idExists)
{
result = _nukiLock.removeTimeControlEntry(entryId);
Log->print(F("Delete time control: "));
Log->print(F("Delete timecontrol: "));
Log->println((int)result);
}
else
@@ -2495,7 +2563,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction;
result = _nukiLock.addTimeControlEntry(entry);
Log->print(F("Add time control: "));
Log->print(F("Add timecontrol: "));
Log->println((int)result);
}
else if (strcmp(action, "update") == 0)
@@ -2533,13 +2601,13 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
if(!foundExisting)
{
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry");
_network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return;
}
}
else
{
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry");
_network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return;
}
@@ -2558,7 +2626,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction;
result = _nukiLock.updateTimeControlEntry(entry);
Log->print(F("Update time control: "));
Log->print(F("Update timecontrol: "));
Log->println((int)result);
}
}
@@ -2591,6 +2659,467 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
}
}
void NukiWrapper::onAuthCommandReceived(const char *value)
{
if(!_nukiConfigValid)
{
_network->publishAuthCommandResult("configNotReady");
return;
}
if(!isPinValid())
{
_network->publishAuthCommandResult("noValidPinSet");
return;
}
if(!_preferences->getBool(preference_auth_control_enabled))
{
_network->publishAuthCommandResult("keypadControlDisabled");
return;
}
JsonDocument json;
DeserializationError jsonError = deserializeJson(json, value);
if(jsonError)
{
_network->publishAuthCommandResult("invalidJson");
return;
}
char oldName[33];
const char *action = json["action"].as<const char*>();
uint16_t authId = json["authId"].as<unsigned int>();
//uint8_t idType = json["idType"].as<unsigned int>();
//unsigned char secretKeyK[32] = {0x00};
uint8_t remoteAllowed;
uint8_t enabled;
uint8_t timeLimited;
String name;
//String sharedKey;
String allowedFrom;
String allowedUntil;
String allowedWeekdays;
String allowedFromTime;
String allowedUntilTime;
if(json.containsKey("remoteAllowed")) remoteAllowed = json["remoteAllowed"].as<unsigned int>();
else remoteAllowed = 2;
if(json.containsKey("enabled")) enabled = json["enabled"].as<unsigned int>();
else enabled = 2;
if(json.containsKey("timeLimited")) timeLimited = json["timeLimited"].as<unsigned int>();
else timeLimited = 2;
if(json.containsKey("name")) name = json["name"].as<String>();
//if(json.containsKey("sharedKey")) sharedKey = json["sharedKey"].as<String>();
if(json.containsKey("allowedFrom")) allowedFrom = json["allowedFrom"].as<String>();
if(json.containsKey("allowedUntil")) allowedUntil = json["allowedUntil"].as<String>();
if(json.containsKey("allowedWeekdays")) allowedWeekdays = json["allowedWeekdays"].as<String>();
if(json.containsKey("allowedFromTime")) allowedFromTime = json["allowedFromTime"].as<String>();
if(json.containsKey("allowedUntilTime")) allowedUntilTime = json["allowedUntilTime"].as<String>();
if(action)
{
bool idExists = false;
if(authId)
{
idExists = std::find(_authIds.begin(), _authIds.end(), authId) != _authIds.end();
}
Nuki::CmdResult result = (Nuki::CmdResult)-1;
_retryCount = 0;
while(_retryCount < _nrOfRetries)
{
if(strcmp(action, "delete") == 0) {
if(idExists)
{
result = _nukiLock.deleteAuthorizationEntry(authId);
delay(250);
Log->print(F("Delete authorization: "));
Log->println((int)result);
}
else
{
_network->publishAuthCommandResult("noExistingAuthIdSet");
return;
}
}
else if(strcmp(action, "add") == 0 || strcmp(action, "update") == 0)
{
if(name.length() < 1)
{
if (strcmp(action, "update") != 0)
{
_network->publishAuthCommandResult("noNameSet");
return;
}
}
/*
if(sharedKey.length() != 64)
{
if (strcmp(action, "update") != 0)
{
_network->publishAuthCommandResult("noSharedKeySet");
return;
}
}
else
{
for(int i=0; i<sharedKey.length();i+=2) secretKeyK[(i/2)] = std::stoi(sharedKey.substring(i, i+2).c_str(), nullptr, 16);
}
*/
unsigned int allowedFromAr[6];
unsigned int allowedUntilAr[6];
unsigned int allowedFromTimeAr[2];
unsigned int allowedUntilTimeAr[2];
uint8_t allowedWeekdaysInt = 0;
if(timeLimited == 1)
{
if(allowedFrom.length() > 0)
{
if(allowedFrom.length() == 19)
{
allowedFromAr[0] = (uint16_t)allowedFrom.substring(0, 4).toInt();
allowedFromAr[1] = (uint8_t)allowedFrom.substring(5, 7).toInt();
allowedFromAr[2] = (uint8_t)allowedFrom.substring(8, 10).toInt();
allowedFromAr[3] = (uint8_t)allowedFrom.substring(11, 13).toInt();
allowedFromAr[4] = (uint8_t)allowedFrom.substring(14, 16).toInt();
allowedFromAr[5] = (uint8_t)allowedFrom.substring(17, 19).toInt();
if(allowedFromAr[0] < 2000 || allowedFromAr[0] > 3000 || allowedFromAr[1] < 1 || allowedFromAr[1] > 12 || allowedFromAr[2] < 1 || allowedFromAr[2] > 31 || allowedFromAr[3] < 0 || allowedFromAr[3] > 23 || allowedFromAr[4] < 0 || allowedFromAr[4] > 59 || allowedFromAr[5] < 0 || allowedFromAr[5] > 59)
{
_network->publishAuthCommandResult("invalidAllowedFrom");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedFrom");
return;
}
}
if(allowedUntil.length() > 0)
{
if(allowedUntil.length() > 0 == 19)
{
allowedUntilAr[0] = (uint16_t)allowedUntil.substring(0, 4).toInt();
allowedUntilAr[1] = (uint8_t)allowedUntil.substring(5, 7).toInt();
allowedUntilAr[2] = (uint8_t)allowedUntil.substring(8, 10).toInt();
allowedUntilAr[3] = (uint8_t)allowedUntil.substring(11, 13).toInt();
allowedUntilAr[4] = (uint8_t)allowedUntil.substring(14, 16).toInt();
allowedUntilAr[5] = (uint8_t)allowedUntil.substring(17, 19).toInt();
if(allowedUntilAr[0] < 2000 || allowedUntilAr[0] > 3000 || allowedUntilAr[1] < 1 || allowedUntilAr[1] > 12 || allowedUntilAr[2] < 1 || allowedUntilAr[2] > 31 || allowedUntilAr[3] < 0 || allowedUntilAr[3] > 23 || allowedUntilAr[4] < 0 || allowedUntilAr[4] > 59 || allowedUntilAr[5] < 0 || allowedUntilAr[5] > 59)
{
_network->publishAuthCommandResult("invalidAllowedUntil");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedUntil");
return;
}
}
if(allowedFromTime.length() > 0)
{
if(allowedFromTime.length() == 5)
{
allowedFromTimeAr[0] = (uint8_t)allowedFromTime.substring(0, 2).toInt();
allowedFromTimeAr[1] = (uint8_t)allowedFromTime.substring(3, 5).toInt();
if(allowedFromTimeAr[0] < 0 || allowedFromTimeAr[0] > 23 || allowedFromTimeAr[1] < 0 || allowedFromTimeAr[1] > 59)
{
_network->publishAuthCommandResult("invalidAllowedFromTime");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedFromTime");
return;
}
}
if(allowedUntilTime.length() > 0)
{
if(allowedUntilTime.length() == 5)
{
allowedUntilTimeAr[0] = (uint8_t)allowedUntilTime.substring(0, 2).toInt();
allowedUntilTimeAr[1] = (uint8_t)allowedUntilTime.substring(3, 5).toInt();
if(allowedUntilTimeAr[0] < 0 || allowedUntilTimeAr[0] > 23 || allowedUntilTimeAr[1] < 0 || allowedUntilTimeAr[1] > 59)
{
_network->publishAuthCommandResult("invalidAllowedUntilTime");
return;
}
}
else
{
_network->publishAuthCommandResult("invalidAllowedUntilTime");
return;
}
}
if(allowedWeekdays.indexOf("mon") >= 0) allowedWeekdaysInt += 64;
if(allowedWeekdays.indexOf("tue") >= 0) allowedWeekdaysInt += 32;
if(allowedWeekdays.indexOf("wed") >= 0) allowedWeekdaysInt += 16;
if(allowedWeekdays.indexOf("thu") >= 0) allowedWeekdaysInt += 8;
if(allowedWeekdays.indexOf("fri") >= 0) allowedWeekdaysInt += 4;
if(allowedWeekdays.indexOf("sat") >= 0) allowedWeekdaysInt += 2;
if(allowedWeekdays.indexOf("sun") >= 0) allowedWeekdaysInt += 1;
}
if(strcmp(action, "add") == 0)
{
_network->publishAuthCommandResult("addActionNotSupported");
return;
NukiLock::NewAuthorizationEntry entry;
memset(&entry, 0, sizeof(entry));
size_t nameLen = name.length();
memcpy(&entry.name, name.c_str(), nameLen > 32 ? 32 : nameLen);
/*
memcpy(&entry.sharedKey, secretKeyK, 32);
if(idType != 1)
{
_network->publishAuthCommandResult("invalidIdType");
return;
}
entry.idType = idType;
*/
entry.remoteAllowed = remoteAllowed == 1 ? 1 : 0;
entry.timeLimited = timeLimited == 1 ? 1 : 0;
if(allowedFrom.length() > 0)
{
entry.allowedFromYear = allowedFromAr[0];
entry.allowedFromMonth = allowedFromAr[1];
entry.allowedFromDay = allowedFromAr[2];
entry.allowedFromHour = allowedFromAr[3];
entry.allowedFromMinute = allowedFromAr[4];
entry.allowedFromSecond = allowedFromAr[5];
}
if(allowedUntil.length() > 0)
{
entry.allowedUntilYear = allowedUntilAr[0];
entry.allowedUntilMonth = allowedUntilAr[1];
entry.allowedUntilDay = allowedUntilAr[2];
entry.allowedUntilHour = allowedUntilAr[3];
entry.allowedUntilMinute = allowedUntilAr[4];
entry.allowedUntilSecond = allowedUntilAr[5];
}
entry.allowedWeekdays = allowedWeekdaysInt;
if(allowedFromTime.length() > 0)
{
entry.allowedFromTimeHour = allowedFromTimeAr[0];
entry.allowedFromTimeMin = allowedFromTimeAr[1];
}
if(allowedUntilTime.length() > 0)
{
entry.allowedUntilTimeHour = allowedUntilTimeAr[0];
entry.allowedUntilTimeMin = allowedUntilTimeAr[1];
}
result = _nukiLock.addAuthorizationEntry(entry);
delay(250);
Log->print(F("Add authorization: "));
Log->println((int)result);
}
else if (strcmp(action, "update") == 0)
{
if(!authId)
{
_network->publishAuthCommandResult("noAuthIdSet");
return;
}
if(!idExists)
{
_network->publishAuthCommandResult("noExistingAuthIdSet");
return;
}
Nuki::CmdResult resultAuth = _nukiLock.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH));
delay(250);
bool foundExisting = false;
if(resultAuth == Nuki::CmdResult::Success)
{
std::list<NukiLock::AuthorizationEntry> entries;
_nukiLock.getAuthorizationEntries(&entries);
for(const auto& entry : entries)
{
if (authId != entry.authId) continue;
else foundExisting = true;
if(name.length() < 1)
{
memset(oldName, 0, sizeof(oldName));
memcpy(oldName, entry.name, sizeof(entry.name));
}
if(remoteAllowed == 2) remoteAllowed = entry.remoteAllowed;
if(enabled == 2) enabled = entry.enabled;
if(timeLimited == 2) timeLimited = entry.timeLimited;
if(allowedFrom.length() < 1)
{
allowedFrom = "old";
allowedFromAr[0] = entry.allowedFromYear;
allowedFromAr[1] = entry.allowedFromMonth;
allowedFromAr[2] = entry.allowedFromDay;
allowedFromAr[3] = entry.allowedFromHour;
allowedFromAr[4] = entry.allowedFromMinute;
allowedFromAr[5] = entry.allowedFromSecond;
}
if(allowedUntil.length() < 1)
{
allowedUntil = "old";
allowedUntilAr[0] = entry.allowedUntilYear;
allowedUntilAr[1] = entry.allowedUntilMonth;
allowedUntilAr[2] = entry.allowedUntilDay;
allowedUntilAr[3] = entry.allowedUntilHour;
allowedUntilAr[4] = entry.allowedUntilMinute;
allowedUntilAr[5] = entry.allowedUntilSecond;
}
if(allowedWeekdays.length() < 1) allowedWeekdaysInt = entry.allowedWeekdays;
if(allowedFromTime.length() < 1)
{
allowedFromTime = "old";
allowedFromTimeAr[0] = entry.allowedFromTimeHour;
allowedFromTimeAr[1] = entry.allowedFromTimeMin;
}
if(allowedUntilTime.length() < 1)
{
allowedUntilTime = "old";
allowedUntilTimeAr[0] = entry.allowedUntilTimeHour;
allowedUntilTimeAr[1] = entry.allowedUntilTimeMin;
}
}
if(!foundExisting)
{
_network->publishAuthCommandResult("failedToRetrieveExistingAuthorizationEntry");
return;
}
}
else
{
_network->publishAuthCommandResult("failedToRetrieveExistingAuthorizationEntry");
return;
}
NukiLock::UpdatedAuthorizationEntry entry;
memset(&entry, 0, sizeof(entry));
entry.authId = authId;
if(name.length() < 1)
{
size_t nameLen = strlen(oldName);
memcpy(&entry.name, oldName, nameLen > 20 ? 20 : nameLen);
}
else
{
size_t nameLen = name.length();
memcpy(&entry.name, name.c_str(), nameLen > 20 ? 20 : nameLen);
}
entry.remoteAllowed = remoteAllowed;
entry.enabled = enabled;
entry.timeLimited = timeLimited;
if(enabled == 1)
{
if(timeLimited == 1)
{
if(allowedFrom.length() > 0)
{
entry.allowedFromYear = allowedFromAr[0];
entry.allowedFromMonth = allowedFromAr[1];
entry.allowedFromDay = allowedFromAr[2];
entry.allowedFromHour = allowedFromAr[3];
entry.allowedFromMinute = allowedFromAr[4];
entry.allowedFromSecond = allowedFromAr[5];
}
if(allowedUntil.length() > 0)
{
entry.allowedUntilYear = allowedUntilAr[0];
entry.allowedUntilMonth = allowedUntilAr[1];
entry.allowedUntilDay = allowedUntilAr[2];
entry.allowedUntilHour = allowedUntilAr[3];
entry.allowedUntilMinute = allowedUntilAr[4];
entry.allowedUntilSecond = allowedUntilAr[5];
}
entry.allowedWeekdays = allowedWeekdaysInt;
if(allowedFromTime.length() > 0)
{
entry.allowedFromTimeHour = allowedFromTimeAr[0];
entry.allowedFromTimeMin = allowedFromTimeAr[1];
}
if(allowedUntilTime.length() > 0)
{
entry.allowedUntilTimeHour = allowedUntilTimeAr[0];
entry.allowedUntilTimeMin = allowedUntilTimeAr[1];
}
}
}
result = _nukiLock.updateAuthorizationEntry(entry);
delay(250);
Log->print(F("Update authorization: "));
Log->println((int)result);
}
}
else
{
_network->publishAuthCommandResult("invalidAction");
return;
}
if(result != Nuki::CmdResult::Success) {
++_retryCount;
}
else break;
}
updateAuth(false);
if((int)result != -1)
{
char resultStr[15];
memset(&resultStr, 0, sizeof(resultStr));
NukiLock::cmdResultToString(result, resultStr);
_network->publishAuthCommandResult(resultStr);
}
}
else
{
_network->publishAuthCommandResult("noActionSet");
return;
}
}
const NukiLock::KeyTurnerState &NukiWrapper::keyTurnerState()
{
return _keyTurnerState;

View File

@@ -53,12 +53,14 @@ private:
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 onAuthCommandReceivedCallback(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);
void onAuthCommandReceived(const char* value);
void updateKeyTurnerState();
void updateBatteryState();
@@ -66,6 +68,7 @@ private:
void updateAuthData(bool retrieved);
void updateKeypad(bool retrieved);
void updateTimeControl(bool retrieved);
void updateAuth(bool retrieved);
void postponeBleWatchdog();
void updateGpioOutputs();
@@ -101,6 +104,7 @@ private:
bool _clearAuthData = false;
std::vector<uint16_t> _keypadCodeIds;
std::vector<uint8_t> _timeControlIds;
std::vector<uint32_t> _authIds;
NukiLock::KeyTurnerState _lastKeyTurnerState;
NukiLock::KeyTurnerState _keyTurnerState;
@@ -122,6 +126,7 @@ private:
bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0;
uint _maxTimeControlEntryCount = 0;
uint _maxAuthEntryCount = 0;
int _nrOfRetries = 0;
int _retryDelay = 0;
int _retryCount = 0;
@@ -137,6 +142,7 @@ private:
int64_t _waitAuthLogUpdateTs = 0;
int64_t _waitKeypadUpdateTs = 0;
int64_t _waitTimeControlUpdateTs = 0;
int64_t _waitAuthUpdateTs = 0;
int64_t _nextKeypadUpdateTs = 0;
int64_t _nextRssiTs = 0;
int64_t _lastRssi = 0;

View File

@@ -96,6 +96,12 @@
#define preference_show_secrets (char*)"showSecr"
#define preference_ble_tx_power (char*)"bleTxPwr"
#define preference_recon_netw_on_mqtt_discon (char*)"recNtwMqttDis"
#define preference_lock_max_auth_entry_count (char*)"maxauth"
#define preference_opener_max_auth_entry_count (char*)"opmaxauth"
#define preference_auth_control_enabled (char*)"authCtrlEna"
#define preference_auth_topic_per_entry (char*)"authPerEntry"
#define preference_auth_info_enabled (char*)"authInfoEna"
#define preference_auth_max_entries (char*)"authmaxentry"
#define preference_network_custom_phy (char*)"ntwPHY"
#define preference_network_custom_addr (char*)"ntwADDR"
#define preference_network_custom_irq (char*)"ntwIRQ"
@@ -273,7 +279,8 @@ private:
preference_update_from_mqtt, preference_show_secrets, preference_ble_tx_power, preference_recon_netw_on_mqtt_discon, preference_webserial_enabled,
preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq,
preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi,
preference_network_custom_pwr, preference_network_custom_mdio, preference_ntw_reconfigure
preference_network_custom_pwr, preference_network_custom_mdio, preference_ntw_reconfigure, preference_lock_max_auth_entry_count, preference_opener_max_auth_entry_count,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries,
};
std::vector<char*> _redact =
{
@@ -288,7 +295,8 @@ private:
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_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, preference_update_from_mqtt,
preference_recon_netw_on_mqtt_discon, preference_webserial_enabled, preference_ntw_reconfigure
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_recon_netw_on_mqtt_discon, preference_webserial_enabled,
preference_ntw_reconfigure
};
std::vector<char*> _bytePrefs =
{

View File

@@ -283,10 +283,9 @@ void WebCfgServer::buildOtaHtml(AsyncWebServerRequest *request, bool debug)
#else
String build_type = "debug";
#endif
response->print("<form onsubmit=\"if(document.getElementById('currentver') == document.getElementById('latestver') && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest release?'); } \" action=\"/autoupdate\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"token\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: green\" value=\"Update to latest release\"></form>");
response->print("<form onsubmit=\"if(document.getElementById('currentver') == document.getElementById('betaver') && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest beta? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/autoupdate\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"beta\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"token\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"color: black; background: yellow\" value=\"Update to latest beta\"></form>");
response->print("<form onsubmit=\"if(document.getElementById('currentver') == document.getElementById('devver') && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest development version? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/autoupdate\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"master\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"token\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: red\" value=\"Update to latest development version\"></form>");
response->print("<form onsubmit=\"if(document.getElementById('currentver').value == document.getElementById('latestver').value && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest release?'); } \" action=\"/autoupdate\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"token\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: green\" value=\"Update to latest release\"></form>");
response->print("<form onsubmit=\"if(document.getElementById('currentver').value == document.getElementById('betaver').value && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest beta? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/autoupdate\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"beta\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"token\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"color: black; background: yellow\" value=\"Update to latest beta\"></form>");
response->print("<form onsubmit=\"if(document.getElementById('currentver').value == document.getElementById('devver').value && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest development version? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/autoupdate\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"master\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"token\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: red\" value=\"Update to latest development version\"></form>");
response->print("<div style=\"clear: both\"></div><br>");
response->print("<b>Current version: </b><span id=\"currentver\">");
@@ -299,10 +298,10 @@ void WebCfgServer::buildOtaHtml(AsyncWebServerRequest *request, bool debug)
#ifndef NUKI_HUB_UPDATER
bool manifestSuccess = false;
JsonDocument doc;
NetworkClientSecure *client = new NetworkClientSecure;
if (client) {
//client->setDefaultCACertBundle();
client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start);
{
HTTPClient https;
@@ -315,42 +314,8 @@ void WebCfgServer::buildOtaHtml(AsyncWebServerRequest *request, bool debug)
if (http_responseCode == HTTP_CODE_OK || http_responseCode == HTTP_CODE_MOVED_PERMANENTLY)
{
JsonDocument doc;
DeserializationError jsonError = deserializeJson(doc, https.getStream());
if (!jsonError)
{
manifestSuccess = true;
response->print("<b>Latest release version: </b><span id=\"latestver\">");
response->print(doc["release"]["fullversion"].as<const char*>());
response->print(" (");
response->print(doc["release"]["build"].as<const char*>());
response->print(")</span>, ");
response->print(doc["release"]["time"].as<const char*>());
response->print("<br>");
response->print("<b>Latest beta version: </b><span id=\"betaver\">");
if(doc["beta"]["fullversion"] != "No beta available")
{
response->print(doc["beta"]["fullversion"].as<const char*>());
response->print(" (");
response->print(doc["beta"]["build"].as<const char*>());
response->print(")</span>, ");
response->print(doc["beta"]["time"].as<const char*>());
}
else
{
response->print(doc["beta"]["fullversion"].as<const char*>());
response->print("</span>");
}
response->print("<br>");
response->print("<b>Latest development version: </b><span id=\"devver\">");
response->print(doc["master"]["fullversion"].as<const char*>());
response->print(" (");
response->print(doc["master"]["build"].as<const char*>());
response->print(")</span>, ");
response->print(doc["master"]["time"].as<const char*>());
response->print("<br>");
}
if (!jsonError) { manifestSuccess = true; }
}
https.end();
}
@@ -362,6 +327,38 @@ void WebCfgServer::buildOtaHtml(AsyncWebServerRequest *request, bool debug)
{
response->print("<span id=\"currentver\" style=\"display: none;\">currentver</span><span id=\"latestver\" style=\"display: none;\">latestver</span><span id=\"devver\" style=\"display: none;\">devver</span><span id=\"betaver\" style=\"display: none;\">betaver</span>");
}
else
{
response->print("<b>Latest release version: </b><span id=\"latestver\">");
response->print(doc["release"]["fullversion"].as<const char*>());
response->print(" (");
response->print(doc["release"]["build"].as<const char*>());
response->print(")</span>, ");
response->print(doc["release"]["time"].as<const char*>());
response->print("<br>");
response->print("<b>Latest beta version: </b><span id=\"betaver\">");
if(doc["beta"]["fullversion"] != "No beta available")
{
response->print(doc["beta"]["fullversion"].as<const char*>());
response->print(" (");
response->print(doc["beta"]["build"].as<const char*>());
response->print(")</span>, ");
response->print(doc["beta"]["time"].as<const char*>());
}
else
{
response->print(doc["beta"]["fullversion"].as<const char*>());
response->print("</span>");
}
response->print("<br>");
response->print("<b>Latest development version: </b><span id=\"devver\">");
response->print(doc["master"]["fullversion"].as<const char*>());
response->print(" (");
response->print(doc["master"]["build"].as<const char*>());
response->print(")</span>, ");
response->print(doc["master"]["time"].as<const char*>());
response->print("<br>");
}
#endif
response->print("<br></div>");
@@ -1458,6 +1455,19 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
}
}
}
else if(key == "AUTHMAX")
{
if(value.toInt() > 0 && value.toInt() < 51)
{
if(_preferences->getInt(preference_auth_max_entries, MAX_AUTH) != value.toInt())
{
_preferences->putInt(preference_auth_max_entries, value.toInt());
Log->print(F("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
}
else if(key == "BUFFSIZE")
{
if(value.toInt() > 4095 && value.toInt() < 32769)
@@ -1565,6 +1575,16 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
configChanged = true;
}
}
else if(key == "AUTHPUB")
{
if(_preferences->getBool(preference_auth_info_enabled, false) != (value == "1"))
{
_preferences->putBool(preference_auth_info_enabled, (value == "1"));
Log->print(F("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "KPPER")
{
if(_preferences->getBool(preference_keypad_topic_per_entry, false) != (value == "1"))
@@ -1595,6 +1615,26 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
configChanged = true;
}
}
else if(key == "AUTHPER")
{
if(_preferences->getBool(preference_auth_topic_per_entry, false) != (value == "1"))
{
_preferences->putBool(preference_auth_topic_per_entry, (value == "1"));
Log->print(F("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "AUTHENA")
{
if(_preferences->getBool(preference_auth_control_enabled, false) != (value == "1"))
{
_preferences->putBool(preference_auth_control_enabled, (value == "1"));
Log->print(F("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "PUBAUTH")
{
if(_preferences->getBool(preference_publish_authdata, false) != (value == "1"))
@@ -2220,7 +2260,7 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
{
if(curAdvancedLockConfigAclPrefs[i] != advancedLockConfigAclPrefs[i])
{
_preferences->putBytes(preference_conf_opener_basic_acl, (byte*)(&advancedLockConfigAclPrefs), sizeof(advancedLockConfigAclPrefs));
_preferences->putBytes(preference_conf_lock_advanced_acl, (byte*)(&advancedLockConfigAclPrefs), sizeof(advancedLockConfigAclPrefs));
Log->print(F("Setting changed: "));
Log->println("ACLCONFADVANCEDLOCK");
configChanged = true;
@@ -2232,7 +2272,7 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
{
if(curBasicOpenerConfigAclPrefs[i] != basicOpenerConfigAclPrefs[i])
{
_preferences->putBytes(preference_conf_lock_advanced_acl, (byte*)(&basicOpenerConfigAclPrefs), sizeof(basicOpenerConfigAclPrefs));
_preferences->putBytes(preference_conf_opener_basic_acl, (byte*)(&basicOpenerConfigAclPrefs), sizeof(basicOpenerConfigAclPrefs));
Log->print(F("Setting changed: "));
Log->println("ACLCONFBASICOPENER");
configChanged = true;
@@ -2450,25 +2490,13 @@ void WebCfgServer::buildImportExportHtml(AsyncWebServerRequest *request)
response->print("<button title=\"Basic export\" onclick=\" window.open('/export', '_self'); return false;\">Basic export</button>");
response->print("<br><br><button title=\"Export with redacted settings\" onclick=\" window.open('/export?redacted=1'); return false;\">Export with redacted settings</button>");
response->print("<br><br><button title=\"Export with redacted settings and pairing data\" onclick=\" window.open('/export?redacted=1&pairing=1'); return false;\">Export with redacted settings and pairing data</button>");
response->print("</div><div id=\"msgdiv\" style=\"visibility:hidden\">Initiating config update. Please be patient.<br>You will be forwarded automatically when the import is complete.</div>");
response->print("<script type=\"text/javascript\">");
response->print("window.addEventListener('load', function () {");
response->print(" var button = document.getElementById(\"submitbtn\");");
response->print(" button.addEventListener('click',hideshow,false);");
response->print(" function hideshow() {");
response->print(" document.getElementById('upform').style.visibility = 'hidden';");
response->print(" document.getElementById('gitdiv').style.visibility = 'hidden';");
response->print(" document.getElementById('msgdiv').style.visibility = 'visible';");
response->print(" }");
response->print("});");
response->print("</script>");
response->print("</body></html>");
response->print("</div></body></html>");
request->send(response);
}
void WebCfgServer::buildCustomNetworkConfigHtml(AsyncWebServerRequest *request)
{
String header = "<script>window.onload = function() { hideopt(document.getElementsByName('NWCUSTPHY')[0].value); var physelect = document.getElementsByName('NWCUSTPHY')[0]; physelect.addEventListener('change', function(event) { var select = event.target; var selectedOption = select.options[select.selectedIndex]; hideopt(selectedOption.getAttribute('value')); }); }; function hideopt(value) { var intopts = document.getElementsByClassName('internalopt'); var extopts = document.getElementsByClassName('externalopt'); if (value >= 1 && value <= 3) { hide(intopts); } else if (value >= 4 && value <= 9) { hide(extopts); } else { hide(intopts); hide(extopts); } } function hide(opts) { for (var i = 0; i < opts.length; i ++) { opts[i].style.display = 'none'; } }</script>";
String header = "<script>window.onload=function(){var physelect=document.getElementsByName('NWCUSTPHY')[0];hideshowopt(physelect.value);physelect.addEventListener('change', function(event){var select=event.target;var selectedOption=select.options[select.selectedIndex];hideshowopt(selectedOption.getAttribute('value'));});};function hideshowopt(value){if(value>=1&&value<=3){hideopt('internalopt',true);hideopt('externalopt',false);}else if(value>=4&&value<=9){hideopt('internalopt', false);hideopt('externalopt', true);}else {hideopt('internalopt', true);hideopt('externalopt', true);}}function hideopt(opts,hide){var hideopts = document.getElementsByClassName(opts);for(var i=0;i<hideopts.length;i++){if(hide==true){hideopts[i].style.display='none';}else{hideopts[i].style.display='block';}}}</script>";
AsyncResponseStream *response = request->beginResponseStream("text/html");
buildHtmlHeader(response, header);
response->print("<form class=\"adapt\" method=\"post\" action=\"savecfg\">");
@@ -2478,16 +2506,16 @@ void WebCfgServer::buildCustomNetworkConfigHtml(AsyncWebServerRequest *request)
printInputField(response, "NWCUSTADDR", "ADDR", _preferences->getInt(preference_network_custom_addr, 1), 6, "");
#if defined(CONFIG_IDF_TARGET_ESP32)
printDropDown(response, "NWCUSTCLK", "CLK", String(_preferences->getInt(preference_network_custom_clk, 0)), getNetworkCustomCLKOptions(), "internalopt");
printInputField(response, "NWCUSTPWR", "PWR", _preferences->getInt(preference_network_custom_pwr, 12), 6, "internalopt");
printInputField(response, "NWCUSTMDIO", "MDIO", _preferences->getInt(preference_network_custom_mdio), 6, "internalopt");
printInputField(response, "NWCUSTMDC", "MDC", _preferences->getInt(preference_network_custom_mdc), 6, "internalopt");
printInputField(response, "NWCUSTPWR", "PWR", _preferences->getInt(preference_network_custom_pwr, 12), 6, "class=\"internalopt\"");
printInputField(response, "NWCUSTMDIO", "MDIO", _preferences->getInt(preference_network_custom_mdio), 6, "class=\"internalopt\"");
printInputField(response, "NWCUSTMDC", "MDC", _preferences->getInt(preference_network_custom_mdc), 6, "class=\"internalopt\"");
#endif
printInputField(response, "NWCUSTIRQ", "IRQ", _preferences->getInt(preference_network_custom_irq, -1), 6, "externalopt");
printInputField(response, "NWCUSTRST", "RST", _preferences->getInt(preference_network_custom_rst, -1), 6, "externalopt");
printInputField(response, "NWCUSTCS", "CS", _preferences->getInt(preference_network_custom_cs, -1), 6, "externalopt");
printInputField(response, "NWCUSTSCK", "SCK", _preferences->getInt(preference_network_custom_sck, -1), 6, "externalopt");
printInputField(response, "NWCUSTMISO", "MISO", _preferences->getInt(preference_network_custom_miso, -1), 6, "externalopt");
printInputField(response, "NWCUSTMOSI", "MOSI", _preferences->getInt(preference_network_custom_mosi, -1), 6, "externalopt");
printInputField(response, "NWCUSTIRQ", "IRQ", _preferences->getInt(preference_network_custom_irq, -1), 6, "class=\"externalopt\"");
printInputField(response, "NWCUSTRST", "RST", _preferences->getInt(preference_network_custom_rst, -1), 6, "class=\"externalopt\"");
printInputField(response, "NWCUSTCS", "CS", _preferences->getInt(preference_network_custom_cs, -1), 6, "class=\"externalopt\"");
printInputField(response, "NWCUSTSCK", "SCK", _preferences->getInt(preference_network_custom_sck, -1), 6, "class=\"externalopt\"");
printInputField(response, "NWCUSTMISO", "MISO", _preferences->getInt(preference_network_custom_miso, -1), 6, "class=\"externalopt\"");
printInputField(response, "NWCUSTMOSI", "MOSI", _preferences->getInt(preference_network_custom_mosi, -1), 6, "class=\"externalopt\"");
response->print("</table>");
@@ -2695,7 +2723,6 @@ void WebCfgServer::buildMqttConfigHtml(AsyncWebServerRequest *request)
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->print("</table>");
response->print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response->print("</form>");
response->print("</body></html>");
@@ -2872,6 +2899,9 @@ void WebCfgServer::partAccLvlHtml(String &partString, int aclPart)
printCheckBox(partString, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), "");
printCheckBox(partString, "TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), "");
printCheckBox(partString, "TCENA", "Add, modify and delete time control entries", _preferences->getBool(preference_timecontrol_control_enabled), "");
printCheckBox(partString, "AUTHPUB", "Publish authorization entries information", _preferences->getBool(preference_auth_info_enabled), "");
printCheckBox(partString, "AUTHPER", "Publish a topic per authorization entry and create HA sensor", _preferences->getBool(preference_auth_topic_per_entry), "");
printCheckBox(partString, "AUTHENA", "Modify and delete authorization entries", _preferences->getBool(preference_auth_control_enabled), "");
printCheckBox(partString, "PUBAUTH", "Publish authorization log", _preferences->getBool(preference_publish_authdata), "");
partString.concat("</table><br>");
partString.concat("<div id=\"acllock\"></div>");
@@ -3514,6 +3544,9 @@ void WebCfgServer::buildInfoHtml(AsyncWebServerRequest *request)
sprintf(tmp, "%02x", authorizationId[i]);
response->print(tmp);
}
uint32_t authorizationIdInt = authorizationId[0] + 256U*authorizationId[1] + 65536U*authorizationId[2] + 16777216U*authorizationId[3];
response->print("\nAuthorizationId (UINT32_T): ");
response->print(authorizationIdInt);
}
}
@@ -3865,9 +3898,17 @@ void WebCfgServer::printInputField(AsyncResponseStream *response,
response->print(isPassword ? "\"password\"" : "\"text\"");
if(strcmp(id, "") != 0)
{
response->print(" id=\"");
response->print(id);
response->print("\"");
if(strncmp(id, "class=", 6) != 0)
{
response->print(" ");
response->print(id);
}
else
{
response->print(" id=\"");
response->print(id);
response->print("\"");
}
}
if(strcmp(value, "") != 0)
{
@@ -4129,4 +4170,4 @@ String WebCfgServer::getPreselectionForGpio(const uint8_t &pin)
return String((int8_t)PinRole::Disabled);
}
#endif
#endif

View File

@@ -154,7 +154,6 @@ void networkTask(void *pvParameters)
}
esp_task_wdt_reset();
delay(100);
}
}
@@ -243,6 +242,7 @@ void bootloopDetection()
preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG);
preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD);
preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL);
preferences->putInt(preference_auth_max_entries, MAX_AUTH);
bootloopCounter = 0;
}
}
@@ -344,10 +344,10 @@ void otaTask(void *pvParameter)
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
Log->println("Firmware upgrade failed, restarting");
esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL));
restartEsp(RestartReason::OTAAborted);
restartEsp(RestartReason::OTAAborted);
}
void setupTasks(bool ota)
@@ -382,6 +382,14 @@ void setup()
Serial.begin(115200);
Log = &Serial;
#ifndef NUKI_HUB_UPDATER
stdout = funopen(NULL, NULL, &write_fn, NULL, NULL);
static char linebuf[1024];
setvbuf(stdout, linebuf, _IOLBF, sizeof(linebuf));
esp_rom_install_channel_putc(1, &ets_putc_handler);
//ets_install_putc1(&ets_putc_handler);
#endif
preferences = new Preferences();
preferences->begin("nukihub", false);
bool firstStart = initPreferences(preferences);