Multiple Line Text File to Template Sensor Attributes

I have a manually created template sensor which contains my streaming radio station data as an attribute in the sensor.

Radio Station Template Sensor

Note: My sensor actually has dozens of stations, it is just cut down here for the example.

    - unique_id: radio_stations
      name: Radio Stations
      icon: mdi:radio
      state: ok
      attributes:
        stations: >
          {{
            [
              {
                "name":"Flex 98.5 - Hip Hop",
                "url":"https://streaming.live365.com/a23768",
                "image":"https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg"
              },
              {
                "name":"That 70's Channel",
                "url":"https://ais-edge37-live365-dal02.cdnstream.com/a09646",
                "image":"https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png"
              },
              {
                "name":"Top 80 Radio",
                "url":"http://uk5.internet-radio.com:8011/stream",
                "image":"https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png"
              }
            ]
          }}

The purpose of this sensor is to populate the radio stations input_selects for my radio launcher, and provide the matching stream and image urls when the radio stream is started. Everything works perfectly with the manually created sensor.

Station Selection Example

image

Automation to Populate Input Selects
- id: radio_station_update
  alias: "[Radio] Station Update"
  description: Update radio station input options.
  mode: restart
  variables:
    selected_stations: "{{ expand('group.radio_selected_stations')|map(attribute='entity_id')|list }}"
    stored_stations: "{{ expand('group.radio_stored_stations')|map(attribute='entity_id')|list }}"
  trigger:
    - platform: state
      id: startup
      entity_id: input_boolean.startup_pending
      to: 'off'

    - platform: state
      entity_id: sensor.radio_stations
      attribute: stations
  action:
    - service: automation.turn_off
      target:
        entity_id: &radio_automations
          - automation.radio_store_station_selection # don't overwrite stored values
          - automation.media_player_media_auto_resume # triggered by input_select.radio_station
      data:
        stop_actions: false

    - service: input_select.set_options
      target:
        entity_id: &radio_selects
          - input_select.radio_station
          - input_select.media_preset_radio_wake
          - input_select.media_preset_radio_morning
          - input_select.media_preset_radio_sleep
          - input_select.media_preset_radio_jason
          - input_select.media_preset_radio_sheri
          - input_select.media_preset_radio_shower
          - input_select.media_preset_radio_company
          - input_select.alarm_clock_radio_auto_alarm
          - input_select.alarm_clock_radio_manual_alarm
          - input_select.alarm_clock_radio_nap_alarm
      data:
        options: >
          [{% for item in state_attr('sensor.radio_stations','stations') -%}
            "{{- item.name }}"{{ ',' if not loop.last -}}{% endfor %}]

    - repeat: # restore previous selection (values reset when options reloaded)
        count: "{{ selected_stations|count|int }}"
        sequence:
          - choose:
              - conditions: "{{ states(stored_stations[repeat.index-1])|lower not in ['','unknown','unavailable','none'] }}"
                sequence:
                  - service: input_select.select_option
                    target:
                      entity_id: "{{ selected_stations[repeat.index-1] }}"
                    data:
                      option: "{{ states(stored_stations[repeat.index-1]) }}"

    - service: automation.turn_on
      target:
        entity_id: *radio_automations
Automation to Store Current Station Selection
- id: radio_store_station_selection
  alias: "[Radio] Store Station Selection"
  description: Update stored radio station selection.
  mode: restart
  trigger:
    - platform: state
      entity_id: *radio_selects
  action:
    - service: input_text.set_value
      target:
        entity_id: "{{ trigger.entity_id|replace('input_select','input_text') }}"
      data:
        value: "{{ states(trigger.entity_id) }}"

Play Media Service Call
- service: media_player.play_media
  data:
    entity_id: "{{ player }}"
    media_content_id: >
      {% for item in state_attr('sensor.radio_stations','stations') -%}
        {% if is_state('input_select.radio_station',item.name) %}
          {{ item.url }}
        {% endif %}
      {% endfor %}
    media_content_type: music
    extra:
      title: "{{ states('input_select.radio_station') }}"
      thumb: >
        {% for item in state_attr('sensor.radio_stations','stations') -%}
          {% if is_state('input_select.radio_station',item.name) %}
            {{ item.image }}
          {% endif %}
        {% endfor %}

What I would like to do is store this data in a text file in a csv format or similar format like this and then be able to turn this file into the stations attribute of the radio stations template sensor.

"Flex 98.5 - Hip Hop","https://streaming.live365.com/a23768","https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg"
"That 70's Channel","https://ais-edge37-live365-dal02.cdnstream.com/a09646","https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png"
"Magic 80s Florida","http://airspectrum.cdnstream1.com:8018/1606_192","https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png"

Eventually the idea is to get to maybe create some sort of UI to manage (add/update/delete) streaming radio stations, but if I can at least get this working I can manage them with a spreadsheet until I figure out a UI.

Even better would be a scraper that could get this data automatically from popular radio stream sites so the even the text file could be automatically created with a few clicks in a UI. The current manual process is rather tedious. I’d like to eventually get to where my wife can maintain her HA radio station list as easily as her Spotify playlists.

But I digress. Any thoughts on getting the text file into a template sensor? I’m not even sure if this is possible or where to start really.

My first thought was to use the File Sensor integration but that only uses the last line in the text file. Unless you can cram all the information into a single very long line (perhaps by structuring it as JSON), it won’t be of much use.

Not sure if this is possible without employing python (and not python_script because it’s sandboxed). Hopefully someone has a simple way to do it.

Pretty much the conclusion I came to after much searching. This might actually be what gets me to dig into a bit of python finally…lol.

Perhaps making them “input_text”? This makes it static and you have to have an input for each station. Length is limited to 255 chars.

input_text:
  flex_985:
  name: Flex 98.5
  icon: hass:radio
  initial: Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768, https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg

Perhaps the file contents can be published to an MQTT topic and used as attributes for an MQTT Sensor (i.e. replacing the Template Sensor you are currently using). The trick is to establish a mechanism (outside of Home Assistant) that automatically publishes the file’s contents whenever it detects the file has been updated.

Perhaps this mechanism doesn’t have to reside entirely outside of Home Assistant. The Folder Watcher integration can monitor the file for changes and, when detected, use the Shell Command integration to publish the file’s contents. Yeah, it’s definitely a Rube Goldberg mechanism … but it seems feasible.

The state value of all entities is limited to 255 characters max.
EDIT Oops, sorry. I overlooked the part that you suggested creating a separate input_text for each radio station. Nevertheless, even then the length of any one radio station’s data must be less than 255 characters.

Inspired by AllHailJ’s suggestion (and assuming you are willing to store the data within Home Assistant’s configuration file as opposed to its own file) you might explore using an input_select. It is effectively a list and has no practical limits on the number of items it can store. The only thing I am not certain is if each item’s length is capped at 255 characters. The longest example you posted is 154 characters long but I imagine there may be longer ones.

"That 70's Channel","https://ais-edge37-live365-dal02.cdnstream.com/a09646","https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png"

One potential pitfall of creating an input_select with a bajillion options is that it might impact the rendering performance of whatever Lovelace page it lives on (I don’t know this for sure but it seems to me that all of those entries will need to be loaded by the browser).

@123
I tried the input select to see what it would do and if it slowed down with multiple entries(54 entries). It didn’t. It might with 100’s but that seems like alot for a manually maintained list.

Here is the input_select.yaml:

input_select:
  stations:
    name: All Stations 
    options:
      - 1Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 2Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 3Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 4Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 5Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 6Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 7Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 8Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 9Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 10Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 11Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 12Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 13Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 14Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 15Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 16Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 17Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 18Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 19Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 20Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 21Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 22Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 23Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 24Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 25Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 26Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 27Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 28Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 29Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 30Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 31Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 32Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 33Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 34Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 35Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 36Flex 98.5 - Hip Hop,https://streaming.live365.com/a23768,https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg
      - 1That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 2That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 3That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 4That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 5That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 6That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 7That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 8That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 9That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 10That 70's Channel,https://ais-edge37-live365-dal02.cdnstream.com/a09646,https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png
      - 1Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png
      - 2Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png
      - 3Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png
      - 4Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png
      - 5Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png
      - 6Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png
      - 7Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png
      - 8Magic 80s Florida,http://airspectrum.cdnstream1.com:8018/1606_192,https://ca0-cdn.onlineradiobox.com/img/l/5/43165.v3.png

Here is a screenshot from lovelace

There was no delay in loading or displaying the selection. Scrolling was smooth.

regards

That’s good news. Unless jazzyisj must store the data in a separate file as he originally proposed, this alternative makes it easier to access from Home Assistant.

@AllHailJ @123

Thank you both for the very creative solutions. Unfortunately I don’t think the proposals simplify the situation and would still require reloading components any time the station list changed. It definitely doesn’t get me any closer to my ultimate goal of the wife-unit maintaining her own stations. It also complicates grabbing the info for the play script. The solution I already have in place is already a little cleaner as it is in dictionary format yadda yadda.

I’m thinking I’m going to have to go the custom integration route. The SpotCast integration does exactly what I’m trying to do (indeed it was my inspiration to work on this) except it’s gets it’s playlist data from an API instead of a text file so I guess I’ll start there and try to figure out how it is done there.

I’m still going to have to figure out how to get the info from the text file to the sensor attributes, but I believe that is going to be more “python help” than “Home Assistant help”.

Again, thanks for looking at it though.

If anyone out there familiar with creating a custom integration has the time and inclination to help me expediate this process I would be most grateful!

Maybe of help. :crossed_fingers:

That definitely looks like it will help point me in the right direction. Thanks!

I’m currently trying to figure out how to create a custom component that will create a sensor. Once I have that figured out I can work on populating it! :laughing:

@123

Just thought I’d post this as it ended up being the exact solution I was looking for. I did end up having to create a custom component but thanks to a push in the right direction from @VDRainer creating the sensor turned out to be pretty simple.

CSV needs to have the attribute names as the first line.

"name","url","image_url"
"Flex 98.5 - Hip Hop","https://streaming.live365.com/a23768","https://media.live365.com/download/a255d860-3c9d-4aad-8a6c-867e082b1861.jpeg"
"Flood FM","https://streaming.live365.com/a78844","https://media.live365.com/download/45adbb09-c946-4711-8438-c12545cd9f92.png"
"That 70's Channel","https://ais-edge37-live365-dal02.cdnstream.com/a09646","https://media.live365.com/download/bb807a58-fc07-4d1e-bda8-d814078f4113.png"
"All Memphis Music","https://streaming.live365.com/a77318","https://media.live365.com/download/0a8a7b54-9b45-487a-a0e1-3be601806688.jpeg"

CSV reader returns a dictionary object of the csv file.

import csv
csv_file = (
    "/config/custom_components/streaming_radio_player/user_data/streaming_radio_stations.csv"
)

csv_reader = csv.DictReader(open(csv_file), delimiter=",")
station_list = [dict(d) for d in csv_reader]

And then in the sensor definition just pass that dictionary object to the sensor attributes.
self._attributes["stations"] = station_list

Easy peasy nice and sleasy!

1 Like

please show the full sensor code.
i cant understand how to use self._attributes["stations"] = station_list

please show the full sensor code.
i cant understand how to use self._attributes["stations"] = station_list

Sorry it took me so long to get to this message!!

That is python code from my own custom component I hacked together to create a radio playlist from a csv file. It is not a template sensor. Are you trying to write something in python?

hey there, just came across this and im trying to get a .csv multi-line into a sensor attribute - can you help here on this last missing piece you mentioned there?