Computed fields from fit file messages

You can now create custom activity and interval fields that process fit file messages with scripts. Tick the “Processes fit file messages” box:

Your script will be run when the file is first processed or re-processed. Here is an example script that extracts heart rate monitor details from the device_info messages:

{
  let hrm
  for (let di of icu.fit.device_info) {
    if (di.device_type?.value !== 120 /* HEART_RATE */) continue
    console.log("di " + di)
    let manufacturer = di.manufacturer?.valueName
    if (!manufacturer) continue
    let product = di.product?.value
    if (product) {
      if (manufacturer === "GARMIN") product = icu.fitSdk.enumValueName('GARMIN_PRODUCT', product)
      else if (manufacturer === "FAVERO") product = icu.fitSdk.enumValueName('FAVERO_PRODUCT', product)
    }
    let serial = di.serial_number?.value
    hrm = manufacturer + (product ? " " + product : "") + (serial ? " " + serial : "")
  }
  hrm
}

You need to poke around on fitfileviewer.com and in the FIT SDK to figure out what to look for.

There is documentation for the fit and fitSdk objects here.

I am going to be adding heart rate monitor related fields soon but its still a nice example of what can be done with this feature.

13 Likes

This is amazing! Thank you David :smiley:

1 Like

What an update, all the possibilities!

1 Like

Here is another example. This one prints out the first 1000 messages from the file with fields:

{
  let c = 0
  for (let m of icu.fit) {
    console.log("[" + c + "] " + m._name + " " + m._num + ": " + m.join(", "))
    if (++c >= 1000) break
  }
}
[0] file_id 0: serial_number=3332779479, time_created=1060332298, manufacturer=1, product=3558, type=4
[1] file_creator 49: software_version=300
[2] unknown 288: unknown=1060332300
[3] event 21: timestamp(s)=1060332300, data=0, event=0, event_type=0, event_group=0
[4] device_info 23: timestamp(s)=1060332300, serial_number=3332779479, manufacturer=1, product=3558, software_version=3.0, device_index=0, source_type=5
[5] device_info 23: timestamp(s)=1060332300, manufacturer=1, product=3558, software_version=3.0, device_index=1, device_type=4, source_type=5
[6] device_info 23: timestamp(s)=1060332300, manufacturer=1, product=2957, software_version=2.5, device_index=2, device_type=0, source_type=5
[7] device_info 23: timestamp(s)=1060332300, serial_number=3437995431, cum_operating_time(s)=507058, unknown=2, unknown=2977473959, manufacturer=1, product=3299, software_version=2.5, battery_voltage(V)=2.99609375, device_index=3, device_type=120, hardware_version=49, battery_status=3, ant_network=1, source_type=1
[8] device_info 23: timestamp(s)=1060332300, serial_number=1482690984, cum_operating_time(s)=123456, unknown=2, unknown=84629600, manufacturer=263, product=12, software_version=4.8, battery_voltage(V)=4.19921875, device_index=4, device_type=11, hardware_version=4, battery_status=2, ant_network=1, source_type=1
[9] device_info 23: timestamp(s)=1060332300, manufacturer=36853, product=10, software_version=126.1, device_index=5, device_type=8, source_type=5
[10] device_info 23: timestamp(s)=1060332300, manufacturer=1, product=3559, software_version=0.06, device_index=6, device_type=12, source_type=5
[11] unknown 22: unknown=1060332300, unknown=2, unknown=2, unknown=4, unknown=1, unknown=3, unknown=5, unknown=4
[12] unknown 141: unknown=1060332300, unknown=1060127982, unknown=1060451982, unknown=1
...

Awesome. Any chance we could use that to match that activity to a gear component to track it without mapping it to the gear.

It would be great if there was a library people can contribute scripts they create so others can use it too, similar to the charts library etc.

You can share custom activity fields with everyone. There are already a lot of them.

1 Like

Unfortunately not. Having components or multiple gear items per activity requires quite a lot of changes.

I did just add custom fields to the ones you can use for gear filters.

1 Like

They’re bound to still be gears what are not connected to the head unit and be written to the fit file.

This is awesome, thank you, David! I was able to figure out how to pull battery voltages and statuses for my power meter and HRMs which is so convenient! :smile:

1 Like

Can you share your script? :slight_smile: I will adjust it to my devices.

@uysek Sure, here they are:

HRM battery status:

{
  let status;

  for (let di of icu.fit.device_info) {
    if (di.device_type?.value !== 120) { // 'HEART_RATE'
      continue;
    }

    if (typeof di.battery_status?.value === 'number') {
      status = icu.fitSdk.enumValueName('BATTERY_STATUS', di.battery_status.value);
    }

    if (typeof voltage === 'string') {
      console.log('status=', status);
    }

    break;
  }

  status;
}

HRM battery voltage:

{
  let voltage;

  for (let di of icu.fit.device_info) {
    if (di.device_type?.value !== 120) { // HEART_RATE
      continue;
    }

    if (typeof di.battery_voltage?.value === 'number') {
      voltage = di.battery_voltage.value;
    }

    if (typeof voltage === 'number' && !Number.isNaN(voltage)) {
      console.log('voltage=', voltage);
    }

    break;
  }

  voltage;
}

Power meter battery status (Garmin Rally Series, in case that matters):

{
  let status;

  for (let di of icu.fit.device_info) {
    if (di.device_type?.value !== 11) { // 'BIKE_POWER'
      continue;
    }

    if (typeof di.battery_status?.value === 'number') {
      status = icu.fitSdk.enumValueName('BATTERY_STATUS', di.battery_status.value);
    }

    if (typeof voltage === 'string') {
      console.log('status=', status);
    }

    break;
  }

  status;
}

Power meter voltage:

{
  let voltage;

  for (let di of icu.fit.device_info) {
    if (di.device_type?.value !== 11) { // BIKE_POWER
      continue;
    }

    // console.log('di ' + di); // prints all fields in debug format
    // console.log(di.battery_voltage?.value);

    if (typeof di.battery_voltage?.value === 'number') {
      voltage = di.battery_voltage.value;
    }

    if (typeof voltage === 'number' && !Number.isNaN(voltage)) {
      console.log('voltage=', voltage);
    }

    break;
  }

  voltage;
}

Shimano di2 battery level:

{
  let level;

  for (let di of icu.fit.device_info) {
    if (di.manufacturer?.value !== 41) { // SHIMANO
      continue;
    }

    if (di.device_type?.value !== 1) { // assuming this is Di2
      continue;
    }

    // console.log('di ' + di); // prints all fields in debug format
    // console.log(di.battery_voltage?.value);

    if (typeof di.battery_level?.value === 'number') {
      level = di.battery_level.value;
    }

    if (typeof level === 'number' && !Number.isNaN(level)) {
      console.log('level=', level);
    }

    break;
  }

  level / 100;
}
1 Like

Thanks for these
Do you know what I need to do to correct this error? It’s a Garmin HRM Dual I’m looking for battery status for.

@nasatt You’re welcome! :smile:

This looks like the result when you don’t check the “Process .fit file” option on the configuration screen:

Make sure that box is checked…

2 Likes

Brilliant. Thanks again. Much appreciated

1 Like

I notice that a lot of these fields actually return a structure/array.

For instance L & R phase angles are actually 4 different values. And the power for position is 2 different values (seated & standing) etc.

It seems when I use the fit_file_code, It only returns the first value in the array. I can use square brackets to select nominate which value to read.

However, if I try to read that array more than once (to access each value), it only processes the last one.

(I know that’s a ridiculous amount of fields on this page, just trying to get it working then I can chart these values)

So here, I’ve called for: max_power_position[1] (standing) and max_power_position[0]. however it only gives Stand Pmax. (Seated Pmax is ?) (all seated values are: ?)

I was trying to display the Gear used on the activity page but all I could find was the Id of the activity gear using:
{
activity.gear.id
}

Is the gear name available?

Yup… totally legit… my first thought as well.

You have to map it using the gear_id
Can’t remmber how I did it, but you can check the swagger docs for intervals.icu

Thanks @app4g, I haven’t played with the API so far. I see in Swagger docs there is bike and shoes info on the Athlete Get method. I’ll need to dig a bit more to see how I can use that for a field (that I don’t really need but would be nice to have) :slightly_smiling_face:

1 Like

These are very helpful. Thank you!

From examining a few *.fit files, I can see multiple rows for each device type in the device_info section. This script grabs the first row it finds for the device type (11), which is the power meter’s voltage at the start of the ride.

Can anybody please help with a version of this script that grabs the voltage at the end of the ride? I am able to muddle through to read the JS code and I understand the logic what the alternate version must do (build an array from the device type’s rows and then pull the voltage value from the last row), but I don’t know how to write this JS code on my own.

The problem I’m trying to solve is to see the battery level at the end of the ride so that I know whether I need to recharge my Wahoo pedals before the next ride. My Garmin head unit (830) doesn’t support showing the battery level from Wahoo pedals. Sure, I could check their battery level in Wahoo app on my phone. But the convenience of having it on the Intervals.ICU activity screen can’t be beat. Thanks in advance to anybody who is able help.