Created
March 10, 2021 20:32
-
-
Save Radiokot/5ee757a0102019b282bfa3c8b1c361be to your computer and use it in GitHub Desktop.
Convert Arduino speedometer serial output to the Garmin TCX file for Strava
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data class RecordPoint( | |
val speedMph: Int, | |
val distanceIncrementMm: Int, | |
val localTime: Date, | |
) | |
@Test | |
fun arduinoToTcx() { | |
//----- CHANGE ME------- | |
val startDay = "2021-03-10" | |
//---------------------- | |
Locale.setDefault(Locale.ENGLISH) | |
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") | |
val inputFilePath = "C:\\Users\\spirit111\\Desktop\\input.txt" | |
val outputFilePath = "C:\\Users\\spirit111\\Desktop\\output.tcx" | |
val points = File(inputFilePath) | |
.bufferedReader(Charsets.UTF_8) | |
.readLines() | |
.mapNotNull { line -> | |
val split = line.split(';', ' ') | |
.takeIf { it.size == 3 } | |
?: return@mapNotNull null | |
RecordPoint( | |
localTime = dateFormat.parse(startDay + "T${split[0]}Z"), | |
speedMph = split.getOrNull(1) | |
?.toIntOrNull() | |
?: return@mapNotNull null, | |
distanceIncrementMm = split.getOrNull(2) | |
?.toIntOrNull() | |
?: return@mapNotNull null, | |
) | |
} | |
File(outputFilePath).outputStream().bufferedWriter(Charsets.UTF_8).use { writer -> | |
writer.write(""" | |
<?xml version="1.0" encoding="UTF-8" standalone="no" ?> | |
<TrainingCenterDatabase xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation | |
="http://www.garmin.com/xmlschemas/ActivityExtension/v2 http://www.garmin.com/xmlschemas/ActivityExtensionv2.xsd http://www.garmin.com/xmlschemas/TrainingCenterDat | |
abase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd"> | |
""".trimIndent()) | |
val startTime = points.first().localTime | |
writer.write( | |
""" | |
| <Activities> | |
| <Activity Sport="Biking"> | |
| <Id>42</Id> | |
| <Lap StartTime="${dateFormat.format(startTime)}"> | |
| | |
""".trimMargin() | |
) | |
val totalTimeSeconds = (points.last().localTime.time - points.first().localTime.time) / 1000 | |
val totalDistanceMeters = points.sumBy { it.distanceIncrementMm } / 1000 | |
writer.write(""" | |
| <TotalTimeSeconds>$totalTimeSeconds</TotalTimeSeconds> | |
| <DistanceMeters>$totalDistanceMeters</DistanceMeters> | |
| <TriggerMethod>Manual</TriggerMethod> | |
| | |
""".trimMargin()) | |
writer.write(""" | |
| <Track> | |
| | |
""".trimMargin()) | |
var distanceMeters = 0.0 | |
points.forEach { point -> | |
distanceMeters += point.distanceIncrementMm.toDouble() / 1000 | |
writer.write(""" | |
| <Trackpoint> | |
| <Time>${dateFormat.format(point.localTime)}</Time> | |
| <DistanceMeters>${"%.2f".format(distanceMeters)}</DistanceMeters> | |
| </Trackpoint> | |
| <Extensions> | |
| <TPX xmlns="https://www8.garmin.com/xmlschemas/ActivityExtensionv2.xsd"> | |
| <Speed>${"%.2f".format(point.speedMph.toDouble() / 3600)}</Speed> | |
| </TPX> | |
| </Extensions> | |
| | |
""".trimMargin()) | |
} | |
writer.write(""" | |
| </Track> | |
| </Lap> | |
| </Activity> | |
| </Activities> | |
|</TrainingCenterDatabase> | |
""".trimMargin()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment