diff --git a/README.md b/README.md index a18c3bc..3353a8f 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Pairing should be automatic.
When pairing is successful, the web interface should show "Paired: Yes" (it might be necessary to reload the page in your browser).
MQTT nodes like lock state and battery level should now reflect the reported values from the lock.

-Note: It is possible to run Nuki Hub alongside a Nuki Bridge. +Note: It is possible to run Nuki Hub alongside a Nuki Bridge. This is not recommended and will lead to excessive battery drain and can lead to either device missing updates. Enable "Register as app" before pairing to allow this. Otherwise the Bridge will be unregistered when pairing the Nuki Hub. @@ -96,7 +96,7 @@ This project is free to use for everyone. However if you feel like donating, you In a browser navigate to the IP address assigned to the ESP32. -### MQTT and Network Configuration +### MQTT and Network Configuration #### Basic MQTT and Network Configuration @@ -110,14 +110,14 @@ In a browser navigate to the IP address assigned to the ESP32. - Home Assistant discovery topic: Set to the Home Assistant auto discovery topic, leave empty to disable auto discovery. Usually "homeassistant" unless you manually changed this setting on the Home Assistant side. - Home Assistant device configuration URL: When using Home Assistant discovery the link to the Nuki Hub Web Configuration will be published to Home Assistant. By default when this setting is left empty this will link to the current IP of the Nuki Hub. When using a reverse proxy to access the Web Configuration you can set a custom URL here. -- Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode (Opener only): By default the lock entity in Home Assistant will enable Ring-to-Open (RTO) when unlocking and disable RTO when locking. By enabling this setting this behaviour will change and now unlocking will enable Continuous Mode and locking will disable Continuous Mode, for more information see the "[Home Assistant Discovery](#home-assistant-discovery-optional)" section of this README. +- Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode (Opener only): By default the lock entity in Home Assistant will enable Ring-to-Open (RTO) when unlocking and disable RTO when locking. By enabling this setting this behaviour will change and now unlocking will enable Continuous Mode and locking will disable Continuous Mode, for more information see the "[Home Assistant Discovery](#home-assistant-discovery-optional)" section of this README. - MQTT SSL CA Certificate: Optionally set to the CA SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional-wi-fi-and-lan8720-only)" section of this README. - MQTT SSL Client Certificate: Optionally set to the Client SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional-wi-fi-and-lan8720-only)" section of this README. - MQTT SSL Client Key: Optionally set to the Client SSL key of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional-wi-fi-and-lan8720-only)" section of this README. - Network hardware: "Wi-Fi only" by default, set to one of the specified ethernet modules if available, see the "Supported Ethernet devices" and "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section of this README. - Disable fallback to Wi-Fi / Wi-Fi config portal: By default the Nuki Hub will fallback to Wi-Fi and open the Wi-Fi configuration portal when the network connection fails. Enable this setting to disable this fallback. - Connect to AP with the best signal in an environment with multiple APs with the same SSID: Enable to perform a scan for the Access Point with the best signal strenght for the specified SSID in a multi AP/Mesh environment. -- RSSI Publish interval: Set to a positive integer to set the amount of seconds between updates to the maintenance/wifiRssi MQTT topic with the current Wi-Fi RSSI, set to -1 to disable, default 60. +- RSSI Publish interval: Set to a positive integer to set the amount of seconds between updates to the maintenance/wifiRssi MQTT topic with the current Wi-Fi RSSI, set to -1 to disable, default 60. - Network Timeout until restart: Set to a positive integer to restart the Nuki Hub after the set amount of seconds has passed without an active connection to the MQTT broker, set to -1 to disable, default 60. - Restart on disconnect: Enable to restart the Nuki Hub after 60 seconds without a connection to a network. - Enable MQTT logging: Enable to fill the maintenance/log MQTT topic with debug log information. @@ -156,7 +156,8 @@ In a browser navigate to the IP address assigned to the ESP32. ### Access Level Configuration #### Nuki General Access Control -- Publish keypad codes 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 - 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 - 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 @@ -173,7 +174,7 @@ In a browser navigate to the IP address assigned to the ESP32. #### Credentials -- User: Pick a username to enable HTTP Basic authentication for the Web Configuration, Set to "#" to disable authentication. +- User: Pick a username to enable HTTP Basic authentication for the Web Configuration, Set to "#" to disable authentication. - Password/Retype password: Pick a password to enable HTTP Basic authentication for the Web Configuration. #### Nuki Lock PIN / Nuki Opener PIN @@ -197,15 +198,16 @@ In a browser navigate to the IP address assigned to the ESP32. ### Lock - lock/action: Allows to execute lock actions. After receiving the action, the value is set to "ack". Possible actions: unlock, lock, unlatch, lockNgo, lockNgoUnlatch, fullLock, fobAction1, fobAction2, fobAction3. -- lock/statusUpdated: 1 when the Nuki Lock/Opener signals the KeyTurner state has been updated, resets to 0 when Nuki Hub has queried the updated state. +- lock/statusUpdated: 1 when the Nuki Lock/Opener signals the KeyTurner state has been updated, resets to 0 when Nuki Hub has queried the updated state. - lock/state: Reports the current lock state as a string. Possible values are: uncalibrated, locked, unlocked, unlatched, unlockedLnga, unlatching, bootRun, motorBlocked. - lock/hastate: Reports the current lock state as a string, specifically for use by Home Assistant. Possible values are: locking, locked, unlocking, unlocked, jammed. - lock/json: Reports the lock state, lockngo_state, trigger, current time, time zone offset, night mode state, last action trigger, last lock action, lock completion status, door sensor state, auth ID and auth name as JSON data. - lock/binaryState: Reports the current lock state as a string, mostly for use by Home Assistant. Possible values are: locked, unlocked. - lock/trigger: The trigger of the last action: autoLock, automatic, button, manual, system. - lock/lastLockAction: Reports the last lock action as a string. Possible values are: Unlock, Lock, Unlatch, LockNgo, LockNgoUnlatch, FullLock, FobAction1, FobAction2, FobAction3, Unknown. -- lock/log: If "Publish auth data" is enabled in the web interface, this topic will be filled with the log of authorization data. +- lock/log: If "Publish auth data" is enabled in the web interface, this topic will be filled with the log of authorization data. By default a maximum of 5 logs are published at a time. - lock/shortLog: If "Publish auth data" is enabled in the web interface, this topic will be filled with the 3 most recent entries in the log of authorization data, updates faster than lock/log. +- lock/rollingLog: If "Publish auth data" is enabled in the web interface, this topic will be filled with the last log entry from the authorization data. Logs are published in order. - lock/completionStatus: Status of the last action as reported by Nuki Lock: success, motorBlocked, canceled, tooRecent, busy, lowMotorVoltage, clutchFailure, motorPowerFailure, incompleteFailure, invalidCode, otherError, unknown. - lock/authorizationId: If enabled in the web interface, this node returns the authorization id of the last lock action. - lock/authorizationName: If enabled in the web interface, this node returns the authorization name of the last lock action. @@ -298,7 +300,7 @@ In a browser navigate to the IP address assigned to the ESP32. - maintenance/freeHeap: Only available when debug mode is enabled. Set to the current size of free heap memory in bytes. - maintenance/restartReasonNukiHub: Only available when debug mode is enabled. Set to the last reason Nuki Hub was restarted. See [RestartReason.h](/RestartReason.h) for possible values - maintenance/restartReasonNukiEsp: Only available when debug mode is enabled. Set to the last reason the ESP was restarted. See [RestartReason.h](/RestartReason.h) for possible values - + ### Misc - presence/devices: List of detected bluetooth devices as CSV. Can be used for presence detection. @@ -465,7 +467,8 @@ If a keypad is connected to the lock, keypad codes can be added, updated and rem Information about current keypad codes is published as JSON data to the "keypad/json" MQTT topic.
This needs to be enabled separately by checking "Publish keypad codes information" under "Access Level Configuration" and saving the configuration. -For security reasons, the code itself is not published. +For security reasons, the code itself is not published, unless this is explicitly enabled in the Nuki Hub settings. +By default a maximum of 10 entries are published. To change Nuki Lock/Opener keypad settings set the `keypad/actionJson` topic to a JSON formatted value containing the following nodes. @@ -492,15 +495,16 @@ Examples: The result of the last configuration change action will be published to the `configuration/commandResultJson` MQTT topic.
Possible values are "noValidPinSet", "keypadControlDisabled", "keypadNotAvailable", "keypadDisabled", "invalidConfig", "invalidJson", "noActionSet", "invalidAction", "noExistingCodeIdSet", "noNameSet", "noValidCodeSet", "noCodeSet", "invalidAllowedFrom", "invalidAllowedUntil", "invalidAllowedFromTime", "invalidAllowedUntilTime", "success", "failed", "timeOut", "working", "notPaired", "error" and "undefined".
- + ## Keypad control (alternative, optional) If a keypad is connected to the lock, keypad codes can be added, updated and removed. This has to enabled first in the configuration portal. Check "Add, modify and delete keypad codes" under "Access Level Configuration" and save the configuration. Information about codes is published under "keypad/code_x", x starting from 0 up the number of configured codes. This needs to be enabled separately by checking "Publish keypad codes information" under "Access Level Configuration" and saving the configuration. +By default a maximum of 10 entries are published. -For security reasons, the code itself is not published. To modify keypad codes, a command +For security reasons, the code itself is not published, unless this is explicitly enabled in the Nuki Hub settings. To modify keypad codes, a command structure is setup under keypad/command: - keypad/command/id: The id of an existing code, found under keypad_code_x @@ -533,6 +537,7 @@ Time control entries can be added, updated and removed. This has to enabled firs Information about current time control entries is published as JSON data to the "timecontrol/json" MQTT topic.
This needs to be enabled separately by checking "Publish time control entries information" under "Access Level Configuration" and saving the configuration. +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. diff --git a/lib/BleScanner/include/README b/lib/BleScanner/include/README deleted file mode 100644 index 45496b1..0000000 --- a/lib/BleScanner/include/README +++ /dev/null @@ -1,39 +0,0 @@ - -This directory is intended for project header files. - -A header file is a file containing C declarations and macro definitions -to be shared between several project source files. You request the use of a -header file in your project source file (C, C++, etc) located in `src` folder -by including it, with the C preprocessing directive `#include'. - -```src/main.c - -#include "header.h" - -int main (void) -{ - ... -} -``` - -Including a header file produces the same results as copying the header file -into each source file that needs it. Such copying would be time-consuming -and error-prone. With a header file, the related declarations appear -in only one place. If they need to be changed, they can be changed in one -place, and programs that include the header file will automatically use the -new version when next recompiled. The header file eliminates the labor of -finding and changing all the copies as well as the risk that a failure to -find one copy will result in inconsistencies within a program. - -In C, the usual convention is to give header files names that end with `.h'. -It is most portable to use only letters, digits, dashes, and underscores in -header file names, and at most one dot. - -Read more about using header files in official GCC documentation: - -* Include Syntax -* Include Operation -* Once-Only Headers -* Computed Includes - -https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/BleScanner/lib/README b/lib/BleScanner/lib/README deleted file mode 100644 index 8c9c29c..0000000 --- a/lib/BleScanner/lib/README +++ /dev/null @@ -1,46 +0,0 @@ - -This directory is intended for project specific (private) libraries. -PlatformIO will compile them to static libraries and link into executable file. - -The source code of each library should be placed in a an own separate directory -("lib/your_library_name/[here are source files]"). - -For example, see a structure of the following two libraries `Foo` and `Bar`: - -|--lib -| | -| |--Bar -| | |--docs -| | |--examples -| | |--src -| | |- Bar.c -| | |- Bar.h -| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html -| | -| |--Foo -| | |- Foo.c -| | |- Foo.h -| | -| |- README --> THIS FILE -| -|- platformio.ini -|--src - |- main.c - -and a contents of `src/main.c`: -``` -#include -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/BleScanner/library.json b/lib/BleScanner/library.json index ba6039d..bac7938 100644 --- a/lib/BleScanner/library.json +++ b/lib/BleScanner/library.json @@ -1,6 +1,6 @@ { "name": "BleScanner", - "version": "1.0.0", + "version": "1.1.0", "description": "Generic BleScanner using NimBle listening to advertisements. Used by NukiBleEsp32 and EnOcean libraries", "keywords": "ble esp32 scanner", "authors": [ @@ -19,7 +19,7 @@ "dependencies": [ { "name": "NimBLE-Arduino", - "version": "h2zero/NimBLE-Arduino @ ^1.3.8" + "version": "h2zero/NimBLE-Arduino @ ^1.4.0" } ] } \ No newline at end of file diff --git a/lib/BleScanner/platformio.ini b/lib/BleScanner/platformio.ini index 4bd1696..f7fcaeb 100644 --- a/lib/BleScanner/platformio.ini +++ b/lib/BleScanner/platformio.ini @@ -4,4 +4,4 @@ board = esp32dev framework = arduino lib_deps = - h2zero/NimBLE-Arduino @ ^1.3.8 \ No newline at end of file + h2zero/NimBLE-Arduino @ ^1.4.0 \ No newline at end of file diff --git a/lib/BleScanner/src/BleScanner.cpp b/lib/BleScanner/src/BleScanner.cpp index 4fdba82..cd76cef 100644 --- a/lib/BleScanner/src/BleScanner.cpp +++ b/lib/BleScanner/src/BleScanner.cpp @@ -22,11 +22,14 @@ Scanner::Scanner(int reservedSubscribers) { void Scanner::initialize(const std::string& deviceName, const bool wantDuplicates, const uint16_t interval, const uint16_t window) { if (!BLEDevice::getInitialized()) { + if (wantDuplicates) { + // reduce memory footprint, cache is not used anyway + NimBLEDevice::setScanDuplicateCacheSize(10); + } BLEDevice::init(deviceName); } bleScan = BLEDevice::getScan(); bleScan->setAdvertisedDeviceCallbacks(this, wantDuplicates); - bleScan->setActiveScan(true); bleScan->setInterval(interval); bleScan->setWindow(window); } @@ -36,15 +39,20 @@ void Scanner::update() { return; } - bleScan->clearResults(); + if (scanDuration == 0) { + // Avoid unbridled growth of results vector + bleScan->setMaxResults(0); + } else { + log_w("Ble scanner max results not 0. Be aware of memory issue due to unbridled growth of results vector"); + } bool result = bleScan->start(scanDuration, nullptr, false); - if (!result) { - scanErrors++; - if (scanErrors % 100 == 0) { - log_w("BLE Scan error (100x)"); - } - } + // if (!result) { + // scanErrors++; + // if (scanErrors % 100 == 0) { + // log_w("BLE Scan error (100x)"); + // } + // } } void Scanner::enableScanning(bool enable) { diff --git a/lib/BleScanner/src/BleScanner.h b/lib/BleScanner/src/BleScanner.h index c4ef202..c7b0bcb 100644 --- a/lib/BleScanner/src/BleScanner.h +++ b/lib/BleScanner/src/BleScanner.h @@ -16,6 +16,10 @@ #include #include "BleInterfaces.h" +// Access to a globally available instance of BleScanner, created when first used +// Note that BLESCANNER.initialize() has to be called somewhere +#define BLESCANNER BleScanner::Scanner::instance() + namespace BleScanner { class Scanner : public Publisher, BLEAdvertisedDeviceCallbacks { @@ -23,6 +27,11 @@ class Scanner : public Publisher, BLEAdvertisedDeviceCallbacks { Scanner(int reservedSubscribers = 10); ~Scanner() = default; + static Scanner& instance() { + static Scanner* scanner = new Scanner(); // only initialized once on first call + return *scanner; + } + /** * @brief Initializes the BLE scanner * @@ -77,7 +86,7 @@ class Scanner : public Publisher, BLEAdvertisedDeviceCallbacks { void onResult(NimBLEAdvertisedDevice* advertisedDevice) override; private: - uint32_t scanDuration = 3; + uint32_t scanDuration = 0; //default indefinite scanning time BLEScan* bleScan = nullptr; std::vector subscribers; uint16_t scanErrors = 0; diff --git a/src/MqttTopics.h b/src/MqttTopics.h index ff2caa3..01a552c 100644 --- a/src/MqttTopics.h +++ b/src/MqttTopics.h @@ -13,6 +13,8 @@ #define mqtt_topic_lock_last_lock_action "/lock/lastLockAction" #define mqtt_topic_lock_log "/lock/log" #define mqtt_topic_lock_log_latest "/lock/shortLog" +#define mqtt_topic_lock_log_rolling "/lock/rollingLog" +#define mqtt_topic_lock_log_rolling_last "lock/lastRollingLog" #define mqtt_topic_lock_auth_id "/lock/authorizationId" #define mqtt_topic_lock_auth_name "/lock/authorizationName" #define mqtt_topic_lock_completionStatus "/lock/completionStatus" diff --git a/src/Network.cpp b/src/Network.cpp index 6ae2b05..1e3ecfa 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -3023,6 +3023,27 @@ void Network::publishHASSConfigAccessLog(char *deviceType, const char *baseTopic "", { { (char*)"ic", (char*)"mdi:format-list-bulleted" }, { (char*)"val_tpl", (char*)"{{ (value_json|selectattr('type', 'eq', 'LockAction')|selectattr('action', 'in', ['Lock', 'Unlock', 'Unlatch'])|first|default).authorizationName|default }}" }}); + + String rollingSate = "~"; + rollingSate.concat(mqtt_topic_lock_log_rolling); + const char *rollingStateChr = rollingSate.c_str(); + + publishHassTopic("sensor", + "rolling_log", + uidString, + "_rolling_log", + "Rolling authorization log", + name, + baseTopic, + String("~") + mqtt_topic_lock_log_rolling, + deviceType, + "", + "", + "diagnostic", + "", + { { (char*)"ic", (char*)"mdi:format-list-bulleted" }, + { (char*)"json_attr_t", (char*)rollingStateChr }, + { (char*)"val_tpl", (char*)"{{value_json.authorizationId}}" }}); } void Network::publishHASSConfigKeypad(char *deviceType, const char *baseTopic, char *name, char *uidString) @@ -3194,6 +3215,7 @@ void Network::removeHASSConfig(char* uidString) removeHassTopic((char*)"sensor", (char*)"sound_level", uidString); removeHassTopic((char*)"sensor", (char*)"last_action_authorization", uidString); removeHassTopic((char*)"sensor", (char*)"keypad_status", uidString); + removeHassTopic((char*)"sensor", (char*)"rolling_log", uidString); removeHassTopic((char*)"sensor", (char*)"wifi_signal_strength", uidString); removeHassTopic((char*)"sensor", (char*)"bluetooth_signal_strength", uidString); removeHassTopic((char*)"binary_sensor", (char*)"continuous_mode", uidString); diff --git a/src/NetworkLock.cpp b/src/NetworkLock.cpp index da3d7a6..d5b8f7b 100644 --- a/src/NetworkLock.cpp +++ b/src/NetworkLock.cpp @@ -116,6 +116,11 @@ void NetworkLock::initialize() _network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--"); } + if(_preferences->getBool(preference_publish_authdata, false)) + { + _network->subscribe(_mqttPath, mqtt_topic_lock_log_rolling_last); + } + _network->addReconnectedCallback([&]() { _reconnected = true; @@ -157,6 +162,13 @@ void NetworkLock::onMqttDataReceived(const char* topic, byte* payload, const uns delay(200); restartEsp(RestartReason::RequestedViaMqtt); } + else if(comparePrefixedPath(topic, mqtt_topic_lock_log_rolling_last)) + { + if(strcmp(value, "") == 0 || + strcmp(value, "--") == 0) return; + + if(atoi(value) > 0 && atoi(value) > _lastRollingLog) _lastRollingLog = atoi(value); + } if(comparePrefixedPath(topic, mqtt_topic_lock_action)) { @@ -394,7 +406,7 @@ void NetworkLock::publishKeyTurnerState(const NukiLock::KeyTurnerState& keyTurne serializeJson(json, _buffer, _bufferSize); publishString(mqtt_topic_lock_json, _buffer); - + serializeJson(jsonBattery, _buffer, _bufferSize); publishString(mqtt_topic_battery_basic_json, _buffer); @@ -439,7 +451,7 @@ void NetworkLock::publishAuthorizationInfo(const std::list& { char str[50]; char authName[33]; - bool authFound = false; + uint32_t authIndex = 0; JsonDocument json; @@ -454,9 +466,9 @@ void NetworkLock::publishAuthorizationInfo(const std::list& memcpy(authName, log.name, sizeName); if(authName[sizeName - 1] != '\0') authName[sizeName] = '\0'; - if(!authFound) + if(log.index > authIndex) { - authFound = true; + authIndex = log.index; _authFound = true; _authId = log.authId; memset(_authName, 0, sizeof(_authName)); @@ -531,6 +543,14 @@ void NetworkLock::publishAuthorizationInfo(const std::list& entry["completionStatus"] = str; break; } + + if(log.index > _lastRollingLog) + { + _lastRollingLog = log.index; + serializeJson(entry, _buffer, _bufferSize); + publishString(mqtt_topic_lock_log_rolling, _buffer); + publishInt(mqtt_topic_lock_log_rolling_last, log.index); + } } serializeJson(json, _buffer, _bufferSize); @@ -538,7 +558,7 @@ void NetworkLock::publishAuthorizationInfo(const std::list& if(latest) publishString(mqtt_topic_lock_log_latest, _buffer); else publishString(mqtt_topic_lock_log, _buffer); - if(authFound) + if(authIndex > 0) { publishUInt(mqtt_topic_lock_auth_id, _authId); publishString(mqtt_topic_lock_auth_name, _authName); @@ -735,6 +755,12 @@ void NetworkLock::publishKeypad(const std::list& entries, auto jsonEntry = json.add(); jsonEntry["codeId"] = entry.codeId; + + if(_preferences->getBool(preference_keypad_publish_code, false)) + { + jsonEntry["code"] = entry.code; + } + jsonEntry["enabled"] = entry.enabled; jsonEntry["name"] = entry.name; char createdDT[20]; @@ -826,6 +852,19 @@ void NetworkLock::publishKeypad(const std::list& entries, ++index; } + + if(!_preferences->getBool(preference_keypad_publish_code, false)) + { + for(int i=0; iremoveTopic(codeTopic, "code"); + } + } } else if (maxKeypadCodeCount > 0) { @@ -838,6 +877,7 @@ void NetworkLock::publishKeypad(const std::list& entries, codeTopic.concat("/"); _network->removeTopic(codeTopic, "id"); _network->removeTopic(codeTopic, "enabled"); + _network->removeTopic(codeTopic, "code"); _network->removeTopic(codeTopic, "name"); _network->removeTopic(codeTopic, "createdYear"); _network->removeTopic(codeTopic, "createdMonth"); @@ -1028,6 +1068,7 @@ void NetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, cha else { _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); + _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); } if(hasKeypad) @@ -1098,6 +1139,12 @@ void NetworkLock::publishKeypadEntry(const String topic, NukiLock::KeypadEntry e publishInt(concat(topic, "/id").c_str(), entry.codeId); publishBool(concat(topic, "/enabled").c_str(), entry.enabled); publishString(concat(topic, "/name").c_str(), codeName); + + if(_preferences->getBool(preference_keypad_publish_code, false)) + { + publishInt(concat(topic, "/code").c_str(), entry.code); + } + publishInt(concat(topic, "/createdYear").c_str(), entry.dateCreatedYear); publishInt(concat(topic, "/createdMonth").c_str(), entry.dateCreatedMonth); publishInt(concat(topic, "/createdDay").c_str(), entry.dateCreatedDay); diff --git a/src/NetworkLock.h b/src/NetworkLock.h index 67e812f..1b1ed97 100644 --- a/src/NetworkLock.h +++ b/src/NetworkLock.h @@ -92,6 +92,7 @@ private: uint32_t _authId = 0; char _authName[33]; bool _authFound = false; + uint32_t _lastRollingLog = 0; char* _buffer; size_t _bufferSize; diff --git a/src/NetworkOpener.cpp b/src/NetworkOpener.cpp index f06f01d..731c7b3 100644 --- a/src/NetworkOpener.cpp +++ b/src/NetworkOpener.cpp @@ -97,6 +97,11 @@ void NetworkOpener::initialize() _network->initTopic(_mqttPath, mqtt_topic_timecontrol_action, "--"); } + if(_preferences->getBool(preference_publish_authdata, false)) + { + _network->subscribe(_mqttPath, mqtt_topic_lock_log_rolling_last); + } + _network->addReconnectedCallback([&]() { _reconnected = true; @@ -116,6 +121,14 @@ void NetworkOpener::onMqttDataReceived(const char* topic, byte* payload, const u { char* value = (char*)payload; + if(comparePrefixedPath(topic, mqtt_topic_lock_log_rolling_last)) + { + if(strcmp(value, "") == 0 || + strcmp(value, "--") == 0) return; + + if(atoi(value) > 0 && atoi(value) > _lastRollingLog) _lastRollingLog = atoi(value); + } + if(comparePrefixedPath(topic, mqtt_topic_lock_action)) { if(strcmp(value, "") == 0 || @@ -406,7 +419,7 @@ void NetworkOpener::publishAuthorizationInfo(const std::list authIndex) { - authFound = true; + authIndex = log.index; _authFound = true; _authId = log.authId; memset(_authName, 0, sizeof(_authName)); @@ -521,6 +534,14 @@ void NetworkOpener::publishAuthorizationInfo(const std::list _lastRollingLog) + { + _lastRollingLog = log.index; + serializeJson(entry, _buffer, _bufferSize); + publishString(mqtt_topic_lock_log_rolling, _buffer); + publishInt(mqtt_topic_lock_log_rolling_last, log.index); + } } serializeJson(json, _buffer, _bufferSize); @@ -528,7 +549,7 @@ void NetworkOpener::publishAuthorizationInfo(const std::list 0) { publishUInt(mqtt_topic_lock_auth_id, _authId); publishString(mqtt_topic_lock_auth_name, _authName); @@ -702,13 +723,23 @@ void NetworkOpener::publishBleAddress(const std::string &address) publishString(mqtt_topic_lock_address, address); } -void NetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction) +void NetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const bool& publishAuthData, char* lockAction, char* unlockAction, char* openAction) { String availabilityTopic = _preferences->getString("mqttpath"); availabilityTopic.concat("/maintenance/mqttConnectionState"); _network->publishHASSConfig(deviceType, baseTopic, name, uidString, availabilityTopic.c_str(), false, lockAction, unlockAction, openAction); _network->publishHASSConfigAdditionalOpenerEntities(deviceType, baseTopic, name, uidString); + + if(publishAuthData) + { + _network->publishHASSConfigAccessLog(deviceType, baseTopic, name, uidString); + } + else + { + _network->removeHASSConfigTopic((char*)"sensor", (char*)"last_action_authorization", uidString); + _network->removeHASSConfigTopic((char*)"sensor", (char*)"rolling_log", uidString); + } } void NetworkOpener::removeHASSConfig(char* uidString) @@ -734,6 +765,12 @@ void NetworkOpener::publishKeypad(const std::list& entrie jsonEntry["codeId"] = entry.codeId; jsonEntry["enabled"] = entry.enabled; jsonEntry["name"] = entry.name; + + if(_preferences->getBool(preference_keypad_publish_code, false)) + { + jsonEntry["code"] = entry.code; + } + char createdDT[20]; sprintf(createdDT, "%04d-%02d-%02d %02d:%02d:%02d", entry.dateCreatedYear, entry.dateCreatedMonth, entry.dateCreatedDay, entry.dateCreatedHour, entry.dateCreatedMin, entry.dateCreatedSec); jsonEntry["dateCreated"] = createdDT; @@ -823,6 +860,19 @@ void NetworkOpener::publishKeypad(const std::list& entrie ++index; } + + if(!_preferences->getBool(preference_keypad_publish_code, false)) + { + for(int i=0; iremoveTopic(codeTopic, "code"); + } + } } else if (maxKeypadCodeCount > 0) { @@ -835,6 +885,7 @@ void NetworkOpener::publishKeypad(const std::list& entrie codeTopic.concat("/"); _network->removeTopic(codeTopic, "id"); _network->removeTopic(codeTopic, "enabled"); + _network->removeTopic(codeTopic, "code"); _network->removeTopic(codeTopic, "name"); _network->removeTopic(codeTopic, "createdYear"); _network->removeTopic(codeTopic, "createdMonth"); @@ -1025,6 +1076,12 @@ void NetworkOpener::publishKeypadEntry(const String topic, NukiLock::KeypadEntry publishInt(concat(topic, "/id").c_str(), entry.codeId); publishBool(concat(topic, "/enabled").c_str(), entry.enabled); publishString(concat(topic, "/name").c_str(), codeName); + + if(_preferences->getBool(preference_keypad_publish_code, false)) + { + publishInt(concat(topic, "/code").c_str(), entry.code); + } + publishInt(concat(topic, "/createdYear").c_str(), entry.dateCreatedYear); publishInt(concat(topic, "/createdMonth").c_str(), entry.dateCreatedMonth); publishInt(concat(topic, "/createdDay").c_str(), entry.dateCreatedDay); diff --git a/src/NetworkOpener.h b/src/NetworkOpener.h index 9368702..270c985 100644 --- a/src/NetworkOpener.h +++ b/src/NetworkOpener.h @@ -31,7 +31,7 @@ public: void publishRssi(const int& rssi); void publishRetry(const std::string& message); void publishBleAddress(const std::string& address); - void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, char* lockAction, char* unlockAction, char* openAction); + void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const bool& publishAuthData, char* lockAction, char* unlockAction, char* openAction); void removeHASSConfig(char* uidString); void publishKeypad(const std::list& entries, uint maxKeypadCodeCount); void publishTimeControl(const std::list& timeControlEntries); @@ -71,7 +71,7 @@ private: void doorbellSuppressionToString(const int dbsupr, char* str); void soundToString(const int sound, char* str); void capabilitiesToString(const int capabilities, char* str); - + String concat(String a, String b); Preferences* _preferences; @@ -93,6 +93,7 @@ private: uint32_t _authId = 0; char _authName[33]; bool _authFound = false; + uint32_t _lastRollingLog = 0; NukiOpener::LockState _currentLockState = NukiOpener::LockState::Undefined; diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 3c977c5..9c1438b 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -333,7 +333,7 @@ void NukiOpenerWrapper::unpair() Preferences nukiBlePref; nukiBlePref.begin("NukiHubopener", false); nukiBlePref.clear(); - nukiBlePref.end(); + nukiBlePref.end(); _deviceId->assignNewId(); _preferences->remove(preference_nuki_id_opener); _paired = false; @@ -521,6 +521,8 @@ void NukiOpenerWrapper::updateAuthData(bool retrieved) log.resize(_preferences->getInt(preference_authlog_max_entries, 3)); } + log.sort([](const NukiOpener::LogEntry& a, const NukiOpener::LogEntry& b) { return a.index < b.index; }); + if(log.size() > 0) { _network->publishAuthorizationInfo(log, true); @@ -537,6 +539,8 @@ void NukiOpenerWrapper::updateAuthData(bool retrieved) log.resize(_preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG)); } + log.sort([](const NukiOpener::LogEntry& a, const NukiOpener::LogEntry& b) { return a.index < b.index; }); + Log->print(F("Log size: ")); Log->println(log.size()); @@ -1359,7 +1363,7 @@ void NukiOpenerWrapper::gpioActionCallback(const GpioAction &action, const int& void NukiOpenerWrapper::onKeypadCommandReceived(const char *command, const uint &id, const String &name, const String &code, const int& enabled) { if(_preferences->getBool(preference_disable_non_json, false)) return; - + if(!_preferences->getBool(preference_keypad_control_enabled)) { _network->publishKeypadCommandResult("KeypadControlDisabled"); @@ -2073,11 +2077,11 @@ void NukiOpenerWrapper::setupHASS() if(_preferences->getBool(preference_opener_continuous_mode)) { - _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, (char*)"deactivateCM", (char*)"activateCM", (char*)"electricStrikeActuation"); + _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _publishAuthData, (char*)"deactivateCM", (char*)"activateCM", (char*)"electricStrikeActuation"); } else { - _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, (char*)"deactivateRTO", (char*)"activateRTO", (char*)"electricStrikeActuation"); + _network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _publishAuthData, (char*)"deactivateRTO", (char*)"activateRTO", (char*)"electricStrikeActuation"); } _hassSetupCompleted = true; diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 86dbea6..0054449 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -496,12 +496,14 @@ void NukiWrapper::updateAuthData(bool retrieved) std::list log; _nukiLock.getLogEntries(&log); - + if(log.size() > _preferences->getInt(preference_authlog_max_entries, 3)) { log.resize(_preferences->getInt(preference_authlog_max_entries, 3)); } + log.sort([](const NukiLock::LogEntry& a, const NukiLock::LogEntry& b) { return a.index < b.index; }); + if(log.size() > 0) { _network->publishAuthorizationInfo(log, true); @@ -517,6 +519,8 @@ void NukiWrapper::updateAuthData(bool retrieved) { log.resize(_preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG)); } + + log.sort([](const NukiLock::LogEntry& a, const NukiLock::LogEntry& b) { return a.index < b.index; }); Log->print(F("Log size: ")); Log->println(log.size()); diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index 6e4b7b2..87add3f 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -51,6 +51,7 @@ #define preference_access_level (char*)"accLvl" #define preference_keypad_info_enabled (char*)"kpInfoEnabled" #define preference_keypad_control_enabled (char*)"kpCntrlEnabled" +#define preference_keypad_publish_code (char*)"kpPubCode" #define preference_timecontrol_control_enabled (char*)"tcCntrlEnabled" #define preference_timecontrol_info_enabled (char*)"tcInfoEnabled" #define preference_publish_authdata (char*)"pubAuth" @@ -97,7 +98,7 @@ private: preference_network_hardware, preference_network_wifi_fallback_disabled, preference_rssi_publish_interval, preference_hostname, preference_find_best_rssi, preference_network_timeout, preference_restart_on_disconnect, preference_restart_ble_beacon_lost, preference_query_interval_lockstate, preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_keypad_control_enabled, - preference_keypad_info_enabled, preference_acl, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, + preference_keypad_info_enabled, preference_keypad_publish_code, preference_acl, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_conf_lock_basic_acl, preference_conf_lock_advanced_acl, preference_conf_opener_basic_acl, preference_conf_opener_advanced_acl, preference_access_level, preference_register_as_app, preference_command_nr_of_retries, preference_command_retry_delay, preference_cred_user, preference_cred_password, preference_publish_authdata, preference_publish_debug_info, preference_presence_detection_timeout, preference_disable_non_json, @@ -115,8 +116,8 @@ private: { preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_opener_enabled, preference_opener_continuous_mode, preference_enable_bootloop_reset, preference_webserver_enabled, preference_find_best_rssi, preference_restart_on_disconnect, preference_keypad_control_enabled, - preference_keypad_info_enabled, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_ip_dhcp_enabled, - preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info, preference_network_wifi_fallback_disabled, preference_disable_non_json + preference_keypad_info_enabled, preference_keypad_publish_code, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, + preference_ip_dhcp_enabled, preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info, preference_network_wifi_fallback_disabled, preference_disable_non_json }; const bool isRedacted(const char* key) const diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index c68e7e4..d42e11a 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -561,6 +561,11 @@ bool WebCfgServer::processArgs(String& message) _preferences->putBool(preference_keypad_info_enabled, (value == "1")); configChanged = true; } + else if(key == "KPCODE") + { + _preferences->putBool(preference_keypad_publish_code, (value == "1")); + configChanged = true; + } else if(key == "KPENA") { _preferences->putBool(preference_keypad_control_enabled, (value == "1")); @@ -1400,12 +1405,13 @@ void WebCfgServer::buildAccLvlHtml(String &response) if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad())) { - printCheckBox(response, "KPPUB", "Publish keypad codes information", _preferences->getBool(preference_keypad_info_enabled), ""); + printCheckBox(response, "KPPUB", "Publish keypad entries information", _preferences->getBool(preference_keypad_info_enabled), ""); + printCheckBox(response, "KPCODE", "Also publish keypad codes (Disadvised for security reasons)", _preferences->getBool(preference_keypad_publish_code, false), ""); printCheckBox(response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), ""); } printCheckBox(response, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), ""); printCheckBox(response, "TCENA", "Add, modify and delete time control entries", _preferences->getBool(preference_timecontrol_control_enabled), ""); - printCheckBox(response, "PUBAUTH", "Publish authorisation log (may reduce battery life)", _preferences->getBool(preference_publish_authdata), ""); + printCheckBox(response, "PUBAUTH", "Publish authorization log (may reduce battery life)", _preferences->getBool(preference_publish_authdata), ""); response.concat("
"); if(_nuki != nullptr) {