Peloton support

For the Node Red guys out there, here are my Fan flow automations, depending on the workout type and equipment bike/tread a number of fans will turn on and off after x mins depending on the class lengh.

1 Like

I follow the implementation however Iā€™d rather just bounce off what others have done. Can anyone post a config.yaml examlpe with all the components elements successfully setup?

2 Likes

Is there a better setup guide to follow for newbs? I followed the github page by adding folders and pasting in the code. After that Iā€™m lost. Trying to piece together bits from this thread but Iā€™m not getting anywhere.

I recommend using hacs, copying the folder and text did not work for me

1 Like

I was just looking at how to display some of this data better using the Influxdb and Iā€™m running into a few hurdles because of how the data reports.

  1. Once you finish a workout, the sensor still holds the last value. So I canā€™t figure out how to do things like show calories over the last day, etc. For example, yesterday I did a 30 minute ride, and then a 5 minute cool down:
SELECT "Distance Mi","Calories KCal" FROM "homeassistant"."autogen"."state" 
WHERE time > now() - 24h AND "entity_id"='peloton_xyz_com'
and "state"='Complete'

image

  1. It would be nice if the integration included non-aggregate figures per update as well. For example, when I look in Influxdb right now, each report is the running total (which is also helpful, just not for this):
SELECT "Distance Mi","Calories KCal" FROM "homeassistant"."autogen"."state" 
WHERE time > now() - 24h AND "entity_id"='peloton_xyz_com'
and "state"!='Complete'

However, you canā€™t easily SUM() that over a year to create summary statistics. I was thinking of making a dashboard similar to how the energy dashboard sums stuff up, but I canā€™t seem to make the data work. If you have better suggestions on how to accomplish this, Iā€™ll change course, it just seemed like this would be the best method. Thanks!

It may be easier to import your data to InfluxDB via NodeJS (and peloton-client-node). Home Assistant doesnā€™t do a great job of allowing integrations to import a series of related data points. I think thatā€™s why the Energy feature had to be built in the first place. IMO even showing stats from the last workout pushes HA to its limits.

The API returns nice data for each workout. The below is an excerpt from mine:

{
  "duration": 1200,
  "is_class_plan_shown": true,
  "segment_list": [
    {
      "id": "560f9ba8890f447dabc743dacb6bc695",
      "length": 363,
      "start_time_offset": 0,
      "icon_url": "https://s3.amazonaws.com/static-cdn.pelotoncycle.com/segment-icons/warmup.png",
      "intensity_in_mets": 3.5,
      "metrics_type": "cycling",
      "icon_name": "warmup",
      "icon_slug": "warmup",
      "name": "Warm Up",
      "is_drill": false
    },
    {
      "id": "23c14866ec4547fa8fc716070fc74a8e",
      "length": 777,
      "start_time_offset": 363,
      "icon_url": "https://s3.amazonaws.com/static-cdn.pelotoncycle.com/segment-icons/cycling.png",
      "intensity_in_mets": 6.0,
      "metrics_type": "cycling",
      "icon_name": "cycling",
      "icon_slug": "cycling",
      "name": "Cycling",
      "is_drill": false
    },
    {
      "id": "1a5eea7c49d542fcaa4123ebf8e4c08e",
      "length": 60,
      "start_time_offset": 1140,
      "icon_url": "https://s3.amazonaws.com/static-cdn.pelotoncycle.com/segment-icons/cooldown.png",
      "intensity_in_mets": 3.5,
      "metrics_type": "cycling",
      "icon_name": "cooldown",
      "icon_slug": "cooldown",
      "name": "Cool Down",
      "is_drill": false
    }
  ],
  "seconds_since_pedaling_start": [
    1, 51, 101, 151, 201, 251, 301, 351, 401, 451, 501, 551, 601, 651, 701, 751,
    801, 851, 901, 951, 1001, 1051, 1101, 1151, 1199
  ],
  "average_summaries": [
    {
      "display_name": "Avg Output",
      "display_unit": "watts",
      "value": 75,
      "slug": "avg_output"
    },
    {
      "display_name": "Avg Cadence",
      "display_unit": "rpm",
      "value": 72,
      "slug": "avg_cadence"
    },
    {
      "display_name": "Avg Resistance",
      "display_unit": "%",
      "value": 36,
      "slug": "avg_resistance"
    },
    {
      "display_name": "Avg Speed",
      "display_unit": "mph",
      "value": 14.0,
      "slug": "avg_speed"
    }
  ],
  "summaries": [
    {
      "display_name": "Total Output",
      "display_unit": "kj",
      "value": 89,
      "slug": "total_output"
    },
    {
      "display_name": "Distance",
      "display_unit": "mi",
      "value": 4.67,
      "slug": "distance"
    },
    {
      "display_name": "Calories",
      "display_unit": "kcal",
      "value": 226,
      "slug": "calories"
    }
  ],
  "metrics": [
    {
      "display_name": "Output",
      "display_unit": "watts",
      "max_value": 186,
      "average_value": 75,
      "values": [
        46, 40, 43, 84, 90, 108, 111, 107, 78, 94, 87, 86, 133, 177, 136, 89,
        101, 106, 114, 81, 88, 114, 166, 37, 31
      ],
      "slug": "output"
    },
    {
      "display_name": "Cadence",
      "display_unit": "rpm",
      "max_value": 112,
      "average_value": 72,
      "values": [
        89, 83, 82, 81, 76, 78, 82, 87, 78, 78, 76, 74, 71, 68, 76, 74, 74, 72,
        76, 82, 79, 79, 111, 94, 84
      ],
      "slug": "cadence"
    },
    {
      "display_name": "Resistance",
      "display_unit": "%",
      "max_value": 60,
      "average_value": 36,
      "values": [
        25, 25, 26, 39, 38, 40, 40, 38, 36, 41, 41, 42, 52, 60, 49, 39, 42, 45,
        45, 35, 41, 41, 38, 20, 20
      ],
      "slug": "resistance"
    },
    {
      "display_name": "Speed",
      "display_unit": "mph",
      "max_value": 20.8,
      "average_value": 14.0,
      "values": [
        11.6, 10.8, 11.2, 15.1, 15.5, 16.8, 16.9, 16.7, 14.7, 15.8, 15.3, 15.3,
        18.3, 20.4, 18.4, 15.5, 16.3, 16.6, 17.2, 14.9, 15.4, 17.1, 19.9, 10.3,
        9.5
      ],
      "slug": "speed"
    },
    {
      "display_name": "Heart Rate",
      "display_unit": "bpm",
      "max_value": 152,
      "average_value": 125,
      "values": [
        110, 109, 113, 110, 118, 123, 123, 133, 125, 126, 121, 116, 141, 145,
        145, 132, 136, 137, 141, 139, 131, 139, 152, 152, 136
      ],
      "slug": "heart_rate",
      "zones": [
        {
          "display_name": "Zone 1",
          "slug": "zone1",
          "range": "<119 bpm",
          "duration": 364,
          "max_value": 118,
          "min_value": 0
        },
        {
          "display_name": "Zone 2",
          "slug": "zone2",
          "range": "119-137 bpm",
          "duration": 659,
          "max_value": 137,
          "min_value": 119
        },
        {
          "display_name": "Zone 3",
          "slug": "zone3",
          "range": "138-155 bpm",
          "duration": 174,
          "max_value": 155,
          "min_value": 138
        },
        {
          "display_name": "Zone 4",
          "slug": "zone4",
          "range": "156-173 bpm",
          "duration": 0,
          "max_value": 173,
          "min_value": 156
        },
        {
          "display_name": "Zone 5",
          "slug": "zone5",
          "range": ">=174 bpm",
          "duration": 0,
          "max_value": 184,
          "min_value": 174
        }
      ],
      "missing_data_duration": 3
    }
  ],
  "has_apple_watch_metrics": false,
  "location_data": [],
  "is_location_data_accurate": null,
  "splits_data": {
    "distance_marker_display_unit": "mi",
    "elevation_change_display_unit": "ft",
    "splits": [
      {
        "distance_marker": 1.0,
        "order": 1,
        "seconds": 291.0,
        "elevation_change": null,
        "is_best": false
      },
      {
        "distance_marker": 2.0,
        "order": 2,
        "seconds": 258.0,
        "elevation_change": null,
        "is_best": false
      },
      {
        "distance_marker": 3.0,
        "order": 3,
        "seconds": 234.0,
        "elevation_change": null,
        "is_best": true
      },
      {
        "distance_marker": 4.0,
        "order": 4,
        "seconds": 244.0,
        "elevation_change": null,
        "is_best": false
      },
      {
        "distance_marker": 0.67,
        "order": 5,
        "seconds": 171.0,
        "elevation_change": null,
        "is_best": false
      }
    ]
  },
  "splits_metrics": {},
  "target_performance_metrics": {
    "target_graph_metrics": [
      {
        "graph_data": {
          "upper": [
            90, 90, 90, 90, 75, 75, 75, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65,
            65, 65, 65, 80, 65, 65, 100
          ],
          "lower": [
            70, 70, 70, 70, 70, 70, 70, 70, 60, 60, 60, 60, 60, 60, 60, 60, 60,
            60, 60, 60, 70, 60, 60, 80
          ],
          "average": [
            80, 80, 80, 80, 72, 72, 72, 72, 62, 62, 62, 62, 62, 62, 62, 62, 62,
            62, 62, 62, 75, 62, 62, 90
          ]
        },
        "max": 100,
        "min": 60,
        "average": 69,
        "type": "cadence"
      },
      {
        "graph_data": {
          "upper": [
            30, 30, 30, 36, 40, 40, 42, 42, 38, 38, 44, 35, 50, 50, 50, 40, 45,
            50, 50, 35, 35, 45, 35, 30
          ],
          "lower": [
            20, 20, 20, 24, 35, 35, 36, 36, 32, 32, 36, 30, 40, 43, 43, 35, 37,
            39, 41, 30, 30, 40, 30, 20
          ],
          "average": [
            25, 25, 25, 30, 37, 37, 39, 39, 35, 35, 40, 32, 45, 46, 46, 37, 41,
            44, 45, 32, 32, 42, 32, 25
          ]
        },
        "max": 50,
        "min": 20,
        "average": 36,
        "type": "resistance"
      }
    ],
    "cadence_time_in_range": 57,
    "resistance_time_in_range": 86
  },
  "target_metrics_performance_data": {
    "target_metrics": [
      {
        "offsets": { "start": 60, "end": 178 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 30.0, "lower": 20.0 },
          { "name": "cadence", "upper": 90.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 179, "end": 190 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 33.0, "lower": 22.0 },
          { "name": "cadence", "upper": 90.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 191, "end": 241 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 36.0, "lower": 24.0 },
          { "name": "cadence", "upper": 90.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 242, "end": 315 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 40.0, "lower": 35.0 },
          { "name": "cadence", "upper": 75.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 316, "end": 369 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 42.0, "lower": 36.0 },
          { "name": "cadence", "upper": 75.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 370, "end": 397 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 40.0, "lower": 35.0 },
          { "name": "cadence", "upper": 75.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 398, "end": 432 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 42.0, "lower": 36.0 },
          { "name": "cadence", "upper": 75.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 433, "end": 456 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 35.0, "lower": 30.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 457, "end": 517 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 38.0, "lower": 32.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 518, "end": 533 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 41.0, "lower": 34.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 534, "end": 588 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 44.0, "lower": 36.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 589, "end": 612 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 35.0, "lower": 30.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 613, "end": 648 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 40.0, "lower": 33.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 649, "end": 656 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 35.0, "lower": 30.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 657, "end": 676 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 50.0, "lower": 40.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 677, "end": 731 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 50.0, "lower": 43.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 732, "end": 748 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 50.0, "lower": 40.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 749, "end": 786 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 50.0, "lower": 43.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 787, "end": 825 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 40.0, "lower": 35.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 826, "end": 886 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 45.0, "lower": 37.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 887, "end": 933 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 50.0, "lower": 39.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 934, "end": 981 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 50.0, "lower": 41.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 982, "end": 1043 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 35.0, "lower": 30.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 1044, "end": 1082 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 35.0, "lower": 30.0 },
          { "name": "cadence", "upper": 80.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 1083, "end": 1114 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 45.0, "lower": 40.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 1115, "end": 1146 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 45.0, "lower": 40.0 },
          { "name": "cadence", "upper": 80.0, "lower": 70.0 }
        ]
      },
      {
        "offsets": { "start": 1147, "end": 1163 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 35.0, "lower": 30.0 },
          { "name": "cadence", "upper": 65.0, "lower": 60.0 }
        ]
      },
      {
        "offsets": { "start": 1164, "end": 1172 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 35.0, "lower": 30.0 },
          { "name": "cadence", "upper": 90.0, "lower": 80.0 }
        ]
      },
      {
        "offsets": { "start": 1173, "end": 1186 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 40.0, "lower": 35.0 },
          { "name": "cadence", "upper": 90.0, "lower": 80.0 }
        ]
      },
      {
        "offsets": { "start": 1187, "end": 1202 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 50.0, "lower": 45.0 },
          { "name": "cadence", "upper": 100.0, "lower": 90.0 }
        ]
      },
      {
        "offsets": { "start": 1203, "end": 1260 },
        "segment_type": "cycling",
        "metrics": [
          { "name": "resistance", "upper": 30.0, "lower": 20.0 },
          { "name": "cadence", "upper": 100.0, "lower": 80.0 }
        ]
      }
    ],
    "time_in_metric": [
      { "name": "resistance", "value": 86 },
      { "name": "cadence", "value": 57 }
    ]
  },
  "effort_zones": {
    "total_effort_points": 11.9,
    "heart_rate_zone_durations": {
      "heart_rate_z1_duration": 364,
      "heart_rate_z2_duration": 659,
      "heart_rate_z3_duration": 174,
      "heart_rate_z4_duration": 0,
      "heart_rate_z5_duration": 0
    }
  },
  "muscle_group_score": [
    {
      "muscle_group": "biceps",
      "score": 777.0,
      "percentage": 6,
      "bucket": 1,
      "display_name": "Biceps"
    },
    {
      "muscle_group": "calves",
      "score": 1554.0,
      "percentage": 11,
      "bucket": 2,
      "display_name": "Calves"
    },
    {
      "muscle_group": "core",
      "score": 1554.0,
      "percentage": 11,
      "bucket": 2,
      "display_name": "Core"
    },
    {
      "muscle_group": "glutes",
      "score": 2331.0,
      "percentage": 17,
      "bucket": 3,
      "display_name": "Glutes"
    },
    {
      "muscle_group": "hamstrings",
      "score": 2331.0,
      "percentage": 17,
      "bucket": 3,
      "display_name": "Hamstrings"
    },
    {
      "muscle_group": "hips",
      "score": 1554.0,
      "percentage": 11,
      "bucket": 2,
      "display_name": "Hips"
    },
    {
      "muscle_group": "quads",
      "score": 2331.0,
      "percentage": 17,
      "bucket": 3,
      "display_name": "Quads"
    },
    {
      "muscle_group": "shoulders",
      "score": 777.0,
      "percentage": 5,
      "bucket": 1,
      "display_name": "Shoulders"
    },
    {
      "muscle_group": "triceps",
      "score": 777.0,
      "percentage": 5,
      "bucket": 1,
      "display_name": "Triceps"
    }
  ]
}

Thereā€™s also an endpoint that lets you pull summary data for the last several workouts.

If you still want to use the HA integration, you could either have InfluxDB key off of the workout start time and end time or adjust your query to group by those same parameters. The DB would then consider any workout with the same start and end time as an existing entry to be a duplicate that would be ignored.

Updated to the newest HACs version today which is now configurable via HA integrations. Updated all the applicable entities/dashboards and have been able to properly show, amongst other things, the workout status and workout type. The only problem is that when a workout starts, everything (all attributes) go into an unavailable status and as I result, my automations do not work. Once the workout is complete, however, it does update the dashboard with all the correct/latest workout states. Is this a bug that others are experiencing as well or am I missing something?

Nope, itā€™s a bug. Sorry about that! I submitted a PR to fix this at https://github.com/edwork/homeassistant-peloton-sensor/pull/43. @edwork, can you take a look?

(Thereā€™s a second, unrelated PR, as well: https://github.com/edwork/homeassistant-peloton-sensor/pull/42.)

Outstandingā€¦all appears to be working fine now with the fix implemented. Great work, thank you!!

1 Like

That makes sense. Good idea, Iā€™ll look into scripting it outside of HA. Perhaps I could have HA trigger an automation when I finish a workout that would then push the summary data into Influx. Thanks!

1 Like

Hi all,

quick question, can someone confirm if the new version of the integration (8.0+) can support multiple profiles? It doesnā€™t seem possible via the gui setup screen and it used to be possible using the old yaml config. If itā€™s not then Iā€™ll look to raise as an issue as this was a useful feature in my setup.

Cheers
Spence

It does. You should be able to just add the integration a second time using the GUI, logging in as the new user:

image

If that doesnā€™t work, try deleting the existing GUI-created profile and make sure that you delete your old config from configuration.yaml. Then reboot and add from the GUI again.

2 Likes

That worked, cheers!

Awesome! For anyone running into similar weird issues with the integration, just be aware that you need to manually switch from the .yaml-based config to the GUI-based config for the plugin to work reliably.

Iā€™m having issues running the HACS integration. I previously had the custom_components integration working. Iā€™ve deleted that folder from custom_components, deleted the config.yaml credentials, rebooted, then added the repository in HACS and installed 0.8.1, but after a reboot I can not find ā€œPelotonā€ in ā€œAdd Integrationā€. I have many HACS components running, so Iā€™m confident Iā€™ve followed the right steps (until Iā€™m notā€¦).

Any chance thereā€™s an issue in Home Assistant 2022.8.6 that I may be running into? Maybe something else I missed?

I see the last post here is from July so thatā€™s the only thing I can see different.

I tried again today and Peloton now drops down. I didnā€™t change anything, so seems to have just taken time to propagate. Weird.

I cantā€™ seem to get the BPM sensor to work. My goal is to adjust overhead RGB lights based on heart rate BPM.

Hereā€™s my sensor

- sensor:
    peloton_bpm:
      icon_template: mdi:heart-pulse
      friendly_name: "Peloton BPM"
      unit_of_measurement: BPM
      value_template: >
        {{ state_attr('sensor.name_here', "Heart Rate Bpm")}}

Any ideas?

Anyone got the Yesoul bike working? I know this is a different bike but I think the Yesoul may be a copy of it?

I cant get this add on to work keep getting

Error

Config flow could not be loaded: 500 Internal Server Error Server got itself in trouble

Anyone any ideas please ?

Hi, Iā€™m brand new to HA. I was wondering if thereā€™s a way to have RGB lighting around my Peloton to light up in the same colour of light that the studio on screen is using to make it feel more immersive?