Force number to render as a quoted string

How do you force a template to render as a quoted string in an automation? I have the following automation that updates my lock codes when an OTP sensor changes:

alias: OTP Update Lock Codes
trigger:
  - platform: state
    entity_id:
      - sensor.otp_code
action:
  - service: zwave_js.invoke_cc_api
    target:
      entity_id: lock.front_door
    data:
      command_class: 99
      method_name: setMany
      parameters:
        - - userId: 2
            userIdStatus: 1
            userCode: "{{ states('sensor.otp_code') }}"

However, when the automation runs, the userCode renders as an unquoted string, which gets interpreted as a number by Z-Wave JS:

params:
  domain: zwave_js
  service: invoke_cc_api
  service_data:
    command_class: 99
    method_name: setMany
    parameters:
      - - userId: 2
          userIdStatus: 1
          userCode: 123456
    entity_id:
      - lock.front_door
  target:
    entity_id:
      - lock.front_door
running_script: false

This is no good because the command requires userCode to be a string:

I can execute the command manually in the Developer console which works as expected:

service: zwave_js.invoke_cc_api
target:
  entity_id: lock.front_door
data:
  command_class: 99
  method_name: setMany
  parameters:
    - - userId: 1
        userIdStatus: 1
        userCode: "123456"

I’ve tried several things to try to force it to render correctly as userCode: "123456", none of which work.

All of these render as an unquoted number:

userCode: "{{ states('sensor.otp_code') | string }}"
userCode: "{{ ('0' + states('sensor.otp_code'))[1:] }}"
userCode: >-
  {% set code = states('sensor.otp_code') %}
  {{ code }}
userCode: >-
  {{
  states('sensor.otp_code')
  }}

Renders with too many quotes:

userCode: '{{ states('sensor.otp_code') | tojson }}'
> '"123456"'

Can you concatenate quotes onto it?

userCode: "{{ '\'' ~ states('sensor.otp_code') ~ '\'' }}"

The backslash escapes the character that comes next, if you’re wondering how that template works.

Unfortunately, once I save the automation, your code gets changed to this representation:

userCode: "{{ \"'\" ~ states('sensor.otp_code') ~ \"'\" }}"

Furthermore, the run details look like this, which includes way too many quotes:

params:
  domain: zwave_js
  service: invoke_cc_api
  service_data:
    command_class: 99
    method_name: setMany
    parameters:
      - - userId: 2
          userIdStatus: 1
          userCode: '''900048'''
    entity_id:
      - lock.front_door
  target:
    entity_id:
      - lock.front_door
running_script: false

How about converting the string to a Buffer:

alias: OTP Update Lock Codes
trigger:
  - platform: state
    entity_id:
      - sensor.otp_code
action:
  - service: zwave_js.invoke_cc_api
    target:
      entity_id: lock.front_door
    data:
      command_class: 99
      method_name: setMany
      parameters:
        - - userId: 2
            userIdStatus: 1
            userCode:
              type: Buffer
              data: "{{ states('sensor.otp_code') | map('ord') | list }}"

If you are only setting a single user code just use the dedicated service call.

action:
  - service: zwave_js.set_lock_usercode
    target:
      entity_id: lock.front_door
    data:
      code_slot: 2
      usercode: "{{ states('sensor.otp_code') }}"

Using the buffer results in a run that looks like this:

params:
  domain: zwave_js
  service: invoke_cc_api
  service_data:
    command_class: 99
    method_name: setMany
    parameters:
      - - userId: 2
          userIdStatus: 1
          userCode:
            type: Buffer
            data:
              - 55
              - 56
              - 49
              - 56
              - 50
              - 48
    entity_id:
      - lock.front_door
  target:
    entity_id:
      - lock.front_door
running_script: false

But I’m still getting an error message in the Z-wave log file: Z-Wave error ZWaveError: codes has the wrong type (ZW0322)

I’m planning on setting multiple user codes but am validating that I can get a single code to work first.

As an experiment, what happens if you use set instead of setMany?

    method_name: set
    parameters:
      - 2
      - 1
      - type: Buffer
        data: "{{ states('sensor.otp_code') }}"

I get Z-Wave error ZWaveError: userCode has the wrong type

Try:

userCode: >
  {{ states('sensor.otp_code') | tojson }}
1 Like

Any chance you didn’t enter it as-is? It was confirmed working here: https://community.home-assistant.io/t/zipato-wintop-keypad-rfid-tag-reader/682586/5?u=freshcoast

I don’t think setMany will work using a Buffer, doesn’t look like the z-wave js server can decode a buffer within an object, only an array.

I’m pretty sure, but I’ll verify this tomorrow. My lock is now unresponsive and won’t reconnect to HA.

I’ll also try this tomorrow when I get my lock reconnected

Not sure I would even bother. If you plan to use setMany, it can’t decode a Buffer embedded in an object.

The result looks OK in the template editor, but when I tried this it reaches the HA backend with the string wrapped in extra quotes: 'args': [[{'userCode': '"123456"', 'userId': 1, 'userIdStatus': 1}]].

I don’t think Z-Wave JS will accept that, but you’ll have to try (I don’t have a lock).

Searching around the forum, it sounds like HA will convert the template type to what it appears as, e.g. if it looks like a number, the type is a number. Doesn’t matter what you try it will persist (see [1] [2] [3]).

If the tojson doesn’t work for you, and unless another solution is proposed, as a workaround you could prefix all of your user codes with 0 (assuming your lock keypad has a 0). The leading zero forces HA to treat it as a string. Not great, but seems to work:'args': [[{'userCode': '0123456', 'userId': 1, 'userIdStatus': 1}]]

There’s another trick of embedding the null byte at the end of the string. This works if you’re displaying something, but I doubt’ it will work when passing the data to Z-Wave JS.

userCode: >
  {{ "{}\x00".format(states("sensor.otp_code")) }}

Results in: 'args': [[{'userCode': '123456\x00', 'userId': 1, 'userIdStatus': 1}]]

1 Like

How about this:

service: zwave_js.invoke_cc_api
data:
  command_class: "99"
  method_name: setMany
  parameters: |
    {%
      set codes = [
        { "userId": 2, "userIdStatus": 1, "userCode": states("sensor.otp_code") }
      ]
    %}
    {{ [codes] }}

It looks like the template resolver doesn’t recurse into an object when evaluating the values, avoiding the conversion to a number. Thanks @petro.

3 Likes

Yes, this should work if userCode needs to be a string. I’ve used this work around to get past the resolver in the past.

3 Likes

Thank you @petro and @freshcoast for helping me accomplish the ideal solution! Here’s my final automation, which includes an input_boolean for disabling user codes:

alias: OTP Update Lock Codes
trigger:
  - platform: state
    entity_id:
      - sensor.otp_user1
action:
  - service: zwave_js.invoke_cc_api
    target:
      entity_id: lock.front_door
    data:
      command_class: 99
      method_name: setMany
      parameters: >-
        {%

        set codes = [

        { "userId": 2, "userIdStatus": 1 if
        states("input_boolean.otp_user1_active") == "on" else 2, "userCode":
        states("sensor.otp_user1") },

        { "userId": 3, "userIdStatus": 1 if
        states("input_boolean.otp_user2_active") == "on" else 2, "userCode":
        states("sensor.otp_user2") }

        ]

        %}

        {{ [codes] }}
1 Like