Nuki Smart Lock 2.0 - support all available API actions (i.e. add lock/unlock)

Taras, you saved me hours of researching and experimenting and most of all 350 HA restarts…:smiley:

As soon as I have some time I’ll work on it and let you know if it works.

I was looking since 2 hours for these:

    - platform: homeassistant
      event: start
{% if trigger.platform == 'webhook' %}
    - platform: homeassistant
      event: start

@Friedrieck pointed me this morning to your FR and the event_template_reloaded.

Thank you, I hope I can contact you in case of problems. :slight_smile:

And to save even more time: you do not need to restart HA each time. Go in the config page, server control, you can reload there the templates entities with a single click!

1 Like

Are you joking? Why didn’t you tell me before? last night I didn’t sleep mostly waiting for HA to restart at every try. :rofl:

That’s the price you pay for inexperience I guess…

That’s why the example I posted includes the following trigger:

    - platform: event
      event_type: event_template_reloaded

It will trigger whenever you execute Configuration > Server Controls > Reload Template Entities.

1 Like

You two saved me hours of unpaid work during the night. I owe you a good italian coffee or a good glass of our wine. :slight_smile:

2 Likes

What fw are you on? Bridge and Lock firmware…

Screenshot_20210528_164248

The Nuki app says my smart lock firmware is up to date. No 2.11 proposed. Are you part of a beta program ?

I tried also 192.168.0.xxx:8080/fwupdate?token=XXXXXXXXX without any change.

I was in beta a long time ago…don’t know if that’s a beta versione of the fw. Check on their forum what’s the latest official version. I suspect they fixed some of the issues you are seeing in that version, but I’m not sure.

Going to get some rest now, tomorrow I’ll release last version of the card, it was a long day and I don’t have the energy to do it now.

Talk to you tomorrow.

I worked a few months in Napoli and Roma so I know the coffee is very good there but try to sleep this night :wink:

I will…trust me…:slight_smile:

We’ll talk about your experience here…good night. :slight_smile:

Hi @123 and @Friedrieck,

today I worked on the implementation of the multiple triggers for the sensor: unfortunately it doesn’t work like it’s supposed to. The only trigger that enables the sensor is the webhook, none of the other two (event/event_template_reloaded and homeassistant/start) sets the sensor as available.

I rewrote the sensor so to distinguish among the trigger platforms and have so one sensor that gets data via callback or polling, but if the other triggers don’t work it’s all useless.

Here’s what I see when I restart HA or do a template reload: you will notice 4 sensors, the first one is a template lock linked to the main sensor (Nuki Door State) that is the triggered sensor. Both of them are unavailable after an HA restart or template reload. The other two sensors are the ones that I get through the polling system, that gets launched immediately after the restart obviously, and are the ones whose values I wanted to use to feed the Nuki Door State when it hasn’t yet received a callback so it is not initialized.

image

As soon as I open the door, obviously the callback arrives, so the webhook gets triggered:

image

I also added an attribute to the sensor to show the trigger platform, and it’s always the webhook, obviously:

Here’s the code of Nuki Door State:

- 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"
      unique_id: 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 %}
      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 = 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 %}
      availability: >
        {% if trigger.platform == 'webhook' %}
          {{ trigger.json.doorsensorState != None }}
        {% else %}
          {{ (states('sensor.nuki_door_sensor_state') >= 1) and (states('sensor.nuki_door_sensor_state') <= 5) }}
        {% endif %}
      attributes:
        nuki_id: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.nukiId }}
          {% else %}
            {{ 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 %}
            {{ 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 %}
            {{ sensor.nuki_lock_sensor_state }}
          {% endif %}
        lock_battery: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.batteryChargeState }}%
          {% else %}
            {{ sensor.nuki_id }}
          {% endif %}
        lock_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ is_state('trigger.json.batteryCritical', 'True') }}
          {% else %}
            {{ sensor.nuki_lock_battery_critical }}
          {% endif %}
        keypad_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ is_state('trigger.json.keypadbatteryCritical', 'True') }}
          {% else %}
            {{ sensor.nuki_keypad_battery_critical }}
          {% endif %}
        last_update: "{{ strptime(as_timestamp(now()) | timestamp_local, '%Y%m%d %H:%M:%S') }}"
        trigger: '{{ trigger.platform }}'

In the documentation, I read this:

And in the Automation Trigger docs I read this:

So, at least in theory, it should work, but it doesn’t. I hope it’s a mistake on my side, open to any suggestion to fix this, so I can finally release the new code.

Thanks a lot,

Alessandro

Please forgive me if it is a stupid suggestion, I would help but I am not very expert in this matter:

when home assistant start event raise, are you sure the polling is already done ? May be you need to use an other event in your template, raised by home assistant start event after a delay ?

All state sensors you see in the card get their state from these two REST sensors, which are correctly initialized at every restart of HA:

- platform: rest
  scan_interval: 300
  resource_template: "http://{{ states('sensor.nuki_bridge_host') }}:{{ states('sensor.nuki_bridge_port') }}/list?&token={{ states('sensor.nuki_bridge_token') }}"
  name: "Nuki Endpoint List"
  value_template: "OK"
  json_attributes:
    - lastKnownState
    - firmwareVersion
    - nukiId
    - name

- platform: rest
  scan_interval: 300
  resource_template: "http://{{ states('sensor.nuki_bridge_host') }}:{{ states('sensor.nuki_bridge_port') }}/info?&token={{ states('sensor.nuki_bridge_token') }}"
  name: "Nuki Endpoint Info"
  value_template: "OK"
  json_attributes:
    - versions
    - scanResults
    - wlanConnected
    - serverConnected

Right ! (so it was a stupid suggestion :grin:)

There are no stupid questions, only stupid answers. :slight_smile:

So I would try again:

which are correctly initialized at every restart of HA.

Yes, but when during the restart process : after or before the start event itself ?

This is after an HA restart: the first 2 sensors are the ones based on the webhook trigger, all the rest are initialized through those 2 REST sensors.

I am not part of the HA dev. team, but I guess that first you initialize everything, then you trigger the events. :slight_smile:

The fact that those sensors are ready after the restart is what led to the idea to make the webhook sensor flexible so that it could get its state both through the webhook AND the polled REST sensors.

Have you renamed this sensor? I don’t think it was in your post #98.
Could it be in fact another entity id?

I’ve changed a lot of things, that one you mentioned is the state received through the REST sensor (polled).

I also switched all sensors to the new Template Sensor format. And I also discovered a limitation: in the legacy format I could have: name of the sensor, friendly_name, unique_id. In the new format the name is equivalent to the friendly, and the real name of the sensor is derived from the name.

Could you post the yaml for that one?

Here’s the templates.yaml file, in which you will find ALL the sensors, triggered and state sensors:

- 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"
      unique_id: 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 %}
      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 = 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 %}
      availability: >
        {% if trigger.platform == 'webhook' %}
          {{ trigger.json.doorsensorState != None }}
        {% else %}
          {{ (states('sensor.nuki_door_sensor_state') >= 1) and (states('sensor.nuki_door_sensor_state') <= 5) }}
        {% endif %}
      attributes:
        nuki_id: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.nukiId }}
          {% else %}
            {{ 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 %}
            {{ 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 %}
            {{ sensor.nuki_lock_sensor_state }}
          {% endif %}
        lock_battery: >
          {% if trigger.platform == 'webhook' %}
            {{ trigger.json.batteryChargeState }}%
          {% else %}
            {{ sensor.nuki_id }}
          {% endif %}
        lock_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ is_state('trigger.json.batteryCritical', 'True') }}
          {% else %}
            {{ sensor.nuki_lock_battery_critical }}
          {% endif %}
        keypad_battery_critical: >
          {% if trigger.platform == 'webhook' %}
            {{ is_state('trigger.json.keypadbatteryCritical', 'True') }}
          {% else %}
            {{ sensor.nuki_keypad_battery_critical }}
          {% endif %}
        last_update: "{{ strptime(as_timestamp(now()) | timestamp_local, '%Y%m%d %H:%M:%S') }}"
        trigger: '{{ trigger.platform }}'

- sensor:
  - name: "Nuki Bridge Host"
    state: !secret nuki_bridge_host

  - name: "Nuki Bridge Port"
    state: !secret nuki_bridge_port

  - name: "Nuki Bridge Token"
    state: !secret nuki_bridge_token

  - name: "Nuki Device Name"
    state: "{{ state_attr('sensor.nuki_endpoint_info','scanResults')[0]['name'] }}"
    icon: mdi:alpha

  - name: "Nuki Bridge FW Version"
    state: "{{ state_attr('sensor.nuki_endpoint_info','versions')['firmwareVersion'] }}"
    icon: mdi:numeric

  - name: "Nuki Bridge wifi FW Version"
    state: "{{ state_attr('sensor.nuki_endpoint_info','versions')['wifiFirmwareVersion'] }}"
    icon: mdi:numeric

  - name: "Nuki Bridge<->Lock BT RSSI"
    unique_id: nuki_bridge_bt_rssi
    device_class: "signal_strength"
    unit_of_measurement: "dB"
    icon: mdi:signal-distance-variant
    state: "{{ state_attr('sensor.nuki_endpoint_info','scanResults')[0]['rssi'] }}"

  - name: "Nuki Bridge WiFi Connected"
    unique_id: nuki_bridge_wlan
    icon: mdi:wifi-cog
    state: "{{ state_attr('sensor.nuki_endpoint_info','wlanConnected') }}"

  - name: "Nuki Bridge Cloud Connected"
    unique_id: nuki_bridge_cloud
    icon: mdi:server-network
    state: "{{ state_attr('sensor.nuki_endpoint_info','serverConnected') }}"

  - name: "Nuki Bridge<->Lock BT State"
    unique_id: nuki_bridge_bt_state
    icon: >-
      {% if state_attr('sensor.nuki_endpoint_info','scanResults')[0]['paired'] %}
        mdi:bluetooth-connect
      {% elif not state_attr('sensor.nuki_endpoint_info','scanResults')[0]['paired'] %}
        mdi:bluetooth-off
      {% else %}
        mdi:bluetooth-audio
      {% endif %}
    state: >-
      {% if state_attr('sensor.nuki_endpoint_info','scanResults')[0]['paired'] %}
        connected
      {% elif not state_attr('sensor.nuki_endpoint_info','scanResults')[0]['paired'] %}
        disconnected
      {% else %}
        Unknown
      {% endif %}

  - name: "Nuki ID"
    unique_id: nuki_id
    icon: mdi:numeric
    state: "{{ state_attr('sensor.nuki_endpoint_list','nukiId') }}"

  - name: "Nuki Friendly Name"
    unique_id: nuki_friendly_name
    icon: mdi:numeric
    state: "{{ state_attr('sensor.nuki_endpoint_list','name') }}"

  - name: "Nuki Door Sensor State"
    unique_id: nuki_door_sensor_state
    icon: mdi:door
    state: >
      {% set my_state = {1: 'deactivated', 2: 'closed', 3: 'open', 4: 'unknown', 5: 'calibrating'} %}
      {{ my_state[state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['doorsensorState']] }}

  - name: "Nuki Lock Sensor State"
    unique_id: nuki_lock_sensor_state
    icon: mdi: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'} %}
      {{ my_state[state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['state']] }}

  - name: "Nuki Lock FW Version"
    unique_id: nuki_lock_fw
    icon: mdi:numeric
    state: "{{ state_attr('sensor.nuki_endpoint_list','firmwareVersion') }}"

  - name: "Nuki Lock Battery Critical State"
    unique_id: nuki_lock_battery_critical
    icon: mdi:battery-alert-variant-outline
    state: "{{ state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['batteryCritical'] }}"

  - name: "Nuki Keypad Battery Critical State"
    unique_id: nuki_keypad_battery_critical
    icon: mdi:battery-alert-variant-outline
    state: "{{ state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['keypadBatteryCritical'] }}"

  - name: "Nuki Lock Battery Level"
    unique_id: nuki_lock_battery_level
    device_class: "battery"
    unit_of_measurement: "%"
    icon: >
      {% set battery_level = state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['batteryChargeState'] | default(0) | int %}
      {% set battery_charging = state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['batteryCharging'] %}
      {% set battery_round = (battery_level / 10) | int * 10 %}
      {% if battery_round >= 100 and not battery_charging %}
        mdi:battery
      {% elif battery_round >= 100 and battery_charging %}
        mdi:battery-charging
      {% elif battery_round > 0 and not battery_charging %}
        mdi:battery-{{ battery_round }}
      {% elif battery_round > 0 and battery_charging %}
        mdi:battery-charging-{{ battery_round }}
      {% else %}
        mdi:battery-alert-variant-outline
      {% endif %}
    state: >
      {% set battery_level = state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['batteryChargeState'] | default(0) | int %}
      {% if state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['batteryCharging'] %}
        {{ battery_level }}
      {% else %}
        {{ battery_level }}
      {% endif %}

  - name: "Nuki Last Activity"
    unique_id: nuki_lock_last_activity
    icon: mdi:clock-check-outline
    state: >
      {{ (as_timestamp(state_attr('sensor.nuki_endpoint_list', 'lastKnownState')['timestamp'])) | timestamp_custom("%H:%M (%b %d)") }}

You are only missing the template lock sensor, the two rest sensors.

If you want, I can publish the whole code so you can try it and modify it. :slight_smile: