Vestaboard Integration

Yep did that in the sample code. checked all the variable names to ensure they matched. vestaboard_api_secret, etc

service: rest_command.vestaboard_message
data:
  vestaboard_api_key: !secret vestaboard_api_key
  vestaboard_api_secret: !secret vestaboard_api_secret
  vestaboard_subscription_id: !secret vestaboard_subscription_id
  text: "The quick brown fox jumps over the lazy dog"

do I need to define the variables somewhere?

The checking you did includes adding them secrets.yaml file and reloading Home Assistant?

The error message states that the variable within the rest_command execution is undefined. That either means passing of the variable from the service call to the rest_command isn’t working or the value in the service call is already undefined (i.e. they are not correctly retrieved from the secrets.yaml file).

These are the two only things I can think of. The invocation itself, assuming the values are correctly set in the secrets.yaml file and loaded, looks correct.

Yes put it in the secrets file and restarted HA

See below for what is in secrets file. Tried with and without quotes

IFTTT_key: 12345
X-Vestaboard-Api-Key: abcde
X-Vestaboard-Api-Secret: fghij
vestaboard_subscription_id: "klmnop"
vestaboard_api_key: "qrstuv"
vestaboard_api_secret: "wxyz1234567890"


Interestingly if I try and use your same code in the developer tools to call service with the actual secrets in the code in quotes it works. My coding skills are rudimentary at best so clearly not not strong enough to work this one out

eg


service: rest_command.vestaboard_message
data:
  vestaboard_api_key: "qrstuv"
  vestaboard_api_secret: "wxyz1234567890"
  vestaboard_subscription_id: "klmnop"
  text: "The quick brown fox jumps over the lazy dog"

I ran into this as well. The issue is that the developer tools is unable to pull the values from the secrets.yaml file. If you instead put the service call in an automation, you should find that it works as expected.

1 Like

This sounds familiar and would make sense.

This is not done done, but I’ve started working on an actual Home Assistant integration that can be used to post messages to Vestaboards instead of having to add custom rest_commands to your yaml configuration.

Right now, the integration is still a custom_integration and not exactly robust, but it does work if installed correctly. You can clone it and check it out here: GitHub - DanielBaulig/vestaboard: Vestaboard Home Assistant integration

Please provide any feedback you may have (that’s not already outlined in the issues section).

1 Like

thankyou. I am currently away from my HA but I will try that.

Drop me a line if I can help. I have this setup and can test/code/etc if you need a hand.

Great stuff, @dev0. Am using your YAML code and that works like expected. One of the use cases I have, would be to pull up the weather on request via a voice assistant (Alexa or Google) but after 5 minutes or so, revert back to the orginal message. I’ve been looking around in the HA documentation, but the only way I can find to use a GET Rest command is to tie it to a Rest Sensor - but that would also mean continuous polling. I would only want to pull the current message into a parameter - post my new message and then after a minute or 5 re-post the previous message. Any thoughts on how to accomplish this?

@Flaker Depending on how you have your Vestaboard setup I could see two possible solutions.

  1. Stateless and declarative rendering of Vestaboard content through a single automation
  2. REST based sensor with explicit update

I personally am doing 1) but that requires you to be driving your Vestaboard exclusively through Home Assistant and from a single automation. The original Vestaboard app becomes orphaned in that case. The way to do it is to have a single automation that is responsible for rendering Vestaboard content. You never call the REST command from anywhere else. Any data or content you want to display on the board gets rendered from that single automation. You heavily use templates to generate the content you want to display, including messages, etc. That way you could have an input_text that holds your message and when that input_text changes, the Vestaboard automation gets triggered and rerenders the board. You can then have a separate automation that is responsible for setting and clearing that message again after a given amount of time. This is very similar to how React works, in case you are familiar with that from the programming world. It also makes coordinating lots of different state updates from lots of different places much easier to handle and is a paradigm I’ve been applying to more and more different parts of my home (i.e. a single automation responsible for automating all aspects of a single device / entity). That way you never have conflicting states. You can look at my Vestboard automation I posted earlier in this thread for an example of how that looks. There is in fact a custom message input included in that automation. Setting and clearing of that input happens outside of the Vestaboard automation itself. The Vestaboard automation just listens to state updates from that input and rerenders if it changes.

In case you want to continue using the Vestaboard app or have other write source to your Vestaboard which’s data you want to be able to restore on the board, another option could be (and I haven’t tested this), to use a REST sensor like the one you described with a disabled / very long polling interval and then call homeassisant.update_entity explicitly before updating the board, such that you can restore it’s value back again from the sensor value. Not sure if homeassistant.update_entity works the way I assume with REST sensors and also not sure if it blocks script execution. If the latter is not the case you may have to throw a sufficiently long delay in there to wait for the update_entity to complete before you change the Vestaboard’s state.

Hope this helps.

Thanks, @dev0. Yeah, option 1 doesn’t work for me. But your suggestions for option 2 worked like a charm. I created a RESTfull sensor to collect the lines from the Vestaboard:

rest:  
  - resource: http://{{vestaboard_local_ip}}:7000/local-api/message
    headers:
       X-Vestaboard-Local-Api-Key: {{vestaboard_local_api_key}}
    scan_interval: 86400 #once a day
    sensor:
      - name: Vestaboard Line1
        value_template: "{{ value_json.message[0] }}"
      - name: Vestaboard Line2
        value_template: "{{ value_json.message[1] }}"
      - name: Vestaboard Line3
        value_template: "{{ value_json.message[2] }}"
      - name: Vestaboard Line4
        value_template: "{{ value_json.message[3] }}"
      - name: Vestaboard Line5
        value_template: "{{ value_json.message[4] }}"
      - name: Vestaboard_Line6
        value_template: "{{ value_json.message[5] }}"

This worked like a charm! With a call to the update, a refresh of above sensor is triggered:

service: homeassistant.update_entity
target:
  entity_id: sensor.vestaboard_line1

Thanks for your help - much appreciated!

1 Like

For those who also want the following functionality:

  1. Read the current status on the board (using the sensor mentioned in the earlier post)
  2. Persist this status (as the sensor might update while displaying the new message)
  3. Write a new message to the board (I use it to write the energy usage to the board)
  4. After a certain period (e.g. 1 minute) write back the previous message

I’ve modified the local rest call to also be able to write back the direct JSON structured lines as retrieved by the rest sensor:

rest_command:
  vestaboard_message:
    url: "http://{{vestaboard_local_ip_address}}:7000/local-api/message"
    method: POST
    content_type: application/json
    verify_ssl: false
    headers:
      X-Vestaboard-Local-Api-Key: {{vestaboard_local_api_key}}
    payload: >
      {% if rawlines is defined %}
        {{ rawlines |to_json }}
      {% else %}
        {% set map = {
          ' ': 0,
          'A': 1,
          'B': 2,
          'C': 3,
          'D': 4,
          'E': 5,
          'F': 6,
          'G': 7,
          'H': 8,
          'I': 9,
          'J': 10,
          'K': 11,
          'L': 12,
          'M': 13,
          'N': 14,
          'O': 15,
          'P': 16,
          'Q': 17,
          'R': 18,
          'S': 19,
          'T': 20,
          'U': 21,
          'V': 22,
          'W': 23,
          'X': 24,
          'Y': 25,
          'Z': 26,
          '1': 27,
          '2': 28,
          '3': 29,
          '4': 30,
          '5': 31,
          '6': 32,
          '7': 33,
          '8': 34,
          '9': 35,
          '0': 36,
          '!': 37,
          '@': 38,
          '#': 39,
          '$': 40,
          '(': 41,
          ')': 42,
          '-': 44,
          '+': 46,
          '&': 47,
          '=': 48,
          ';': 49,
          ':': 50,
          "'": 52,
          '"': 53,
          '%': 54,
          ',': 55,
          '.': 56,
          '/': 59,
          '?': 60,
          '°': 62,
          '\xc1': 63,
          '\xc2': 64,
          '\xc3': 65,
          '\xc4': 66,
          '\xc5': 67,
          '\xc6': 68,
          '\xc7': 69,
          '~': 0
        } %}
        {% set l = lines %}
        {% set ns = namespace(l = [], m = []) %}
        {% for i in range(6) %}
          {% set s = "" if i >= l|length else l[i] %}
          {% set a = '{:<22}'.format(s)|upper|list %}
          {% set ns.l = [] %}
          {% for c in a %}
            {% set ns.l = ns.l + ([map[c if c in map else '?']]) %}
          {% endfor %}
          {% set ns.m = ns.m + [ns.l] %}
        {% endfor %}
        {{ ns.m |to_json }}
      {% endif %}

Note you can now use the lines: or rawlines: when transmitting data. Using rawlines will expect a JSON formatted line as retrieved by the sensor in my earlier post.

You will need to create text_input parameters to store the lines (can also be done as a helper in the GUI):

input_text:
  vb_line1:
    name: Param Vb Line1
  vb_line2:
    name: Param Vb Line2
  vb_line3:
    name: Param Vb Line3
  vb_line4:
    name: Param Vb Line4
  vb_line5:
    name: Param Vb Line5
  vb_line6:
    name: Param Vb Line6

Now for an example of a script - which I’ve made discoverable by Alexa:

vestaboard_show_message:
  alias: Vestaboard - Show Message
  sequence:
  - service: homeassistant.update_entity
    data: {}
    target:
      entity_id: sensor.vestaboard_line1
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - service: input_text.set_value
    data:
      value: '{{states(''sensor.vestaboard_line1'')}}'
    target:
      entity_id: input_text.vb_line1
  - service: input_text.set_value
    data:
      value: '{{states(''sensor.vestaboard_line2'')}}'
    target:
      entity_id: input_text.vb_line2
  - service: input_text.set_value
    data:
      value: '{{states(''sensor.vestaboard_line3'')}}'
    target:
      entity_id: input_text.vb_line3
  - service: input_text.set_value
    data:
      value: '{{states(''sensor.vestaboard_line4'')}}'
    target:
      entity_id: input_text.vb_line4
  - service: input_text.set_value
    data:
      value: '{{states(''sensor.vestaboard_line5'')}}'
    target:
      entity_id: input_text.vb_line5
  - service: input_text.set_value
    data:
      value: '{{states(''sensor.vestaboard_line6'')}}'
    target:
      entity_id: input_text.vb_line6
  - service: rest_command.vestaboard_message
    data:
      lines:
      - "Your text here  - line 1"
      - "Your text here  - line 2"
      - "Your text here  - line 3"
      - "Your text here  - line 4"
      - "Your text here  - line 5"
      - "Your text here  - line 6"
  - delay:
      hours: 0
      minutes: 1
      seconds: 0
      milliseconds: 0
  - service: rest_command.vestaboard_message
    data:
      rawlines:
      - "{{states('input_text.vb_line1')}}"
      - "{{states('input_text.vb_line2')}}"
      - "{{states('input_text.vb_line3')}}"
      - "{{states('input_text.vb_line4')}}"
      - "{{states('input_text.vb_line5')}}"
      - "{{states('input_text.vb_line6')}}"
  mode: single
  icon: mdi:counter

Hope this is of help to anyone :slight_smile:

2 Likes

@Flaker many thanks, I got a Vestaboard this weekend and I’m working on my own custom display from a Weatherflow Tempest. Your work and Daniel’s gave me the start I needed. I’m still working on mine, but will be glad to share later in case there are other folks who would like to put their local weather onto their Vestaboard.

Hi all, I am having trouble getting this to work. I have configuration.yaml and secrets.yaml defined. I can call the restful command from developer tools if I input the secrets myself as described in an earlier post. When I go to define a script or automation and Call Service and the Restful command the command pulls up but in U/I mode I cannot add any data. If I switch to YAML mode and input data like so:

service: rest_command.vestaboard_message
data:
  vestaboard_api_key: !secret vestaboard_api_key
  vestaboard_api_secret: !secret vestaboard_api_secret
  vestaboard_subscription_id: !secret vestaboard_subscription_id
  text: "The quick brown fox jumps over the lazy dog"

The U/I accepts the above data but when saving it says: Message malformed: template value is None for dictionary value @ data[‘action’][0][‘data’].

If I create an automated via file editor like so:

 id: '1682645912728'
  alias: Test vboard
  description: ''
  trigger: []
  condition: []
  action:
  - service: rest_command.vestaboard_message
    data: 
        vestaboard_api_key: !secret vestaboard_api_key
        vestaboard_api_secret: !secret vestaboard_api_secret
        vestaboard_subscription_id: !secret vestaboard_subscription_id
        text: "The quick brown fox jumps over the lazy dog"
  mode: single

The U/I when viewing the automation says: This automation cannot be edited from the UI, because it is not stored in the automations.yaml file, or doesn’t have an ID.

The rest command in configurations.yaml looks like so:

rest_command:
  vestaboard_message:
    url: "https://platform.vestaboard.com/subscriptions/{{ vestaboard_subscription_id}}/message"
    method: POST
    content_type: application/json
    headers:
      X-Vestaboard-Api-Secret: "{{vestaboard_api_secret}}"
      X-Vestaboard-Api-Key: "{{vestaboard_api_key}}"
    verify_ssl: true
    payload: >
      {% if text is defined %}
        {{ { "text": text }|to_json }}
      {% else %}
        {% set map = {
          ' ': 0,
          'A': 1,
          'B': 2,
          'C': 3,
          'D': 4,
          'E': 5,
          'F': 6,
          'G': 7,
          'H': 8,
          'I': 9,
          'J': 10,
          'K': 11,
          'L': 12,
          'M': 13,
          'N': 14,
          'O': 15,
          'P': 16,
          'Q': 17,
          'R': 18,
          'S': 19,
          'T': 20,
          'U': 21,
          'V': 22,
          'W': 23,
          'X': 24,
          'Y': 25,
          'Z': 26,
          '1': 27,
          '2': 28,
          '3': 29,
          '4': 30,
          '5': 31,
          '6': 32,
          '7': 33,
          '8': 34,
          '9': 35,
          '0': 36,
          '!': 37,
          '@': 38,
          '#': 39,
          '$': 40,
          '(': 41,
          ')': 42,
          '-': 44,
          '+': 46,
          '&': 47,
          '=': 48,
          ';': 49,
          ':': 50,
          "'": 52,
          '"': 53,
          '%': 54,
          ',': 55,
          '.': 56,
          '/': 59,
          '?': 60,
          '°': 62,
          '\xc1': 63,
          '\xc2': 64,
          '\xc3': 65,
          '\xc4': 66,
          '\xc5': 67,
          '\xc6': 68,
          '\xc7': 69,
          '~': 0
        } %}
        {% set l = lines %}
        {% set ns = namespace(l = [], m = []) %}
        {% for i in range(6) %}
          {% set s = "" if i >= l|length else l[i] %}
          {% set a = '{:<22}'.format(s)|upper|list %}
          {% set ns.l = [] %}
          {% for c in a %}
            {% set ns.l = ns.l + ([map[c if c in map else '?']]) %}
          {% endfor %}
          {% set ns.m = ns.m + [ns.l] %}
        {% endfor %}
        {{ {'characters': ns.m }|to_json }}
      {% endif %}

As the error says, you need to add the automation into the automations.yaml file and it needs to have an id for it to be editable from the UI. If you break your automations into separate files or similar, you won’t be able to edit them from the UI.

Also, I believe you cannot use !secret from the UI at all, but only from configuration files. Add a trigger to your the yaml automation you have created with an editor and see if it works, i.e. if it will post to your Vestaboard.

Note, if you want, you can also try the custom integration husk. It does barely more than the REST command, but is an actual custom integration that you can add via the UI and it will store credentials in a config entry (instead of in yaml). It only supports local API though. That you would be able to call from UI based automations though.

Inspired by @Flaker, I’ve just updated the custom integration to now also provide six sensors, one for each line, that represents the most recent Vestaboard state. They decode the Vestaboard state to the same format you would provide to the vestaboard.post service, so you can just post the state of the sensors to reproduce the exact same message or use regular string manipulation to change or update the current message on the board.

Hello @dev0 and thank you for your efforts in creating this custom integration.

I’m running into an issue trying to install it, and I’m curious if you have any suggestions.

I successfully installed the integration via HACS - then when adding it to my list of Integrations, I provided the Vestaboard IP address and API key, it accepted it, but then said the integration had a problem. Upon further inspection, the logs state the following:

2023-09-06 15:37:18.935 WARNING (MainThread) [homeassistant.config_entries] Config entry '192.168.86.77' for vestaboard integration not ready yet: Expecting value: line 1 column 1 (char 0); Retrying in background

Tracking down the value in core.config_entries, everything seems to be rational/in order to me:

{
        "entry_id": "349ac25a999dda4a976906d0d906160e",
        "version": 1,
        "domain": "vestaboard",
        "title": "192.168.86.77",
        "data": {
          "local_api_host": "192.168.86.77",
          "local_api_key": "<my API key>"
        },
        "options": {},
        "pref_disable_new_entities": false,
        "pref_disable_polling": false,
        "source": "user",
        "unique_id": "192.168.86.77",
        "disabled_by": null
      }

@slikrik98 Please make sure you are using a local API key. You will need to request a local API enablement token here: Local API Request
Once you have received the local API enablement token activate the local API by sending an HTTP request as described in “Enabling Local API” here:
Vestaboard | Documentation

See the curl example. This will give you your local API key, which you will need to provide to the custom integration.

Yes, I know this is all a little convoluted and could be made easier. If instead you want to use the cloud API, feel free to use the rest_command implementation outlined in the very first post on this thread. They continue to work with the cloud API secret and key.

1 Like

I guess it helps to read the documentation :-/

Thanks - I missed the step about enabling the API (I thought my request to Vestaboard was essentially enabling the API). In any event, I’ve successfully enabled it, which resulted in my local key – and the Integration seems to be good to go now. Thank you again :slight_smile: