Merge branch 'master' of github.com:technyon/nuki_hub

This commit is contained in:
technyon
2024-11-11 03:42:47 +01:00
32 changed files with 4092 additions and 3897 deletions

View File

@@ -49,7 +49,7 @@ See the "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section f
## Recommended ESP32 devices
- If WIFI6 is required: ESP32-C6
- If WIFI6 is absolutely required: ESP32-C6
- If PoE is required: Any of the above mentioned devices with PoE or any other ESP device in combination with a SPI Ethernet module ([W5500](https://www.aliexpress.com/w/wholesale-w5500.html)) and [PoE to Ethernet and USB type B/C splitter](https://aliexpress.com/w/wholesale-poe-splitter-usb-c.html)
- If you want maximum performance and intend to run any or multiple of the following:
- a Nuki Lock and Nuki Opener and/or
@@ -69,17 +69,17 @@ The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using P
| Feature | Nuki Hub | Nuki Bridge |
|---|---|---|
| Bridge API | | x |
| Smart Lock remote control | (optional via smarthome solution) | x |
| Smart Lock remote control | x | x |
| Smart Home integration via Matter | | x |
| Apple HomeKit integration via Matter | | x |
| MQTT API | x | x (only for SL > 3, Pro models) |
| MQTT API | x | x (only for SL 3, 4 and 4 Pro models) |
| Wired LAN support | x | |
| Power over Ethernet (PoE) | x (if supported by LAN/ESP module) | |
| WLAN support | x | x (only for SL > 3, Pro Models) || Home Assistant integration | x (full integration of most Nuki features) | x |
| WLAN support | x | x (only for SL 3 and 4 Pro Models) || Home Assistant integration | x (full integration of most Nuki features) | x |
| Home Automation platform integration | x | x |
| Cloud support | (optional via smarthome solution) | x |
| Cloud-less operation | x | x (since fw 3.8.2, to be tested) |
| Smarthome app integration | | x |
| Cloud support | x (optional via smarthome solution) | x |
| Cloud-less operation | x | x (since fw 3.8.2) |
| Official Nuki app integration | | x |
| Nuki Smartlocks all models | x | x |
| Nuki opener | x | x |
| Nuki Keypad (1.0 and 2.0) | x | x |
@@ -87,14 +87,12 @@ The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using P
| Fine-grained access control of MQTT API | x | |
| Export of lock actions via MQTT API | x | |
| Control via GPIO | x | |
| Hybrid mode for Pro locks | x | |
| Hybrid mode for WiFI and Thread connected locks | x | |
## Support Nuki Hub development
If you haven't ordered your Nuki product yet, you can support me by using my referrer code when placing your order:<br>
REF443RM5HR5X<br>
REF2BJHFVHZKK<br>
This will also give you a 10% discount on your order.<br>
<br>
This project is free to use for everyone. However if you feel like donating, you can buy me a coffee at ko-fi.com:<br>
@@ -203,10 +201,11 @@ In a browser navigate to the IP address assigned to the ESP32.
- MQTT User: If using authentication on the MQTT broker set to a username with read/write rights on the MQTT broker, set to # to clear
- MQTT Password : If using authentication on the MQTT broker set to the password belonging to a username with read/write rights on the MQTT broker, set to # to clear
- MQTT NukiHub Path: Set to the preferred MQTT root topic for NukiHub, defaults to "nukihub". Make sure this topic is unique when using multiple ESP32 NukiHub devices
- Enable Home Assistant auto discovery: Enable Home Assistant MQTT auto discovery. Will automatically create entities in Home Assistant for NukiHub and connected Nuki Lock and/or Opener when enabled.
#### Advanced MQTT Configuration
- 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 discovery topic: Set to the Home Assistant auto discovery topic. Usually "homeassistant" unless you manually changed this setting on the Home Assistant side.
- 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)" 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)" section of this README.
@@ -344,39 +343,39 @@ After importing the device will reboot.
- opener/retry: Reports the current number of retries for the current command. 0 when command is successful, "failed" if the number of retries is greater than the maximum configured number of retries.
### Configuration
- [lock/opener]/configuration/buttonEnabled: 1 if the Nuki Lock/Opener button is enabled, otherwise 0.
- [lock/opener]/configuration/ledEnabled: 1 if the Nuki Lock/Opener LED is enabled, otherwise 0.
- [lock/opener]/configuration/ledBrightness: Set to the brightness of the LED on the Nuki Lock (0=min; 5=max) (Lock only).
- [lock/opener]/configuration/singleLock: 0 if the Nuki Lock is set to double-lock the door, otherwise 1 (= single-lock) (Lock only).
- [lock/opener]/configuration/autoLock: 1 if the Nuki Lock is set to Auto Lock, otherwise 0 (Lock only).
- [lock/opener]/configuration/autoUnlock: 1 if the Nuki Lock is set to Auto Unlock, otherwise 0 (Lock only).
- [lock/opener]/configuration/soundLevel: Set to the volume for sounds the Nuki Opener plays (0 = min; 255 = max) (Opener only).
- [lock/opener]/configuration/action: Allows changing configuration settings of the Nuki Lock/Opener using a JSON formatted value. After receiving the action, the value is set to "--". See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible actions/values
- [lock/opener]/configuration/commandResult: Result of the last configuration change action as JSON data. See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible values
- [lock/opener]/configuration/basicJson: The current basic configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--set-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--set-config) for available settings. Please note: Longitude and Latitude of the Lock/Opener are not published to MQTT by design. These values can still be changed though.
- [lock/opener]/configuration/advancedJson: The current advanced configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--advanced-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--advanced-config) for available settings.
- [lock/opener/]configuration/buttonEnabled: 1 if the Nuki Lock/Opener button is enabled, otherwise 0.
- [lock/opener/]configuration/ledEnabled: 1 if the Nuki Lock/Opener LED is enabled, otherwise 0.
- [lock/opener/]configuration/ledBrightness: Set to the brightness of the LED on the Nuki Lock (0=min; 5=max) (Lock only).
- [lock/opener/]configuration/singleLock: 0 if the Nuki Lock is set to double-lock the door, otherwise 1 (= single-lock) (Lock only).
- [lock/opener/]configuration/autoLock: 1 if the Nuki Lock is set to Auto Lock, otherwise 0 (Lock only).
- [lock/opener/]configuration/autoUnlock: 1 if the Nuki Lock is set to Auto Unlock, otherwise 0 (Lock only).
- [lock/opener/]configuration/soundLevel: Set to the volume for sounds the Nuki Opener plays (0 = min; 255 = max) (Opener only).
- [lock/opener/]configuration/action: Allows changing configuration settings of the Nuki Lock/Opener using a JSON formatted value. After receiving the action, the value is set to "--". See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible actions/values
- [lock/opener/]configuration/commandResult: Result of the last configuration change action as JSON data. See the "[Changing Nuki Lock/Opener Configuration](#changing-nuki-lockopener-configuration)" section of this README for possible values
- [lock/opener/]configuration/basicJson: The current basic configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--set-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--set-config) for available settings. Please note: Longitude and Latitude of the Lock/Opener are not published to MQTT by design. These values can still be changed though.
- [lock/opener/]configuration/advancedJson: The current advanced configuration of the Nuki Lock/Opener as JSON data. See [Nuki Smart Lock API](https://developer.nuki.io/page/nuki-smart-lock-api-2/2/#heading--advanced-config) and [Nuki Opener API](https://developer.nuki.io/page/nuki-opener-api-1/7/#heading--advanced-config) for available settings.
### Query
- [lock/opener]/query/lockstate: Set to 1 to trigger query lockstate. Auto-resets to 0.
- [lock/opener]/query/config: Set to 1 to trigger query config. Auto-resets to 0.
- [lock/opener]/query/keypad: Set to 1 to trigger query keypad. Auto-resets to 0.
- [lock/opener]/query/battery: Set to 1 to trigger query battery. Auto-resets to 0.
- [lock/opener]/query/lockstateCommandResult: Set to 1 to trigger query lockstate command result. Auto-resets to 0.
- [lock/opener/]query/lockstate: Set to 1 to trigger query lockstate. Auto-resets to 0.
- [lock/opener/]query/config: Set to 1 to trigger query config. Auto-resets to 0.
- [lock/opener/]query/keypad: Set to 1 to trigger query keypad. Auto-resets to 0.
- [lock/opener/]query/battery: Set to 1 to trigger query battery. Auto-resets to 0.
- [lock/opener/]query/lockstateCommandResult: Set to 1 to trigger query lockstate command result. Auto-resets to 0.
### Battery
- [lock/opener]/battery/level: Battery level in percent (Lock only).
- [lock/opener]/battery/critical: 1 if battery level is critical, otherwise 0.
- [lock/opener]/battery/charging: 1 if charging, otherwise 0 (Lock only).
- [lock/opener]/battery/voltage: Current Battery voltage (V).
- [lock/opener]/battery/drain: The drain of the last lock action in Milliwattseconds (mWs) (Lock only).
- [lock/opener]/battery/maxTurnCurrent: The highest current of the turn motor during the last lock action (A) (Lock only).
- [lock/opener]/battery/lockDistance: The total distance during the last lock action in centidegrees (Lock only).
- [lock/opener]/battery/keypadCritical: 1 if the battery level of a connected keypad is critical, otherwise 0.
- [lock/opener]/battery/doorSensorCritical (only available in hybdrid mode): 1 if the battery level of a connected doorsensor is critical, otherwise 0.
- [lock/opener]/battery/basicJson: The current battery state (critical, charging, level and keypad critical) of the Nuki Lock/Opener as JSON data.
- [lock/opener]/battery/advancedJson: : The current battery state (critical, batteryDrain, batteryVoltage, lockAction, startVoltage, lowestVoltage, lockDistance, startTemperature, maxTurnCurrent and batteryResistance) of the Nuki Lock/Opener as JSON data.
- [lock/opener/]battery/level: Battery level in percent (Lock only).
- [lock/opener/]battery/critical: 1 if battery level is critical, otherwise 0.
- [lock/opener/]battery/charging: 1 if charging, otherwise 0 (Lock only).
- [lock/opener/]battery/voltage: Current Battery voltage (V).
- [lock/opener/]battery/drain: The drain of the last lock action in Milliwattseconds (mWs) (Lock only).
- [lock/opener/]battery/maxTurnCurrent: The highest current of the turn motor during the last lock action (A) (Lock only).
- [lock/opener/]battery/lockDistance: The total distance during the last lock action in centidegrees (Lock only).
- [lock/opener/]battery/keypadCritical: 1 if the battery level of a connected keypad is critical, otherwise 0.
- [lock/opener/]battery/doorSensorCritical (only available in hybrid mode): 1 if the battery level of a connected doorsensor is critical, otherwise 0.
- [lock/opener/]battery/basicJson: The current battery state (critical, charging, level and keypad critical) of the Nuki Lock/Opener as JSON data.
- [lock/opener/]battery/advancedJson: : The current battery state (critical, batteryDrain, batteryVoltage, lockAction, startVoltage, lowestVoltage, lockDistance, startTemperature, maxTurnCurrent and batteryResistance) of the Nuki Lock/Opener as JSON data.
### Keypad
@@ -389,8 +388,8 @@ After importing the device will reboot.
### Info
- info/nukiHubVersion: Set to the current version number of the Nuki Hub firmware.
- info/firmwareVersion: Set to the current version number of the Nuki Lock/Opener firmware.
- info/hardwareVersion: Set to the hardware version number of the Nuki Lock/Opener.
- [lock/opener/]info/firmwareVersion: Set to the current version number of the Nuki Lock/Opener firmware.
- [lock/opener/]info/hardwareVersion: Set to the hardware version number of the Nuki Lock/Opener.
- info/nukiHubIp: Set to the IP of the Nuki Hub.
- info/nukiHubLatest: Set to the latest available Nuki Hub firmware version number (if update checking is enabled in the settings).
@@ -518,10 +517,10 @@ If Home Assistant discovery is enabled (see the [Home Assistant Discovery](#hom
After the initial installation of the Nuki Hub firmware via serial connection, further updates can be deployed via OTA update from a browser.<br>
In the configuration portal, select "Firmware update" from the main page.<br>
<br>
The easiest way to upgrade Nuki Hub, if Nuki Hub is connected to the internet, is to select "Auto Update".<br>
The easiest way to upgrade Nuki Hub, if Nuki Hub is connected to the internet, is to select "Update to latest version".<br>
This will download the latest Nuki Hub and Nuki Hub updater and automatically upgrade both applications.<br>
Nuki Hub will reboot 3 times during this process, which will take about 5 minutes.<br>
If you have enabled "Allow updating using MQTT" you can also use the Home Assistant updater or write "1" to the `nukihub/maintanance/reset` topic to start the update process.<br>
If you have enabled "Allow updating using MQTT" you can also use the Home Assistant updater or write "1" to the `nukihub/maintanance/update` topic to start the update process.<br>
<br>
Alternatively you can select a binary file from your file system to update Nuki Hub or the Nuki Hub updator manually<br>
You can only update Nuki Hub from the Nuki Hub updater and update the updater only from Nuki Hub<br>
@@ -565,7 +564,7 @@ openssl req -new -key server.key -out server.csr -subj "/C=US/ST=YourState/L=You
## Home Assistant Discovery (optional)
This software supports [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/) for integrating Nuki Hub with Home Assistant.<br>
To enable autodiscovery, supply the discovery topic that is configured in your Home Assistant instance (If you have not changed this setting in Home Assistant the default is "homeassistant") in the MQTT Configuration page.<br>
To enable autodiscovery, enable the checkbox on the "MQTT Configuration" page.<br>
Once enabled, the Nuki Lock and/or Opener and related entities should automatically appear in your Home Assistant MQTT devices.
The following mapping between Home Assistant services and Nuki commands is setup when enabling autodiscovery:
@@ -733,7 +732,7 @@ To enable GPIO control, go the the "GPIO Configuration" page where each GPIO can
If you prefer to connect to via Ethernet instead of Wi-Fi, you either use one of the supported ESP32 modules with built-in ethernet (see "[Supported devices](#supported-devices)" section)
or wire a seperate SPI Ethernet module.<Br>
Currently the Wiznet W5x00 Module (W5100, W5200, W5500), DN9051 and KSZ8851SNL chips are supported.<br>
To use a supported module, flash the firmware, connect via Wi-Fi and select the correct network hardware in the "MQTT and Network Configuration" section.
To use a supported module, flash the firmware, connect via Wi-Fi and select the correct network hardware in the "Network Configuration" section.
To wire an external W5x00 module to the ESP, use this wiring scheme:
@@ -748,7 +747,7 @@ To wire an external W5x00 module to the ESP, use this wiring scheme:
Now connect via Wi-Fi and change the network hardware to "Generic W5500".<br>
If Ethernet hwardware isn't detected, Wi-Fi is used as a fallback, unless this is disabled in the settings.<br>
If Ethernet hardware isn't detected or initialised properly after changing the network device, Wi-Fi will be used as a fallback.<br>
<br>
Note: LAN8720 modules are only supported on the ESP32 and ESP32-Solo1, not on the ESP32-S3, ESP32-C3 or ESP-C6<br>
@@ -757,7 +756,7 @@ Note: LAN8720 modules are only supported on the ESP32 and ESP32-Solo1, not on th
### Random Wi-Fi disconnects
Unfortunately the ESP32 has problems with some access points and reconnecting fails.<br>
As a workaround you can navigate to "MQTT and Network Configuration" and enable "Restart on disconnect".<br>
As a workaround you can navigate to "Network Configuration" and enable "Restart on disconnect".<br>
This will reboot the ESP as soon as it gets disconnected from Wi-Fi.<br>
Also, this reduces the config portal timeout to three minutes to prevent the ESP being stuck in config mode in case an access point is offline temporarily.<br>
If this still doesn't fix the disconnects and the ESP becomes unreachable, the "Restart timer" option can be used as a last resort.<br>
@@ -780,7 +779,7 @@ A note about the [M5Stack PoESP32 Unit](https://docs.m5stack.com/en/unit/poesp32
Make sure you are using at least version 2023.8.0 of Home Assistant.<br>
The Home Assistant developers have made changes to MQTT auto discovery which break support for older version and Nuki Hub has adopted these changes.<br>
This unfortunately means that older versions of Home Assistant are not supported by the Nuki Hub discovery implemenation anymore.
This unfortunately means that older versions of Home Assistant are not supported by the Nuki Hub discovery implementation anymore.
## FAQ
@@ -814,7 +813,7 @@ This button is disabled by default, but can be enabled in the Home Assistant UI.
### When controlling two locks (or openers) connected to two ESPs, both devices react to the same command. When using Home Asistant, the same status is display for both locks.
When using multiple Nuki devices, different paths for each device have to be configured.<br>
Navigate to "Nuki Configuration" and change the "MQTT Nuki Smartlock Path" or "MQTT Nuki Opener Path" under "Basic Nuki Configuration" for at least one of the devices.<br>
Navigate to "MQTT Configuration" and change the "MQTT NukiHub Path" under "Basic MQTT Configuration" for at least one of the devices.<br>
### The Nuki battery is draining quickly.

37
boards/nuki-esp32dev.json Normal file
View File

@@ -0,0 +1,37 @@
{
"build": {
"arduino":{
"ldscript": "esp32_out.ld"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue",
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "esp32"
},
"connectivity": [
"wifi",
"bluetooth",
"ethernet",
"can"
],
"debug": {
"openocd_board": "esp-wroom-32.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "Espressif ESP32 Dev Module",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://en.wikipedia.org/wiki/ESP32",
"vendor": "AI Thinker"
}

View File

@@ -49,6 +49,7 @@ set(SRCFILES
../src/util/NetworkUtil.cpp
../src/enums/NetworkDeviceType.h
../src/util/NetworkDeviceInstantiator.cpp
../src/HomeAssistantDiscovery.cpp
../src/NukiOfficial.cpp
../src/NukiPublisher.cpp
../src/EspMillis.h

View File

@@ -69,7 +69,7 @@ monitor_filters =
time
[env:esp32]
board = esp32dev
board = nuki-esp32dev
board_build.cmake_extra_args =
-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release.defaults;sdkconfig.defaults.esp32"
extra_scripts =

View File

@@ -4,7 +4,7 @@
#define NUKI_HUB_VERSION "9.02"
#define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2024-11-02"
#define NUKI_HUB_DATE "2024-11-08"
#define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"
@@ -22,6 +22,7 @@
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32c3.bin"
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32c3.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32c3.bin"
#define NUKI_HUB_HW (char*)"ESP32-C3"
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#if defined(CONFIG_SPIRAM_MODE_OCT)
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3oct.bin"
@@ -36,6 +37,7 @@
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32s3oct.bin"
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32s3oct.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32s3oct.bin"
#define NUKI_HUB_HW (char*)"ESP32-S3 (Octal PSRAM)"
#else
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32s3.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32s3.bin"
@@ -49,6 +51,7 @@
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32s3.bin"
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32s3.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32s3.bin"
#define NUKI_HUB_HW (char*)"ESP32-S3"
#endif
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32c6.bin"
@@ -63,6 +66,7 @@
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32c6.bin"
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32c6.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32c6.bin"
#define NUKI_HUB_HW (char*)"ESP32-C6"
#elif defined(CONFIG_IDF_TARGET_ESP32H2)
#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32h2.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32h2.bin"
@@ -76,6 +80,7 @@
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32h2.bin"
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32h2.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32h2.bin"
#define NUKI_HUB_HW (char*)"ESP32-H2"
#else
#if defined(CONFIG_FREERTOS_UNICORE)
#define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32-solo1.bin"
@@ -90,6 +95,7 @@
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32-solo1.bin"
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32-solo1.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32-solo1.bin"
#define NUKI_HUB_HW (char*)"ESP32-SOLO1"
#else
#define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32.bin"
#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32.bin"
@@ -103,6 +109,7 @@
#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32.bin"
#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32.bin"
#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32.bin"
#define NUKI_HUB_HW (char*)"ESP32"
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
#pragma once
#include <Preferences.h>
#include <ArduinoJson.h>
#include "networkDevices/NetworkDevice.h"
class HomeAssistantDiscovery
{
public:
explicit HomeAssistantDiscovery(NetworkDevice* device, Preferences* preferences, char* buffer, size_t bufferSize);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
void disableHASS();
void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
void publishHassTopic(const String& mqttDeviceType,
const String& mqttDeviceName,
const String& uidString,
const String& uidStringPostfix,
const String& displayName,
const String& name,
const String& baseTopic,
const String& stateTopic,
const String& deviceType,
const String& deviceClass,
const String& stateClass = "",
const String& entityCat = "",
const String& commandTopic = "",
std::vector<std::pair<char*, char*>> additionalEntries = {}
);
private:
void publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction, char *unlockAction, char *openAction);
void publishHASSDeviceConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction);
void publishHASSNukiHubConfig();
void publishHASSConfigAdditionalLockEntities(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigAdditionalOpenerEntities(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigKeypad(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigWifiRssi(char* deviceType, const char* baseTopic, char* name, char* uidString);
void removeHASSConfig(char* uidString);
void removeHASSConfigTopic(char* deviceType, char* name, char* uidString);
String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
JsonDocument createHassJson(const String& uidString,
const String& uidStringPostfix,
const String& displayName,
const String& name,
const String& baseTopic,
const String& stateTopic,
const String& deviceType,
const String& deviceClass,
const String& stateClass = "",
const String& entityCat = "",
const String& commandTopic = "",
std::vector<std::pair<char*, char*>> additionalEntries = {}
);
NetworkDevice* _device = nullptr;
Preferences* _preferences = nullptr;
String _discoveryTopic;
String _baseTopic;
String _hostname;
char _nukiHubUidString[20];
bool _offEnabled = false;
bool _checkUpdates = false;
bool _updateFromMQTT = false;
char* _buffer;
const size_t _bufferSize;
};

View File

@@ -1,7 +1,5 @@
#pragma once
#include <Arduino.h>
class MqttReceiver
{
public:

View File

@@ -110,7 +110,7 @@
#define mqtt_topic_restart_reason_esp "/maintenance/restartReasonNukiEsp"
#define mqtt_topic_mqtt_connection_state "/maintenance/mqttConnectionState"
#define mqtt_topic_network_device "/maintenance/networkDevice"
#define mqtt_hybrid_state "/hybridConnected"
#define mqtt_topic_hybrid_state "/hybridConnected"
#define mqtt_topic_gpio_prefix "/gpio"
#define mqtt_topic_gpio_pin "/pin_"

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
#include "Gpio.h"
#include <ArduinoJson.h>
#include "NukiConstants.h"
#include "HomeAssistantDiscovery.h"
#endif
class NukiNetwork
@@ -56,37 +57,31 @@ public:
void publishLongLong(const char* prefix, const char* topic, int64_t value, bool retain);
void publishBool(const char* prefix, const char* topic, const bool value, bool retain);
void publishString(const char* prefix, const char* topic, const char* value, bool retain);
void publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const char* availabilityTopic, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction);
void publishHASSConfigAdditionalLockEntities(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigDoorSensor(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigAdditionalOpenerEntities(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigAccessLog(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSConfigKeypad(char* deviceType, const char* baseTopic, char* name, char* uidString);
void publishHASSWifiRssiConfig(char* deviceType, const char* baseTopic, char* name, char* uidString);
void removeHASSConfig(char* uidString);
void removeHASSConfigTopic(char* deviceType, char* name, char* uidString);
void publishHassTopic(const String& mqttDeviceType,
const String& mqttDeviceName,
const String& uidString,
const String& uidStringPostfix,
const String& displayName,
const String& name,
const String& baseTopic,
const String& stateTopic,
const String& deviceType,
const String& deviceClass,
const String& stateClass = "",
const String& entityCat = "",
const String& commandTopic = "",
std::vector<std::pair<char*, char*>> additionalEntries = {}
);
void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
void publish(const char *topic, const char *value, bool retain);
void removeTopic(const String& mqttPath, const String& mqttTopic);
void batteryTypeToString(const Nuki::BatteryType battype, char* str);
void advertisingModeToString(const Nuki::AdvertisingMode advmode, char* str);
void timeZoneIdToString(const Nuki::TimeZoneId timeZoneId, char* str);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
void disableHASS();
void publishHassTopic(const String& mqttDeviceType,
const String& mqttDeviceName,
const String& uidString,
const String& uidStringPostfix,
const String& displayName,
const String& name,
const String& baseTopic,
const String& stateTopic,
const String& deviceType,
const String& deviceClass,
const String& stateClass,
const String& entityCat,
const String& commandTopic,
std::vector<std::pair<char*, char*>> additionalEntries
);
void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
int mqttConnectionState(); // 0 = not connected; 1 = connected; 2 = connected and mqtt processed
bool mqttRecentlyConnected();
bool pathEquals(const char* prefix, const char* path, const char* referencePath);
@@ -113,39 +108,26 @@ private:
NetworkDeviceType _networkDeviceType = (NetworkDeviceType)-1;
bool _firstBootAfterDeviceChange = false;
bool _webEnabled = true;
bool _updateFromMQTT = false;
bool _offEnabled = false;
#ifndef NUKI_HUB_UPDATER
static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
void onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total);
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length);
void onMqttConnect(const bool& sessionPresent);
void onMqttDisconnect(const espMqttClientTypes::DisconnectReason& reason);
void parseGpioTopics(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total);
void gpioActionCallback(const GpioAction& action, const int& pin);
String createHassTopicPath(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
JsonDocument createHassJson(const String& uidString,
const String& uidStringPostfix,
const String& displayName,
const String& name,
const String& baseTopic,
const String& stateTopic,
const String& deviceType,
const String& deviceClass,
const String& stateClass = "",
const String& entityCat = "",
const String& commandTopic = "",
std::vector<std::pair<char*, char*>> additionalEntries = {}
);
bool comparePrefixedPath(const char* fullPath, const char* subPath);
void buildMqttPath(const char *path, char *outPath);
void buildMqttPath(char* outPath, std::initializer_list<const char*> paths);
const char* _lastWillPayload = "offline";
char _mqttConnectionStateTopic[211] = {0};
String _lockPath;
String _discoveryTopic;
String _brokerAddr;
HomeAssistantDiscovery* _hadiscovery = nullptr;
Gpio* _gpio;
int _mqttConnectionState = 0;
@@ -155,6 +137,7 @@ private:
bool _connectReplyReceived = false;
bool _firstDisconnected = true;
int64_t _publishedUpTime = 0;
int64_t _nextReconnect = 0;
char _mqttBrokerAddr[101] = {0};
char _mqttUser[31] = {0};

View File

@@ -7,8 +7,6 @@
#include "RestartReason.h"
#include <ArduinoJson.h>
#include <ctype.h>
#include <HTTPClient.h>
#include <NetworkClientSecure.h>
extern bool forceEnableWebServer;
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
@@ -169,152 +167,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return;
}
if(comparePrefixedPath(topic, mqtt_topic_reset) && strcmp(data, "1") == 0)
{
Log->println(F("Restart requested via MQTT."));
_network->clearWifiFallback();
delay(200);
restartEsp(RestartReason::RequestedViaMqtt);
}
else if(comparePrefixedPath(topic, mqtt_topic_update) && strcmp(data, "1") == 0 && _preferences->getBool(preference_update_from_mqtt, false))
{
Log->println(F("Update requested via MQTT."));
bool otaManifestSuccess = false;
JsonDocument doc;
NetworkClientSecure *client = new NetworkClientSecure;
if (client)
{
client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start);
{
HTTPClient https;
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
https.useHTTP10(true);
if (https.begin(*client, GITHUB_OTA_MANIFEST_URL))
{
int httpResponseCode = https.GET();
if (httpResponseCode == HTTP_CODE_OK || httpResponseCode == HTTP_CODE_MOVED_PERMANENTLY)
{
DeserializationError jsonError = deserializeJson(doc, https.getStream());
if (!jsonError)
{
otaManifestSuccess = true;
}
}
}
https.end();
}
delete client;
}
if (otaManifestSuccess)
{
String currentVersion = NUKI_HUB_VERSION;
if(atof(doc["release"]["version"]) >= atof(currentVersion.c_str()))
{
if(strcmp(NUKI_HUB_VERSION, doc["release"]["fullversion"].as<const char*>()) == 0 && strcmp(NUKI_HUB_BUILD, doc["release"]["build"].as<const char*>()) == 0 && strcmp(NUKI_HUB_DATE, doc["release"]["time"].as<const char*>()) == 0)
{
Log->println(F("Nuki Hub is already on the latest release version, OTA update aborted."));
}
else
{
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL);
Log->println(F("Updating to latest release version."));
delay(200);
restartEsp(RestartReason::OTAReboot);
}
}
else if(currentVersion.indexOf("beta") > 0)
{
if(strcmp(NUKI_HUB_VERSION, doc["beta"]["fullversion"].as<const char*>()) == 0 && strcmp(NUKI_HUB_BUILD, doc["beta"]["build"].as<const char*>()) == 0 && strcmp(NUKI_HUB_DATE, doc["beta"]["time"].as<const char*>()) == 0)
{
Log->println(F("Nuki Hub is already on the latest beta version, OTA update aborted."));
}
else
{
_preferences->putString(preference_ota_updater_url, GITHUB_BETA_RELEASE_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_BETA_UPDATER_BINARY_URL);
Log->println(F("Updating to latest beta version."));
delay(200);
restartEsp(RestartReason::OTAReboot);
}
}
else if(currentVersion.indexOf("master") > 0)
{
if(strcmp(NUKI_HUB_VERSION, doc["master"]["fullversion"].as<const char*>()) == 0 && strcmp(NUKI_HUB_BUILD, doc["master"]["build"].as<const char*>()) == 0 && strcmp(NUKI_HUB_DATE, doc["master"]["time"].as<const char*>()) == 0)
{
Log->println(F("Nuki Hub is already on the latest development version, OTA update aborted."));
}
else
{
_preferences->putString(preference_ota_updater_url, GITHUB_MASTER_RELEASE_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_MASTER_UPDATER_BINARY_URL);
Log->println(F("Updating to latest developmemt version."));
delay(200);
restartEsp(RestartReason::OTAReboot);
}
}
else
{
if(strcmp(NUKI_HUB_VERSION, doc["release"]["fullversion"].as<const char*>()) == 0 && strcmp(NUKI_HUB_BUILD, doc["release"]["build"].as<const char*>()) == 0 && strcmp(NUKI_HUB_DATE, doc["release"]["time"].as<const char*>()) == 0)
{
Log->println(F("Nuki Hub is already on the latest release version, OTA update aborted."));
}
else
{
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL);
Log->println(F("Updating to latest release version."));
delay(200);
restartEsp(RestartReason::OTAReboot);
}
}
}
else
{
Log->println(F("Failed to retrieve OTA manifest, OTA update aborted."));
}
}
else if(comparePrefixedPath(topic, mqtt_topic_webserver_action))
{
if(strcmp(data, "") == 0 ||
strcmp(data, "--") == 0)
{
return;
}
if(strcmp(data, "1") == 0)
{
if(_preferences->getBool(preference_webserver_enabled, true) || forceEnableWebServer)
{
return;
}
Log->println(F("Webserver enabled, restarting."));
_preferences->putBool(preference_webserver_enabled, true);
}
else if (strcmp(data, "0") == 0)
{
if(!_preferences->getBool(preference_webserver_enabled, true) && !forceEnableWebServer)
{
return;
}
Log->println(F("Webserver disabled, restarting."));
_preferences->putBool(preference_webserver_enabled, false);
}
publishString(mqtt_topic_webserver_action, "--", true);
_network->clearWifiFallback();
delay(200);
restartEsp(RestartReason::ReconfigureWebServer);
}
else if(comparePrefixedPath(topic, mqtt_topic_lock_log_rolling_last))
if(comparePrefixedPath(topic, mqtt_topic_lock_log_rolling_last))
{
if(strcmp(data, "") == 0 ||
strcmp(data, "--") == 0)
@@ -1663,53 +1516,6 @@ bool NukiNetworkLock::comparePrefixedPath(const char *fullPath, const char *subP
return strcmp(fullPath, prefixedPath) == 0;
}
void NukiNetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, char *name, char *uidString, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char *lockAction,
char *unlockAction, char *openAction)
{
String availabilityTopic = _preferences->getString(preference_mqtt_lock_path);
availabilityTopic.concat("/maintenance/mqttConnectionState");
_network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, lockAction, unlockAction, openAction);
_network->publishHASSConfigAdditionalLockEntities(deviceType, baseTopic, name, uidString);
if(hasDoorSensor)
{
_network->publishHASSConfigDoorSensor(deviceType, baseTopic, name, uidString);
}
else
{
_network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"door_sensor", uidString);
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
_network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString);
#endif
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);
}
if(hasKeypad)
{
_network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString);
}
else
{
_network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString);
_network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString);
}
}
void NukiNetworkLock::removeHASSConfig(char *uidString)
{
_network->removeHASSConfig(uidString);
}
void NukiNetworkLock::publishOffAction(const int value)
{
_network->publishInt(_nukiOfficial->getMqttPath(), mqtt_topic_official_lock_action, value, false);
@@ -1792,6 +1598,11 @@ uint8_t NukiNetworkLock::queryCommands()
return qc;
}
void NukiNetworkLock::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad)
{
_network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad);
}
void NukiNetworkLock::buttonPressActionToString(const NukiLock::ButtonPressAction btnPressAction, char* str)
{
switch (btnPressAction)

View File

@@ -37,8 +37,6 @@ 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, const char *softwareVersion, const char *hardwareVersion, const bool& hasDoorSensor, const bool& hasKeypad, const bool& publishAuthData, char* lockAction, char* unlockAction, char* openAction);
void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishTimeControl(const std::list<NukiLock::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount);
void publishAuth(const std::list<NukiLock::AuthorizationEntry>& authEntries, uint maxAuthEntryCount);
@@ -58,6 +56,7 @@ public:
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 setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
void publishFloat(const char* topic, const float value, bool retain, const uint8_t precision = 2);
void publishInt(const char* topic, const int value, bool retain);

View File

@@ -43,6 +43,7 @@ void NukiNetworkOpener::initialize()
_network->initTopic(_mqttPath, mqtt_topic_query_lockstate, "0");
_network->initTopic(_mqttPath, mqtt_topic_query_battery, "0");
_network->initTopic(_mqttPath, mqtt_topic_lock_binary_ring, "standby");
_network->initTopic(_mqttPath, mqtt_topic_lock_ring, "standby");
_network->subscribe(_mqttPath, mqtt_topic_query_config);
_network->subscribe(_mqttPath, mqtt_topic_query_lockstate);
_network->subscribe(_mqttPath, mqtt_topic_query_battery);
@@ -128,6 +129,7 @@ void NukiNetworkOpener::update()
{
_resetRingStateTs = 0;
publishString(mqtt_topic_lock_binary_ring, "standby", true);
publishString(mqtt_topic_lock_ring, "standby", true);
}
}
@@ -647,10 +649,25 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::Log
if(log.index > _lastRollingLog)
{
_lastRollingLog = log.index;
serializeJson(entry, _buffer, _bufferSize);
publishString(mqtt_topic_lock_log_rolling, _buffer, true);
publishInt(mqtt_topic_lock_log_rolling_last, log.index, true);
if(log.loggingType == NukiOpener::LoggingType::DoorbellRecognition && _lastRollingLog > 0)
{
if((log.data[0] & 3) == 0)
{
Log->println(F("Nuki opener: Ring detected (Locked)"));
publishRing(true);
}
else
{
Log->println(F("Nuki opener: Ring detected (Open)"));
publishRing(false);
}
}
_lastRollingLog = log.index;
}
}
@@ -658,7 +675,7 @@ void NukiNetworkOpener::publishAuthorizationInfo(const std::list<NukiOpener::Log
if(latest)
{
publishString(mqtt_topic_lock_log_latest, _buffer, true);
publishString(mqtt_topic_lock_log_latest, _buffer, true);
}
else
{
@@ -842,38 +859,6 @@ void NukiNetworkOpener::publishBleAddress(const std::string &address)
publishString(mqtt_topic_lock_address, address, true);
}
void NukiNetworkOpener::publishHASSConfig(char* deviceType, const char* baseTopic, char* name, char* uidString, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction)
{
String availabilityTopic = _preferences->getString(preference_mqtt_lock_path);
availabilityTopic.concat("/maintenance/mqttConnectionState");
_network->publishHASSConfig(deviceType, baseTopic, name, uidString, softwareVersion, hardwareVersion, availabilityTopic.c_str(), hasKeypad, 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);
}
if(hasKeypad)
{
_network->publishHASSConfigKeypad(deviceType, baseTopic, name, uidString);
}
else
{
_network->removeHASSConfigTopic((char*)"sensor", (char*)"keypad_status", uidString);
_network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"keypad_battery_low", uidString);
}
}
void NukiNetworkOpener::removeHASSConfig(char* uidString)
{
_network->removeHASSConfig(uidString);
}
void NukiNetworkOpener::publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount)
{
bool publishCode = _preferences->getBool(preference_keypad_publish_code, false);
@@ -1571,6 +1556,11 @@ uint8_t NukiNetworkOpener::queryCommands()
return qc;
}
void NukiNetworkOpener::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad)
{
_network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad);
}
void NukiNetworkOpener::buttonPressActionToString(const NukiOpener::ButtonPressAction btnPressAction, char* str)
{
switch (btnPressAction)

View File

@@ -30,8 +30,6 @@ 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, const char *softwareVersion, const char *hardwareVersion, const bool& publishAuthData, const bool& hasKeypad, char* lockAction, char* unlockAction, char* openAction);
void removeHASSConfig(char* uidString);
void publishKeypad(const std::list<NukiLock::KeypadEntry>& entries, uint maxKeypadCodeCount);
void publishTimeControl(const std::list<NukiOpener::TimeControlEntry>& timeControlEntries, uint maxTimeControlEntryCount);
void publishAuth(const std::list<NukiLock::AuthorizationEntry>& authEntries, uint maxAuthEntryCount);
@@ -49,6 +47,7 @@ public:
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 setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
int mqttConnectionState();
bool reconnected(); //SETBACK

View File

@@ -13,7 +13,6 @@ NukiOfficial::NukiOfficial(Preferences *preferences)
_disableNonJSON = preferences->getBool(preference_disable_non_json, false);
}
void NukiOfficial::setUid(const uint32_t& uid)
{
char uidString[20];
@@ -43,7 +42,6 @@ void NukiOfficial::setPublisher(NukiPublisher *publisher)
_publisher = publisher;
}
const char *NukiOfficial::getMqttPath() const
{
return mqttPath;
@@ -88,7 +86,7 @@ void NukiOfficial::onOfficialUpdateReceived(const char *topic, const char *value
bool publishBatteryJson = false;
memset(&str, 0, sizeof(str));
Log->println("Official Nuki change recieved");
Log->println("Official Nuki change received");
Log->print(F("Topic: "));
Log->println(topic);
Log->print(F("Value: "));
@@ -99,14 +97,14 @@ void NukiOfficial::onOfficialUpdateReceived(const char *topic, const char *value
Log->print(F("Connected: "));
Log->println((strcmp(value, "true") == 0 ? 1 : 0));
offConnected = (strcmp(value, "true") == 0 ? 1 : 0);
_publisher->publishBool(mqtt_hybrid_state, offConnected, true);
_publisher->publishBool(mqtt_topic_hybrid_state, offConnected, true);
}
else if(strcmp(topic, mqtt_topic_official_state) == 0)
{
offState = atoi(value);
_statusUpdated = true;
Log->println(F("Lock: Updating status on Hybrid state change"));
_publisher->publishBool(mqtt_hybrid_state, offConnected, true);
_publisher->publishBool(mqtt_topic_hybrid_state, offConnected, true);
NukiLock::lockstateToString((NukiLock::LockState)offState, str);
_publisher->publishString(mqtt_topic_lock_state, str, true);

View File

@@ -54,7 +54,7 @@ void NukiOpenerWrapper::initialize()
_nukiOpener.setConnectTimeout(3);
_nukiOpener.setDisconnectTimeout(5000);
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_hassEnabled = _preferences->getBool(preference_mqtt_hass_enabled, false);
readSettings();
}
@@ -218,72 +218,6 @@ void NukiOpenerWrapper::update()
_nukiOpener.updateConnectionState();
if(_network->mqttConnectionState() == 2)
{
if(_statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0)
{
_statusUpdated = false;
_nextLockStateUpdateTs = ts + _intervalLockstate * 1000;
updateKeyTurnerState();
_network->publishStatusUpdated(_statusUpdated);
}
if(_nextBatteryReportTs == 0 || ts > _nextBatteryReportTs || (queryCommands & QUERY_COMMAND_BATTERY) > 0)
{
_nextBatteryReportTs = ts + _intervalBattery * 1000;
updateBatteryState();
}
if(_nextConfigUpdateTs == 0 || ts > _nextConfigUpdateTs || (queryCommands & QUERY_COMMAND_CONFIG) > 0)
{
_nextConfigUpdateTs = ts + _intervalConfig * 1000;
updateConfig();
}
if(_waitAuthLogUpdateTs != 0 && ts > _waitAuthLogUpdateTs)
{
_waitAuthLogUpdateTs = 0;
updateAuthData(true);
}
if(_waitKeypadUpdateTs != 0 && ts > _waitKeypadUpdateTs)
{
_waitKeypadUpdateTs = 0;
updateKeypad(true);
}
if(_waitTimeControlUpdateTs != 0 && ts > _waitTimeControlUpdateTs)
{
_waitTimeControlUpdateTs = 0;
updateTimeControl(true);
}
if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs)
{
_waitAuthUpdateTs = 0;
updateAuth(true);
}
if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted)
{
setupHASS();
}
if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs))
{
_nextRssiTs = ts + _rssiPublishInterval;
int rssi = _nukiOpener.getRssi();
if(rssi != _lastRssi)
{
_network->publishRssi(rssi);
_lastRssi = rssi;
}
}
if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs || (queryCommands & QUERY_COMMAND_KEYPAD) > 0))
{
_nextKeypadUpdateTs = ts + _intervalKeypad * 1000;
updateKeypad(false);
}
}
if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck)
{
_invalidCount--;
}
if(_nextLockAction != (NukiOpener::LockAction)0xff)
{
int retryCount = 0;
@@ -336,11 +270,79 @@ void NukiOpenerWrapper::update()
_nextLockAction = (NukiOpener::LockAction) 0xff;
}
}
if(_clearAuthData)
if(_statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0)
{
_network->clearAuthorizationInfo();
_clearAuthData = false;
updateKeyTurnerState();
_nextLockStateUpdateTs = ts + _intervalLockstate * 1000;
_statusUpdated = false;
_network->publishStatusUpdated(_statusUpdated);
}
if(_network->mqttConnectionState() == 2)
{
if(!_statusUpdated)
{
if(_nextBatteryReportTs == 0 || ts > _nextBatteryReportTs || (queryCommands & QUERY_COMMAND_BATTERY) > 0)
{
_nextBatteryReportTs = ts + _intervalBattery * 1000;
updateBatteryState();
}
if(_nextConfigUpdateTs == 0 || ts > _nextConfigUpdateTs || (queryCommands & QUERY_COMMAND_CONFIG) > 0)
{
_nextConfigUpdateTs = ts + _intervalConfig * 1000;
updateConfig();
}
if(_waitAuthLogUpdateTs != 0 && ts > _waitAuthLogUpdateTs)
{
_waitAuthLogUpdateTs = 0;
updateAuthData(true);
}
if(_waitKeypadUpdateTs != 0 && ts > _waitKeypadUpdateTs)
{
_waitKeypadUpdateTs = 0;
updateKeypad(true);
}
if(_waitTimeControlUpdateTs != 0 && ts > _waitTimeControlUpdateTs)
{
_waitTimeControlUpdateTs = 0;
updateTimeControl(true);
}
if(_waitAuthUpdateTs != 0 && ts > _waitAuthUpdateTs)
{
_waitAuthUpdateTs = 0;
updateAuth(true);
}
if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted)
{
_network->setupHASS(2, _nukiConfig.nukiId, (char*)_nukiConfig.name, _firmwareVersion.c_str(), _hardwareVersion.c_str(), false, _hasKeypad);
_hassSetupCompleted = true;
}
if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs))
{
_nextRssiTs = ts + _rssiPublishInterval;
int rssi = _nukiOpener.getRssi();
if(rssi != _lastRssi)
{
_network->publishRssi(rssi);
_lastRssi = rssi;
}
}
if(_hasKeypad && _keypadEnabled && (_nextKeypadUpdateTs == 0 || ts > _nextKeypadUpdateTs || (queryCommands & QUERY_COMMAND_KEYPAD) > 0))
{
_nextKeypadUpdateTs = ts + _intervalKeypad * 1000;
updateKeypad(false);
}
}
if(_clearAuthData)
{
_network->clearAuthorizationInfo();
_clearAuthData = false;
}
if(_checkKeypadCodes && _invalidCount > 0 && (ts - (120000 * _invalidCount)) > _lastCodeCheck)
{
_invalidCount--;
}
}
memcpy(&_lastKeyTurnerState, &_keyTurnerState, sizeof(NukiOpener::OpenerState));
@@ -447,7 +449,8 @@ void NukiOpenerWrapper::updateKeyTurnerState()
}
_retryLockstateCount = 0;
if(_statusUpdated &&
if((!isPinValid() || !_publishAuthData) &&
_statusUpdated &&
_keyTurnerState.lockState == NukiOpener::LockState::Locked &&
_lastKeyTurnerState.lockState == NukiOpener::LockState::Locked &&
_lastKeyTurnerState.nukiState == _keyTurnerState.nukiState)
@@ -457,7 +460,8 @@ void NukiOpenerWrapper::updateKeyTurnerState()
}
else
{
if(_keyTurnerState.lockState != _lastKeyTurnerState.lockState &&
if((!isPinValid() || !_publishAuthData) &&
_keyTurnerState.lockState != _lastKeyTurnerState.lockState &&
_keyTurnerState.lockState == NukiOpener::LockState::Open &&
_keyTurnerState.trigger == NukiOpener::Trigger::Manual)
{
@@ -465,8 +469,15 @@ void NukiOpenerWrapper::updateKeyTurnerState()
_network->publishRing(false);
}
_network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState);
if(_publishAuthData)
{
Log->println(F("Publishing auth data"));
updateAuthData(false);
Log->println(F("Done publishing auth data"));
}
updateGpioOutputs();
_network->publishKeyTurnerState(_keyTurnerState, _lastKeyTurnerState);
if(_keyTurnerState.nukiState == NukiOpener::State::ContinuousMode)
{
@@ -478,13 +489,6 @@ void NukiOpenerWrapper::updateKeyTurnerState()
Log->println(lockStateStr);
}
if(_publishAuthData)
{
Log->println(F("Publishing auth data"));
updateAuthData(false);
Log->println(F("Done publishing auth data"));
}
postponeBleWatchdog();
Log->println(F("Done querying opener state"));
}
@@ -540,7 +544,7 @@ void NukiOpenerWrapper::updateConfig()
if(_preferences->getUInt(preference_nuki_id_opener, 0) == _nukiConfig.nukiId)
{
_hasKeypad = _nukiConfig.hasKeypad == 1 || _nukiConfig.hasKeypadV2 == 1;
_hasKeypad = _nukiConfig.hasKeypad == 1 || (_nukiConfig.hasKeypadV2 > 0 && _nukiConfig.hasKeypadV2 != 252);
_firmwareVersion = std::to_string(_nukiConfig.firmwareVersion[0]) + "." + std::to_string(_nukiConfig.firmwareVersion[1]) + "." + std::to_string(_nukiConfig.firmwareVersion[2]);
_hardwareVersion = std::to_string(_nukiConfig.hardwareRevision[0]) + "." + std::to_string(_nukiConfig.hardwareRevision[1]);
if(_preferences->getBool(preference_conf_info_enabled, true))
@@ -607,13 +611,13 @@ void NukiOpenerWrapper::updateConfig()
}
else
{
Log->println(F("Invalid/Unexpected opener config recieved, ID does not matched saved ID"));
Log->println(F("Invalid/Unexpected opener config received, ID does not matched saved ID"));
expectedConfig = false;
}
}
else
{
Log->println(F("Invalid/Unexpected opener config recieved, Config is not valid"));
Log->println(F("Invalid/Unexpected opener config received, Config is not valid"));
expectedConfig = false;
}
@@ -630,7 +634,7 @@ void NukiOpenerWrapper::updateConfig()
}
else
{
Log->println(F("Invalid/Unexpected opener advanced config recieved, Advanced config is not valid"));
Log->println(F("Invalid/Unexpected opener advanced config received, Advanced config is not valid"));
expectedConfig = false;
}
}
@@ -643,7 +647,7 @@ void NukiOpenerWrapper::updateConfig()
else
{
++_retryConfigCount;
Log->println(F("Invalid/Unexpected opener config and/or advanced config recieved, retrying in 10 seconds"));
Log->println(F("Invalid/Unexpected opener config and/or advanced config received, retrying in 10 seconds"));
int64_t ts = espMillis();
_nextConfigUpdateTs = ts + 10000;
}
@@ -3879,11 +3883,21 @@ BleScanner::Scanner *NukiOpenerWrapper::bleScanner()
void NukiOpenerWrapper::notify(Nuki::EventType eventType)
{
if(!_pairedAsApp && eventType == Nuki::EventType::KeyTurnerStatusUpdated && !_statusUpdated)
if(eventType == Nuki::EventType::KeyTurnerStatusReset)
{
Log->println("KeyTurnerStatusUpdated");
_statusUpdated = true;
_network->publishStatusUpdated(_statusUpdated);
_newSignal = 0;
Log->println("KeyTurnerStatusReset");
}
else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated)
{
if(!_statusUpdated && _newSignal < 5)
{
_newSignal++;
Log->println("KeyTurnerStatusUpdated");
_statusUpdated = true;
_statusUpdatedTs = espMillis();
_network->publishStatusUpdated(_statusUpdated);
}
}
}
@@ -3944,43 +3958,6 @@ void NukiOpenerWrapper::readAdvancedConfig()
postponeBleWatchdog();
}
void NukiOpenerWrapper::setupHASS()
{
if(!_nukiConfigValid)
{
return;
}
if(_preferences->getUInt(preference_nuki_id_opener, 0) != _nukiConfig.nukiId)
{
return;
}
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
baseTopic.concat("/opener");
char uidString[20];
itoa(_nukiConfig.nukiId, uidString, 16);
if(_preferences->getBool(preference_opener_continuous_mode, false))
{
_network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateCM", (char*)"activateCM", (char*)"electricStrikeActuation");
}
else
{
_network->publishHASSConfig((char*)"Opener", baseTopic.c_str(), (char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), _publishAuthData, _hasKeypad, (char*)"deactivateRTO", (char*)"activateRTO", (char*)"electricStrikeActuation");
}
_hassSetupCompleted = true;
Log->println("HASS setup for opener completed.");
}
void NukiOpenerWrapper::disableHASS()
{
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16);
_network->removeHASSConfig(uidString);
}
void NukiOpenerWrapper::printCommandResult(Nuki::CmdResult result)
{
char resultStr[15];

View File

@@ -31,9 +31,6 @@ public:
uint16_t getPin();
void unpair();
void disableHASS();
void disableWatchdog();
const NukiOpener::OpenerState& keyTurnerState();
@@ -77,8 +74,6 @@ private:
void readConfig();
void readAdvancedConfig();
void setupHASS();
void printCommandResult(Nuki::CmdResult result);
NukiOpener::LockAction lockActionToEnum(const char* str); // char array at least 14 characters
@@ -135,12 +130,14 @@ private:
bool _paired = false;
bool _statusUpdated = false;
int _newSignal = 0;
bool _hasKeypad = false;
bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0;
uint _maxTimeControlEntryCount = 0;
uint _maxAuthEntryCount = 0;
int _rssiPublishInterval = 0;
int64_t _statusUpdatedTs = 0;
int64_t _nextLockStateUpdateTs = 0;
int64_t _nextBatteryReportTs = 0;
int64_t _nextConfigUpdateTs = 0;

View File

@@ -1,6 +1,3 @@
#ifndef CONFIG_IDF_TARGET_ESP32H2
#include "esp_wifi.h"
#endif
#include "NukiWrapper.h"
#include "PreferencesKeys.h"
#include "MqttTopics.h"
@@ -50,7 +47,7 @@ NukiWrapper::~NukiWrapper()
}
void NukiWrapper::initialize(const bool& firstStart)
void NukiWrapper::initialize()
{
_nukiLock.initialize();
_nukiLock.registerBleScanner(_bleScanner);
@@ -58,75 +55,7 @@ void NukiWrapper::initialize(const bool& firstStart)
_nukiLock.setConnectTimeout(3);
_nukiLock.setDisconnectTimeout(5000);
if(firstStart)
{
Log->println("First start, setting preference defaults");
#ifndef CONFIG_IDF_TARGET_ESP32H2
wifi_config_t wifi_cfg;
if(esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK)
{
Log->println("Failed to get Wi-Fi configuration in RAM");
}
if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK)
{
Log->println("Failed to set storage Wi-Fi");
}
memset(wifi_cfg.sta.ssid, 0, sizeof(wifi_cfg.sta.ssid));
memset(wifi_cfg.sta.password, 0, sizeof(wifi_cfg.sta.password));
if (esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK)
{
Log->println("Failed to clear NVS Wi-Fi configuration");
}
#endif
_preferences->putString(preference_mqtt_lock_path, "nukihub");
_preferences->putBool(preference_check_updates, true);
_preferences->putBool(preference_opener_continuous_mode, false);
_preferences->putBool(preference_official_hybrid_enabled, false);
_preferences->putBool(preference_official_hybrid_actions, false);
_preferences->putBool(preference_official_hybrid_retry, false);
_preferences->putBool(preference_disable_non_json, false);
_preferences->putBool(preference_update_from_mqtt, false);
_preferences->putBool(preference_ip_dhcp_enabled, true);
_preferences->putBool(preference_enable_bootloop_reset, false);
_preferences->putBool(preference_show_secrets, false);
_preferences->putBool(preference_conf_info_enabled, true);
_preferences->putBool(preference_keypad_info_enabled, false);
_preferences->putBool(preference_keypad_topic_per_entry, false);
_preferences->putBool(preference_keypad_publish_code, false);
_preferences->putBool(preference_keypad_control_enabled, false);
_preferences->putBool(preference_timecontrol_info_enabled, false);
_preferences->putBool(preference_timecontrol_topic_per_entry, false);
_preferences->putBool(preference_timecontrol_control_enabled, false);
_preferences->putBool(preference_publish_authdata, false);
_preferences->putBool(preference_register_as_app, false);
_preferences->putBool(preference_register_opener_as_app, false);
_preferences->putInt(preference_mqtt_broker_port, 1883);
_preferences->putInt(preference_buffer_size, CHAR_BUFFER_SIZE);
_preferences->putInt(preference_task_size_network, NETWORK_TASK_SIZE);
_preferences->putInt(preference_task_size_nuki, NUKI_TASK_SIZE);
_preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG);
_preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD);
_preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL);
_preferences->putInt(preference_query_interval_hybrid_lockstate, 600);
_preferences->putInt(preference_rssi_publish_interval, 60);
_preferences->putInt(preference_network_timeout, 60);
_preferences->putInt(preference_command_nr_of_retries, 3);
_preferences->putInt(preference_command_retry_delay, 100);
_preferences->putInt(preference_restart_ble_beacon_lost, 60);
_preferences->putInt(preference_query_interval_lockstate, 1800);
_preferences->putInt(preference_query_interval_configuration, 3600);
_preferences->putInt(preference_query_interval_battery, 1800);
_preferences->putInt(preference_query_interval_keypad, 1800);
}
_hassEnabled = _preferences->getString(preference_mqtt_hass_discovery) != "";
_hassEnabled = _preferences->getBool(preference_mqtt_hass_enabled, false);
readSettings();
}
@@ -359,16 +288,16 @@ void NukiWrapper::update()
_nextLockAction = (NukiLock::LockAction) 0xff;
}
}
if(_nukiOfficial->getStatusUpdated() || _statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0)
{
Log->println("Updating Lock state based on status, timer or query");
updateKeyTurnerState();
_statusUpdated = false;
_nextLockStateUpdateTs = ts + _intervalLockstate * 1000;
_network->publishStatusUpdated(_statusUpdated);
}
if(_network->mqttConnectionState() == 2)
{
if(_nukiOfficial->getStatusUpdated() || _statusUpdated || _nextLockStateUpdateTs == 0 || ts >= _nextLockStateUpdateTs || (queryCommands & QUERY_COMMAND_LOCKSTATE) > 0)
{
Log->println("Updating Lock state based on status, timer or query");
_statusUpdated = false;
_nextLockStateUpdateTs = ts + _intervalLockstate * 1000;
updateKeyTurnerState();
_network->publishStatusUpdated(_statusUpdated);
}
if(!_statusUpdated)
{
if(_nextBatteryReportTs == 0 || ts > _nextBatteryReportTs || (queryCommands & QUERY_COMMAND_BATTERY) > 0)
@@ -405,7 +334,8 @@ void NukiWrapper::update()
}
if(_hassEnabled && _nukiConfigValid && _nukiAdvancedConfigValid && !_hassSetupCompleted)
{
setupHASS();
_network->setupHASS(1, _nukiConfig.nukiId, (char*)_nukiConfig.name, _firmwareVersion.c_str(), _hardwareVersion.c_str(), hasDoorSensor(), _hasKeypad);
_hassSetupCompleted = true;
}
if(_rssiPublishInterval > 0 && (_nextRssiTs == 0 || ts > _nextRssiTs))
{
@@ -628,7 +558,7 @@ void NukiWrapper::updateConfig()
if(_preferences->getUInt(preference_nuki_id_lock, 0) == _nukiConfig.nukiId)
{
_hasKeypad = _nukiConfig.hasKeypad == 1 || _nukiConfig.hasKeypadV2 == 1;
_hasKeypad = _nukiConfig.hasKeypad == 1 || (_nukiConfig.hasKeypadV2 > 0 && _nukiConfig.hasKeypadV2 != 252);
_firmwareVersion = std::to_string(_nukiConfig.firmwareVersion[0]) + "." + std::to_string(_nukiConfig.firmwareVersion[1]) + "." + std::to_string(_nukiConfig.firmwareVersion[2]);
_hardwareVersion = std::to_string(_nukiConfig.hardwareRevision[0]) + "." + std::to_string(_nukiConfig.hardwareRevision[1]);
if(_preferences->getBool(preference_conf_info_enabled, true))
@@ -693,13 +623,13 @@ void NukiWrapper::updateConfig()
}
else
{
Log->println(F("Invalid/Unexpected lock config recieved, ID does not matched saved ID"));
Log->println(F("Invalid/Unexpected lock config received, ID does not matched saved ID"));
expectedConfig = false;
}
}
else
{
Log->println(F("Invalid/Unexpected lock config recieved, Config is not valid"));
Log->println(F("Invalid/Unexpected lock config received, Config is not valid"));
expectedConfig = false;
}
@@ -716,7 +646,7 @@ void NukiWrapper::updateConfig()
}
else
{
Log->println(F("Invalid/Unexpected lock advanced config recieved, Advanced config is not valid"));
Log->println(F("Invalid/Unexpected lock advanced config received, Advanced config is not valid"));
expectedConfig = false;
}
}
@@ -729,7 +659,7 @@ void NukiWrapper::updateConfig()
else
{
++_retryConfigCount;
Log->println(F("Invalid/Unexpected lock config and/or advanced config recieved, retrying in 10 seconds"));
Log->println(F("Invalid/Unexpected lock config and/or advanced config received, retrying in 10 seconds"));
int64_t ts = espMillis();
_nextConfigUpdateTs = ts + 10000;
}
@@ -3998,12 +3928,21 @@ void NukiWrapper::notify(Nuki::EventType eventType)
}
else
{
if(!_pairedAsApp && eventType == Nuki::EventType::KeyTurnerStatusUpdated && !_statusUpdated)
if(eventType == Nuki::EventType::KeyTurnerStatusReset)
{
Log->println("KeyTurnerStatusUpdated");
_statusUpdated = true;
_statusUpdatedTs = espMillis();
_network->publishStatusUpdated(_statusUpdated);
_newSignal = 0;
Log->println("KeyTurnerStatusReset");
}
else if(eventType == Nuki::EventType::KeyTurnerStatusUpdated)
{
if(!_statusUpdated && _newSignal < 5)
{
_newSignal++;
Log->println("KeyTurnerStatusUpdated");
_statusUpdated = true;
_statusUpdatedTs = espMillis();
_network->publishStatusUpdated(_statusUpdated);
}
}
}
}
@@ -4065,28 +4004,6 @@ void NukiWrapper::readAdvancedConfig()
}
}
void NukiWrapper::setupHASS()
{
if(!_nukiConfigValid)
{
return;
}
if(_preferences->getUInt(preference_nuki_id_lock, 0) != _nukiConfig.nukiId)
{
return;
}
String baseTopic = _preferences->getString(preference_mqtt_lock_path);
baseTopic.concat("/lock");
char uidString[20];
itoa(_nukiConfig.nukiId, uidString, 16);
_network->publishHASSConfig((char*)"SmartLock", baseTopic.c_str(),(char*)_nukiConfig.name, uidString, _firmwareVersion.c_str(), _hardwareVersion.c_str(), hasDoorSensor(), _hasKeypad, _publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch");
_hassSetupCompleted = true;
Log->println("HASS setup for lock completed.");
}
bool NukiWrapper::hasDoorSensor() const
{
return _keyTurnerState.doorSensorState == Nuki::DoorSensorState::DoorClosed ||
@@ -4094,13 +4011,6 @@ bool NukiWrapper::hasDoorSensor() const
_keyTurnerState.doorSensorState == Nuki::DoorSensorState::Calibrating;
}
void NukiWrapper::disableHASS()
{
char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
_network->removeHASSConfig(uidString);
}
const BLEAddress NukiWrapper::getBleAddress() const
{
return _nukiLock.getBleAddress();

View File

@@ -17,7 +17,7 @@ public:
NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences);
virtual ~NukiWrapper();
void initialize(const bool& firstStart);
void initialize();
void readSettings();
void update();
@@ -33,8 +33,6 @@ public:
uint16_t getPin();
void unpair();
void disableHASS();
void disableWatchdog();
const NukiLock::KeyTurnerState& keyTurnerState();
@@ -81,8 +79,6 @@ private:
void readConfig();
void readAdvancedConfig();
void setupHASS();
void printCommandResult(Nuki::CmdResult result);
NukiLock::LockAction lockActionToEnum(const char* str); // char array at least 14 characters
@@ -134,6 +130,7 @@ private:
bool _pairedAsApp = false;
bool _paired = false;
bool _statusUpdated = false;
int _newSignal = 0;
bool _hasKeypad = false;
bool _keypadEnabled = false;
uint _maxKeypadCodeCount = 0;

View File

@@ -2,6 +2,9 @@
#include <vector>
#include "Config.h"
#ifndef CONFIG_IDF_TARGET_ESP32H2
#include "esp_wifi.h"
#endif
//CHANGE REQUIRES REBOOT TO TAKE EFFECT
#define preference_ip_dhcp_enabled (char*)"dhcpena"
@@ -46,7 +49,9 @@
#define preference_cred_user (char*)"crdusr"
#define preference_cred_password (char*)"crdpass"
#define preference_gpio_configuration (char*)"gpiocfg"
#define preference_mqtt_hass_enabled (char*)"hassena"
#define preference_mqtt_hass_discovery (char*)"hassdiscovery"
#define preference_hass_device_discovery (char*)"hassdevdisc"
#define preference_webserver_enabled (char*)"websrvena"
#define preference_update_from_mqtt (char*)"updMqtt"
#define preference_disable_non_json (char*)"disnonjson"
@@ -127,17 +132,17 @@
#define preference_presence_detection_timeout (char*)"prdtimeout"
#define preference_network_wifi_fallback_disabled (char*)"nwwififb"
inline bool initPreferences(Preferences* preferences)
inline void initPreferences(Preferences* preferences)
{
#ifdef NUKI_HUB_UPDATER
bool firstStart = false;
return firstStart;
return;
#else
bool firstStart = !preferences->getBool(preference_started_before);
#endif
if(firstStart)
{
Serial.println("First start, setting preference defaults");
preferences->putBool(preference_started_before, true);
preferences->putBool(preference_lock_enabled, true);
uint32_t aclPrefs[17] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
@@ -150,6 +155,69 @@ inline bool initPreferences(Preferences* preferences)
preferences->putBytes(preference_conf_lock_advanced_acl, (byte*)(&advancedLockConfigAclPrefs), sizeof(advancedLockConfigAclPrefs));
uint32_t advancedOpenerConfigAclPrefs[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
preferences->putBytes(preference_conf_opener_advanced_acl, (byte*)(&advancedOpenerConfigAclPrefs), sizeof(advancedOpenerConfigAclPrefs));
#ifndef CONFIG_IDF_TARGET_ESP32H2
wifi_config_t wifi_cfg;
if(esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK)
{
Serial.println("Failed to get Wi-Fi configuration in RAM");
}
if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK)
{
Serial.println("Failed to set storage Wi-Fi");
}
memset(wifi_cfg.sta.ssid, 0, sizeof(wifi_cfg.sta.ssid));
memset(wifi_cfg.sta.password, 0, sizeof(wifi_cfg.sta.password));
if (esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK)
{
Serial.println("Failed to clear NVS Wi-Fi configuration");
}
#endif
preferences->putString(preference_mqtt_lock_path, "nukihub");
preferences->putBool(preference_check_updates, true);
preferences->putBool(preference_opener_continuous_mode, false);
preferences->putBool(preference_official_hybrid_enabled, false);
preferences->putBool(preference_official_hybrid_actions, false);
preferences->putBool(preference_official_hybrid_retry, false);
preferences->putBool(preference_disable_non_json, false);
preferences->putBool(preference_update_from_mqtt, false);
preferences->putBool(preference_ip_dhcp_enabled, true);
preferences->putBool(preference_enable_bootloop_reset, false);
preferences->putBool(preference_show_secrets, false);
preferences->putBool(preference_conf_info_enabled, true);
preferences->putBool(preference_keypad_info_enabled, false);
preferences->putBool(preference_keypad_topic_per_entry, false);
preferences->putBool(preference_keypad_publish_code, false);
preferences->putBool(preference_keypad_control_enabled, false);
preferences->putBool(preference_timecontrol_info_enabled, false);
preferences->putBool(preference_timecontrol_topic_per_entry, false);
preferences->putBool(preference_timecontrol_control_enabled, false);
preferences->putBool(preference_publish_authdata, false);
preferences->putBool(preference_register_as_app, false);
preferences->putBool(preference_register_opener_as_app, false);
preferences->putInt(preference_mqtt_broker_port, 1883);
preferences->putInt(preference_buffer_size, CHAR_BUFFER_SIZE);
preferences->putInt(preference_task_size_network, NETWORK_TASK_SIZE);
preferences->putInt(preference_task_size_nuki, NUKI_TASK_SIZE);
preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG);
preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD);
preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL);
preferences->putInt(preference_query_interval_hybrid_lockstate, 600);
preferences->putInt(preference_rssi_publish_interval, 60);
preferences->putInt(preference_network_timeout, 60);
preferences->putInt(preference_command_nr_of_retries, 3);
preferences->putInt(preference_command_retry_delay, 100);
preferences->putInt(preference_restart_ble_beacon_lost, 60);
preferences->putInt(preference_query_interval_lockstate, 1800);
preferences->putInt(preference_query_interval_configuration, 3600);
preferences->putInt(preference_query_interval_battery, 1800);
preferences->putInt(preference_query_interval_keypad, 1800);
}
else
{
@@ -264,8 +332,7 @@ inline bool initPreferences(Preferences* preferences)
preferences->putInt(preference_config_version, atof(NUKI_HUB_VERSION) * 100);
}
}
return firstStart;
#endif
}
class DebugPreferences
@@ -293,7 +360,7 @@ private:
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_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, preference_wifi_ssid, preference_wifi_pass,
preference_keypad_check_code_enabled, preference_disable_network_not_connected
preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_mqtt_hass_enabled, preference_hass_device_discovery
};
std::vector<char*> _redact =
{
@@ -306,9 +373,9 @@ private:
preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled,
preference_restart_on_disconnect, preference_keypad_control_enabled, preference_keypad_info_enabled, preference_keypad_publish_code, preference_show_secrets,
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_official_hybrid_enabled,
preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, preference_mqtt_hass_enabled,
preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled, preference_hass_device_discovery,
preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected
};
std::vector<char*> _bytePrefs =

View File

@@ -78,10 +78,10 @@ void WebCfgServer::initialize()
{
_psychicServer->on("/", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(!_network->isApOpen())
{
#ifndef NUKI_HUB_UPDATER
@@ -100,26 +100,26 @@ void WebCfgServer::initialize()
_psychicServer->on("/style.css", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return sendCss(request);
});
_psychicServer->on("/favicon.ico", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return sendFavicon(request);
});
_psychicServer->on("/reboot", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
String value = "";
if(request->hasParam("CONFIRMTOKEN"))
@@ -150,15 +150,15 @@ void WebCfgServer::initialize()
#ifndef CONFIG_IDF_TARGET_ESP32H2
_psychicServer->on("/ssidlist", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildSSIDListHtml(request);
});
_psychicServer->on("/savewifi", HTTP_POST, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
@@ -181,193 +181,210 @@ void WebCfgServer::initialize()
#ifndef NUKI_HUB_UPDATER
_psychicServer->on("/import", HTTP_POST, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
String message = "";
bool restart = processImport(request, message);
return buildConfirmHtml(request, message, 3, true);
});
_psychicServer->on("/export", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return sendSettings(request);
});
_psychicServer->on("/impexpcfg", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildImportExportHtml(request);
});
_psychicServer->on("/status", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildStatusHtml(request);
});
_psychicServer->on("/acclvl", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildAccLvlHtml(request);
});
_psychicServer->on("/custntw", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildCustomNetworkConfigHtml(request);
});
_psychicServer->on("/advanced", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildAdvancedConfigHtml(request);
});
_psychicServer->on("/cred", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildCredHtml(request);
});
_psychicServer->on("/ntwconfig", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildNetworkConfigHtml(request);
});
_psychicServer->on("/mqttconfig", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildMqttConfigHtml(request);
});
_psychicServer->on("/nukicfg", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildNukiConfigHtml(request);
});
_psychicServer->on("/gpiocfg", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildGpioConfigHtml(request);
});
#ifndef CONFIG_IDF_TARGET_ESP32H2
_psychicServer->on("/wifi", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildConfigureWifiHtml(request);
});
_psychicServer->on("/wifimanager", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(_allowRestartToPortal)
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
esp_err_t res = buildConfirmHtml(request, "Restarting. Connect to ESP access point (\"NukiHub\" with password \"NukiHubESP32\") to reconfigure Wi-Fi.", 0);
waitAndProcess(false, 1000);
_network->reconfigureDevice();
return res;
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return(ESP_OK);
String value = "";
if(request->hasParam("CONFIRMTOKEN"))
{
const PsychicWebParameter* p = request->getParam("CONFIRMTOKEN");
if(p->value() != "")
{
value = p->value();
}
}
else
{
return buildConfirmHtml(request, "No confirm code set.", 3, true);
}
if(value != _confirmCode)
{
return request->redirect("/");
}
if(!_allowRestartToPortal)
{
return buildConfirmHtml(request, "Can't reset WiFi when network device is Ethernet", 3, true);
}
esp_err_t res = buildConfirmHtml(request, "Restarting. Connect to ESP access point (\"NukiHub\" with password \"NukiHubESP32\") to reconfigure Wi-Fi.", 0);
waitAndProcess(false, 1000);
_network->reconfigureDevice();
return res;
});
#endif
_psychicServer->on("/unpairlock", HTTP_POST, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return processUnpair(request, false);
});
_psychicServer->on("/unpairopener", HTTP_POST, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return processUnpair(request, true);
});
_psychicServer->on("/factoryreset", HTTP_POST, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return processFactoryReset(request);
});
_psychicServer->on("/infopg", HTTP_GET, [&](PsychicRequest *request)
_psychicServer->on("/info", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildInfoHtml(request);
});
_psychicServer->on("/debugon", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
_preferences->putBool(preference_publish_debug_info, true);
return buildConfirmHtml(request, "Debug On", 3, true);
});
_psychicServer->on("/debugoff", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
_preferences->putBool(preference_publish_debug_info, false);
return buildConfirmHtml(request, "Debug Off", 3, true);
});
_psychicServer->on("/savecfg", HTTP_POST, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
String message = "";
bool restart = processArgs(request, message);
return buildConfirmHtml(request, message, 3, true);
});
_psychicServer->on("/savegpiocfg", HTTP_POST, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
processGpioArgs(request);
esp_err_t res = buildConfirmHtml(request, "Saving GPIO configuration. Restarting.", 3, true);
Log->println(F("Restarting"));
@@ -378,26 +395,26 @@ void WebCfgServer::initialize()
#endif
_psychicServer->on("/ota", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildOtaHtml(request);
});
_psychicServer->on("/otadebug", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return buildOtaHtml(request, true);
});
_psychicServer->on("/reboottoota", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
String value = "";
if(request->hasParam("CONFIRMTOKEN"))
{
@@ -424,10 +441,10 @@ void WebCfgServer::initialize()
});
_psychicServer->on("/autoupdate", HTTP_GET, [&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
#ifndef NUKI_HUB_UPDATER
return processUpdate(request);
#else
@@ -438,20 +455,20 @@ void WebCfgServer::initialize()
PsychicUploadHandler *updateHandler = new PsychicUploadHandler();
updateHandler->onUpload([&](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
return handleOtaUpload(request, filename, index, data, len, final);
}
);
updateHandler->onRequest([&](PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
}
String result;
if (!Update.hasError())
@@ -586,9 +603,8 @@ esp_err_t WebCfgServer::buildWifiConnectHtml(PsychicRequest *request)
response.print("</table>");
response.print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.print("</form>");
response.print("<form action=\"/reboot?CONFIRMTOKEN=");
response.print(_confirmCode);
response.print("\" method=\"get\"><br><input type=\"submit\" value=\"Reboot\" /></form>");
response.print("<form action=\"/reboot\" method=\"get\"><br>");
response.print("<input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><input type=\"submit\" value=\"Reboot\" /></form>");
response.print("</body></html>");
return response.endSend();
}
@@ -882,9 +898,8 @@ esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, bool debug)
response.print("<h4><a onclick=\"hideshowmanual();\">Manually update Nuki Hub</a></h4><div id=\"manualupdate\" style=\"display: none\">");
response.print("<div id=\"rebootform\"><h4>Reboot to Nuki Hub Updater</h4>");
response.print("Click on the button to reboot to the Nuki Hub updater, where you can select the latest Nuki Hub binary to update");
response.print("<form action=\"/reboottoota?CONFIRMTOKEN=");
response.print(_confirmCode);
response.print("\" method=\"get\"><br><input type=\"submit\" value=\"Reboot to Nuki Hub Updater\" /></form><br><br></div>");
response.print("<form action=\"/reboottoota\" method=\"get\"><br>");
response.print("<input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><input type=\"submit\" value=\"Reboot to Nuki Hub Updater\" /></form><br><br></div>");
response.print("<div id=\"upform\"><h4>Update Nuki Hub Updater</h4>");
response.print("Select the latest Nuki Hub updater binary to update the Nuki Hub updater");
response.print("<form enctype=\"multipart/form-data\" action=\"/uploadota\" method=\"post\">Choose the nuki_hub_updater.bin file to upload: <input name=\"uploadedfile\" type=\"file\" accept=\".bin\" /><br/>");
@@ -894,9 +909,8 @@ esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, bool debug)
response.print("<div id=\"manualupdate\">");
response.print("<div id=\"rebootform\"><h4>Reboot to Nuki Hub</h4>");
response.print("Click on the button to reboot to Nuki Hub");
response.print("<form action=\"/reboottoota?CONFIRMTOKEN=");
response.print(_confirmCode);
response.print("\" method=\"get\"><br><input type=\"submit\" value=\"Reboot to Nuki Hub\" /></form><br><br></div>");
response.print("<form action=\"/reboottoota\" method=\"get\"><br>");
response.print("<input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><input type=\"submit\" value=\"Reboot to Nuki Hub\" /></form><br><br></div>");
response.print("<div id=\"upform\"><h4>Update Nuki Hub</h4>");
response.print("Select the latest Nuki Hub binary to update Nuki Hub");
response.print("<form enctype=\"multipart/form-data\" action=\"/uploadota\" method=\"post\">Choose the nuki_hub.bin file to upload: <input name=\"uploadedfile\" type=\"file\" accept=\".bin\" /><br/>");
@@ -1726,18 +1740,33 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message)
//configChanged = true;
}
}
else if(key == "HADEVDISC")
{
if(_preferences->getBool(preference_hass_device_discovery, false) != (value == "1"))
{
_network->disableHASS();
_preferences->putBool(preference_hass_device_discovery, (value == "1"));
Log->print(F("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "ENHADISC")
{
if(_preferences->getBool(preference_mqtt_hass_enabled, false) != (value == "1"))
{
_network->disableHASS();
_preferences->putBool(preference_mqtt_hass_enabled, (value == "1"));
Log->print(F("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "HASSDISCOVERY")
{
if(_preferences->getString(preference_mqtt_hass_discovery, "") != value)
{
if (_nuki != nullptr)
{
_nuki->disableHASS();
}
if (_nukiOpener != nullptr)
{
_nukiOpener->disableHASS();
}
_network->disableHASS();
_preferences->putString(preference_mqtt_hass_discovery, value);
Log->print(F("Setting changed: "));
Log->println(key);
@@ -3345,37 +3374,37 @@ esp_err_t WebCfgServer::buildHtml(PsychicRequest *request)
printParameter(&response, "Nuki Opener PIN status", openerState.c_str(), "", "openerPin");
}
}
printParameter(&response, "Firmware", NUKI_HUB_VERSION, "/infopg", "firmware");
printParameter(&response, "Firmware", NUKI_HUB_VERSION, "/info?", "firmware");
if(_preferences->getBool(preference_check_updates))
{
printParameter(&response, "Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota", "ota");
printParameter(&response, "Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota?", "ota");
}
response.print("</table><br>");
response.print("<ul id=\"tblnav\">");
buildNavigationMenuEntry(&response, "Network Configuration", "/ntwconfig");
buildNavigationMenuEntry(&response, "MQTT Configuration", "/mqttconfig", _brokerConfigured ? "" : "Please configure MQTT broker");
buildNavigationMenuEntry(&response, "Nuki Configuration", "/nukicfg");
buildNavigationMenuEntry(&response, "Access Level Configuration", "/acclvl");
buildNavigationMenuEntry(&response, "Credentials", "/cred", _pinsConfigured ? "" : "Please configure PIN");
buildNavigationMenuEntry(&response, "GPIO Configuration", "/gpiocfg");
buildNavigationMenuEntry(&response, "Firmware update", "/ota");
buildNavigationMenuEntry(&response, "Import/Export Configuration", "/impexpcfg");
buildNavigationMenuEntry(&response, "Network Configuration", "/ntwconfig?");
buildNavigationMenuEntry(&response, "MQTT Configuration", "/mqttconfig?", _brokerConfigured ? "" : "Please configure MQTT broker");
buildNavigationMenuEntry(&response, "Nuki Configuration", "/nukicfg?");
buildNavigationMenuEntry(&response, "Access Level Configuration", "/acclvl?");
buildNavigationMenuEntry(&response, "Credentials", "/cred?", _pinsConfigured ? "" : "Please configure PIN");
buildNavigationMenuEntry(&response, "GPIO Configuration", "/gpiocfg?");
buildNavigationMenuEntry(&response, "Firmware update", "/ota?");
buildNavigationMenuEntry(&response, "Import/Export Configuration", "/impexpcfg?");
if(_preferences->getInt(preference_network_hardware, 0) == 11)
{
buildNavigationMenuEntry(&response, "Custom Ethernet Configuration", "/custntw");
buildNavigationMenuEntry(&response, "Custom Ethernet Configuration", "/custntw?");
}
if (_preferences->getBool(preference_publish_debug_info, false))
{
buildNavigationMenuEntry(&response, "Advanced Configuration", "/advanced");
buildNavigationMenuEntry(&response, "Advanced Configuration", "/advanced?");
}
if(_preferences->getBool(preference_webserial_enabled, false))
{
buildNavigationMenuEntry(&response, "Open Webserial", "/webserial");
buildNavigationMenuEntry(&response, "Open Webserial", "/webserial?");
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
if(_allowRestartToPortal)
{
buildNavigationMenuEntry(&response, "Configure Wi-Fi", "/wifi");
buildNavigationMenuEntry(&response, "Configure Wi-Fi", "/wifi?");
}
#endif
String rebooturl = "/reboot?CONFIRMTOKEN=" + _confirmCode;
@@ -3507,11 +3536,13 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request)
printInputField(&response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true);
printInputField(&response, "MQTTPASS", "MQTT Password", "*", 30, "", true, true);
printInputField(&response, "MQTTPATH", "MQTT NukiHub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, "");
printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), "chkHass");
response.print("</table><br>");
response.print("<h3>Advanced MQTT Configuration</h3>");
response.print("<table>");
printInputField(&response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, "");
printInputField(&response, "HASSDISCOVERY", "Home Assistant discovery topic (usually \"homeassistant\")", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, "class=\"chkHass\"");
//printCheckBox(&response, "HADEVDISC", "Use Home Assistant device based discovery (2024.11+)", _preferences->getBool(preference_hass_device_discovery), "");
if(_preferences->getBool(preference_opener_enabled, false))
{
printCheckBox(&response, "OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), "");
@@ -3531,7 +3562,9 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request)
response.print("* If no encryption is configured for the MQTT broker, leave empty.<br><br>");
response.print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.print("</form>");
response.print("</body></html>");
response.print("</body>");
response.print("<script>window.onload = function() { var hassChk; var hassTxt; for (var el of document.getElementsByClassName('chkHass')) { if (el.constructor.name === 'HTMLInputElement' && el.type === 'checkbox') { hassChk = el; el.addEventListener('change', hassChkChange); } else if (el.constructor.name==='HTMLInputElement' && el.type==='text') { hassTxt=el; el.addEventListener('keyup', hassTxtChange); } } function hassChkChange() { if(hassChk.checked == true) { if(hassTxt.value.length == 0) { hassTxt.value = 'homeassistant'; } } else { hassTxt.value = ''; } } function hassTxtChange() { if(hassTxt.value.length == 0) { hassChk.checked = false; } else { hassChk.checked = true; } } };</script>");
response.print("</html>");
return response.endSend();
}
@@ -3547,7 +3580,7 @@ esp_err_t WebCfgServer::buildAdvancedConfigHtml(PsychicRequest *request)
response.print("<tr><td>Current bootloop prevention state</td><td>");
response.print(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Enabled" : "Disabled");
response.print("</td></tr>");
printCheckBox(&response, "DISNTWNOCON", "Disable Network if not connected within 60s", _preferences->getBool(preference_disable_network_not_connected, false), "");
printCheckBox(&response, "DISNTWNOCON", "Disable Network if not connected within 60s", _preferences->getBool(preference_disable_network_not_connected, false), "");
printCheckBox(&response, "WEBLOG", "Enable WebSerial logging", _preferences->getBool(preference_webserial_enabled), "");
printCheckBox(&response, "BTLPRST", "Enable Bootloop prevention (Try to reset these settings to default on bootloop)", true, "");
printInputField(&response, "BUFFSIZE", "Char buffer size (min 4096, max 32768)", _preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE), 6, "");
@@ -3721,7 +3754,7 @@ esp_err_t WebCfgServer::buildAccLvlHtml(PsychicRequest *request)
printCheckBox(&response, "KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), "");
printCheckBox(&response, "KPCODE", "Also publish keypad codes (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_publish_code, false), "");
printCheckBox(&response, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), "");
printCheckBox(&response, "KPCHECK", "Allow checking if keypad codes are valid (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_check_code_enabled, false), "");
printCheckBox(&response, "KPCHECK", "Allow checking if keypad codes are valid (<span class=\"warning\">Disadvised for security reasons</span>)", _preferences->getBool(preference_keypad_check_code_enabled, false), "");
}
printCheckBox(&response, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), "");
printCheckBox(&response, "TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), "");
@@ -3981,7 +4014,8 @@ esp_err_t WebCfgServer::buildConfigureWifiHtml(PsychicRequest *request)
buildHtmlHeader(&response);
response.print("<h3>Wi-Fi</h3>");
response.print("Click confirm to remove saved WiFi settings and restart ESP into Wi-Fi configuration mode. After restart, connect to ESP access point to reconfigure Wi-Fi.<br><br>");
buildNavigationButton(&response, "Confirm", "/wifimanager");
String wifiMgrUrl = "/wifimanager?CONFIRMTOKEN=" + _confirmCode;
buildNavigationButton(&response, "Confirm", wifiMgrUrl.c_str());
response.print("</body></html>");
return response.endSend();
}
@@ -4170,7 +4204,7 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request)
response.print(_preferences->getInt(preference_command_nr_of_retries, 3));
response.print("\nBluetooth command retry delay (ms): ");
response.print(_preferences->getInt(preference_command_retry_delay, 100));
response.print("\nSeconds until reboot when no BLE beacons recieved: ");
response.print("\nSeconds until reboot when no BLE beacons received: ");
response.print(_preferences->getInt(preference_restart_ble_beacon_lost, 60));
response.print("\n\n------------ QUERY / PUBLISH SETTINGS ------------");
response.print("\nLock/Opener state query interval (s): ");
@@ -4604,14 +4638,14 @@ esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, bool opener)
if(!opener && _nuki != nullptr)
{
_nuki->disableHASS();
_nuki->unpair();
}
if(opener && _nukiOpener != nullptr)
{
_nukiOpener->disableHASS();
_nukiOpener->unpair();
}
_network->disableHASS();
waitAndProcess(false, 1000);
restartEsp(RestartReason::DeviceUnpaired);
return res;
@@ -4730,15 +4764,14 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request)
if(_nuki != nullptr)
{
_nuki->disableHASS();
_nuki->unpair();
}
if(_nukiOpener != nullptr)
{
_nukiOpener->disableHASS();
_nukiOpener->unpair();
}
_network->disableHASS();
_preferences->clear();
#ifndef CONFIG_IDF_TARGET_ESP32H2

View File

@@ -2,7 +2,9 @@
#include <Preferences.h>
#include <PsychicHttp.h>
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
#include <PsychicHttpsServer.h>
#endif
#include "esp_ota_ops.h"
#include "Config.h"

View File

@@ -46,7 +46,7 @@ bool wifiConnected = false;
TaskHandle_t nukiTaskHandle = nullptr;
int64_t restartTs = ((2^64) - (5 * 1000 * 60000)) / 1000;
int64_t restartTs = (pow(2,64) - (5 * 1000 * 60000)) / 1000;
#else
#include "../../src/WebCfgServer.h"
@@ -201,7 +201,7 @@ void nukiTask(void *pvParameters)
if (needsPairing)
{
delay(5000);
delay(2500);
}
else if (!whiteListed)
{
@@ -446,7 +446,7 @@ void setup()
preferences = new Preferences();
preferences->begin("nukihub", false);
bool firstStart = initPreferences(preferences);
initPreferences(preferences);
bool doOta = false;
uint8_t partitionType = checkPartition();
@@ -468,13 +468,6 @@ void setup()
doOta = true;
}
#ifndef NUKI_HUB_UPDATER
if(preferences->getBool(preference_enable_bootloop_reset, false))
{
bootloopDetection();
}
#endif
#ifdef NUKI_HUB_UPDATER
Log->print(F("Nuki Hub OTA version "));
Log->println(NUKI_HUB_VERSION);
@@ -500,15 +493,22 @@ void setup()
if(!doOta)
{
psychicServer = new PsychicHttpServer;
psychicServer->config.max_uri_handlers = 40;
psychicServer->config.stack_size = HTTPD_TASK_SIZE;
psychicServer->listen(80);
webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer);
webCfgServer->initialize();
psychicServer->listen(80);
psychicServer->onNotFound([](PsychicRequest* request)
{
return request->redirect("/");
});
}
#else
if(preferences->getBool(preference_enable_bootloop_reset, false))
{
bootloopDetection();
}
Log->print(F("Nuki Hub version "));
Log->println(NUKI_HUB_VERSION);
Log->print(F("Nuki Hub build "));
@@ -569,7 +569,7 @@ void setup()
}
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences);
nuki->initialize(firstStart);
nuki->initialize();
}
Log->println(openerEnabled ? F("Nuki Opener enabled") : F("Nuki Opener disabled"));
@@ -591,8 +591,6 @@ void setup()
psychicServer = new PsychicHttpServer;
psychicServer->config.max_uri_handlers = 40;
psychicServer->config.stack_size = HTTPD_TASK_SIZE;
psychicServer->maxUploadSize = 8192;
psychicServer->maxRequestBodySize = 8192;
psychicServer->listen(80);
if(forceEnableWebServer || preferences->getBool(preference_webserver_enabled, true))

View File

@@ -1,17 +1,14 @@
#include "EthernetDevice.h"
#include "../PreferencesKeys.h"
#include "../Logger.h"
#ifndef NUKI_HUB_UPDATER
#include "../MqttTopics.h"
#include "espMqttClient.h"
#endif
#include "../RestartReason.h"
#include "../EspMillis.h"
extern bool ethCriticalFailure;
extern bool wifiFallback;
EthernetDevice::EthernetDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration, const std::string& deviceName, uint8_t phy_addr, int power, int mdc, int mdio, eth_phy_type_t ethtype, eth_clock_mode_t clock_mode)
: NetworkDevice(hostname, ipConfiguration),
: NetworkDevice(hostname, preferences, ipConfiguration),
_deviceName(deviceName),
_phy_addr(phy_addr),
_power(power),
@@ -22,7 +19,9 @@ EthernetDevice::EthernetDevice(const String& hostname, Preferences* preferences,
_useSpi(false),
_preferences(preferences)
{
init();
#ifndef NUKI_HUB_UPDATER
NetworkDevice::init();
#endif
}
EthernetDevice::EthernetDevice(const String &hostname,
@@ -37,7 +36,7 @@ EthernetDevice::EthernetDevice(const String &hostname,
int spi_miso,
int spi_mosi,
eth_phy_type_t ethtype)
: NetworkDevice(hostname, ipConfiguration),
: NetworkDevice(hostname, preferences, ipConfiguration),
_deviceName(deviceName),
_phy_addr(phy_addr),
_cs(cs),
@@ -49,52 +48,9 @@ EthernetDevice::EthernetDevice(const String &hostname,
_type(ethtype),
_useSpi(true),
_preferences(preferences)
{
init();
}
void EthernetDevice::init()
{
#ifndef NUKI_HUB_UPDATER
size_t caLength = _preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE);
size_t crtLength = _preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE);
size_t keyLength = _preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE);
_useEncryption = caLength > 1; // length is 1 when empty
if(_useEncryption)
{
Log->println(F("MQTT over TLS."));
_mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
_mqttClientSecure->setCACert(_ca);
if(crtLength > 1 && keyLength > 1) // length is 1 when empty
{
Log->println(F("MQTT with client certificate."));
_mqttClientSecure->setCertificate(_cert);
_mqttClientSecure->setPrivateKey(_key);
}
} else
{
Log->println(F("MQTT without TLS."));
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
}
if(_preferences->getBool(preference_mqtt_log_enabled, false) || _preferences->getBool(preference_webserial_enabled, false))
{
MqttLoggerMode mode;
if(_preferences->getBool(preference_mqtt_log_enabled, false) && _preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb;
else if (_preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb;
else mode = MqttLoggerMode::MqttAndSerial;
_path = new char[200];
memset(_path, 0, sizeof(_path));
String pathStr = _preferences->getString(preference_mqtt_lock_path);
pathStr.concat(mqtt_topic_log);
strcpy(_path, pathStr.c_str());
Log = new MqttLogger(*getMqttClient(), _path, mode);
}
NetworkDevice::init();
#endif
}

View File

@@ -11,9 +11,6 @@
#include <NetworkClientSecure.h>
#include <Preferences.h>
#include "NetworkDevice.h"
#ifndef NUKI_HUB_UPDATER
#include "espMqttClient.h"
#endif
class EthernetDevice : public NetworkDevice
{
@@ -61,21 +58,24 @@ public:
private:
Preferences* _preferences;
void init();
void onDisconnected();
void onNetworkEvent(arduino_event_id_t event, arduino_event_info_t info);
bool _connected = false;
char* _path;
bool _hardwareInitialized = false;
bool _useSpi = false;
int64_t _checkIpTs = -1;
const std::string _deviceName;
uint8_t _phy_addr;
eth_phy_type_t _type;
// LAN8720
int _power;
int _mdc;
int _mdio;
eth_clock_mode_t _clock_mode;
// W55000 and DM9051
int _cs;
@@ -84,16 +84,4 @@ private:
int _spi_sck;
int _spi_miso;
int _spi_mosi;
int64_t _checkIpTs = -1;
eth_phy_type_t _type;
eth_clock_mode_t _clock_mode;
bool _useSpi = false;
#ifndef NUKI_HUB_UPDATER
char _ca[TLS_CA_MAX_SIZE] = {0};
char _cert[TLS_CERT_MAX_SIZE] = {0};
char _key[TLS_KEY_MAX_SIZE] = {0};
#endif
};

View File

@@ -2,19 +2,19 @@
#ifndef CONFIG_IDF_TARGET_ESP32
typedef enum {
ETH_CLOCK_GPIO0_IN = 0,
ETH_CLOCK_GPIO16_OUT = 2,
ETH_CLOCK_GPIO17_OUT = 3
} eth_clock_mode_t;
ETH_CLOCK_GPIO0_IN = 0,
ETH_CLOCK_GPIO16_OUT = 2,
ETH_CLOCK_GPIO17_OUT = 3
} eth_clock_mode_t;
#define ETH_PHY_TYPE_LAN8720 ETH_PHY_MAX
#define ETH_PHY_TYPE_LAN8720 ETH_PHY_MAX
#else
#define ETH_PHY_TYPE_LAN8720 ETH_PHY_LAN8720
#endif
#define ETH_CLK_MODE_LAN8720 ETH_CLOCK_GPIO0_IN
#define ETH_PHY_ADDR_LAN8720 0
#define ETH_PHY_MDC_LAN8720 23
#define ETH_PHY_MDIO_LAN8720 18
#define ETH_PHY_POWER_LAN8720 -1
#define ETH_RESET_PIN_LAN8720 1
#define ETH_CLK_MODE_LAN8720 ETH_CLOCK_GPIO0_IN
#define ETH_PHY_ADDR_LAN8720 0
#define ETH_PHY_MDC_LAN8720 23
#define ETH_PHY_MDIO_LAN8720 18
#define ETH_PHY_POWER_LAN8720 -1
#define ETH_RESET_PIN_LAN8720 1

View File

@@ -2,13 +2,52 @@
#include "NetworkDevice.h"
#include "../Logger.h"
void NetworkDevice::printError()
{
Log->print(F("Free Heap: "));
Log->println(ESP.getFreeHeap());
}
#ifndef NUKI_HUB_UPDATER
#include "../MqttTopics.h"
#include "PreferencesKeys.h"
void NetworkDevice::init()
{
size_t caLength = _preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE);
size_t crtLength = _preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE);
size_t keyLength = _preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE);
_useEncryption = caLength > 1; // length is 1 when empty
if(_useEncryption)
{
Log->println(F("MQTT over TLS."));
_mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
_mqttClientSecure->setCACert(_ca);
if(crtLength > 1 && keyLength > 1) // length is 1 when empty
{
Log->println(F("MQTT with client certificate."));
_mqttClientSecure->setCertificate(_cert);
_mqttClientSecure->setPrivateKey(_key);
}
} else
{
Log->println(F("MQTT without TLS."));
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
}
if(_preferences->getBool(preference_mqtt_log_enabled, false) || _preferences->getBool(preference_webserial_enabled, false))
{
MqttLoggerMode mode;
if(_preferences->getBool(preference_mqtt_log_enabled, false) && _preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb;
else if (_preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb;
else mode = MqttLoggerMode::MqttAndSerial;
_path = new char[200];
memset(_path, 0, sizeof(_path));
String pathStr = _preferences->getString(preference_mqtt_lock_path);
pathStr.concat(mqtt_topic_log);
strcpy(_path, pathStr.c_str());
Log = new MqttLogger(*getMqttClient(), _path, mode);
}
}
void NetworkDevice::update()
{
if (_mqttEnabled)
@@ -90,7 +129,7 @@ bool NetworkDevice::mqttDisconnect(bool force)
return getMqttClient()->disconnect(force);
}
void NetworkDevice::setWill(const char *topic, uint8_t qos, bool retain, const char *payload)
void NetworkDevice::mqttSetWill(const char *topic, uint8_t qos, bool retain, const char *payload)
{
if (_useEncryption)
{
@@ -155,7 +194,7 @@ uint16_t NetworkDevice::mqttSubscribe(const char *topic, uint8_t qos)
return getMqttClient()->subscribe(topic, qos);
}
void NetworkDevice::disableMqtt()
void NetworkDevice::mqttDisable()
{
getMqttClient()->disconnect();
_mqttEnabled = false;

View File

@@ -2,16 +2,15 @@
#ifndef NUKI_HUB_UPDATER
#include "espMqttClient.h"
#include "MqttClientSetup.h"
#endif
#include "IPConfiguration.h"
#include "../EspMillis.h"
class NetworkDevice
{
public:
explicit NetworkDevice(const String& hostname, const IPConfiguration* ipConfiguration)
explicit NetworkDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration)
: _hostname(hostname),
_preferences(preferences),
_ipConfiguration(ipConfiguration)
{}
@@ -19,7 +18,6 @@ public:
virtual void initialize() = 0;
virtual void reconfigure() = 0;
virtual void printError();
virtual void update();
virtual void scan(bool passive = false, bool async = true) = 0;
@@ -31,36 +29,45 @@ public:
virtual String BSSIDstr() = 0;
#ifndef NUKI_HUB_UPDATER
virtual bool mqttConnect();
virtual bool mqttDisconnect(bool force);
virtual void mqttDisable();
virtual bool mqttConnected() const;
virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const char* payload);
virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length);
virtual uint16_t mqttSubscribe(const char* topic, uint8_t qos);
virtual void mqttSetServer(const char* host, uint16_t port);
virtual void mqttSetClientId(const char* clientId);
virtual void mqttSetCleanSession(bool cleanSession);
virtual void mqttSetKeepAlive(uint16_t keepAlive);
virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const char* payload);
virtual uint16_t mqttPublish(const char* topic, uint8_t qos, bool retain, const uint8_t* payload, size_t length);
virtual bool mqttConnected() const;
virtual void mqttSetServer(const char* host, uint16_t port);
virtual bool mqttConnect();
virtual bool mqttDisconnect(bool force);
virtual void setWill(const char* topic, uint8_t qos, bool retain, const char* payload);
virtual void mqttSetWill(const char* topic, uint8_t qos, bool retain, const char* payload);
virtual void mqttSetCredentials(const char* username, const char* password);
virtual void mqttOnMessage(espMqttClientTypes::OnMessageCallback callback);
virtual void mqttOnConnect(espMqttClientTypes::OnConnectCallback callback);
virtual void mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback);
virtual void disableMqtt();
virtual uint16_t mqttSubscribe(const char* topic, uint8_t qos);
#endif
protected:
const IPConfiguration* _ipConfiguration = nullptr;
Preferences* _preferences = nullptr;
#ifndef NUKI_HUB_UPDATER
espMqttClient *_mqttClient = nullptr;
espMqttClientSecure *_mqttClientSecure = nullptr;
bool _useEncryption = false;
bool _mqttEnabled = true;
void init();
MqttClient *getMqttClient() const;
bool _useEncryption = false;
bool _mqttEnabled = true;
char* _path;
char _ca[TLS_CA_MAX_SIZE] = {0};
char _cert[TLS_CERT_MAX_SIZE] = {0};
char _key[TLS_KEY_MAX_SIZE] = {0};
#endif
const String _hostname;
const IPConfiguration* _ipConfiguration = nullptr;
};

View File

@@ -1,61 +1,20 @@
#include "WifiDevice.h"
#include "esp_wifi.h"
#include <WiFi.h>
#include "WifiDevice.h"
#include "../PreferencesKeys.h"
#include "../Logger.h"
#ifndef NUKI_HUB_UPDATER
#include "../MqttTopics.h"
#include "espMqttClient.h"
#endif
#include "../RestartReason.h"
#include "../EspMillis.h"
WifiDevice::WifiDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration)
: NetworkDevice(hostname, ipConfiguration),
: NetworkDevice(hostname, preferences, ipConfiguration),
_preferences(preferences)
{
#ifndef NUKI_HUB_UPDATER
size_t caLength = preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE);
size_t crtLength = preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE);
size_t keyLength = preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE);
_useEncryption = caLength > 1; // length is 1 when empty
if(_useEncryption)
{
Log->println(F("MQTT over TLS."));
_mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
_mqttClientSecure->setCACert(_ca);
if(crtLength > 1 && keyLength > 1) // length is 1 when empty
{
Log->println(F("MQTT with client certificate."));
_mqttClientSecure->setCertificate(_cert);
_mqttClientSecure->setPrivateKey(_key);
}
}
else
{
Log->println(F("MQTT without TLS."));
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
}
if(preferences->getBool(preference_mqtt_log_enabled, false) || preferences->getBool(preference_webserial_enabled, false))
{
MqttLoggerMode mode;
if(preferences->getBool(preference_mqtt_log_enabled, false) && preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb;
else if (preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb;
else mode = MqttLoggerMode::MqttAndSerial;
_path = new char[200];
memset(_path, 0, sizeof(_path));
String pathStr = preferences->getString(preference_mqtt_lock_path);
pathStr.concat(mqtt_topic_log);
strcpy(_path, pathStr.c_str());
Log = new MqttLogger(*getMqttClient(), _path, mode);
}
#endif
#ifndef NUKI_HUB_UPDATER
NetworkDevice::init();
#endif
}
const String WifiDevice::deviceName() const
{
return "Built-in Wi-Fi";

View File

@@ -1,7 +1,5 @@
#pragma once
#include <WiFiClient.h>
#include <NetworkClientSecure.h>
#include <Preferences.h>
#include "NetworkDevice.h"
#include "IPConfiguration.h"
@@ -29,10 +27,10 @@ private:
void onDisconnected();
void onConnected();
bool connect();
char* _path;
Preferences* _preferences = nullptr;
char* _path;
int _foundNetworks = 0;
int _disconnectCount = 0;
bool _connectOnScanDone = false;
@@ -45,10 +43,4 @@ private:
uint8_t _connectedChannel = 0;
uint8_t* _connectedBSSID;
int64_t _disconnectTs = 0;
#ifndef NUKI_HUB_UPDATER
char _ca[TLS_CA_MAX_SIZE] = {0};
char _cert[TLS_CERT_MAX_SIZE] = {0};
char _key[TLS_KEY_MAX_SIZE] = {0};
#endif
};
};

View File

@@ -63,7 +63,7 @@ monitor_filters =
time
[env:updater_esp32]
board = esp32dev
board = nuki-esp32dev
extra_scripts =
pre:pio_package_pre.py
post:pio_package_post.py