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

@@ -171,8 +171,10 @@ In a browser navigate to the IP address assigned to the ESP32.
- Publish keypad entries information (Only available when a Keypad is detected): Enable to publish information about keypad codes through MQTT, see the "[Keypad control](#keypad-control-optional)" section of this README - Publish keypad entries information (Only available when a Keypad is detected): Enable to publish information about keypad codes through MQTT, see the "[Keypad control](#keypad-control-optional)" section of this README
- Also publish keypad codes (Only available when a Keypad is detected): Enable to publish the actual keypad codes through MQTT, note that is could be considered a security risk - Also publish keypad codes (Only available when a Keypad is detected): Enable to publish the actual keypad codes through MQTT, note that is could be considered a security risk
- Add, modify and delete keypad codes (Only available when a Keypad is detected): Enable to allow configuration of keypad codes through MQTT, see the "[Keypad control](#keypad-control-optional)" section of this README - Add, modify and delete keypad codes (Only available when a Keypad is detected): Enable to allow configuration of keypad codes through MQTT, see the "[Keypad control](#keypad-control-optional)" section of this README
- Publish time control information: Enable to publish information about time control entries through MQTT, see the "[Time Control](#time-control)" section of this README - Publish timecontrol information: Enable to publish information about timecontrol entries through MQTT, see the "[Timecontrol](#timecontrol)" section of this README
- Add, modify and delete time control entries: Enable to allow configuration of time control entries through MQTT, see the "[Time Control](#time-control)" section of this README - Add, modify and delete timecontrol entries: Enable to allow configuration of timecontrol entries through MQTT, see the "[Timecontrol](#timecontrol)" section of this README
- Publish authorization information: Enable to publish information about authorization entries through MQTT, see the "[Authorization](#authorization)" section of this README
- Modify and delete authorization entries: Enable to allow configuration of authorization entries through MQTT, see the "[Authorization](#authorization)" section of this README
- Publish auth data: Enable to publish authorization data to the MQTT topic lock/log. Requires the Nuki security code / PIN to be set, see "[Nuki Lock PIN / Nuki Opener PIN](#nuki-lock-pin--nuki-opener-pin)" below. - Publish auth data: Enable to publish authorization data to the MQTT topic lock/log. Requires the Nuki security code / PIN to be set, see "[Nuki Lock PIN / Nuki Opener PIN](#nuki-lock-pin--nuki-opener-pin)" below.
#### Nuki Lock/Opener Access Control #### Nuki Lock/Opener Access Control
@@ -556,15 +558,15 @@ For example, to add a code:
- write 1 to enabled - write 1 to enabled
- write "add" to action - write "add" to action
## Time control using JSON (optional) ## Timecontrol using JSON (optional)
Time control entries can be added, updated and removed. This has to enabled first in the configuration portal. Check "Add, modify and delete time control entries" under "Access Level Configuration" and save the configuration. Timecontrol entries can be added, updated and removed. This has to enabled first in the configuration portal. Check "Add, modify and delete timecontrol entries" under "Access Level Configuration" and save the configuration.
Information about current time control entries is published as JSON data to the "timecontrol/json" MQTT topic.<br> Information about current timecontrol entries is published as JSON data to the "timecontrol/json" MQTT topic.<br>
This needs to be enabled separately by checking "Publish time control entries information" under "Access Level Configuration" and saving the configuration. This needs to be enabled separately by checking "Publish timecontrol entries information" under "Access Level Configuration" and saving the configuration.
By default a maximum of 10 entries are published. By default a maximum of 10 entries are published.
To change Nuki Lock/Opener time control settings set the `timecontrol/actionJson` topic to a JSON formatted value containing the following nodes. To change Nuki Lock/Opener timecontrol settings set the `timecontrol/actionJson` topic to a JSON formatted value containing the following nodes.
| Node | Delete | Add | Update | Usage | Possible values | | Node | Delete | Add | Update | Usage | Possible values |
|------------------|----------|----------|----------|------------------------------------------------------------------------------------------|----------------------------------------------------------------| |------------------|----------|----------|----------|------------------------------------------------------------------------------------------|----------------------------------------------------------------|
@@ -580,6 +582,36 @@ Examples:
- Add: `{ "action": "add", "weekdays": [ "wed", "thu", "fri" ], "time": "08:00", "lockAction": "Unlock" }` - Add: `{ "action": "add", "weekdays": [ "wed", "thu", "fri" ], "time": "08:00", "lockAction": "Unlock" }`
- Update: `{ "action": "update", "entryId": "1234", "enabled": "1", "weekdays": [ "mon", "tue", "sat", "sun" ], "time": "08:00", "lockAction": "Lock" }` - Update: `{ "action": "update", "entryId": "1234", "enabled": "1", "weekdays": [ "mon", "tue", "sat", "sun" ], "time": "08:00", "lockAction": "Lock" }`
## Authorization entries control using JSON (optional)
Authorization entries can be updated and removed. This has to enabled first in the configuration portal. Check "Modify and delete authorization entries" under "Access Level Configuration" and save the configuration.
It is currently not (yet) possible to add authorization entries this way.
Information about current authorization entries is published as JSON data to the "authorization/json" MQTT topic.<br>
This needs to be enabled separately by checking "Publish authorization entries information" under "Access Level Configuration" and saving the configuration.
By default a maximum of 10 entries are published.
To change Nuki Lock/Opener authorization settings set the `authorization/actionJson` topic to a JSON formatted value containing the following nodes.
| Node | Delete | Add | Update | Usage | Possible values |
|------------------|----------|----------|----------|------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| action | Required | Required | Required | The action to execute | "delete", "add", "update" |
| authId | Required | Not used | Required | The auth ID of the existing entry to delete or update | Integer |
| enabled | Not used | Not used | Optional | Enable or disable the authorization, always enabled on add | 1 = enabled, 0 = disabled |
| name | Not used | Required | Optional | The name of the authorization to create or update | String, max 20 chars |
| remoteAllowed | Not used | Optional | Optional | If this authorization is allowed remote access, requires enabled = 1 | 1 = enabled, 0 = disabled |
| timeLimited | Not used | Optional | Optional | If this authorization is restricted to access only at certain times, requires enabled = 1 | 1 = enabled, 0 = disabled |
| allowedFrom | Not used | Optional | Optional | The start timestamp from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "YYYY-MM-DD HH:MM:SS" |
| allowedUntil | Not used | Optional | Optional | The end timestamp until access should be allowed (requires enabled = 1 and timeLimited = 1) | "YYYY-MM-DD HH:MM:SS" |
| allowedWeekdays | Not used | Optional | Optional | Weekdays on which access should be allowed (requires enabled = 1 and timeLimited = 1) | Array of days: "mon", "tue", "wed", "thu" , "fri" "sat", "sun"|
| allowedFromTime | Not used | Optional | Optional | The start time per day from which access should be allowed (requires enabled = 1 and timeLimited = 1) | "HH:MM" |
| allowedUntilTime | Not used | Optional | Optional | The end time per day until access should be allowed (requires enabled = 1 and timeLimited = 1) | "HH:MM" |
Examples:
- Delete: `{ "action": "delete", "authId": "1234" }`
- Update: `{ "action": "update", "authId": "1234", "enabled": "1", "name": "Test", "timeLimited": "1", "allowedFrom": "2024-04-12 10:00:00", "allowedUntil": "2034-04-12 10:00:00", "allowedWeekdays": [ "mon", "tue", "sat", "sun" ], "allowedFromTime": "08:00", "allowedUntilTime": "16:00" }`
## GPIO lock control (optional) ## GPIO lock control (optional)
The lock can be controlled via GPIO.<br> The lock can be controlled via GPIO.<br>

View File

@@ -6,6 +6,7 @@ from pathlib import Path
def get_board_name(env): def get_board_name(env):
board = env.get('BOARD_MCU') board = env.get('BOARD_MCU')
if env.get('BOARD') == 'esp32-solo1': if env.get('BOARD') == 'esp32-solo1':
board = env.get('BOARD').replace('-', '') board = env.get('BOARD').replace('-', '')
return board return board

View File

@@ -80,7 +80,7 @@ extra_scripts =
build_flags = build_flags =
${env.build_flags} ${env.build_flags}
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=128 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DCONFIG_ASYNC_TCP_STACK_SIZE=8192
-DWS_MAX_QUEUED_MESSAGES=128 -DWS_MAX_QUEUED_MESSAGES=128
-DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=0 -DCONFIG_NIMBLE_CPP_LOG_LEVEL=0
@@ -137,8 +137,8 @@ build_flags =
-DDEBUG_NUKI_COMMUNICATION -DDEBUG_NUKI_COMMUNICATION
;-DDEBUG_NUKI_HEX_DATA ;-DDEBUG_NUKI_HEX_DATA
-DDEBUG_NUKI_READABLE_DATA -DDEBUG_NUKI_READABLE_DATA
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DCONFIG_ASYNC_TCP_STACK_SIZE=8192
-DWS_MAX_QUEUED_MESSAGES=512 -DWS_MAX_QUEUED_MESSAGES=512
[env:esp32-c3_dbg] [env:esp32-c3_dbg]
@@ -158,8 +158,8 @@ build_flags =
-DDEBUG_NUKI_COMMUNICATION -DDEBUG_NUKI_COMMUNICATION
;-DDEBUG_NUKI_HEX_DATA ;-DDEBUG_NUKI_HEX_DATA
-DDEBUG_NUKI_READABLE_DATA -DDEBUG_NUKI_READABLE_DATA
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DCONFIG_ASYNC_TCP_STACK_SIZE=8192
-DWS_MAX_QUEUED_MESSAGES=512 -DWS_MAX_QUEUED_MESSAGES=512
[env:esp32-c6_dbg] [env:esp32-c6_dbg]
@@ -179,8 +179,8 @@ build_flags =
-DDEBUG_NUKI_COMMUNICATION -DDEBUG_NUKI_COMMUNICATION
;-DDEBUG_NUKI_HEX_DATA ;-DDEBUG_NUKI_HEX_DATA
-DDEBUG_NUKI_READABLE_DATA -DDEBUG_NUKI_READABLE_DATA
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DCONFIG_ASYNC_TCP_STACK_SIZE=8192
-DWS_MAX_QUEUED_MESSAGES=512 -DWS_MAX_QUEUED_MESSAGES=512
[env:esp32-h2_dbg] [env:esp32-h2_dbg]
@@ -201,8 +201,8 @@ build_flags =
-DDEBUG_NUKI_COMMUNICATION -DDEBUG_NUKI_COMMUNICATION
;-DDEBUG_NUKI_HEX_DATA ;-DDEBUG_NUKI_HEX_DATA
-DDEBUG_NUKI_READABLE_DATA -DDEBUG_NUKI_READABLE_DATA
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DCONFIG_ASYNC_TCP_STACK_SIZE=8192
-DWS_MAX_QUEUED_MESSAGES=512 -DWS_MAX_QUEUED_MESSAGES=512
[env:esp32-s3_dbg] [env:esp32-s3_dbg]
@@ -222,8 +222,8 @@ build_flags =
-DDEBUG_NUKI_COMMUNICATION -DDEBUG_NUKI_COMMUNICATION
;-DDEBUG_NUKI_HEX_DATA ;-DDEBUG_NUKI_HEX_DATA
-DDEBUG_NUKI_READABLE_DATA -DDEBUG_NUKI_READABLE_DATA
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DCONFIG_ASYNC_TCP_STACK_SIZE=8192
-DWS_MAX_QUEUED_MESSAGES=512 -DWS_MAX_QUEUED_MESSAGES=512
[env:esp32-solo1_dbg] [env:esp32-solo1_dbg]
@@ -243,6 +243,6 @@ build_flags =
-DDEBUG_NUKI_COMMUNICATION -DDEBUG_NUKI_COMMUNICATION
;-DDEBUG_NUKI_HEX_DATA ;-DDEBUG_NUKI_HEX_DATA
-DDEBUG_NUKI_READABLE_DATA -DDEBUG_NUKI_READABLE_DATA
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DCONFIG_ASYNC_TCP_STACK_SIZE=8192
-DWS_MAX_QUEUED_MESSAGES=512 -DWS_MAX_QUEUED_MESSAGES=512

View File

@@ -0,0 +1,41 @@
USERTrust ECC Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2
0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez
nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV
HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB
HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu
9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----
DigiCert Global Root G2
=====================================
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

View File

@@ -80,4 +80,6 @@ CONFIG_ETH_SPI_ETHERNET_W5500=y
CONFIG_ETH_SPI_ETHERNET_DM9051=y CONFIG_ETH_SPI_ETHERNET_DM9051=y
CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE=y
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y
CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="resources/github_root_ca.pem"

View File

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

View File

@@ -85,6 +85,12 @@
#define mqtt_topic_timecontrol_action "/timecontrol/action" #define mqtt_topic_timecontrol_action "/timecontrol/action"
#define mqtt_topic_timecontrol_command_result "/timecontrol/commandResult" #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_hardware_version "/info/hardwareVersion"
#define mqtt_topic_info_firmware_version "/info/firmwareVersion" #define mqtt_topic_info_firmware_version "/info/firmwareVersion"
#define mqtt_topic_info_nuki_hub_version "/info/nukiHubVersion" #define mqtt_topic_info_nuki_hub_version "/info/nukiHubVersion"

View File

@@ -107,7 +107,7 @@ void NukiNetwork::setupDevice()
_networkDeviceType = NetworkDeviceType::WiFi; _networkDeviceType = NetworkDeviceType::WiFi;
#else #else
int custEth = _preferences->getInt(preference_network_custom_phy, 0); int custEth = _preferences->getInt(preference_network_custom_phy, 0);
if(custEth<3) custEth++; if(custEth<3) custEth++;
else custEth = 0; else custEth = 0;
_preferences->putInt(preference_network_custom_phy, custEth); _preferences->putInt(preference_network_custom_phy, custEth);
@@ -377,7 +377,7 @@ void NukiNetwork::setupDevice()
_preferences->getInt(preference_network_custom_mosi, -1), _preferences->getInt(preference_network_custom_mosi, -1),
ETH_PHY_SPI_FREQ_MHZ, ETH_PHY_SPI_FREQ_MHZ,
ETH_PHY_W5500); ETH_PHY_W5500);
break; break;
#endif #endif
} }
@@ -570,7 +570,6 @@ void NukiNetwork::initialize()
bool NukiNetwork::update() bool NukiNetwork::update()
{ {
int64_t ts = (esp_timer_get_time() / 1000); int64_t ts = (esp_timer_get_time() / 1000);
_device->update(); _device->update();
if(!_mqttEnabled) if(!_mqttEnabled)
@@ -630,9 +629,9 @@ bool NukiNetwork::update()
return false; return false;
} }
_mqttConnectCounter = 0; _mqttConnectCounter = 0;
if(forceEnableWebServer && !_webEnabled) if(forceEnableWebServer && !_webEnabled)
{ {
forceEnableWebServer = false; forceEnableWebServer = false;
delay(200); delay(200);
restartEsp(RestartReason::ReconfigureWebServer); restartEsp(RestartReason::ReconfigureWebServer);
} }
@@ -654,12 +653,13 @@ bool NukiNetwork::update()
} }
_lastConnectedTs = ts; _lastConnectedTs = ts;
#if PRESENCE_DETECTION_ENABLED #if PRESENCE_DETECTION_ENABLED
if(_presenceDetection != nullptr && (_lastPresenceTs == 0 || (ts - _lastPresenceTs) > 3000)) if(_presenceDetection != nullptr && (_lastPresenceTs == 0 || (ts - _lastPresenceTs) > 3000))
{ {
char* presenceCsv = _presenceDetection->generateCsv(); char* presenceCsv = _presenceDetection->generateCsv();
bool success = publishString(_mqttPresencePrefix, mqtt_topic_presence, presenceCsv, true); bool success = publishString(_mqttPresencePrefix, mqtt_topic_presence, presenceCsv, true);
if(!success) if(!success)
{ {
Log->println(F("Failed to publish presence CSV data.")); Log->println(F("Failed to publish presence CSV data."));
@@ -706,11 +706,12 @@ bool NukiNetwork::update()
if(_lastUpdateCheckTs == 0 || (ts - _lastUpdateCheckTs) > 86400000) if(_lastUpdateCheckTs == 0 || (ts - _lastUpdateCheckTs) > 86400000)
{ {
_lastUpdateCheckTs = ts; _lastUpdateCheckTs = ts;
bool otaManifestSuccess = false;
JsonDocument doc;
NetworkClientSecure *client = new NetworkClientSecure; NetworkClientSecure *client = new NetworkClientSecure;
if (client) { 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; HTTPClient https;
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
@@ -721,28 +722,29 @@ bool NukiNetwork::update()
if (httpResponseCode == HTTP_CODE_OK || httpResponseCode == HTTP_CODE_MOVED_PERMANENTLY) if (httpResponseCode == HTTP_CODE_OK || httpResponseCode == HTTP_CODE_MOVED_PERMANENTLY)
{ {
JsonDocument doc;
DeserializationError jsonError = deserializeJson(doc, https.getStream()); DeserializationError jsonError = deserializeJson(doc, https.getStream());
if (!jsonError) if (!jsonError) { otaManifestSuccess = true; }
{
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);
}
} }
} }
https.end(); https.end();
} }
delete client; 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(); _mqttConnectedTs = millis();
_mqttConnectionState = 1; _mqttConnectionState = 1;
delay(100); delay(100);
_device->mqttOnMessage(NukiNetwork::onMqttDataReceivedCallback); _device->mqttOnMessage(NukiNetwork::onMqttDataReceivedCallback);
for(const String& topic : _subscribedTopics) 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); explicit NukiNetwork(Preferences* preferences, PresenceDetection* presenceDetection, Gpio* gpio, const String& maintenancePathPrefix, char* buffer, size_t bufferSize);
void registerMqttReceiver(MqttReceiver* receiver); void registerMqttReceiver(MqttReceiver* receiver);
#if PRESENCE_DETECTION_ENABLED #if PRESENCE_DETECTION_ENABLED
void setMqttPresencePath(char* path); void setMqttPresencePath(char* path);
#endif #endif
void disableAutoRestarts(); // disable on OTA start void disableAutoRestarts(); // disable on OTA start
void disableMqtt(); void disableMqtt();
String localIP(); String localIP();
@@ -105,7 +105,6 @@ public:
bool encryptionSupported(); bool encryptionSupported();
bool mqttRecentlyConnected(); bool mqttRecentlyConnected();
bool pathEquals(const char* prefix, const char* path, const char* referencePath); bool pathEquals(const char* prefix, const char* path, const char* referencePath);
uint16_t subscribe(const char* topic, uint8_t qos); uint16_t subscribe(const char* topic, uint8_t qos);
void addReconnectedCallback(std::function<void()> reconnectedCallback); void addReconnectedCallback(std::function<void()> reconnectedCallback);

View File

@@ -68,7 +68,6 @@ void NukiNetworkLock::initialize()
_network->subscribe(_mqttPath, mqtt_topic_lock_action); _network->subscribe(_mqttPath, mqtt_topic_lock_action);
_network->initTopic(_mqttPath, mqtt_topic_config_action, "--"); _network->initTopic(_mqttPath, mqtt_topic_config_action, "--");
_network->subscribe(_mqttPath, mqtt_topic_config_action); _network->subscribe(_mqttPath, mqtt_topic_config_action);
_network->subscribe(_mqttPath, mqtt_topic_reset); _network->subscribe(_mqttPath, mqtt_topic_reset);
_network->initTopic(_mqttPath, mqtt_topic_reset, "0"); _network->initTopic(_mqttPath, mqtt_topic_reset, "0");
@@ -154,6 +153,12 @@ void NukiNetworkLock::initialize()
_network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--"); _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) if(_offEnabled)
{ {
char uidString[20]; char uidString[20];
@@ -200,7 +205,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
{ {
Log->println(F("Update requested via MQTT.")); Log->println(F("Update requested via MQTT."));
String currentVersion = NUKI_HUB_VERSION; String currentVersion = NUKI_HUB_VERSION;
if(atof(_preferences->getString(preference_latest_version).c_str()) >= atof(currentVersion.c_str())) if(atof(_preferences->getString(preference_latest_version).c_str()) >= atof(currentVersion.c_str()))
{ {
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); _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); 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) 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) void NukiNetworkLock::publishConfigCommandResult(const char* result)
{ {
publishString(mqtt_topic_config_action_command_result, result, true); 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); 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) void NukiNetworkLock::publishStatusUpdated(const bool statusUpdated)
{ {
publishBool(mqtt_topic_lock_status_updated, statusUpdated, true); publishBool(mqtt_topic_lock_status_updated, statusUpdated, true);
@@ -1319,6 +1487,11 @@ void NukiNetworkLock::setTimeControlCommandReceivedCallback(void (*timeControlCo
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback; _timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
} }
void NukiNetworkLock::setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char *))
{
_authCommandReceivedReceivedCallback = authCommandReceivedReceivedCallback;
}
void NukiNetworkLock::buildMqttPath(const char* path, char* outPath, bool offPath) void NukiNetworkLock::buildMqttPath(const char* path, char* outPath, bool offPath)
{ {
int offset = 0; int offset = 0;

View File

@@ -39,11 +39,13 @@ public:
void removeHASSConfig(char* uidString); void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount); void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount); 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 publishStatusUpdated(const bool statusUpdated);
void publishConfigCommandResult(const char* result); void publishConfigCommandResult(const char* result);
void publishKeypadCommandResult(const char* result); void publishKeypadCommandResult(const char* result);
void publishKeypadJsonCommandResult(const char* result); void publishKeypadJsonCommandResult(const char* result);
void publishTimeControlCommandResult(const char* result); void publishTimeControlCommandResult(const char* result);
void publishAuthCommandResult(const char* result);
void publishOffAction(const int value); void publishOffAction(const int value);
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* 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 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 setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char* value));
void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(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 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); 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 (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr; void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_timeControlCommandReceivedReceivedCallback)(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_battery_keypad_critical);
//_network->removeTopic(_mqttPath, mqtt_topic_presence); //_network->removeTopic(_mqttPath, mqtt_topic_presence);
} }
if(!_preferences->getBool(preference_conf_info_enabled, true)) if(!_preferences->getBool(preference_conf_info_enabled, true))
{ {
_network->removeTopic(_mqttPath, mqtt_topic_config_basic_json); _network->removeTopic(_mqttPath, mqtt_topic_config_basic_json);
@@ -110,6 +110,12 @@ void NukiNetworkOpener::initialize()
_network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--"); _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)) if(_preferences->getBool(preference_publish_authdata, false))
{ {
_network->subscribe(_mqttPath, mqtt_topic_lock_log_rolling_last); _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); 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) 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)); memset(str, 0, sizeof(str));
NukiOpener::lockactionToString((NukiOpener::LockAction)log.data[0], str); NukiOpener::lockactionToString((NukiOpener::LockAction)log.data[0], str);
entry["action"] = str; entry["action"] = str;
switch(log.data[1]) switch(log.data[1])
{ {
case 0: case 0:
@@ -527,7 +545,7 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::Log
NukiOpener::completionStatusToString((NukiOpener::CompletionStatus)log.data[2], str); NukiOpener::completionStatusToString((NukiOpener::CompletionStatus)log.data[2], str);
entry["completionStatus"] = str; entry["completionStatus"] = str;
} }
entry["codeId"] = 256U*log.data[4]+log.data[3]; entry["codeId"] = 256U*log.data[4]+log.data[3];
break; break;
case NukiOpener::LoggingType::DoorbellRecognition: 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]; entry["codeId"] = 256U*log.data[7]+log.data[6];
break; break;
} }
if(log.index > _lastRollingLog) if(log.index > _lastRollingLog)
{ {
_lastRollingLog = log.index; _lastRollingLog = log.index;
@@ -654,7 +672,7 @@ void NukiNetworkOpener::publishConfig(const NukiOpener::Config &config)
itoa(config.nukiId, uidString, 16); itoa(config.nukiId, uidString, 16);
JsonDocument json; JsonDocument json;
memset(_nukiName, 0, sizeof(_nukiName)); memset(_nukiName, 0, sizeof(_nukiName));
memcpy(_nukiName, config.name, sizeof(config.name)); 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, "createdSec");
_network->removeTopic(codeTopic, "lockCount"); _network->removeTopic(codeTopic, "lockCount");
} }
for(int j=entries.size(); j<maxKeypadCodeCount; j++) for(int j=entries.size(); j<maxKeypadCodeCount; j++)
{ {
String codesTopic = _mqttPath; String codesTopic = _mqttPath;
@@ -1084,7 +1102,7 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
memset(str, 0, sizeof(str)); memset(str, 0, sizeof(str));
NukiOpener::lockactionToString(entry.lockAction, str); NukiOpener::lockactionToString(entry.lockAction, str);
jsonEntry["lockAction"] = str; jsonEntry["lockAction"] = str;
if(topicPerEntry) if(topicPerEntry)
{ {
String basePath = mqtt_topic_timecontrol; String basePath = mqtt_topic_timecontrol;
@@ -1093,7 +1111,6 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
jsonEntry["index"] = index; jsonEntry["index"] = index;
serializeJson(jsonEntry, _buffer, _bufferSize); serializeJson(jsonEntry, _buffer, _bufferSize);
publishString(basePath.c_str(), _buffer, true); publishString(basePath.c_str(), _buffer, true);
String basePathPrefix = "~"; String basePathPrefix = "~";
basePathPrefix.concat(basePath); basePathPrefix.concat(basePath);
const char *basePathPrefixChr = basePathPrefix.c_str(); 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 mqttDeviceName = std::string("timecontrol_") + std::to_string(index);
std::string uidStringPostfix = std::string("_") + mqttDeviceName; std::string uidStringPostfix = std::string("_") + mqttDeviceName;
std::string displayName = std::string("Timecontrol - ") + std::to_string(entry.entryId); std::string displayName = std::string("Timecontrol - ") + std::to_string(entry.entryId);
_network->publishHassTopic("switch", _network->publishHassTopic("switch",
mqttDeviceName.c_str(), mqttDeviceName.c_str(),
uidString, uidString,
@@ -1124,13 +1141,13 @@ void NukiNetworkOpener::publishTimeControl(const std::list<NukiOpener::TimeContr
{ (char*)"stat_on", (char*)"1" }, { (char*)"stat_on", (char*)"1" },
{ (char*)"stat_off", (char*)"0" }}); { (char*)"stat_off", (char*)"0" }});
} }
++index; ++index;
} }
serializeJson(json, _buffer, _bufferSize); serializeJson(json, _buffer, _bufferSize);
publishString(mqtt_topic_timecontrol_json, _buffer, true); publishString(mqtt_topic_timecontrol_json, _buffer, true);
for(int j=timeControlEntries.size(); j<maxTimeControlEntryCount; j++) for(int j=timeControlEntries.size(); j<maxTimeControlEntryCount; j++)
{ {
String entriesTopic = _mqttPath; 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) void NukiNetworkOpener::publishConfigCommandResult(const char* result)
{ {
publishString(mqtt_topic_config_action_command_result, result, true); 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); 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) void NukiNetworkOpener::publishStatusUpdated(const bool statusUpdated)
{ {
publishBool(mqtt_topic_lock_status_updated, statusUpdated, true); publishBool(mqtt_topic_lock_status_updated, statusUpdated, true);
@@ -1194,6 +1362,11 @@ void NukiNetworkOpener::setTimeControlCommandReceivedCallback(void (*timeControl
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback; _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) void NukiNetworkOpener::publishFloat(const char *topic, const float value, bool retain, const uint8_t precision)
{ {
_network->publishFloat(_mqttPath, topic, value, retain, precision); _network->publishFloat(_mqttPath, topic, value, retain, precision);

View File

@@ -33,17 +33,20 @@ public:
void removeHASSConfig(char* uidString); void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount); void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount); 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 publishStatusUpdated(const bool statusUpdated);
void publishConfigCommandResult(const char* result); void publishConfigCommandResult(const char* result);
void publishKeypadCommandResult(const char* result); void publishKeypadCommandResult(const char* result);
void publishKeypadJsonCommandResult(const char* result); void publishKeypadJsonCommandResult(const char* result);
void publishTimeControlCommandResult(const char* result); void publishTimeControlCommandResult(const char* result);
void publishAuthCommandResult(const char* result);
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value)); void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value));
void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(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 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 setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char* value));
void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(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 onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
bool reconnected(); 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 (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr;
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr; void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr;
void (*_timeControlCommandReceivedReceivedCallback)(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); network->setConfigUpdateReceivedCallback(nukiOpenerInst->onConfigUpdateReceivedCallback);
if(_preferences->getBool(preference_disable_non_json, false)) network->setKeypadCommandReceivedCallback(nukiOpenerInst->onKeypadCommandReceivedCallback); if(_preferences->getBool(preference_disable_non_json, false)) network->setKeypadCommandReceivedCallback(nukiOpenerInst->onKeypadCommandReceivedCallback);
network->setKeypadJsonCommandReceivedCallback(nukiOpenerInst->onKeypadJsonCommandReceivedCallback); network->setKeypadJsonCommandReceivedCallback(nukiOpenerInst->onKeypadJsonCommandReceivedCallback);
network->setTimeControlCommandReceivedCallback(nukiOpenerInst->onTimeControlCommandReceivedCallback);
network->setAuthCommandReceivedCallback(nukiOpenerInst->onAuthCommandReceivedCallback);
_gpio->addCallback(NukiOpenerWrapper::gpioActionCallback); _gpio->addCallback(NukiOpenerWrapper::gpioActionCallback);
} }
@@ -72,6 +74,7 @@ void NukiOpenerWrapper::initialize()
_publishAuthData = _preferences->getBool(preference_publish_authdata); _publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_opener_max_keypad_code_count); _maxKeypadCodeCount = _preferences->getUInt(preference_opener_max_keypad_code_count);
_maxTimeControlEntryCount = _preferences->getUInt(preference_opener_max_timecontrol_entry_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); _restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != ""; _hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200); _nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200);
@@ -219,6 +222,11 @@ void NukiOpenerWrapper::update()
_waitTimeControlUpdateTs = 0; _waitTimeControlUpdateTs = 0;
updateTimeControl(true); updateTimeControl(true);
} }
if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs)
{
_waitAuthUpdateTs = 0;
updateAuth(true);
}
if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && _network->reconnected()) if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && _network->reconnected())
{ {
setupHASS(); setupHASS();
@@ -492,6 +500,7 @@ void NukiOpenerWrapper::updateConfig()
if(_preferences->getBool(preference_conf_info_enabled, true)) _network->publishConfig(_nukiConfig); if(_preferences->getBool(preference_conf_info_enabled, true)) _network->publishConfig(_nukiConfig);
_retryConfigCount = 0; _retryConfigCount = 0;
if(_preferences->getBool(preference_timecontrol_info_enabled, false)) updateTimeControl(false); 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); const int pinStatus = _preferences->getInt(preference_opener_pin_status, 4);
@@ -729,7 +738,7 @@ void NukiOpenerWrapper::updateTimeControl(bool retrieved)
while(_retryCount < _nrOfRetries + 1) while(_retryCount < _nrOfRetries + 1)
{ {
Log->print(F("Querying opener time control: ")); Log->print(F("Querying opener timecontrol: "));
result = _nukiOpener.retrieveTimeControlEntries(); result = _nukiOpener.retrieveTimeControlEntries();
if(result != Nuki::CmdResult::Success) { if(result != Nuki::CmdResult::Success) {
@@ -749,7 +758,7 @@ void NukiOpenerWrapper::updateTimeControl(bool retrieved)
std::list<NukiOpener::TimeControlEntry> timeControlEntries; std::list<NukiOpener::TimeControlEntry> timeControlEntries;
_nukiOpener.getTimeControlEntries(&timeControlEntries); _nukiOpener.getTimeControlEntries(&timeControlEntries);
Log->print(F("Opener time control entries: ")); Log->print(F("Opener timecontrol entries: "));
Log->println(timeControlEntries.size()); Log->println(timeControlEntries.size());
timeControlEntries.sort([](const NukiOpener::TimeControlEntry& a, const NukiOpener::TimeControlEntry& b) { return a.entryId < b.entryId; }); 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(); 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() void NukiOpenerWrapper::postponeBleWatchdog()
{ {
_disableBleWatchdogTs = (esp_timer_get_time() / 1000) + 15000; _disableBleWatchdogTs = (esp_timer_get_time() / 1000) + 15000;
@@ -1477,6 +1547,16 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceivedCallback(const char *value)
nukiOpenerInst->onKeypadJsonCommandReceived(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) void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{ {
switch(action) switch(action)
@@ -2161,7 +2241,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
if(idExists) if(idExists)
{ {
result = _nukiOpener.removeTimeControlEntry(entryId); result = _nukiOpener.removeTimeControlEntry(entryId);
Log->print(F("Delete time control: ")); Log->print(F("Delete timecontrol: "));
Log->println((int)result); Log->println((int)result);
} }
else else
@@ -2219,7 +2299,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction; entry.lockAction = timeControlLockAction;
result = _nukiOpener.addTimeControlEntry(entry); result = _nukiOpener.addTimeControlEntry(entry);
Log->print(F("Add time control: ")); Log->print(F("Add timecontrol: "));
Log->println((int)result); Log->println((int)result);
} }
else if (strcmp(action, "update") == 0) else if (strcmp(action, "update") == 0)
@@ -2257,13 +2337,13 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
if(!foundExisting) if(!foundExisting)
{ {
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry"); _network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return; return;
} }
} }
else else
{ {
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry"); _network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return; return;
} }
@@ -2281,7 +2361,7 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction; entry.lockAction = timeControlLockAction;
result = _nukiOpener.updateTimeControlEntry(entry); result = _nukiOpener.updateTimeControlEntry(entry);
Log->print(F("Update time control: ")); Log->print(F("Update timecontrol: "));
Log->println((int)result); 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() const NukiOpener::OpenerState &NukiOpenerWrapper::keyTurnerState()
{ {
return _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 onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
static void onKeypadJsonCommandReceivedCallback(const char* value); static void onKeypadJsonCommandReceivedCallback(const char* value);
static void onTimeControlCommandReceivedCallback(const char* value); static void onTimeControlCommandReceivedCallback(const char* value);
static void onAuthCommandReceivedCallback(const char* value);
static void gpioActionCallback(const GpioAction& action, const int& pin); 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 onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
void onConfigUpdateReceived(const char* value); void onConfigUpdateReceived(const char* value);
void onKeypadJsonCommandReceived(const char* value); void onKeypadJsonCommandReceived(const char* value);
void onTimeControlCommandReceived(const char* value); void onTimeControlCommandReceived(const char* value);
void onAuthCommandReceived(const char* value);
void updateKeyTurnerState(); void updateKeyTurnerState();
void updateBatteryState(); void updateBatteryState();
@@ -66,6 +68,7 @@ private:
void updateAuthData(bool retrieved); void updateAuthData(bool retrieved);
void updateKeypad(bool retrieved); void updateKeypad(bool retrieved);
void updateTimeControl(bool retrieved); void updateTimeControl(bool retrieved);
void updateAuth(bool retrieved);
void postponeBleWatchdog(); void postponeBleWatchdog();
void updateGpioOutputs(); void updateGpioOutputs();
@@ -109,6 +112,7 @@ private:
int64_t _nextRetryTs = 0; int64_t _nextRetryTs = 0;
std::vector<uint16_t> _keypadCodeIds; std::vector<uint16_t> _keypadCodeIds;
std::vector<uint8_t> _timeControlIds; std::vector<uint8_t> _timeControlIds;
std::vector<uint32_t> _authIds;
NukiOpener::OpenerState _lastKeyTurnerState; NukiOpener::OpenerState _lastKeyTurnerState;
NukiOpener::OpenerState _keyTurnerState; NukiOpener::OpenerState _keyTurnerState;
@@ -129,6 +133,7 @@ private:
bool _keypadEnabled = false; bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0; uint _maxKeypadCodeCount = 0;
uint _maxTimeControlEntryCount = 0; uint _maxTimeControlEntryCount = 0;
uint _maxAuthEntryCount = 0;
int _rssiPublishInterval = 0; int _rssiPublishInterval = 0;
int64_t _nextLockStateUpdateTs = 0; int64_t _nextLockStateUpdateTs = 0;
int64_t _nextBatteryReportTs = 0; int64_t _nextBatteryReportTs = 0;
@@ -136,6 +141,7 @@ private:
int64_t _waitAuthLogUpdateTs = 0; int64_t _waitAuthLogUpdateTs = 0;
int64_t _waitKeypadUpdateTs = 0; int64_t _waitKeypadUpdateTs = 0;
int64_t _waitTimeControlUpdateTs = 0; int64_t _waitTimeControlUpdateTs = 0;
int64_t _waitAuthUpdateTs = 0;
int64_t _nextKeypadUpdateTs = 0; int64_t _nextKeypadUpdateTs = 0;
int64_t _nextPairTs = 0; int64_t _nextPairTs = 0;
int64_t _nextRssiTs = 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); if(_disableNonJSON) network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback);
network->setKeypadJsonCommandReceivedCallback(nukiInst->onKeypadJsonCommandReceivedCallback); network->setKeypadJsonCommandReceivedCallback(nukiInst->onKeypadJsonCommandReceivedCallback);
network->setTimeControlCommandReceivedCallback(nukiInst->onTimeControlCommandReceivedCallback); network->setTimeControlCommandReceivedCallback(nukiInst->onTimeControlCommandReceivedCallback);
network->setAuthCommandReceivedCallback(nukiInst->onAuthCommandReceivedCallback);
_gpio->addCallback(NukiWrapper::gpioActionCallback); _gpio->addCallback(NukiWrapper::gpioActionCallback);
} }
@@ -76,6 +77,7 @@ void NukiWrapper::initialize(const bool& firstStart)
_publishAuthData = _preferences->getBool(preference_publish_authdata); _publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_lock_max_keypad_code_count); _maxKeypadCodeCount = _preferences->getUInt(preference_lock_max_keypad_code_count);
_maxTimeControlEntryCount = _preferences->getUInt(preference_lock_max_timecontrol_entry_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); _restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != ""; _hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200); _nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200);
@@ -262,7 +264,6 @@ void NukiWrapper::update()
cmdResult = _nukiLock.lockAction(_nextLockAction, 0, 0); cmdResult = _nukiLock.lockAction(_nextLockAction, 0, 0);
char resultStr[15] = {0}; char resultStr[15] = {0};
NukiLock::cmdResultToString(cmdResult, resultStr); NukiLock::cmdResultToString(cmdResult, resultStr);
_network->publishCommandResult(resultStr); _network->publishCommandResult(resultStr);
Log->print(F("Lock action result: ")); Log->print(F("Lock action result: "));
@@ -344,6 +345,11 @@ void NukiWrapper::update()
_waitTimeControlUpdateTs = 0; _waitTimeControlUpdateTs = 0;
updateTimeControl(true); updateTimeControl(true);
} }
if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs)
{
_waitAuthUpdateTs = 0;
updateAuth(true);
}
if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && _network->reconnected()) if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && _network->reconnected())
{ {
setupHASS(); setupHASS();
@@ -446,7 +452,6 @@ void NukiWrapper::updateKeyTurnerState()
Log->print(_retryCount + 1); Log->print(_retryCount + 1);
Log->print("): "); Log->print("): ");
result =_nukiLock.requestKeyTurnerState(&_keyTurnerState); result =_nukiLock.requestKeyTurnerState(&_keyTurnerState);
if(result != Nuki::CmdResult::Success) { if(result != Nuki::CmdResult::Success) {
++_retryCount; ++_retryCount;
} }
@@ -566,6 +571,7 @@ void NukiWrapper::updateConfig()
_hardwareVersion = std::to_string(_nukiConfig.hardwareRevision[0]) + "." + std::to_string(_nukiConfig.hardwareRevision[1]); _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_conf_info_enabled, true)) _network->publishConfig(_nukiConfig);
if(_preferences->getBool(preference_timecontrol_info_enabled)) updateTimeControl(false); 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); const int pinStatus = _preferences->getInt(preference_lock_pin_status, 4);
@@ -577,7 +583,6 @@ void NukiWrapper::updateConfig()
while(_retryCount < _nrOfRetries + 1) while(_retryCount < _nrOfRetries + 1)
{ {
result = _nukiLock.verifySecurityPin(); result = _nukiLock.verifySecurityPin();
if(result != Nuki::CmdResult::Success) { if(result != Nuki::CmdResult::Success) {
++_retryCount; ++_retryCount;
} }
@@ -665,7 +670,6 @@ void NukiWrapper::updateAuthData(bool retrieved)
{ {
Log->print(F("Retrieve log entries: ")); Log->print(F("Retrieve log entries: "));
result = _nukiLock.retrieveLogEntries(0, _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 1, false); result = _nukiLock.retrieveLogEntries(0, _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 1, false);
if(result != Nuki::CmdResult::Success) { if(result != Nuki::CmdResult::Success) {
++_retryCount; ++_retryCount;
} }
@@ -737,7 +741,6 @@ void NukiWrapper::updateKeypad(bool retrieved)
{ {
Log->print(F("Querying lock keypad: ")); Log->print(F("Querying lock keypad: "));
result = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD)); result = _nukiLock.retrieveKeypadEntries(0, _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
if(result != Nuki::CmdResult::Success) { if(result != Nuki::CmdResult::Success) {
++_retryCount; ++_retryCount;
} }
@@ -802,9 +805,8 @@ void NukiWrapper::updateTimeControl(bool retrieved)
while(_retryCount < _nrOfRetries + 1) while(_retryCount < _nrOfRetries + 1)
{ {
Log->print(F("Querying lock time control: ")); Log->print(F("Querying lock timecontrol: "));
result = _nukiLock.retrieveTimeControlEntries(); result = _nukiLock.retrieveTimeControlEntries();
if(result != Nuki::CmdResult::Success) { if(result != Nuki::CmdResult::Success) {
++_retryCount; ++_retryCount;
} }
@@ -822,7 +824,7 @@ void NukiWrapper::updateTimeControl(bool retrieved)
std::list<NukiLock::TimeControlEntry> timeControlEntries; std::list<NukiLock::TimeControlEntry> timeControlEntries;
_nukiLock.getTimeControlEntries(&timeControlEntries); _nukiLock.getTimeControlEntries(&timeControlEntries);
Log->print(F("Lock time control entries: ")); Log->print(F("Lock timecontrol entries: "));
Log->println(timeControlEntries.size()); Log->println(timeControlEntries.size());
timeControlEntries.sort([](const NukiLock::TimeControlEntry& a, const NukiLock::TimeControlEntry& b) { return a.entryId < b.entryId; }); 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(); 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() void NukiWrapper::postponeBleWatchdog()
{ {
_disableBleWatchdogTs = (esp_timer_get_time() / 1000) + 15000; _disableBleWatchdogTs = (esp_timer_get_time() / 1000) + 15000;
@@ -1725,6 +1788,11 @@ void NukiWrapper::onTimeControlCommandReceivedCallback(const char *value)
nukiInst->onTimeControlCommandReceived(value); nukiInst->onTimeControlCommandReceived(value);
} }
void NukiWrapper::onAuthCommandReceivedCallback(const char *value)
{
nukiInst->onAuthCommandReceived(value);
}
void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin) void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{ {
switch(action) switch(action)
@@ -2436,7 +2504,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
if(idExists) if(idExists)
{ {
result = _nukiLock.removeTimeControlEntry(entryId); result = _nukiLock.removeTimeControlEntry(entryId);
Log->print(F("Delete time control: ")); Log->print(F("Delete timecontrol: "));
Log->println((int)result); Log->println((int)result);
} }
else else
@@ -2495,7 +2563,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction; entry.lockAction = timeControlLockAction;
result = _nukiLock.addTimeControlEntry(entry); result = _nukiLock.addTimeControlEntry(entry);
Log->print(F("Add time control: ")); Log->print(F("Add timecontrol: "));
Log->println((int)result); Log->println((int)result);
} }
else if (strcmp(action, "update") == 0) else if (strcmp(action, "update") == 0)
@@ -2533,13 +2601,13 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
if(!foundExisting) if(!foundExisting)
{ {
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry"); _network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return; return;
} }
} }
else else
{ {
_network->publishTimeControlCommandResult("failedToRetrieveExistingKeypadEntry"); _network->publishTimeControlCommandResult("failedToRetrieveExistingTimeControlEntry");
return; return;
} }
@@ -2558,7 +2626,7 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
entry.lockAction = timeControlLockAction; entry.lockAction = timeControlLockAction;
result = _nukiLock.updateTimeControlEntry(entry); result = _nukiLock.updateTimeControlEntry(entry);
Log->print(F("Update time control: ")); Log->print(F("Update timecontrol: "));
Log->println((int)result); 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() const NukiLock::KeyTurnerState &NukiWrapper::keyTurnerState()
{ {
return _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 onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
static void onKeypadJsonCommandReceivedCallback(const char* value); static void onKeypadJsonCommandReceivedCallback(const char* value);
static void onTimeControlCommandReceivedCallback(const char* value); static void onTimeControlCommandReceivedCallback(const char* value);
static void onAuthCommandReceivedCallback(const char* value);
static void gpioActionCallback(const GpioAction& action, const int& pin); 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 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 onOfficialUpdateReceived(const char* topic, const char* value);
void onConfigUpdateReceived(const char* value); void onConfigUpdateReceived(const char* value);
void onKeypadJsonCommandReceived(const char* value); void onKeypadJsonCommandReceived(const char* value);
void onTimeControlCommandReceived(const char* value); void onTimeControlCommandReceived(const char* value);
void onAuthCommandReceived(const char* value);
void updateKeyTurnerState(); void updateKeyTurnerState();
void updateBatteryState(); void updateBatteryState();
@@ -66,6 +68,7 @@ private:
void updateAuthData(bool retrieved); void updateAuthData(bool retrieved);
void updateKeypad(bool retrieved); void updateKeypad(bool retrieved);
void updateTimeControl(bool retrieved); void updateTimeControl(bool retrieved);
void updateAuth(bool retrieved);
void postponeBleWatchdog(); void postponeBleWatchdog();
void updateGpioOutputs(); void updateGpioOutputs();
@@ -101,6 +104,7 @@ private:
bool _clearAuthData = false; bool _clearAuthData = false;
std::vector<uint16_t> _keypadCodeIds; std::vector<uint16_t> _keypadCodeIds;
std::vector<uint8_t> _timeControlIds; std::vector<uint8_t> _timeControlIds;
std::vector<uint32_t> _authIds;
NukiLock::KeyTurnerState _lastKeyTurnerState; NukiLock::KeyTurnerState _lastKeyTurnerState;
NukiLock::KeyTurnerState _keyTurnerState; NukiLock::KeyTurnerState _keyTurnerState;
@@ -122,6 +126,7 @@ private:
bool _keypadEnabled = false; bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0; uint _maxKeypadCodeCount = 0;
uint _maxTimeControlEntryCount = 0; uint _maxTimeControlEntryCount = 0;
uint _maxAuthEntryCount = 0;
int _nrOfRetries = 0; int _nrOfRetries = 0;
int _retryDelay = 0; int _retryDelay = 0;
int _retryCount = 0; int _retryCount = 0;
@@ -137,6 +142,7 @@ private:
int64_t _waitAuthLogUpdateTs = 0; int64_t _waitAuthLogUpdateTs = 0;
int64_t _waitKeypadUpdateTs = 0; int64_t _waitKeypadUpdateTs = 0;
int64_t _waitTimeControlUpdateTs = 0; int64_t _waitTimeControlUpdateTs = 0;
int64_t _waitAuthUpdateTs = 0;
int64_t _nextKeypadUpdateTs = 0; int64_t _nextKeypadUpdateTs = 0;
int64_t _nextRssiTs = 0; int64_t _nextRssiTs = 0;
int64_t _lastRssi = 0; int64_t _lastRssi = 0;

View File

@@ -96,6 +96,12 @@
#define preference_show_secrets (char*)"showSecr" #define preference_show_secrets (char*)"showSecr"
#define preference_ble_tx_power (char*)"bleTxPwr" #define preference_ble_tx_power (char*)"bleTxPwr"
#define preference_recon_netw_on_mqtt_discon (char*)"recNtwMqttDis" #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_phy (char*)"ntwPHY"
#define preference_network_custom_addr (char*)"ntwADDR" #define preference_network_custom_addr (char*)"ntwADDR"
#define preference_network_custom_irq (char*)"ntwIRQ" #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_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_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_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 = 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_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_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_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 = std::vector<char*> _bytePrefs =
{ {

View File

@@ -283,10 +283,9 @@ void WebCfgServer::buildOtaHtml(AsyncWebServerRequest *request, bool debug)
#else #else
String build_type = "debug"; String build_type = "debug";
#endif #endif
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') == 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').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') == 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').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("<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("<div style=\"clear: both\"></div><br>"); response->print("<div style=\"clear: both\"></div><br>");
response->print("<b>Current version: </b><span id=\"currentver\">"); response->print("<b>Current version: </b><span id=\"currentver\">");
@@ -299,10 +298,10 @@ void WebCfgServer::buildOtaHtml(AsyncWebServerRequest *request, bool debug)
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
bool manifestSuccess = false; bool manifestSuccess = false;
JsonDocument doc;
NetworkClientSecure *client = new NetworkClientSecure; NetworkClientSecure *client = new NetworkClientSecure;
if (client) { 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; 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) if (http_responseCode == HTTP_CODE_OK || http_responseCode == HTTP_CODE_MOVED_PERMANENTLY)
{ {
JsonDocument doc;
DeserializationError jsonError = deserializeJson(doc, https.getStream()); DeserializationError jsonError = deserializeJson(doc, https.getStream());
if (!jsonError) { manifestSuccess = true; }
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>");
}
} }
https.end(); 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>"); 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 #endif
response->print("<br></div>"); 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") else if(key == "BUFFSIZE")
{ {
if(value.toInt() > 4095 && value.toInt() < 32769) if(value.toInt() > 4095 && value.toInt() < 32769)
@@ -1565,6 +1575,16 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
configChanged = true; 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") else if(key == "KPPER")
{ {
if(_preferences->getBool(preference_keypad_topic_per_entry, false) != (value == "1")) if(_preferences->getBool(preference_keypad_topic_per_entry, false) != (value == "1"))
@@ -1595,6 +1615,26 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
configChanged = true; 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") else if(key == "PUBAUTH")
{ {
if(_preferences->getBool(preference_publish_authdata, false) != (value == "1")) 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]) 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->print(F("Setting changed: "));
Log->println("ACLCONFADVANCEDLOCK"); Log->println("ACLCONFADVANCEDLOCK");
configChanged = true; configChanged = true;
@@ -2232,7 +2272,7 @@ bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message)
{ {
if(curBasicOpenerConfigAclPrefs[i] != basicOpenerConfigAclPrefs[i]) 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->print(F("Setting changed: "));
Log->println("ACLCONFBASICOPENER"); Log->println("ACLCONFBASICOPENER");
configChanged = true; 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("<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\" 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("<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("</div></body></html>");
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>");
request->send(response); request->send(response);
} }
void WebCfgServer::buildCustomNetworkConfigHtml(AsyncWebServerRequest *request) 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"); AsyncResponseStream *response = request->beginResponseStream("text/html");
buildHtmlHeader(response, header); buildHtmlHeader(response, header);
response->print("<form class=\"adapt\" method=\"post\" action=\"savecfg\">"); 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, ""); printInputField(response, "NWCUSTADDR", "ADDR", _preferences->getInt(preference_network_custom_addr, 1), 6, "");
#if defined(CONFIG_IDF_TARGET_ESP32) #if defined(CONFIG_IDF_TARGET_ESP32)
printDropDown(response, "NWCUSTCLK", "CLK", String(_preferences->getInt(preference_network_custom_clk, 0)), getNetworkCustomCLKOptions(), "internalopt"); 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, "NWCUSTPWR", "PWR", _preferences->getInt(preference_network_custom_pwr, 12), 6, "class=\"internalopt\"");
printInputField(response, "NWCUSTMDIO", "MDIO", _preferences->getInt(preference_network_custom_mdio), 6, "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, "internalopt"); printInputField(response, "NWCUSTMDC", "MDC", _preferences->getInt(preference_network_custom_mdc), 6, "class=\"internalopt\"");
#endif #endif
printInputField(response, "NWCUSTIRQ", "IRQ", _preferences->getInt(preference_network_custom_irq, -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, "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, "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, "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, "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, "externalopt"); printInputField(response, "NWCUSTMOSI", "MOSI", _preferences->getInt(preference_network_custom_mosi, -1), 6, "class=\"externalopt\"");
response->print("</table>"); 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, "IPGTW", "Default gateway", _preferences->getString(preference_ip_gateway).c_str(), 15, "");
printInputField(response, "DNSSRV", "DNS Server", _preferences->getString(preference_ip_dns_server).c_str(), 15, ""); printInputField(response, "DNSSRV", "DNS Server", _preferences->getString(preference_ip_dns_server).c_str(), 15, "");
response->print("</table>"); response->print("</table>");
response->print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">"); response->print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response->print("</form>"); response->print("</form>");
response->print("</body></html>"); 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, "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, "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, "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), ""); printCheckBox(partString, "PUBAUTH", "Publish authorization log", _preferences->getBool(preference_publish_authdata), "");
partString.concat("</table><br>"); partString.concat("</table><br>");
partString.concat("<div id=\"acllock\"></div>"); partString.concat("<div id=\"acllock\"></div>");
@@ -3514,6 +3544,9 @@ void WebCfgServer::buildInfoHtml(AsyncWebServerRequest *request)
sprintf(tmp, "%02x", authorizationId[i]); sprintf(tmp, "%02x", authorizationId[i]);
response->print(tmp); 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\""); response->print(isPassword ? "\"password\"" : "\"text\"");
if(strcmp(id, "") != 0) if(strcmp(id, "") != 0)
{ {
response->print(" id=\""); if(strncmp(id, "class=", 6) != 0)
response->print(id); {
response->print("\""); response->print(" ");
response->print(id);
}
else
{
response->print(" id=\"");
response->print(id);
response->print("\"");
}
} }
if(strcmp(value, "") != 0) if(strcmp(value, "") != 0)
{ {
@@ -4129,4 +4170,4 @@ String WebCfgServer::getPreselectionForGpio(const uint8_t &pin)
return String((int8_t)PinRole::Disabled); return String((int8_t)PinRole::Disabled);
} }
#endif #endif

View File

@@ -154,7 +154,6 @@ void networkTask(void *pvParameters)
} }
esp_task_wdt_reset(); esp_task_wdt_reset();
delay(100); delay(100);
} }
} }
@@ -243,6 +242,7 @@ void bootloopDetection()
preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG); preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG);
preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD); preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD);
preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL); preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL);
preferences->putInt(preference_auth_max_entries, MAX_AUTH);
bootloopCounter = 0; bootloopCounter = 0;
} }
} }
@@ -344,10 +344,10 @@ void otaTask(void *pvParameter)
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
} }
} }
Log->println("Firmware upgrade failed, restarting"); Log->println("Firmware upgrade failed, restarting");
esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL));
restartEsp(RestartReason::OTAAborted); restartEsp(RestartReason::OTAAborted);
} }
void setupTasks(bool ota) void setupTasks(bool ota)
@@ -382,6 +382,14 @@ void setup()
Serial.begin(115200); Serial.begin(115200);
Log = &Serial; 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 = new Preferences();
preferences->begin("nukihub", false); preferences->begin("nukihub", false);
bool firstStart = initPreferences(preferences); bool firstStart = initPreferences(preferences);