Skip to content

Instantly share code, notes, and snippets.

@Radiokot
Created March 10, 2021 20:32
Show Gist options
  • Save Radiokot/5ee757a0102019b282bfa3c8b1c361be to your computer and use it in GitHub Desktop.
Save Radiokot/5ee757a0102019b282bfa3c8b1c361be to your computer and use it in GitHub Desktop.
Convert Arduino speedometer serial output to the Garmin TCX file for Strava
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