How to extract data from JSON (again)

Ah, well you can get the count.

do you have a link to the restful api for the tfl?

Do you mean this?
Home - Transport for London - API (tfl.gov.uk)

You don’t need to register for the ‘Open Data’

Yes, hold on a sec

So looking at the API, there are ways to trim the information, but everything returns a list, which shoots us in the foot basically.

Ooh this is a fun one. Ok so my understanding is that you’re calling this API:
https://api.tfl.gov.uk/Line/Mode/dlr,elizabeth-line,overground,tube/Status

And want a sensor with its state set to a count of the number of lineStatuses in all the items in the response that have something other then Good Service in their statusSeverityDescription description field. And you also want an attribute which has all the lines and a list of disruptions for each one.

So as noted above you’re going to have challenges trying to do this with a REST sensor. Fortunately, we have curl and jq if we use the shell. Instead of a rest sensor we’ll use a command line sensor that curls the response and then manipulates it into a way that works better for HA like this:

- platform: command_line
  name: London lines disruptions
  command: >-
    curl https://api.tfl.gov.uk/Line/Mode/dlr,elizabeth-line,overground,tube/Status 2> /dev/null
    | jq '{"data":[.[] | {"name": .name, "lineStatuses": [.lineStatuses[] | {"disruption": .statusSeverityDescription, "name": .lineId}]}]}'
  value_template: >-
    {{ value_json.data
      | map(attribute="lineStatuses")
      | map('rejectattr', 'disruption', 'eq', 'Good Service')
      | map('list') | map('count')
      | select('gt', 0) | list | count }}
  json_attributes:
    - data

What I do here is first wrap their array response in an object. This way I can stash an attribute containing all the data returned. I also use jq to strip out everything we don’t need (basically just keeping the name and the list of disruptions). Then the template uses rejectattr within a map to turn each item into a count of actual disruptions and sums them up, filters out the lines without a disruption and counts the remaining ones .

I just tested this and got 10. Let me know if that’s wrong.

Also the attribute will contain all disruptions including Good Service (which isn’t actually a disruption). JQ can be used to filter out Good Service and just leave the actual disruptions its just more work. Let me know if its necessary.

1 Like

well, you don’t actually need to do the curl to make this easier. The api has a direct disruptions call, but it doesn’t list the lines. So simply…

  - platform: rest
    resource: https://api.tfl.gov.uk/Line/Mode/dlr,elizabeth-line,overground,tube/Disruption
    name: TfL All Lines Status
    value_template: >
      {{ value_json | count }}

But he’s looking for the name of the line and the disruption as well.

It’s always bothered me that the rest integration doesn’t have attribute templates. And having that (like template sensors do) would solve this problem.

The rest sensor isn’t built for a list as the first object, which makes json_attributes 100% useless.

3 Likes

Ok like the lineId from within lineStatuses? Ok I’ll modify to add that

Yea I know, it’s very annoying. I basically always just use command_line sensors with curl and jq instead. I rarely can make rest sensor work as is.

Ah, I see what you did with the command line, you mapped it to data. That would totally solve this.

1 Like

Yep exactly. When the API returns a top-level list, jq to the rescue!

1 Like

Thanks, I really appreciate the help here!

@CentralCommand
That almost works brilliantly. One small point, It should be value_template not state.

More importantly, it does count the lines with disruption wrongly. It looks like it is returning the count of disruptions. As said above somewhere one line can have more than one disruption.

I’m not a Linux person so curl and (especially) jq are new to me but now I have something to go on.

I do also agree with @petro about not being able to template the attributes.

Whoops, my bad

Oh I think I misunderstood. So you want a count of lines that have at least one disruption in lineStatuses, not a count of the number of disruptions in lineStatuses across all lines.

Ok going to adjust to account for those two in a moment, give it a shot

Not quite there…

TypeError: object of type 'generator' has no len()

1 Like

Ugh. I admit I didn’t test that one first. I really don’t understand why jinja makes you call list before count. Other reducing filters like sum handle generators just fine but not the most common one. Annoying. Anyway fixed that.

2 Likes

Perfect!
Thank you so much, both of you.

Now I just have go and have a read about what jq actually does!!

1 Like

jq is your absolute friend. Look at this crazy template I use to reorganize JSON from the insteon integation:

- platform: command_line
  scan_interval: 30
  name: insteon_groups
  command: "jq '{ groups: [ .[0].\"address\" as $modemaddress | .[] | .\"address\" as $device_address | select(.\"address\" != $modemaddress) | .aldb | .[] | .target=$modemaddress |  { group: .\"group\", in_use: .\"in_use\", device_address: $device_address, target: .\"target\", controller: .\"controller\", brightness: .\"data1\", ramp_rate: .\"data2\", button: .\"data3\"} ]  | sort_by(.group) | map(select(.\"group\" > 20)) | map(select(.in_use))}' insteon_devices.json"
  value_template: "{{ now() }}"
  json_attributes:
    - groups

You can do everything you want and put it in attributes.

I’ve been playing with jq.
I agree… it is very powerful!!

One quick question.
Do command line sensors have a deafult scan_interval?
The docs say so but mine don’t seem to be updating by themselves.

should be 30 seconds IIRC

I just looked at the source and I think it defaults to 60 secs.
SCAN_INTERVAL = timedelta(seconds=60)

I did a restart rather than just reloading all my shiny new command sensors and I think they are updating now. But I have to catch a change in disruptions to be sure.

Does it sound plausible that reloading sensors doesn’t force a scan interval but an HA restart does?