Storing the last 10 state values of a sensor in a list that I can display on a dashboard

This is the sensor I want to store the last ten values for, ignoring unavailable and unknown:-

    - name: Jellyfin Now Playing
      unique_id: jellyfin_now_playing
      value_template: >
        {% set sessions = value_json
          | selectattr('NowPlayingItem', 'defined')
          | list %}

        {% set ns = namespace(out=[]) %}

        {% for s in sessions %}
          {% set item = s.NowPlayingItem %}
        {% set user = s.UserName or 'Unknown' %}

          {% if item.Type == 'Episode' %}
            {% set line = '📺 ' ~ item.SeriesName ~ ' — ' ~ item.Name ~ ' (' ~ user ~ ')' %}
        {% elif item.Type == 'Movie' %}
            {% set line = '🎬 Film — ' ~ item.Name ~ ' (' ~ user ~ ')' %}
        {% else %}
            {% set line = '▶ ' ~ item.Name ~ ' (' ~ user ~ ')'  %}
        {% endif %}

        {% set ns.out = ns.out + [line] %}
        {% endfor %}

        {{ ns.out | join('\n') }}

The slight complication is that this sensor is in itself potentially a list of things being watched at any given moment, and can be more than 1 line (1 per item being watched)

In the perfect world, I want to display the last 10 things watched that are no longer being actively watched now.

How might I go about this ?

Ideally then i just use a markdown card to display.

Use an attribute of a trigger-based Template sensor:

You would set the trigger-based template sensor to fire based on the state of your “Jellyfin Now Playing” sensor.

Here’s what I use to list the last 5 tracks played by a media_player (Sonos in this case).

This example, and the previous one posted above, relies on a Trigger-based Template Sensor’s ability to define custom attributes.


What kind of sensor is your example?

It employs the value_json variable which is supported by REST, MQTT, and Command Line.

Is it the RESTful integration?

If it is then it presents a challenge because it doesn’t support defining custom attributes. Its json_attributes option is limited to extracting keys, and their values, directly from value_json (no templating).

1 Like

I think the trigger based template sensor and attribute solution is the way I’ll probably go, but how do I do this:-

The source sensor (which is a rest sensor) has a state that is multiple lines, one for each currently playing item from jellyfin which I just render out to a dashboard using a markdown card. So for sake of example, lets say its got 3 ‘lines’ in its current state.

The trigger sensor for sake of example, has 2 attributes for the previously played items already stored.

I need to map the ‘lines’ from the rest sensor to the attributes of the trigger template sensor, such that I dont get duplicates and it always shows the last 10 latest items.

Here’s how I would handle it (it’s similar to what I do for another application).

  • The REST Sensor uses its json_attributes option to store the details of what is currently playing.

  • A Trigger-based Template Sensor uses a State Trigger to monitor the REST Sensor. Whenever the REST Sensor’s state/attributes changes, the Template Sensor appends the REST Sensor’s data to a list held in its custom attributes (basically what the examples above are doing).

If you want to do it this way, your first task is to employ the json_attributes option in your REST Sensor.

This is what I ended up doing, which lets me list the last 10 things played, but only after they have stopped playing:-

template:
  - trigger:
      - platform: state
        entity_id: sensor.jellyfin_now_playing

    sensor:
      - name: Jellyfin Recent
        unique_id: jellyfin_recent
        state: "{{ now().isoformat() }}"

        attributes:
          recent: >
            {% set old =
              trigger.from_state.state.split('\n')
              if trigger.from_state
              and trigger.from_state.state not in ['unknown','unavailable','']
              else []
            %}

            {% set new =
              trigger.to_state.state.split('\n')
              if trigger.to_state
              and trigger.to_state.state not in ['unknown','unavailable','']
              else []
            %}

            {# find entries that disappeared #}
            {% set stopped = old | reject('in', new) | list %}

            {# existing attribute, safely #}
            {% set history = state_attr(this.entity_id, 'recent') or [] %}

            {# prepend stopped items #}
            {% set combined = stopped + history %}

            {# de-duplicate while preserving order #}
            {% set ns = namespace(out=[]) %}
            {% for item in combined %}
              {% if item and item not in ns.out %}
                {% set ns.out = ns.out + [item] %}
              {% endif %}
            {% endfor %}

            {{ ns.out[:10] }}

Your REST Sensor assembles pieces of data into a single string and then the Trigger-based Template Sensor disassembles the string into pieces of data.

I would not suggest this as the best way of storing and sharing data, but if it works for you then fine.

I see your point. Would you suggest creating attributes on the rest sensor and just pulling those instead ?

Although I understand you cant create custom attributes on a rest sensor, so I guess the rest sensor just becomes a raw blob that just gets the data, and I create a template sensor with the attributes off that ?

Yes, that’s what I had suggested in my previous post.

Any progress to report?

Getting slightly no where unfortunately. Any assistance greatly appreciated.

So this is an example of a sessions response from jellyfin:-

[
  {
    "Id": "a1b2c3d4e5f6g7h8i9j0",
    "UserId": "user_uuid_12345",
    "UserName": "Admin",
    "Client": "Jellyfin Web",
    "DeviceName": "Chrome Windows",
    "DeviceId": "device_uuid_67890",
    "ApplicationVersion": "10.8.10",
    "IsActive": true,
    "SupportsMediaControl": true,
    "NowPlayingItem": {
      "Name": "Big Buck Bunny",
      "Id": "item_uuid_555",
      "Type": "Movie",
      "RunTimeTicks": 5960000000
    },
    "PlayState": {
      "PositionTicks": 1200000000,
      "IsPaused": false,
      "VolumeLevel": 100,
      "RepeatMode": "RepeatNone"
    },
    "TranscodingInfo": {
      "AudioCodec": "aac",
      "VideoCodec": "h264",
      "IsVideoDirect": false,
      "IsAudioDirect": true
    }
  }
]

This could be an array and I want to extract (an array of) the NowPlayingItem.name and UserName from the response of a restful sensor. Given the top level of each array item has no key name, how would I do this.

I've tried to just get the whole json response that I can use later on using something like this:-

- resource: http://192.168.0.999:8096/Sessions
  headers:
    X-Emby-Token: xyz
  scan_interval: 30

  sensor:
    - name: Jellyfin Sessions
      unique_id: jellyfin_sessions

      value_template: "{{ value_json | length }}"

      json_attributes_path: "$"
      json_attributes:
        - ""

But this doesnt work either because I think you need a keyname in the restful sensor.

To simulate your Jellyfin server, I created an HTTP endpoint (in Node-Red) that, when queried, replied with the JSON payload you posted above.

Then I created the following REST Sensor.

- platform: rest
  name: JSON TEST
  resource: http://localhost:1880/endpoint/hello
  value_template: "{{ now().isoformat() }}"
  json_attributes_path: "$[0]"
  json_attributes:
    - UserName
    - NowPlayingItem

Upon reloading, the sensor successfully reported the desired information.

Specific attributes can be extracted as follows:

{{ state_attr('sensor.json_test', 'UserName') }}
{{ state_attr('sensor.json_test', 'NowPlayingItem').Name }}

Based on those results, your sensor configuration can look something like this:

- resource: http://192.168.0.999:8096/Sessions
  headers:
    X-Emby-Token: xyz
  scan_interval: 30

  sensor:
    - name: Jellyfin Sessions
      unique_id: jellyfin_sessions
      value_template: "{{ now().isoformat() }}"
      json_attributes_path: "$[0]"
      json_attributes:
        - UserName
        - NowPlayingItem

Thanks so much for the help, thats worked, with one exception.

If I have 2 session objects in the response, i am only getting the first one in the attributes

I did not realize there can be more than one item in the list. It presents a challenge to extract the desired information out of a list of dictionary items.

Based on tests using JSONpath.com, this should work.

However, when I try $[*] in a REST Sensor, it only returns information for the first item in the list (just like with $[0]).

      json_attributes_path: "$[*]"
      json_attributes:
        - UserName
        - NowPlayingItem

Unfortunately, it seems like it's a deadend.

It appears you will need to use the sensor's state to hold the desired information (like you originally planned). The state can only hold a maximum of 255 characters so let's hope the desired information from all of your Jellyfin sessions never exceeds that limit (otherwise it will be truncated).

Example

- platform: rest
  name: JSON TEST
  resource: http://localhost:1880/endpoint/hello
  value_template: >
    {% set ns = namespace(items=[]) %}
    {% for i in value_json %}
      {% set ns.items = ns.items + [dict(username=i.UserName, title=i.NowPlayingItem.Name)] %}
    {% endfor %}
    {{ ns.items | to_json }}

I can iterate through the sensor's state value like this:

Ty so much for your time and help on this. The state solution seems to work ok with up to about four concurrent users before it is in danger of being truncated ,which is enough for my use case.

To make more room for additional sessions, we can abbreviate the key names from username and title to simply u and t.

  value_template: >
    {% set ns = namespace(items=[]) %}
    {% for i in value_json %}
      {% set ns.items = ns.items + [dict(u=i.UserName, t=i.NowPlayingItem.Name)] %}
    {% endfor %}
    {{ ns.items | to_json }}

Next step is to create a Trigger-based Template Sensor to keep a record of the last 10 sessions.

I have another idea that can do everything with just one Trigger-based Template Sensor (i.e. an additional Template Sensor is not needed). It requires the use of a RESTful Command.

If it interests you, the first step is to confirm that the following rest_command.jellyfin works properly. Enter it in your configuration.yaml file (replace the obfuscated data with actual data) then restart Home Assistant. Check Logs for any associated errors.

rest_command:
  jellyfin:
    url: http://192.168.0.999:8096/Sessions
    method: GET
    headers:
      X-Emby-Token: xyz
    content_type: "application/json"

Assuming there are no errors, go to Developer Tools -> Actions, select rest_command.jellyfin then click the "Perform Action" button. If it works correctly, it will display the JSON response from your Jellyfin server.

Assuming it works, rest_command.jellyfin can be used as an action in the Trigger-based Template Sensor. The data it reports can be processed and stored in attributes. The sensor's trigger would be a Time Pattern Trigger set to trigger every 30 seconds (just like the sensor.jellyfin_now_playing you had created).


Regardless if you choose this method or something else, it all leads to an important consideration. Assume a single session exists (i.e. someone is watching a movie). You're polling the Jellyfin server every 30 seconds so it will reply with virtually the same data each time. Obviously you can't simply store the received data (UserName and NowPlayingItem.Name), otherwise you'll have a record of 10 identical items. How were you planning to prevent needless duplication?

Perhaps check if the value of Id (or other key) is different from the most recently stored one?

Using the rest command worked perfectly and returned the json response from the sessions endpoint (showing as yaml in dev tools).

Its actually massive, nearly 50000 lines which is much much bigger than the sample provided on the jellyfin website. I'm hoping there is a way I can cut down that response, as it takes nearly 10 seconds for dev tools to render it.

Requesting that much data every 30 seconds doesn't seem practical. Especially when you consider your requirements are simply to know who is currently playing what.

The jellyfin docs pointed me at this query param:-

?activeWithinSeconds=60

Which has cut the response down to 600 lines.