Nuki Card with callbacks (official integration not needed)

Tags: #<Tag:0x00007fc4048220b0> #<Tag:0x00007fc404821f70> #<Tag:0x00007fc404821e08> #<Tag:0x00007fc404821cc8>

If you have a Nuki Lock system, and you are not satisfied by the official integration, here’s a way to overcome all the current limitations and monitor/operate the Nuki in a better way.

I know devs are working on the official integration to implement some of the things you’ll find in this configuration, but since I wanted to learn HA better, I decided to start doing this for the Nuki.

Main Features of this card:

  • doesn’t require the official integration
  • lock device support through bridge callback (fast updates, 10-15s)
  • door sensor support through bridge callback (fast updates, 10-15s)
  • ALL Nuki info mapped to sensors (polling via 2 REST sensors)
  • Dynamic state icons (battery state and type, lock, door, etc.)
  • Easy configuration - it only requires 4 items in secrets.yaml
  • Automatic Bridge Callback Configuration and Enforcement (add/delete duplicate entries) with HA persistent notification of the actions

@HITMIK, @Joerg, @Friedrieck, @mahikeulbody @123 @spokin: thanks for your help and contributions of ideas and tests.

-= Nuki Card v8 =-

Changelog:

  • v3.0 added the Door sensor and Nuki Lock switch based on callbacks.
  • v3.1 changed the lock entity from Template Switch to Template Lock
  • v4.0 changed all legacy state sensors to the new Template State Sensors and recoded the Door Sensor to be ready for multiple triggers (not yet completed, it will allow to overcome the problem mentioned in the following note.
  • v4.1 fixed some bugs in templates.yaml (if you configured v4.0, you just need to reconfigure templates.yaml)
  • v4.2 fixed other minor things in templates.yaml (if you configured v4.1, you just need to reconfigure templates.yaml)
  • v4.3 fixed some code in the trigger door sensor (if you configured v4.2, you just need to reconfigure templates.yaml)
  • v4.4 fixed some code in several sensors (if you configured v4.3, you just need to reconfigure templates.yaml) that finally solved the problem of the initialization after a restart. thx to @Friedrieck and @123 for the advices and the debugging
  • v4.5 fixed some nasty bugs introduced in 4.4 and updated icons for door/lock polled sensors (if you configured v4.4, you just need to reconfigure templates.yaml)
  • v4.6 finally solved the HA restart issue, using a state trigger to link the door triggered sensor to the polled state sensor. (if you configured v4.5, you just need to reconfigure templates.yaml)
  • v4.7 removed homeassistant/start event from the door triggered sensor, added content_type to the rest_command, modified icon of device name sensor, moved door/lock polled sensors to attributes of the door triggered sensor - updated files: templates.yaml rest_commands.yaml
  • v5.0 reworked all sensors to avoid log errors at startup while REST sensors were not initialized yet - updated files: templates.yaml
  • v5.1 modified rest_command (removed content_type) and keypad sensor to avoid log errors at startup for users without the keypad - updated files: templates.yaml and rest_commands.yaml
  • v6.0 Automation version in package (supereasy installation)
  • v7.0 automated bridge callback configuration and enforcement (with persistent notification) (super-super-easy installation), optimized some code, renamed some sensors, unified nuki secrets url variables
  • v8.0 (major release): rewrote many parts of the package, optimized the code, and fixed 2 bugs regarding the automatic bridge configuration. Now the reconfiguration of the bridge should be much quicker (1m max.).
  • v8.1 last minute minor changes
  • v8.2 couple of bugs fixed, automation settings changed (thanks @spokin)
  • v8.3 optimized automation settings and added checks everywhere a sensor was referenced in the code to avoid as much as possible errors in HA log, mainly in init/restart phase.
  • v8.4 reconfigured automation mode and rest sensors polling times to avoid concurrent calls to the bridge
  • v8.5 changed lock actions to Simple Lock Actions (thanks @kvj), device configuration driven, so if you configured the lock with a knob (instead of a handle) it will unlatch instead of simply unlocking. Min. bridge fw required: v2.5.3.
  • v8.6 further optimized the code to avoid concurrent http requests to the bridge, that is poorly designed, supporting only 1 request at a time
  • v8.7 localization support by @spokin (just use input_select.nuki_language to configure it). Removed the duplicated door/lock polling sensors. Relaxed the REST sensors polling periods even further. Lot of optimizations to the code. Please re-edit the lovelace card, as sensor names have changed.
  • v8.8 typo in json file corrected, reference to an old binary_sensor corrected in the code.

image


image

Now let’s go to the configuration of the card. :slight_smile:

NOTES:

  • the code is based on Nuki Bridge API v1.12, make sure the bridge fw is updated to last available version before starting the configuration of this integration. min. version supported: v2.5.3
  • Nuki Bridge doesn’t support https, so if you have HA secured with https-only, you need to allow http access locally (LAN/WLAN access, you can leave https from the internet). The NGINX HA addon allows this kind of configuration.
  • 1st step is the creation of a long-lived token in HA profile’s page, used for the callback’s webhook. Create it and take note of it.

NOTE: Please don’t confuse the Nuki access token with the HA long-term token for the webhook, the first is needed by HA to communicate with the Nuki Bridge API, the second is needed by the Nuki Bridge to communicate with HA

  • in secrets.yaml you need to configure 4 items: Nuki Bridge full url path, Bridge API token you create through Nuki App or the web console, long-lived token (webhook) you created in the first step, HA full url path.
nuki_bridge_url: "http://nuki-bridge.axel.dom:8080"
nuki_bridge_token: "yyxxzz"
nuki_bridge_webhook: "hdCI6MTYyMTcyODg0MSwiZXhwIjoxOTM3MDg4ODQxfQ.-ECrJx_lbPLRRJwRTfJsoW-RfIaIioKVtcyfEvJiHZE"
nuki_ha_internal_url: "http://hass.axel.dom:8123"
  • in previous versions (up to 6.1), you had to configure the callback on the bridge manually through curl or browser. this is no longer the case, v7.0 introduces automatic callback configuration/enforcement of the bridge. It does this through a script and a couple of new sensors, one of which (sensor.nuki_bridge_callback_list) is configured to check the bridge callback config every 120s. Another sensor (binary_sensor.nuki_bridge_callback) checks if the bridge config contains our specific webhook, and if not, it calls script.nuki_bridge_check_callback, generating a persistent notification so you can be alerted of the change (see screenshots above).

  • create a folder called packages in the main CONFIG folder, and in that folder create two files called nuki_card_callback.yaml and nuki_card_callback_languages.json , you can download them here or copy the code from it, whatever way you prefer: Nuki Card v8.8 · GitHub

  • in configuration.yaml, under the homeassistant: section, reference the directory containing all packages:

image

  • and finally the code for the card, I included all sensors created, customize the list as you prefer. If you don’t have the Nuki Keypad, remove that line.
- type: entities
entities:
  - entity: lock.nuki_lock_action
    secondary_info: last-updated
  - entity: sensor.nuki_door_sensor_state
    secondary_info: last-updated
  - entity: binary_sensor.nuki_bridge_callback
    secondary_info: last-updated
  - entity: sensor.nuki_last_activity
  - entity: input_select.nuki_language
  - entity: sensor.nuki_id
  - entity: sensor.nuki_friendly_name
  - entity: sensor.nuki_device_name
  - entity: sensor.nuki_bridge_lock_bt_state
  - entity: sensor.nuki_bridge_lock_bt_rssi
  - entity: sensor.nuki_lock_battery_level
  - entity: sensor.nuki_lock_fw_version
  - entity: sensor.nuki_bridge_fw_version
  - entity: sensor.nuki_bridge_wifi_fw_version
  - entity: sensor.nuki_bridge_wifi_connected
  - entity: sensor.nuki_bridge_cloud_connected
  - entity: sensor.nuki_lock_battery_critical_state
  - entity: sensor.nuki_keypad_battery_critical_state
state_color: true
title: Nuki Card
8 Likes

@123 @Friedrieck here’s the state of the two sensors after a restart or a template reload:

image image

I’m testing without the availability section for binary_sensor.nuki_door_state

@mahikeulbody @Joerg please let me know if the instructions work, unfortunately many things changed, so you have to redo all the configuration from scratch. sorry for that. :slight_smile:

That seems ok here. Good job.
I will re-try tomorrow removing Nuki integration.

Strange. If there is no availability section, as it is a binary_sensor, and the state does not evaluate to true, then it should be false, not unavailable. I think it could happen only during the actual reload of the sensor, but this is short.
Probably no impact, but I would have written the condition like this:
{{ is_state('sensor.nuki_door_sensor_state', 'open') or is_state('sensor.nuki_door_sensor_state', 'closed') }}
I think you could also test the sensor’s own state:
{{ is_state('binary_sensor.nuki_door_state', 'on') or is_state('binary_sensor.nuki_door_state', 'off') }}

Also to correct, I think:

        lock_battery: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.batteryChargeState }}%
          {% else %}
            {{ sensor.nuki_id }}

More importantly, there is a couple of this:

        lock_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ is_state('trigger.json.batteryCritical', 'True') }}

I don’t think you can use is_state() with the trigger object. I would assume it only is for entity objects. It could be the cause of the invalidation of the sensor, but maybe I’m wrong. Have you checked the logs?

You’re right. I forgot to optimize those.

I thought about that, but then I thought: what happens the first time? Does it work? :slight_smile:

Cut&paste errors, fixed both of them:

        lock_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.batteryCritical }}
          {% else %}
            {{ sensor.nuki_lock_battery_level }}
          {% endif %}
        keypad_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.keypadbatteryCritical }}
          {% else %}
            {{ sensor.nuki_keypad_battery_critical_state }}
          {% endif %}

I’ll recheck the logs and test with these modifications…

Thanks a lot.

v4.2 released, fixed some bugs thanks to @Friedrieck. Just copy&paste the updated templates.yaml and you’re done. Sorry.

@Friedrieck tested with the fixes, same thing, it’s unavailable after template reload. But I noticed this now in the logs:

And also this now (I reloaded templates):

Logger: homeassistant.helpers.template
Source: helpers/template.py:1377
First occurred: 1:08:45 (2 occurrences)
Last logged: 1:10:07

Template variable error: 'sensor' is undefined when rendering '{% if trigger.platform == 'webhook' %} {% set my_state = {1: 'deactivated', 2: 'closed', 3: 'open', 4: 'unknown', 5: 'calibrating'} %} {% set trigdoor = my_state[trigger.json.doorsensorState] %} {% set my_state = {0: 'uncalibrated', 1: 'locked', 2:'unlocking', 3: 'unlocked', 4: 'locking', 5: 'unlatched', 6: "unlocked (lock ‘n’ go)", 7: 'unlatching', 254: 'motor blocked', 255: 'undefined'} %} {% set triglock = my_state[trigger.json.state] %} {% else %} {% set trigdoor = sensor.nuki_door_sensor_state %} {% set triglock = sensor.nuki_lock_sensor_state %} {% endif %} {% if (trigdoor == 'open') %} mdi:door-open {% elif trigdoor == 'closed' and triglock == 'locked' %} mdi:door-closed-lock {% elif trigdoor == 'closed' and triglock == 'unlocked' %} mdi:door-closed {% else %} mdi:alert-box-outline {% endif %}'

Hi Alex,
works here as well, I had the Nuki integration removed already the other day.
Great job, thank you for this.

IMO there’s a glitch in the documentation:
Now in rest_commands.yaml file, paste this code, it is required for the lock/unlock action of the previously created lock sensor.
The file should be named rest_command.yaml.

1 Like

I have in config.yaml
rest_command: !include rest_commands.yaml

You can choice any name of file, I think.

1 Like

:roll_eyes:
Sure. I shouldn’t do something like this before having at least the third coffee.
I used
rest_commands: !include rest_commands.yaml
and got an error that this integration doesn’t exist. :rofl:

I just copy&paste the updated templates.yaml and it is not ok (all sensors are unavailable). May be you changed also another file ?

Anyway, to be sure, I copy&paste all v4.2 files and now it is ok. So v4.2 is ok but may be not the description of the diff with v4.1 ?

PS. I finally removed Nuki integration.

I suggest a very small cosmetic modification:

name: "Nuki Friendly Name"
icon: mdi:alpha

instead of mdi:numeric

Is the availability template into locks.yaml file correct ?
(I am not very good in this yaml syntax)

So, the logs are instructive, and tell where to look at: there is a problem with the icon part.
And indeed, I think this may cause troubles:

[...]
        {% else %}
          {% set trigdoor = sensor.nuki_door_sensor_state %}
          {% set triglock = sensor.nuki_lock_sensor_state %}
        {% endif %}
        {% if (trigdoor == 'open') %}
          mdi:door-open
        {% elif trigdoor == 'closed' and triglock == 'locked' %}
          mdi:door-closed-lock
        {% elif trigdoor == 'closed' and triglock == 'unlocked' %}
[...]

The two first lines should probably be corrected like this:

          {% set trigdoor = states(sensor.nuki_door_sensor_state) %}
          {% set triglock = states(sensor.nuki_lock_sensor_state) %}

And the same goes for your corrections in your post here: Nuki Card with callbacks (official integration not needed) - #6 by alexdelprete

1 Like

I’m at the first coffee, so I better not answer anything now…:smiley:

I removed all unique_id from templates.yaml. So in order to start clean, in case you have orphan entities, you need to remove everything and reconfigure. Sorry for that. But I touched only templates.yaml.

ok, no problem.

Thanks for spotting all those, corrected here:

- trigger:
    - platform: event
      event_type: event_template_reloaded
    - platform: homeassistant
      event: start
    - platform: webhook
      webhook_id: !secret nuki_bridge_webhook
  binary_sensor:
    - name: "Nuki Door State"
      device_class: door
      state: >
        {% if trigger.platform == 'webhook' %}
          {{ trigger.json.doorsensorState == 3 }}
        {% else %}
          {{ is_state('sensor.nuki_door_sensor_state', 'open') }}
        {% endif %}
      availability: >
        {% if trigger.platform == 'webhook' %}
          {{ trigger.json.doorsensorState != None }}
        {% else %}
          {{ (is_state('sensor.nuki_door_sensor_state', 'open') or is_state('sensor.nuki_door_sensor_state', 'closed')) }}
        {% endif %}
      icon: >
        {% if trigger.platform == 'webhook' %}
          {% set my_state = {1: 'deactivated', 2: 'closed', 3: 'open', 4: 'unknown', 5: 'calibrating'} %}
          {% set trigdoor = my_state[trigger.json.doorsensorState] %}
          {% set my_state = {0: 'uncalibrated', 1: 'locked', 2:'unlocking', 3: 'unlocked', 4: 'locking', 5: 'unlatched', 6: "unlocked (lock ‘n’ go)", 7: 'unlatching', 254: 'motor blocked', 255: 'undefined'} %}
          {% set triglock = my_state[trigger.json.state] %}
        {% else %}
          {% set trigdoor = states(sensor.nuki_door_sensor_state) %}
          {% set triglock = states(sensor.nuki_lock_sensor_state) %}
        {% endif %}
        {% if (trigdoor == 'open') %}
          mdi:door-open
        {% elif trigdoor == 'closed' and triglock == 'locked' %}
          mdi:door-closed-lock
        {% elif trigdoor == 'closed' and triglock == 'unlocked' %}
          mdi:door-closed
        {% else %}
          mdi:alert-box-outline
        {% endif %}
      attributes:
        nuki_id: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.nukiId }}
          {% else %}
            {{ states(sensor.nuki_id) }}
          {% endif %}
        door_state: >
          {% set my_state = {1: 'deactivated', 2: 'closed', 3: 'open', 4: 'unknown', 5: 'calibrating'} %}
          {% if trigger.platform == 'webhook' %}
            {{ my_state[trigger.json.doorsensorState] }}
          {% else %}
            {{ states(sensor.nuki_door_sensor_state) }}
          {% endif %}
        lock_state: >
          {% set my_state = {0: 'uncalibrated', 1: 'locked', 2:'unlocking', 3: 'unlocked', 4: 'locking', 5: 'unlatched', 6: "unlocked (lock ‘n’ go)", 7: 'unlatching', 254: 'motor blocked', 255: 'undefined'} %}          
          {% if trigger.platform == 'webhook' %}
            {{ my_state[trigger.json.state] }}
          {% else %}
            {{ states(sensor.nuki_lock_sensor_state) }}
          {% endif %}
        lock_battery: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.batteryChargeState }}%
          {% else %}
            {{ states(sensor.nuki_lock_battery_level) }}
          {% endif %}
        lock_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.batteryCritical }}
          {% else %}
            {{ states(sensor.nuki_lock_battery_critical_state) }}
          {% endif %}
        keypad_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.keypadBatteryCritical }}
          {% else %}
            {{ states(sensor.nuki_keypad_battery_critical_state) }}
          {% endif %}
        last_update: "{{ strptime(as_timestamp(now()) | timestamp_local, '%Y%m%d %H:%M:%S') }}"
        trigger: '{{ trigger.platform }}'

I just did a template reload, and same result, unfortunately.

image

Here’s what’s in the logs after the reload:

Logger: homeassistant.components.template.binary_sensor
Source: components/template/trigger_entity.py:141
Integration: Template (documentation, issues)
First occurred: 1:59:04 (2 occurrences)
Last logged: 12:00:12

Error rendering icon template for binary_sensor.nuki_door_state: UndefinedError: ‘sensor’ is undefined

Logger: homeassistant.helpers.template
Source: helpers/template.py:1377
First occurred: 1:59:04 (2 occurrences)
Last logged: 12:00:12

Template variable error: ‘sensor’ is undefined when rendering ‘{% if trigger.platform == ‘webhook’ %} {% set my_state = {1: ‘deactivated’, 2: ‘closed’, 3: ‘open’, 4: ‘unknown’, 5: ‘calibrating’} %} {% set trigdoor = my_state[trigger.json.doorsensorState] %} {% set my_state = {0: ‘uncalibrated’, 1: ‘locked’, 2:‘unlocking’, 3: ‘unlocked’, 4: ‘locking’, 5: ‘unlatched’, 6: “unlocked (lock ‘n’ go)”, 7: ‘unlatching’, 254: ‘motor blocked’, 255: ‘undefined’} %} {% set triglock = my_state[trigger.json.state] %} {% else %} {% set trigdoor = sensor.nuki_door_sensor_state %} {% set triglock = sensor.nuki_lock_sensor_state %} {% endif %} {% if (trigdoor == ‘open’) %} mdi:door-open {% elif trigdoor == ‘closed’ and triglock == ‘locked’ %} mdi:door-closed-lock {% elif trigdoor == ‘closed’ and triglock == ‘unlocked’ %} mdi:door-closed {% else %} mdi:alert-box-outline {% endif %}’
Template variable error: ‘sensor’ is undefined when rendering ‘{% if trigger.platform == ‘webhook’ %} {% set my_state = {1: ‘deactivated’, 2: ‘closed’, 3: ‘open’, 4: ‘unknown’, 5: ‘calibrating’} %} {% set trigdoor = my_state[trigger.json.doorsensorState] %} {% set my_state = {0: ‘uncalibrated’, 1: ‘locked’, 2:‘unlocking’, 3: ‘unlocked’, 4: ‘locking’, 5: ‘unlatched’, 6: “unlocked (lock ‘n’ go)”, 7: ‘unlatching’, 254: ‘motor blocked’, 255: ‘undefined’} %} {% set triglock = my_state[trigger.json.state] %} {% else %} {% set trigdoor = states(sensor.nuki_door_sensor_state) %} {% set triglock = states(sensor.nuki_lock_sensor_state) %} {% endif %} {% if (trigdoor == ‘open’) %} mdi:door-open {% elif trigdoor == ‘closed’ and triglock == ‘locked’ %} mdi:door-closed-lock {% elif trigdoor == ‘closed’ and triglock == ‘unlocked’ %} mdi:door-closed {% else %} mdi:alert-box-outline {% endif %}’

Sorry, my bad (even if at my third coffee already, but not Italian ones!).
I forgot the ' around the sensor object:

          {% set trigdoor = states('sensor.nuki_door_sensor_state') %}
          {% set triglock = states('sensor.nuki_lock_sensor_state') %}

Also to correct in several of the attributes.
Don’t worry, there is some light at the end of the tunnel…