Code Monkey home page Code Monkey logo

Comments (20)

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

joelbarrette avatar joelbarrette commented on June 26, 2024

Hi Tijmen,

Thanks for your quick response! I'll use this as a starting point, I'd already started exploring some of the js objects so thank you for pointing me right to where the data I need is.

I shouldn't have much trouble writing the code to generate the TCX format from the data. TCX is the simplest but only supports a few data streams so if I figure that out I may also look into .FIT which is more complicated but extensible. This would open up the option of including the split pace as a data stream.

I think this will have significant utility, from what I've seen no other applications can do this for the older PM3 and PM4s (which is what I have) not even RowPro, they only provide a summary entry which is useless if you want to compare HR and Power graphs within efforts of a single activity which I do all the time.

Joel

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

joelbarrette avatar joelbarrette commented on June 26, 2024

Hi Tijmen,

I was successful in building a working TCX file content but I've run into a few bumps.

First thing is for some reason, the timestamp I'm trying to use here: pm3.log.logs[0].timeStampDate seem to just be the current date. To generate the TCX properly I'll need the actual date of the activity. So in the meantime I'm just setting the date manually.

I'm also wondering if there's a way for me to get the map location as well, if I had lat/long per stroke that could be included as well which would allow for speed splits. I could also just hardcode it at random location and just increment either the lat/long by converting the value of pm3.log.logs,stroke,distance to degrees and using that.

Let me know what you think, also unfortunately all the spaces/indetations in the strings are necessary, Garmin connect can handle TCX without indents but Strava is more picky.

      var logs = pm3.log.logs;
      var testlog = logs[0]
      var startDate = new Date('February 16, 2024 2:49:00')
      var TCX = "";
      // appending the start of the XML
      TCX += "\<?xml version=\"1.0\" encoding=\"UTF-8\"?\>\n\<TrainingCenterDatabase\n  xsi:schemaLocation=\"http:\/\/www.garmin.com\/xmlschemas\/TrainingCenterDatabase\/v2 http:\/\/www.garmin.com\/xmlschemas\/TrainingCenterDatabasev2.xsd\"\n  xmlns:ns5=\"http:\/\/www.garmin.com\/xmlschemas\/ActivityGoals\/v1\"\n  xmlns:ns3=\"http:\/\/www.garmin.com\/xmlschemas\/ActivityExtension\/v2\"\n  xmlns:ns2=\"http:\/\/www.garmin.com\/xmlschemas\/UserProfile\/v2\"\n  xmlns=\"http:\/\/www.garmin.com\/xmlschemas\/TrainingCenterDatabase\/v2\"\n  xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xmlns:ns4=\"http:\/\/www.garmin.com\/xmlschemas\/ProfileExtension\/v1\"\>\n\  <Activities\>\n\    <Activity Sport=\"Other\"\>\n"
      // adding date as ID
      TCX += "      \<Id\>" + startDate.toISOString() + "\<\/Id\>\n"
      // adding lap time
      TCX += "      \<Lap StartTime=\"" + startDate.toISOString() +  "\"\>\n"
      // adding duration
      TCX += "        \<TotalTimeSeconds\>" + testlog.endDuration/1000 +  "\<\/TotalTimeSeconds\>\n"
      // adding other parts
      TCX += "        \<TriggerMethod\>Manual\<\/TriggerMethod\>\n"
      TCX += "        \<Track\>\n"

      //iterate accross strokes  
      for(let stroke = 0; stroke < testlog.strokes.length; stroke += 2){
        TCX += "          \<Trackpoint\>\n"
        // adding timestamp, this is where I'm just hardcoding it the same as above
        var timestamp = new Date('February 16, 2024 2:49:00')
        timestamp.setSeconds(startDate.getSeconds() + (testlog.strokes[stroke].workTime.getMilliseconds() + testlog.strokes[stroke].workTime.getSeconds()*1000 + (testlog.strokes[stroke].workTime.getMinutes()*60000))/1000)

        TCX += "            \<Time\>" + timestamp.toISOString() + "\<\/Time\>\n"
        // adding HR
        TCX += "            \<HeartRateBpm\>\n             \<Value\>" + testlog.strokes[stroke].heartRate  + "\<\/Value\>\n            \<\/HeartRateBpm\>\n"  
        // adding Stroke Rate
        TCX += "            \<Cadence\>" + testlog.strokes[stroke].strokesPerMinute + "\<\/Cadence\>\n"
        // adding power
        TCX += "            \<Extensions\>\n              \<ns3:TPX\>\n                \<ns3:Watts\>"+ testlog.strokes[stroke].power.toString() +"\<\/ns3:Watts\>\n              \<\/ns3:TPX\>\n            \<\/Extensions\>\n"
        // close trackpoint tag
        TCX += "          \<\/Trackpoint\>\n"
      }

      TCX += "        \<\/Track\>\n      \<\/Lap\>\n"
      TCX += "      \<Creator xsi:type=\"Device_t\"\>\n        \<Name\>--No GPS SELECTED--\<\/Name\>\n        \<UnitId\>0\<\/UnitId\>\n        \<ProductID\>0\<\/ProductID\>\n        \<Version\>\n          \<VersionMajor\>0\<\/VersionMajor\>\n          \<VersionMinor\>0\<\/VersionMinor\>\n          \<BuildMajor\>1\<\/BuildMajor\>\n          \<BuildMinor\>1\<\/BuildMinor\>\n        \<\/Version\>\n      \<\/Creator\>\n    \<\/Activity\>\n            \<\/Activities\>\n            \<Author xsi:type=\"Application_t\"\>\n    \<Name\>GOTOES STRAVA TOOLS\<\/Name\>\n    \<Build\>\n      \<Version\>\n        \<VersionMajor\>23\<\/VersionMajor\>\n        \<VersionMinor\>9\<\/VersionMinor\>\n        \<BuildMajor\>1\<\/BuildMajor\>\n        \<BuildMinor\>1\<\/BuildMinor\>\n      \<\/Version\>\n    \<\/Build\>\n    \<LangID\>en\<\/LangID\>\n    \<PartNumber\>1\<\/PartNumber\>\n  \<\/Author\>\n\<\/TrainingCenterDatabase\>"
      console.log(TCX)

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

I have published 5.4.0 with the distanceToMapPosition feature.

from ergometerspace.

joelbarrette avatar joelbarrette commented on June 26, 2024

Hi Tijmen,

Thanks again for you work getting me up and running. I really appreciate the part about Typescript strings, I'd figured there was something like that but I haven't coded in JS/TS in a while.

module tijmenvangulik_examples_valuewidgets { //make a name space to prevent mix ups

    class ExamplePlugin extends ExternalPlugin {
        private _oldExport  : any;
        
        public  exportCSV(exportItem : pm3.WorkoutLogItem) : string {

            //Forming first part of the TCX file
            var TCXcontent = `<?xml version="1.0" encoding="UTF-8"?><TrainingCenterDatabase
  xsi:schemaLocation="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd"
  xmlns:ns5="http://www.garmin.com/xmlschemas/ActivityGoals/v1"
  xmlns:ns3="http://www.garmin.com/xmlschemas/ActivityExtension/v2"
  xmlns:ns2="http://www.garmin.com/xmlschemas/UserProfile/v2"
  xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns4="http://www.garmin.com/xmlschemas/ProfileExtension/v1">
  <Activities>
    <Activity Sport="Other">
      <Id>${exportItem.timeStampDate.toISOString()}</Id>    
      <Lap StartTime="${exportItem.timeStampDate.toISOString()}">
        <TotalTimeSeconds>${exportItem.endDuration/1000}</TotalTimeSeconds>
        <DistanceMeters>${exportItem.distance}</DistanceMeters>
        <TriggerMethod>Distance</TriggerMethod>
        <Track\>
`          
            //Creating TCX trackpoints via iterating across log.strokes 
            var startDate = new Date(exportItem.timeStampDate.toISOString())
            for(let stroke = 1; stroke < exportItem.strokes.length-1; stroke += 2){
                var timestamp = new Date(exportItem.timeStampDate.toISOString())
                timestamp.setSeconds(startDate.getSeconds() + (exportItem.strokes[stroke].workTime.getMilliseconds() + exportItem.strokes[stroke].workTime.getSeconds()*1000 + (exportItem.strokes[stroke].workTime.getMinutes()*60000))/1000)
                TCXcontent += `          <Trackpoint>
            <Time>${timestamp.toISOString()}</Time>
            <Position>
              <LatitudeDegrees>${ergometerWidgets.mapWidget.distanceToMapPosition(exportItem.strokes[stroke].distance).latitude}</LatitudeDegrees>
              <LongitudeDegrees>${ergometerWidgets.mapWidget.distanceToMapPosition(exportItem.strokes[stroke].distance).longitude}</LongitudeDegrees>
            </Position>
            <DistanceMeters>${exportItem.strokes[stroke].distance}</DistanceMeters>
            <HeartRateBpm>
             <Value>${exportItem.strokes[stroke].heartRate}</Value>
            </HeartRateBpm>
            <Cadence>${exportItem.strokes[stroke].strokesPerMinute}</Cadence>
            <Extensions>
              <ns3:TPX>
                <ns3:Watts>${exportItem.strokes[stroke].power.toString()}</ns3:Watts>            
              </ns3:TPX>
            </Extensions>          
          </Trackpoint>          
`
            }

            // adding TCX ending content
            TCXcontent += `        </Track>
      </Lap>
      <Creator xsi:type="Device_t">
        <Name>--No GPS SELECTED--</Name>
        <UnitId>0</UnitId>
        <ProductID>0</ProductID>
        <Version>
          <VersionMajor>0</VersionMajor>
          <VersionMinor>0</VersionMinor>
          <BuildMajor>1</BuildMajor>
          <BuildMinor>1</BuildMinor>
        </Version>
      </Creator>
    </Activity>
   </Activities>
   <Author xsi:type="Application_t">
    <Name>GOTOES STRAVA TOOLS</Name>
    <Build>
      <Version>
        <VersionMajor>23</VersionMajor>
        <VersionMinor>9</VersionMinor>
        <BuildMajor>1</BuildMajor>
        <BuildMinor>1</BuildMinor>
      </Version>
    </Build>
    <LangID>en</LangID>
    <PartNumber>1</PartNumber>
  </Author>
</TrainingCenterDatabase>`

          return TCXcontent
        }
        public init() {
            this._oldExport=pm3.log.exportCSV;
            pm3.log.exportCSV=this.exportCSV.bind(this);
        }
        public remove() {
            pm3.log.exportCSV=this._oldExport;
        }
    }

    var plugin : ExamplePlugin;
    plugin = new ExamplePlugin();
}

This now works almost as expected, the only thing is the exported file is a .CSV and it also includes the CSV export at the end of the file.

The other thing that I found was when iterating across log.logs.strokes is that there seems to be duplicate entries, ie, every second stroke has the same power, hr, timestamp etc. (I'm assuming this is for Power/rest halves of the stroke) To get around this I was iterating my for loop ++2 but after I added the Lat/long part I found that the final stroke has a distance of "0" which causes the lat/long lookup to fail. Is there a better way I could be doing this?

Thanks,
Joel

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

joelbarrette avatar joelbarrette commented on June 26, 2024

Hi Tijmen,

Thanks for the update, I'll copy my code into that new plugin template. Here's a file that shows the duplicate strokes I was referring to. It's a 1k Erg I did yesterday on my C2 PM4:
WorkoutData20242191351.JSON

What I see is that after the first stroke, every second stroke has duplicate data, ie. stroke 2 & 3, I'm assuming that's because item 2 is the power phase and item 3 is the recovery phase. Since I don't need that extra data currently I'm just iterating across every power entry and then ignoring the last one since it has a distance of 0 for some reason. It works fine pretty much other than the total distance is off by one stroke. I could also add a case at the end that takes the total distance for the last stroke just so that it looks nicer. Up to you.

Item 2 & 3

{
                    "splitTime": "1970-01-01T00:01:43.000Z",
                    "power": 322,
                    "strokesPerMinute": 34,
                    "workTime": "1970-01-01T00:00:02.710Z",
                    "distance": 12,
                    "heartRate": 98,
                    "recoveryRatio": 0,
                    "strokeCount": 2,
                    "strokeDistance": 0,
                    "forceCurve": [
                        85,
                        30,
                        58,
                        14,
                        47,
                        52,
                        0,
                        0
                    ]
                },
                {
                    "splitTime": "1970-01-01T00:01:43.000Z",
                    "power": 322,
                    "strokesPerMinute": 34,
                    "workTime": "1970-01-01T00:00:02.710Z",
                    "distance": 12,
                    "heartRate": 98,
                    "recoveryRatio": 71.28666666666666,
                    "strokeCount": 2,
                    "strokeDistance": 0
                },

Last item in the strokes array:

                {
                    "splitTime": "1970-01-01T00:01:52.000Z",
                    "power": 250,
                    "strokesPerMinute": 27,
                    "workTime": "1970-01-01T00:03:29.430Z",
                    "distance": 0,
                    "heartRate": 163,
                    "recoveryRatio": 72.045,
                    "strokeCount": 95,
                    "strokeDistance": 0
                }

from ergometerspace.

joelbarrette avatar joelbarrette commented on June 26, 2024

Scratch what I said about the distance problems, I was able to fix it by changing how my for loop iterates. Here's the final code so far, I had to change the file type to TCX but other than that it's looking good:

module joel_export_TCX { //make a name space to prevent mix ups


    class ExamplePlugin extends ExternalPlugin {
        private _oldExport  : any;

        public  doExport(exportItem : pm3.WorkoutLogItem) : string {


            //Forming first part of the TCX file
            var TCXcontent = `<?xml version="1.0" encoding="UTF-8"?><TrainingCenterDatabase
  xsi:schemaLocation="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd"
  xmlns:ns5="http://www.garmin.com/xmlschemas/ActivityGoals/v1"
  xmlns:ns3="http://www.garmin.com/xmlschemas/ActivityExtension/v2"
  xmlns:ns2="http://www.garmin.com/xmlschemas/UserProfile/v2"
  xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns4="http://www.garmin.com/xmlschemas/ProfileExtension/v1">
  <Activities>
    <Activity Sport="Other">
      <Id>${exportItem.timeStampDate.toISOString()}</Id>    
      <Lap StartTime="${exportItem.timeStampDate.toISOString()}">
        <TotalTimeSeconds>${exportItem.endDuration/1000}</TotalTimeSeconds>
        <DistanceMeters>${exportItem.distance}</DistanceMeters>
        <TriggerMethod>Distance</TriggerMethod>
        <Track\>
`          
            //Creating TCX trackpoints via iterating across log.strokes 
            var startDate = new Date(exportItem.timeStampDate.toISOString())
            for(let stroke = 0; stroke < exportItem.strokes.length-1; stroke += 2){
                var timestamp = new Date(exportItem.timeStampDate.toISOString())
                timestamp.setSeconds(startDate.getSeconds() + (exportItem.strokes[stroke].workTime.getMilliseconds() + exportItem.strokes[stroke].workTime.getSeconds()*1000 + (exportItem.strokes[stroke].workTime.getMinutes()*60000))/1000)
                TCXcontent += `          <Trackpoint>
            <Time>${timestamp.toISOString()}</Time>
            <Position>
              <LatitudeDegrees>${ergometerWidgets.mapWidget.distanceToMapPosition(exportItem.strokes[stroke].distance).latitude}</LatitudeDegrees>
              <LongitudeDegrees>${ergometerWidgets.mapWidget.distanceToMapPosition(exportItem.strokes[stroke].distance).longitude}</LongitudeDegrees>
            </Position>
            <DistanceMeters>${exportItem.strokes[stroke].distance}</DistanceMeters>
            <HeartRateBpm>
             <Value>${exportItem.strokes[stroke].heartRate}</Value>
            </HeartRateBpm>
            <Cadence>${exportItem.strokes[stroke].strokesPerMinute}</Cadence>
            <Extensions>
              <ns3:TPX>
                <ns3:Watts>${exportItem.strokes[stroke].power.toString()}</ns3:Watts>            
              </ns3:TPX>
            </Extensions>          
          </Trackpoint>          
`
            }

            // adding TCX ending content
            TCXcontent += `        </Track>
      </Lap>
      <Creator xsi:type="Device_t">
        <Name>--No GPS SELECTED--</Name>
        <UnitId>0</UnitId>
        <ProductID>0</ProductID>
        <Version>
          <VersionMajor>0</VersionMajor>
          <VersionMinor>0</VersionMinor>
          <BuildMajor>1</BuildMajor>
          <BuildMinor>1</BuildMinor>
        </Version>
      </Creator>
    </Activity>
   </Activities>
   <Author xsi:type="Application_t">
    <Name>GOTOES STRAVA TOOLS</Name>
    <Build>
      <Version>
        <VersionMajor>23</VersionMajor>
        <VersionMinor>9</VersionMinor>
        <BuildMajor>1</BuildMajor>
        <BuildMinor>1</BuildMinor>
      </Version>
    </Build>
    <LangID>en</LangID>
    <PartNumber>1</PartNumber>
  </Author>
</TrainingCenterDatabase>`

          return TCXcontent

        }
        public init() {
            pm3.log.registerCustomExport(this,"Demo export","demo.tcx","application/tcx",this.doExport.bind(this));
        }
        public remove() {
            pm3.log.deRegisterCustomExport(this.doExport);
        }
    }

    var plugin : ExamplePlugin;
    plugin = new ExamplePlugin();

}

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

The next release will include the improved version. Can I mention your name in the credits?

from ergometerspace.

tijmenvangulik avatar tijmenvangulik commented on June 26, 2024

I have not received feedback for the last days, I have released the feature, so I close the issue.

from ergometerspace.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.