Skip to content

Instantly share code, notes, and snippets.

@marguslt
Last active January 28, 2021 10:21
Show Gist options
  • Save marguslt/a79ea204f99b45ab015b6ed1ff7529a4 to your computer and use it in GitHub Desktop.
Save marguslt/a79ea204f99b45ab015b6ed1ff7529a4 to your computer and use it in GitHub Desktop.
Exploring Movesount workout handling. #suunto #movescount
mrg@TPXT:~/mc$ # http - https://httpie.org/ - a user-friendly command-line HTTP client for the API era
mrg@TPXT:~/mc$ # jq - https://stedolan.github.io/jq/ - a lightweight and flexible command-line JSON processor
mrg@TPXT:~/mc$ # starting with a clean profile, restored defaults watch values at movescount.com
mrg@TPXT:~/mc$ # load variables
mrg@TPXT:~/mc$ . mc_env.sh
mrg@TPXT:~/mc$ # encode Workout json
mrg@TPXT:~/mc$ jq -rc . 00-myworkout.json | base64 -w0 > 01-myworkout.base64
mrg@TPXT:~/mc$ # replace Name and Source in rule template
mrg@TPXT:~/mc$ sed -e "s/RULE_NAME/My Workout/g" -e "s/RULE_SRC/$(<01-myworkout.base64)/g" < 10-rule_template.json > 02-myworkout_rule.json
mrg@TPXT:~/mc$ # list active Workouts (guidance type rules)
mrg@TPXT:~/mc$ http get https://uiservices.movescount.com/userdevices/$AMBIT_SN/rules type==guidance appkey=="$APPKEY" userkey=="$USERKEY" email=="$MCUMAIL" User-Agent:"$MCUAGENT" | jq ".[] | del(.Binary, .Source)"
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T19:02:04.3",
"Name": "uus",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644506,
"SelfURI": "rules/13644506",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T13:37:36.0",
"Name": "goobay",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644368,
"SelfURI": "rules/13644368",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
mrg@TPXT:~/mc$ # list all Workouts (guidance type rules)
mrg@TPXT:~/mc$ http get https://uiservices.movescount.com/rules/private type==guidance appkey=="$APPKEY" userkey=="$USERKEY" email=="$MCUMAIL" User-Agent:"$MCUAGENT" | jq ".[] | del(.Binary, .Source)"
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T20:29:25.9",
"Name": "k6igeuuem",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644540,
"SelfURI": "rules/13644540",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T19:02:04.3",
"Name": "uus",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644506,
"SelfURI": "rules/13644506",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T13:37:36.0",
"Name": "goobay",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644368,
"SelfURI": "rules/13644368",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
mrg@TPXT:~/mc$ # add new workout
mrg@TPXT:~/mc$ jq -cr . 02-myworkout_rule.json | http post https://uiservices.movescount.com/rules/ appkey=="$APPKEY" userkey=="$USERKEY" email=="$MCUMAIL" User-Agent:"$MCUAGENT"
HTTP/1.1 201 Created
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 47
Content-Type: application/json; charset=utf-8
Date: Wed, 30 Sep 2020 19:15:57 GMT
Expires: -1
Pragma: no-cache
Server: Microsoft-IIS/8.5
Via: 1.1 f9fdb36a35b271b2043714f4c4655495.cloudfront.net (CloudFront)
X-Amz-Cf-Id: kPMYYmeCvnvHqK7gHRmkdUwvDiwEeTTyiw8AB3V3NM8Rp58Kc21TjQ==
X-Amz-Cf-Pop: ARN53
X-AspNet-Version: 4.0.30319
X-Cache: Miss from cloudfront
X-Powered-By: ASP.NET
{
"RuleID": 13670761,
"RuleURI": "rules/13670761"
}
mrg@TPXT:~/mc$ # list all Workouts (guidance type rules), new rule should be added
mrg@TPXT:~/mc$ http get https://uiservices.movescount.com/rules/private type==guidance appkey=="$APPKEY" userkey=="$USERKEY" email=="$MCUMAIL" User-Agent:"$MCUAGENT" | jq ".[] | del(.Binary, .Source)"
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-09-30T19:15:56.4",
"Name": "My Workout",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13670761,
"SelfURI": "rules/13670761",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T20:29:25.9",
"Name": "k6igeuuem",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644540,
"SelfURI": "rules/13644540",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T19:02:04.3",
"Name": "uus",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644506,
"SelfURI": "rules/13644506",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T13:37:36.0",
"Name": "goobay",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644368,
"SelfURI": "rules/13644368",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
mrg@TPXT:~/mc$ # list active Workouts (guidance type rules), same - adding a rule does not change device settings
mrg@TPXT:~/mc$ http get https://uiservices.movescount.com/userdevices/$AMBIT_SN/rules type==guidance appkey=="$APPKEY" userkey=="$USERKEY" email=="$MCUMAIL" User-Agent:"$MCUAGENT" | jq ".[] | del(.Binary, .Source)"
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T19:02:04.3",
"Name": "uus",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644506,
"SelfURI": "rules/13644506",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-08-01T13:37:36.0",
"Name": "goobay",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13644368,
"SelfURI": "rules/13644368",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
mrg@TPXT:~/mc$ # replace old workout list with a new rule 13670761
mrg@TPXT:~/mc$ # set watch S/N and rule list in settings template, pipe through httpie
mrg@TPXT:~/mc$ sed -e "s/AMBIT_SN/$AMBIT_SN/g" -e "s/RULE_ID_LIST/13670761/g" < 20-settings_template.json | jq -cr . |
> http put https://uiservices.movescount.com/userdevices/$AMBIT_SN/ resetchangedsettings==true appkey=="$APPKEY" userkey=="$USERKEY" email=="$
MCUMAIL" User-Agent:"$MCUAGENT"
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 0
Date: Wed, 30 Sep 2020 19:38:23 GMT
Expires: -1
Pragma: no-cache
Server: Microsoft-IIS/8.5
Via: 1.1 482a78f386817c034ae15d18a92d3c98.cloudfront.net (CloudFront)
X-Amz-Cf-Id: vT95S1LInskSGKpFrEwaEMW62P0rwI7jST2n-Y7ELYvscs5tQETCIw==
X-Amz-Cf-Pop: ARN53
X-AspNet-Version: 4.0.30319
X-Cache: Miss from cloudfront
X-Powered-By: ASP.NET
mrg@TPXT:~/mc$ # list active Workouts (guidance type rules)
mrg@TPXT:~/mc$ http get https://uiservices.movescount.com/userdevices/$AMBIT_SN/rules type==guidance appkey=="$APPKEY" userkey=="$USERKEY" email=="$MCUMAIL" User-Agent:"$MCUAGENT" | jq ".[] | del(.Binary, .Source)"
{
"ActivityID": 1,
"Category": "guidance",
"Description": null,
"IsPublic": false,
"LastModifiedDate": "2020-09-30T19:15:56.4",
"Name": "My Workout",
"OutputFormat": "onedecimal",
"Postfix": null,
"Prefix": null,
"RuleID": 13670761,
"SelfURI": "rules/13670761",
"TargetVirtualMachineVersion": "0.08, build 15.8.18.0",
"Type": "guidance"
}
mrg@TPXT:~/mc$
{
"name": "My Workout",
"steps": [
{
"duration": {
"durationName": "time",
"value": 600
},
"target": {
"targetName": "hr",
"valueRange": {
"max": 2.1666666666666665,
"min": 2
}
},
"text": "Easy jogging",
"type": {
"typeName": "warmup"
}
},
{
"type": {
"typeName": "repeatStart",
"value": 3
}
},
{
"duration": {
"durationName": "distance",
"value": 1000
},
"target": {
"targetName": "pace",
"valueRange": {
"max": 0.25,
"min": 0.27
}
},
"text": "Run fast for 1km",
"type": {
"typeName": "interval"
}
},
{
"duration": {
"durationName": "hr_below",
"value": 2.1666666666666665
},
"target": {
"targetName": "none",
"valueRange": {
"max": 0,
"min": 0
}
},
"text": "Walk to recover",
"type": {
"typeName": "recovery"
}
},
{
"type": {
"typeName": "repeatEnd"
}
},
{
"duration": {
"durationName": "time",
"value": 600
},
"target": {
"targetName": "hr",
"valueRange": {
"max": 1.8333333333333333,
"min": 1.3333333333333333
}
},
"text": "Walk to cool down",
"type": {
"typeName": "cooldown"
}
}
],
"workoutDescription": "Basic running workout"
}
eyJuYW1lIjoiTXkgV29ya291dCIsInN0ZXBzIjpbeyJkdXJhdGlvbiI6eyJkdXJhdGlvbk5hbWUiOiJ0aW1lIiwidmFsdWUiOjYwMH0sInRhcmdldCI6eyJ0YXJnZXROYW1lIjoiaHIiLCJ2YWx1ZVJhbmdlIjp7Im1heCI6Mi4xNjY2NjY2NjY2NjY2NjY1LCJtaW4iOjJ9fSwidGV4dCI6IkVhc3kgam9nZ2luZyIsInR5cGUiOnsidHlwZU5hbWUiOiJ3YXJtdXAifX0seyJ0eXBlIjp7InR5cGVOYW1lIjoicmVwZWF0U3RhcnQiLCJ2YWx1ZSI6M319LHsiZHVyYXRpb24iOnsiZHVyYXRpb25OYW1lIjoiZGlzdGFuY2UiLCJ2YWx1ZSI6MTAwMH0sInRhcmdldCI6eyJ0YXJnZXROYW1lIjoicGFjZSIsInZhbHVlUmFuZ2UiOnsibWF4IjowLjI1LCJtaW4iOjAuMjd9fSwidGV4dCI6IlJ1biBmYXN0IGZvciAxa20iLCJ0eXBlIjp7InR5cGVOYW1lIjoiaW50ZXJ2YWwifX0seyJkdXJhdGlvbiI6eyJkdXJhdGlvbk5hbWUiOiJocl9iZWxvdyIsInZhbHVlIjoyLjE2NjY2NjY2NjY2NjY2NjV9LCJ0YXJnZXQiOnsidGFyZ2V0TmFtZSI6Im5vbmUiLCJ2YWx1ZVJhbmdlIjp7Im1heCI6MCwibWluIjowfX0sInRleHQiOiJXYWxrIHRvIHJlY292ZXIiLCJ0eXBlIjp7InR5cGVOYW1lIjoicmVjb3ZlcnkifX0seyJ0eXBlIjp7InR5cGVOYW1lIjoicmVwZWF0RW5kIn19LHsiZHVyYXRpb24iOnsiZHVyYXRpb25OYW1lIjoidGltZSIsInZhbHVlIjo2MDB9LCJ0YXJnZXQiOnsidGFyZ2V0TmFtZSI6ImhyIiwidmFsdWVSYW5nZSI6eyJtYXgiOjEuODMzMzMzMzMzMzMzMzMzMywibWluIjoxLjMzMzMzMzMzMzMzMzMzMzN9fSwidGV4dCI6IldhbGsgdG8gY29vbCBkb3duIiwidHlwZSI6eyJ0eXBlTmFtZSI6ImNvb2xkb3duIn19XSwid29ya291dERlc2NyaXB0aW9uIjoiQmFzaWMgcnVubmluZyB3b3Jrb3V0In0K
{
"ActivityID": 1,
"Category": "guidance",
"IsPublic": false,
"Name": "My Workout",
"OutputFormat": "onedecimal",
"Source": "eyJuYW1lIjoiTXkgV29ya291dCIsInN0ZXBzIjpbeyJkdXJhdGlvbiI6eyJkdXJhdGlvbk5hbWUiOiJ0aW1lIiwidmFsdWUiOjYwMH0sInRhcmdldCI6eyJ0YXJnZXROYW1lIjoiaHIiLCJ2YWx1ZVJhbmdlIjp7Im1heCI6Mi4xNjY2NjY2NjY2NjY2NjY1LCJtaW4iOjJ9fSwidGV4dCI6IkVhc3kgam9nZ2luZyIsInR5cGUiOnsidHlwZU5hbWUiOiJ3YXJtdXAifX0seyJ0eXBlIjp7InR5cGVOYW1lIjoicmVwZWF0U3RhcnQiLCJ2YWx1ZSI6M319LHsiZHVyYXRpb24iOnsiZHVyYXRpb25OYW1lIjoiZGlzdGFuY2UiLCJ2YWx1ZSI6MTAwMH0sInRhcmdldCI6eyJ0YXJnZXROYW1lIjoicGFjZSIsInZhbHVlUmFuZ2UiOnsibWF4IjowLjI1LCJtaW4iOjAuMjd9fSwidGV4dCI6IlJ1biBmYXN0IGZvciAxa20iLCJ0eXBlIjp7InR5cGVOYW1lIjoiaW50ZXJ2YWwifX0seyJkdXJhdGlvbiI6eyJkdXJhdGlvbk5hbWUiOiJocl9iZWxvdyIsInZhbHVlIjoyLjE2NjY2NjY2NjY2NjY2NjV9LCJ0YXJnZXQiOnsidGFyZ2V0TmFtZSI6Im5vbmUiLCJ2YWx1ZVJhbmdlIjp7Im1heCI6MCwibWluIjowfX0sInRleHQiOiJXYWxrIHRvIHJlY292ZXIiLCJ0eXBlIjp7InR5cGVOYW1lIjoicmVjb3ZlcnkifX0seyJ0eXBlIjp7InR5cGVOYW1lIjoicmVwZWF0RW5kIn19LHsiZHVyYXRpb24iOnsiZHVyYXRpb25OYW1lIjoidGltZSIsInZhbHVlIjo2MDB9LCJ0YXJnZXQiOnsidGFyZ2V0TmFtZSI6ImhyIiwidmFsdWVSYW5nZSI6eyJtYXgiOjEuODMzMzMzMzMzMzMzMzMzMywibWluIjoxLjMzMzMzMzMzMzMzMzMzMzN9fSwidGV4dCI6IldhbGsgdG8gY29vbCBkb3duIiwidHlwZSI6eyJ0eXBlTmFtZSI6ImNvb2xkb3duIn19XSwid29ya291dERlc2NyaXB0aW9uIjoiQmFzaWMgcnVubmluZyB3b3Jrb3V0In0K",
"Type": "guidance"
}
{
"ActivityID": 1,
"Category": "guidance",
"IsPublic": false,
"Name": "RULE_NAME",
"OutputFormat": "onedecimal",
"Source": "RULE_SRC",
"Type": "guidance"
}
{
"DeviceName": "Emu",
"FirmwareVersion": "2.4.17",
"HardwareVersion": "69.4.17414",
"SerialNumber": "AMBIT_SN",
"Settings": {
"RuleIDs": [
RULE_ID_LIST
]
}
}
#Watch Serial number:
AMBIT_SN=...
#AppKey (for Suuntolink: %LocalAppData%\Suuntolink\app-3.1.4\resources\app\production.json )
APPKEY=...
#UserKey (for Suuntolink: %AppData%\Suuntolink\suuntolink_data.json )
USERKEY=...
#Mail used with MC account
MCMAIL=...
#User-Agent used for MC queries:
MCUAGENT="Komposti/2.29.0 (Android=5.1.1) ArREST/1.0 libcurl/7.47.0"
@dgvalde
Copy link

dgvalde commented Oct 16, 2020

Wow, this is so amazing, you just made my life easier! many thanks for this feature.

Are these steps arelady part of any application to use them more easily?
Do you plan to integrate this somewhere?

The best thing is that while on the Smartphone app it only lets you set power steps in hundreds (e.g. 200 - 300) on the file I can set any power target (e.g. 262 - 287) 👍

Many thanks

@dgvalde
Copy link

dgvalde commented Oct 16, 2020

Hey I just created few very simple scripts to upload and download workouts following your steps.
Check here: https://gist.github.com/dgvalde/4bb9a9dc2162c27440a978b217c01b7e

Many thanks!

P.D: I notice that you have a typo on the http calls. You have $MCUMAIL instead of $MCMAIL. Anyhow, it seems it still works without email.

@marguslt
Copy link
Author

marguslt commented Oct 16, 2020

@dgvalde 👍

Though a word of warning :/ - the way I'm updating settings here ( setting only RuleIDs array ) might have a slight negative effect on your Movescount settings. At least I managed to clear all my deactivated sport modes somehow, and its probably due this partial Settings JSON or resetchangedsettings request parameter. Sending back complete Settings JSON is probably a safer option. And creating a separate Movescount account for those experiments can't hurt.

I haven't built anything around those bits yet, it was more of an attempt to explore and document Workout handling before Movescount app access to MC service was closed. And as some folks at forums.suunto.com were interested, I had a reason to give it a bit of structure.

Though I toyed around with few ideas, like implementing a Workout builder as Chrome extension so it would extend Movescount.com; or as a patch to Suuntolink (it's Electron app, should be doable), so it would be kind of integrated into sync tool; or perhaps just integrate it into Openambit. But as I don't feel quite at home with any of those options, it would take a lot more time and effort I'm willing to spare right now.

Oh, and thanks for catching that typo 🙃

BTW, when exploring Movescount Android APK a bit (e.g. wirh jadx), you'll find all the bits to build those Workout JSONs from scratch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment