Custom activity streams from fit file messages

Custom activity streams now support computing streams from fit file messages (not just record messages) using Javascript. Note that this cannot be used with the simpler “Record Field” streams but the process for creating the stream is the same:

Tick the “Processes fit file messages” box and create a script instead of entering a record field name.

This script just grabs the heart_rate data from the record messages:

{
  for (let m of icu.fit.record) {
    data.setAt(m.timestamp.value, m.heart_rate?.value)
  }
}

Use the data array to record the stream. Note that you do not have to process only record messages. The fit object provides access to all messages in the file relevant to the activity.

You can maximise the window and run the script to see what it produces from the selected activity:

The streams are created when the activity file is processed. You need to reprocess existing activities to see new streams (Action → Reprocess file under the ride timeline chart or use the activity list view to do this in bulk).

8 Likes

I’m trying to pull total_ascent and total_descent from the FIT session. Hopefully this will allow me to make a Fitness graph in week and month chunks that display Ascent and Descent in Hiking and Alpine Skiing activities.

What am I doing wrong? (Clearly, I know zero about scripting.)

Maybe I’m supposed to follow Computed activity fields. :man_shrugging: TBH, I’m having better luck learning にほんご than trying to (understand and) script.

You are creating a custom activity stream and not a custom activity field. But I see you have those fields on your skiing activities so you figured it out?

1 Like

There’s a cross-post

1 Like

Yes, @MedTechCD set me straight. :+1:

1 Like

Anyone has an idea how I can use the ‘Proces FIT file message’ for a Custom Stream from a field with a space in the name?
I have a record field called ‘Effort Pace’ but I’m unable to find a solution for using it in a script.
The space causes an error and I don’t know what I should use, &nbsp or Hex code isn’t working either.
{
for (let m of icu.fit.record) {
data.setAt(m.timestamp.value, m.Effort Pace?.value)
}
}

Try

{
for (let m of icu.fit.record) {
data.setAt(m.timestamp.value, m[‘Effort Pace’]?.value)
}
}

2 Likes

Thank you, that works!

I’ve made a custom stream to display the Garmin Edge battery status over time. It kinda works, but the data is only once per minute, and the graph only displays peaks at that exact time, instead of just keeping the last value until the value is updated.

This is the code:

{
  for (let m of icu.fit) {
    
	// Only view mesg_num 104
    if (m._num !== 104) continue
	
    // Timestamp is in field 253
	let ts=m.f_253.onlyvalue

    // Battery percentage is in field 2
    if (ts) data.setAt(ts, m.f_2.value)
  }
}

What am I missing?

You need to add a post processing step to fill in the missing values with the previous value:

{
  for (let m of icu.fit) {
    if (m._num !== 104) continue
    let ts = m.f_253.value
    if (ts) data.setAt(ts, m.f_2.value)
  }
  let prev
  for (let i = 0; i < data.length; i++) {
    if (data[i]) prev = data[i]
    else data[i] = prev
  }
}
2 Likes

Hello David, hello all contributors & users !

I am trying to create my own activity streams graphics, and I am facing some issues to catch the data from the fit file (as it seems from my different attempts)
If you have some ideas/solutions/tips this would be greatly helpful !

I also give the bike computer type : Wahoo elemnt (in case someone found the right names/formats)

Blockquote
{
let alt = icu.streams.altitude
let dist = icu.streams.distance
let cad = icu.streams.cadence
let vit = icu.streams.Speed
let temp = icu.streams.Temperature
let grade = icu.streams.Grade
let kg = activity.icu_weight+16
// for (let m of icu.fit) {
// data.setAt(m.timestamp.value, m.Speed?.value)
// }
for (let i = 0; i < data.length; i++) data[i] = vit[i]
}

in my for loop i would compute some values based on the different one from the streams. Here is just a test to display each of the streams and see which one is blocking and find a solution :

  • altitude, distance and cadence are perfectly displayed
  • but speed, temperature, grade seems to be NULL (TypeError: Cannot read property “0” from null)

I tried different names for the streams (speed, Speed, spd etc… ) but this is worst as it doesn’t recognize the stream => the error is : Invalid stream type [speed]. So only the spelling here above displayed seems to be found in the fit file… but still data are empty…

in fitfile viewer, we can see the data are present :

on runanalyze, same, I can see the raw data as below :

I tried to replace : “icu.streams” by “icu.fit”, => same results

As you may see in the lines in comments, i tried building the data array with the other way, but not working neither. losing my mind, I tried many different ways (in case of chance), but no…

  • for (let m of icu.fit.record) TypeError: Cannot read property “record” from null
  • for (let m of icu.fit) TypeError: Cannot read property “Symbol(Symbol.iterator)” from null
  • _for (let m of icu.record)_TypeError: Cannot read property “Symbol(Symbol.iterator)” from null

Hope you could help me a bit here !
Thank you in advance for your answers !

Best regards,
Pask

Intervals.icu maps many “standard” fields from the fit file records to different names internally. Some of this is to match the names Strava uses since that is how this project started.

You can see the names here:

https://forum.intervals.icu/t/server-side-data-model-for-scripts/25781#ActivityStream

So it’s velocity_smooth instead of speed and so on.

Stream names that start with a Capital letter are for custom streams which may or may not exist. That is why you don’t get an error for “Speed” but do for “speed” (no such standard stream).

1 Like

I made a custom activity stream which displays correctly when I click on the “play arrow”. But in the activity page the graph doesn’t get displayed.
The fit file is re-processed and the process fit file box is ticked.
Any idea what could be wrong?

I had some trouble with a stream having a space in the name. Had to use
{
for (let m of icu.fit.record) {
data.setAt(m.timestamp.value, m[‘Effort Pace’]?.value)
}
}

To get it right.
Could also be a problem with the default axis values if you convert Pace/Speed…
Can you show screenshot of the Type and Script tabs?

Screenshot 2024-08-16 at 13.59.59

Two remarks:

  • Try with Format .0f to ditch decimals. Don’t think that it causes a problem but you never know…
  • Add a condition where you avoid dividing by 0 for the left_torque. If left_torque is not available or 0, it will completely throw off the automatic axis boundaries. This is probably what’s happening but is not visible in the preview.

Changed code to the following, in order to handle the 0 values better. But still doesn’t render anything. :confused:

{

let streams = icu.streams;
let act = icu.activity;
let pwr = streams.get('fixed_watts').data;
let left_torque = streams.get('left_torque_effectiveness').data;
let left_leg_power = streams.get('PowerLeftLeg').data;

let minThreshold = 0;   // Minimum threshold for valid values
let maxThreshold = 1000; // Maximum threshold for valid values

for (let i = 0; i < left_leg_power.length; i++) {
    if (left_torque[i] === 0 || left_leg_power[i] === 0) {
        data[i] = 0;
    } else {
        let calculatedValue = (left_leg_power[i] * 100) / left_torque[i];if (!isFinite(calculatedValue) || calculatedValue < minThreshold || calculatedValue > maxThreshold) {
            data[i] = 0;
        } else {
            data[i] = calculatedValue;
        }
    }
}

}

I just had a look at that stream on one of your activities (18th August) and it seems to be rendering now? Is it working for you now?

He’s got it working by deleting the custom stream first and starting over with the same code. Probably was a cache error.