Vestaboard Integration

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:

Glad you were able to figure it out. I also went ahead and rewrote the entire config flow for some major improvements. Hope that makes it easier to figure out for the next person :slight_smile:

I am happy to announce that I now feel comfortable saying that I now consider the custom Vestaboard integration ready for production use. I just significantly improved the setup and config flow and it should be much more robust and will provide the neccesary guidance to complete it.

There’s still a lot that can be (and will be) improved. So stay tuned.

2 Likes

Out of curiosity, how difficult would it be to support the version of the local call that only requires the message parameter (a pre-formatted string)? As opposed to specifying the individual lines, this leverages the Vestaboard’s auto-aligning features.

Edit: after actually reading the API, I see this endpoint only formats the message, it doesn’t send to the board. Nevertheless, it would be awesome if your local integration supported a version that just takes a message and auto-formats it with this endpoint!

I’ve got a new post up using this integration. Many thanks!!!

I’m using alexa/google sheets to pull in a new quote

1 Like

Glad it was useful!

I understand that it might be useful to have an option to auto-layout the message, but I currently have no intention on expanding on the cloud API support. But I will look into adding some form of auto layout support to the Vestaboard integration directly over using the cloud API.

totally makes sense – initially I was thinking the endpoint in question was a local one, but I see that it’s just their cloud API.

Is there any interest in adding the auto alignment functionality to the integration itself? Not using their API and cloud, but doing it within the integration itself? This wouldn’t be a particularly complicated task, but I don’t actually know what programming language integrations use, as I’ve never written one. Thoughts?