I recently deployed https://github.com/jaroslawhartman/withings-sync to sync my Withings scale to my Garmin account. Here’s a quick Powershell script I whipped up that runs via Scheduled Tasks on Windows and uses a Discord Webhook to notify you of its progress:
| # function adapted from https://stackoverflow.com/posts/42995301/revisions | |
| Function Execute-Command ($commandTitle, $commandPath, $commandArguments) | |
| { | |
| Try { | |
| $pinfo = New-Object System.Diagnostics.ProcessStartInfo | |
| $pinfo.FileName = $commandPath | |
| $pinfo.RedirectStandardOutput = $true | |
| $pinfo.UseShellExecute = $false | |
| $pinfo.Arguments = $commandArguments | |
| $p = New-Object System.Diagnostics.Process | |
| $p.StartInfo = $pinfo | |
| $p.Start() | Out-Null | |
| [pscustomobject]@{ | |
| commandTitle = $commandTitle | |
| stdout = $p.StandardOutput.ReadToEnd() | |
| } | |
| $p.WaitForExit() | |
| } | |
| Catch { | |
| exit | |
| } | |
| } | |
| # call this with garmin username & password to sync from withings | |
| function withings-sync( $username, $password ) | |
| { | |
| # setup vars | |
| $hoursToPause = 2 | |
| $fromDate = Get-Date | |
| $loop = $true | |
| $countOfTries = 0 | |
| $maxTries = 10 | |
| $user = $username | |
| $pwd = $password | |
| $wsPath = "C:\python38\scripts\withings-sync.exe" | |
| # discord setup | |
| $discordBot = "Withings -> Garmin Sync" | |
| $hookUrl = "https://discord.com/api/webhooks/xxxx" | |
| $content = "{0:HH:mm:ss}: Garmin upload beginning for $user." -f ( (Get-Date) ) | |
| $section = New-DiscordSection -Title "Info" -Description $content -Color LightGrey | |
| # send discord update on start | |
| Write-Host $content | |
| Send-DiscordMessage -webHookUrl $hookUrl -Sections $section -AvatarName $discordBot | |
| do | |
| { | |
| # only retry $maxTries times | |
| if($countOfTries -lt $maxTries) | |
| { | |
| # create args, then try withings-sync upload and capture output | |
| $args = ("–garmin-username ${user} –garmin-password ${pwd} –fromdate {0:yyyy-MM-dd}" -f $fromDate).ToString() | |
| $process = Execute-Command -commandTitle "Withings->Garmin Sync" -commandPath $wsPath -commandArguments $args | |
| $outputText = $process.stdout | |
| Write-Host $outputText | |
| # if upload worked, break loops | |
| if( $outputText.Contains("Fit file uploaded to Garmin Connect") ) | |
| { | |
| $content = "{0:HH:mm:ss}: Garmin upload successful for $user." -f ( (Get-Date) ) | |
| $section = New-DiscordSection -Title "Info" -Description $content -Color Green | |
| $loop = $false | |
| } | |
| # if no measurements, wait and try again later | |
| elseif( $outputText.Contains("No measurements to upload for date or period specified") ) | |
| { | |
| $content = "{0:HH:mm:ss}: No Withings measurements found for $user; sleeping $hoursToPause hour(s) and trying again." -f ( (Get-Date) ) | |
| $section = New-DiscordSection -Title "Alert" -Description $content -Color Yellow | |
| } | |
| # if withings refresh failed | |
| elseif( $outputText.Contains("withings – ERROR") ) | |
| { | |
| $content = "{0:HH:mm:ss}: Withings refresh failed for $user; sleeping $hoursToPause hour(s) and trying again." -f ( (Get-Date) ) | |
| $section = New-DiscordSection -Title "Alert" -Description $content -Color Red | |
| } | |
| # if garmin upload failed | |
| else | |
| { | |
| $content = "{0:HH:mm:ss}: Garmin upload failed for $user; sleeping $hoursToPause hour(s) and trying again." -f ( (Get-Date) ) | |
| $section = New-DiscordSection -Title "Alert" -Description $content -Color Red | |
| } | |
| # send discord update | |
| Write-Host $content | |
| Send-DiscordMessage -webHookUrl $hookUrl -Sections $section -AvatarName $discordBot | |
| # pause for $countOfTries hour(s) and try again | |
| $countOfTries = $countOfTries + 1 | |
| if($loop) { Start-Sleep ( $hoursToPause * 60 * 60 ) } | |
| } | |
| # after #maxTries failures, send discord update and exit the loop | |
| else | |
| { | |
| $content = "{0:HH:mm:ss}: Garmin upload failed $maxTries times, aborting." -f ( (Get-Date) ) | |
| $section = New-DiscordSection -Title "Alert" -Description $content -Color Red | |
| Write-Host $content | |
| Send-DiscordMessage -webHookUrl $hookUrl -Sections $section -AvatarName $discordBot | |
| $loop = $false | |
| } | |
| } | |
| while ($loop -eq $true) | |
| } |
Install the PSDiscord powershell module. Set $hookUrl with your Discord Webhook URL. Setup your path to the Python withings-sync executable in the $wsPath variable, then call the withings-sync function from Powershell after initializing your Withings token with withings-sync manually, as per the github instructions (pass your Garmin credentials as the $username and $password parameters). The script will auto-retry up to $maxTries times every $hoursToPause.
Edited 2021-09-21: removed some code from the Execute-Command function that could cause a deadlock when Garmin spits out its 403 error and changed the order of the if/else statements slightly. Cheers!

Leave a Reply