For the fun of it, I decided to reimplement a basic version of pm5conv in LaymansHex and a bit of Bash - to parse workout data of a Concept2 PM5 monitor.
I basically took the data format description and translated it to LaymansHex definitions and Bash glue:
% for i in `ls | grep -E "(layhex|bash)"`; do echo "==> $i\n"; cat $i; echo; done
==> LogDataAccessTbl.bin.layhex
little endian
: byte[offset*32]
Magic : byte[1]
WorkoutType : uint8
: byte[10]
NoSplits : uint16
: byte[2]
Offset : uint16
: byte[6]
Size : uint16
Index : uint16
: byte[4]
Marker : byte[32]
==> LogDataStorage.bin-Workout1.layhex
big endian
: byte[offset]
: byte[1]
: byte[1]
: byte[2]
: byte[4]
Timestamp : byte[4]
UserID : uint16
: byte[4]
RecordID : uint8
: byte[3]
TotalDuration : uint16
TotalDistance : uint32
SPM : uint8
: byte[1]
SplitSize : uint16
: byte[18]
: byte[splitNo*32]
SplitDistance : uint16
SplitHeartRate : uint8
SplitSPM : uint8
: byte[28]
==> pm5conv.bash
#!/bin/bash
cmd="laymanshex"
accTable="LogDataAccessTbl.bin"
storage="LogDataStorage.bin"
# uses evil eval
function printNonEmpty {
varname='$'"$1"
eval "val=$varname"
if [ "$val" != "" ]; then
echo "$1=$val"
fi
}
function printTimestamp {
timestamp="0x$1"
year="$(( ((timestamp & 0xFE000000) >> 25) + 2000 ))"
day="$(( (timestamp & 0x01F00000) >> 20 ))"
month="$(( (timestamp & 0x000F0000) >> 16 ))"
hour="$(( (timestamp & 0x0000FF00) >> 8 ))"
minute="$(( timestamp & 0x000000FF ))"
printf "Date=%d-%02d-%02d %02d:%02d\n" $year $month $day $hour $minute
}
function printSplit {
outputSplit="$($cmd -nopadding -fvar=offset=$1,splitNo=$2 $storage-Workout$3.layhex $storage 2> /dev/null)"
status=$?
if [ $status -eq 0 ]; then
eval "$outputSplit"
echo
echo "Split $(($2+1))"
echo "----------"
printNonEmpty "SplitDuration"
printNonEmpty "SplitDistance"
printNonEmpty "SplitHeartRate"
printNonEmpty "SplitSPM"
fi
}
function printWorkout {
echo "Workout $1"
echo "============="
outputHeader="$($cmd -nopadding -fvar=offset=$2,splitNo=0 $storage-Workout$3.layhex $storage 2> /dev/null)"
status=$?
if [ $status -eq 0 ]; then
eval "$outputHeader"
printTimestamp "$Timestamp"
printNonEmpty "TotalDuration"
printNonEmpty "TotalDistance"
printNonEmpty "SplitSize"
printNonEmpty "SPM"
j=0
while [ "$j" -lt "$NoSplits" ]; do
printSplit $2 $j $3
j=$((j+1))
done
echo
fi
}
function printAll {
i=0
while : ; do
offset=$(($i))
output="$($cmd -nopadding -fvar=offset=$offset $accTable.layhex $accTable 2>/dev/null)"
status=$?
if [ $status -ne 0 ]; then
break
else
eval "$output"
printWorkout $Index $Offset $WorkoutType
fi
i=$((i+1))
done
}
printAll
Output of pm5conv.bash:
% ./pm5conv.bash
Workout 1
=============
Date=2018-02-27 21:00
TotalDuration=962
TotalDistance=363
SplitSize=3000
SPM=29
Split 1
----------
SplitDistance=363
SplitHeartRate=0
SplitSPM=27
Workout 2
=============
Date=2018-02-28 20:41
TotalDuration=4208
TotalDistance=1559
SplitSize=3000
SPM=27
Split 1
----------
SplitDistance=1121
SplitHeartRate=0
SplitSPM=29
Split 2
----------
SplitDistance=438
SplitHeartRate=0
SplitSPM=26
Workout 3
=============
Date=2018-03-01 18:47
TotalDuration=12058
TotalDistance=4166
SplitSize=3000
SPM=28
Split 1
----------
SplitDistance=1056
SplitHeartRate=0
SplitSPM=29
Split 2
----------
SplitDistance=1060
SplitHeartRate=0
SplitSPM=28
Split 3
----------
SplitDistance=1007
SplitHeartRate=0
SplitSPM=28
Split 4
----------
SplitDistance=1024
SplitHeartRate=0
SplitSPM=29
Split 5
----------
SplitDistance=19
SplitHeartRate=0
SplitSPM=0
[...]
2020-04-28 22:26 UTC