Skip to content

Instantly share code, notes, and snippets.

@victorypoint
Created November 7, 2024 00:01
Show Gist options
  • Select an option

  • Save victorypoint/b814601d37754ee1da35ff8a15ce2788 to your computer and use it in GitHub Desktop.

Select an option

Save victorypoint/b814601d37754ee1da35ff8a15ce2788 to your computer and use it in GitHub Desktop.
Zwift workouts auto-incline and auto-speed control of treadmill via ADB and OCR
' iFit2-Zwift-Workout - Zwift workouts auto-incline and auto-speed control of treadmill via ADB and OCR
' Author: Al Udell
' Revised: November 6, 2024
' To debug - enable wscript.echo and run by cscript in command line
' On Error Resume Next
' Display startup message
createobject("wscript.shell").popup "Ensure treadmill is in manual workout mode with onscreen speed and incline controls visible. " _
& vbCrLf & vbCrLf & "Also ensure Zwift is running in game with workout dashboard showing.", 10, "Warning", 64
' Initialize
Set wso = CreateObject("wscript.shell")
Set fso = CreateObject("Scripting.FileSystemObject")
buttonCount = 8 ' Number of displayed buttons
swipeAmount = 400 ' Y swipe amount
swipeSpeed = 300 ' Swipe speed in ms
oldzwiftSpeed = 0
oldzwiftIncline = 0
' iFit2 wolflog filename (static name, no date included)
infilename2 = "/sdcard/android/data/com.ifit.glassos_service/files/.valinorlogs/log.latest.txt"
' *** Start - NordicTrack C2950 Treadmill ***
' Define button arrays for speed and incline
speedButtons = Array(2, 3, 4, 6, 8, 10, 12, 14, 16, 19)
inclineButtons = Array(-3, 0, 1, 2, 4, 6, 8, 10, 12, 15)
minSpeed = 1
maxSpeed = 19.3
' Define button parameters
speedX = 1808 ' x position of middle of speed buttons
inclineX = 114 ' X position of middle of incline buttons
' Define button positions and layout
Select Case buttonCount
Case 10
buttonBottomY = 977 ' Y position of middle of bottom button (swipe up)
swipeBottomY = 0 ' Y position of middle of bottom button (swipe down)
buttonTopY = 186 ' Y position of middle of top button (swipe up)
swipeShift = 0 ' Y button shift for swipe
Case 9
buttonBottomY = 915
swipeBottomY = 889
buttonTopY = 211
swipeShift = 63
Case 8
buttonBottomY = 827
swipeBottomY = 801
buttonTopY = 212
swipeShift = 150
Case 7
buttonBottomY = 752
swipeBottomY = 714
buttonTopY = 227
swipeShift = 225
Case 6
buttonBottomY = 672
swipeBottomY = 625
buttonTopY = 233
swipeShift = 305
End Select
' *** End - NordicTrack C2950 Treadmill ***
buttonSpacingY = (buttonBottomY - buttonTopY) / (buttonCount - 1) ' Vertical spacing between buttons
Do ' Process treadmill and Zwift workout metrics
' Query Zwift for workout speed and incline
workout = GetZwiftWorkout()
zwiftSpeed = workout(0)
zwiftIncline = workout(1)
' Auto-speed ----------
' Query treadmill for speed
cmdstring = "adb shell tail -n5000 " & infilename2 & " | grep -a ""Changed KPH""" & " | tail -n1 | grep -oP ""(?<=to\s)\d+(\.\d+)?(?=\skph)"""
' Use synchronous exec
set oexec = wso.exec("cmd /c " & cmdstring) ' Execute adb command
' Wait for completion
Do While oexec.Status = 0
wscript.sleep 100
Loop
sValue = oexec.stdout.readline
' Process treadmill speed
If sValue <> "" then
treadmillSpeed = formatnumber(csng(sValue),1)
WScript.Echo "Treadmill speed: " & treadmillSpeed
Else
WScript.Echo "Waiting for treadmill to come online..."
End If
If IsEmpty(zwiftSpeed) Then
WScript.Echo "Waiting for Zwift to come online..."
Else
WScript.Echo "Zwift speed: " & zwiftSpeed
WScript.Echo "Old Zwift speed: " & oldzwiftSpeed
' Initialize variables for finding the closest speed
closestSpeed = speedButtons(0)
minSpeedDifference = Abs(zwiftSpeed - speedButtons(0))
closestSpeedIndex = 0
' Loop through the speed buttons to find the closest speed
For i = 1 To UBound(speedButtons)
currentSpeedDifference = Abs(zwiftSpeed - speedButtons(i))
If currentSpeedDifference < minSpeedDifference Then
minSpeedDifference = currentSpeedDifference
closestSpeed = speedButtons(i)
closestSpeedIndex = i
End If
Next
WScript.Echo "Closest speed: " & closestSpeed
' Calculate pixel location for center of the closest speed button
If (closestSpeedIndex + 1) > buttonCount Then
speedY = Round(buttonBottomY - (buttonSpacingY * closestSpeedIndex) + swipeShift) ' Center y-coordinate of the speed button
Else
speedY = Round(buttonBottomY - (buttonSpacingY * closestSpeedIndex)) ' Center y-coordinate of the speed button
End If
' Process speed commands if Zwift speed has changed
If zwiftSpeed <> oldzwiftSpeed Then
' Process speed swipe for button visibility
If buttonCount <> 10 Then
If (closestSpeedIndex + 1) > buttonCount Then
cmdString = "adb shell input swipe " & speedX & " " & buttonTopY & " " & speedX & " " & (buttonTopY + swipeAmount) & " " & swipeSpeed ' Swipe down
Else
cmdString = "adb shell input swipe " & speedX & " " & buttonBottomY & " " & speedX & " " & (buttonBottomY - swipeAmount) & " " & swipeSpeed ' Swipe up
End If
' Use synchronous exec
set oexec = wso.exec("cmd /c " & cmdstring) ' Execute adb command
' Wait for completion
Do While oexec.Status = 0
wscript.sleep 100
Loop
sValue = oexec.stdout.readline
wscript.echo "Speed swipe: " & cmdString
End If
' Process speed button click
cmdString = "adb shell input tap " & speedX & " " & speedY
' Use synchronous exec
set oexec = wso.exec("cmd /c " & cmdstring) ' Execute adb command
'wait for completion
Do While oexec.Status = 0
wscript.sleep 100
Loop
sValue = oexec.stdout.readline
WScript.Echo "Speed tap: " & cmdString
oldzwiftSpeed = zwiftSpeed
End If
End If
WScript.Echo
' Auto-incline ----------
' Query treadmill for incline
cmdString = "adb shell tail -n5000 " & infilename2 & " | grep -a ""Changed INCLINE""" & " | tail -n1 | grep -oP ""(?<=to\s)[+-]?\d+(\.\d+)?(?=\s%)"""
' Use synchronous exec
Set oexec = wso.Exec("cmd /c " & cmdString) ' Execute adb command
' Wait for completion
Do While oexec.Status = 0
WScript.Sleep 100
Loop
sValue = oexec.StdOut.ReadLine
' Process treadmill incline
If sValue <> "" Then
treadmillIncline = FormatNumber(CSng(sValue), 1)
WScript.Echo "Treadmill incline: " & treadmillIncline
Else
WScript.Echo "Waiting for treadmill to come online..."
End If
If IsEmpty(zwiftIncline) Then
WScript.Echo "Waiting for Zwift to come online..."
Else
WScript.Echo "Zwift incline: " & zwiftIncline
WScript.Echo "Old Zwift incline: " & oldzwiftIncline
' Find the closest incline
closestIncline = inclineButtons(0)
minInclineDifference = Abs(zwiftIncline - inclineButtons(0))
closestInclineIndex = 0
For i = 1 To UBound(inclineButtons)
currentInclineDifference = Abs(zwiftIncline - inclineButtons(i))
If currentInclineDifference < minInclineDifference Then
minInclineDifference = currentInclineDifference
closestIncline = inclineButtons(i)
closestInclineIndex = i
End If
Next
WScript.Echo "Closest incline: " & closestIncline
' Calculate pixel location for center of the closest incline button
inclineY = Round(buttonBottomY - (buttonSpacingY * closestInclineIndex))
If (closestInclineIndex + 1) > buttonCount Then
inclineY = Round(inclineY + swipeShift)
End If
' Process incline commands if Zwift incline has changed
If zwiftIncline <> oldzwiftIncline Then
' Process incline swipe for button visibility
If buttonCount <> 10 Then
If (closestInclineIndex + 1) > buttonCount Then
cmdString = "adb shell input swipe " & inclineX & " " & buttonTopY & " " & inclineX & " " & (buttonTopY + swipeAmount) & " " & swipeSpeed
Else
cmdString = "adb shell input swipe " & inclineX & " " & buttonBottomY & " " & inclineX & " " & (buttonBottomY - swipeAmount) & " " & swipeSpeed
End If
' Use synchronous exec
Set oexec = wso.Exec("cmd /c " & cmdString)
' Wait for completion
Do While oexec.Status = 0
WScript.Sleep 100
Loop
WScript.Echo "Incline swipe: " & cmdString
End If
' Process incline button click
cmdString = "adb shell input tap " & inclineX & " " & inclineY
' Use synchronous exec
Set oexec = wso.Exec("cmd /c " & cmdString)
' Wait for completion
Do While oexec.Status = 0
WScript.Sleep 100
Loop
WScript.Echo "Incline tap: " & cmdString
oldzwiftIncline = zwiftIncline
End If
End If
WScript.Echo
Loop ' Process treadmill and Zwift workout metrics
'--- Functions ---
Function GetZwiftWorkout()
' Take a screenshot of Zwift, save it to disk, then OCR image for workout metrics
Set wshShell = WScript.CreateObject("WScript.Shell")
Set fso = WScript.CreateObject("Scripting.FileSystemObject")
strComputer = "."
FindProc = "zwiftapp.exe"
' Is Zwift running?
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcessList = objWMIService.ExecQuery _
("Select Name from Win32_Process WHERE Name='" & FindProc & "'")
' Process Zwift metrics
If colProcessList.count > 0 then
cmdString = "python zwift-workout.py"
' Use synchronous exec
Set oexec = wso.Exec("cmd /c " & cmdString)
' Wait for completion
Do While oexec.Status = 0
Wscript.sleep 100
Loop
sValue = oexec.StdOut.ReadLine
metrics = Split(sValue, ";")
If UBound(metrics) = 0 Then metrics = array(Empty,Empty)
sSpeed = metrics(0)
sIncline = metrics(1)
' Validate speed is numeric
If IsNumeric(sSpeed) Then
nSpeed = FormatNumber(CDbl(sSpeed), 1)
'WScript.Echo "Zwift speed: " & nSpeed
End If
' Validate incline is numeric
If IsNumeric(sIncline) Then
nIncline = FormatNumber(CDbl(sIncline), 1)
'WScript.Echo "Zwift incline: " & nIncline
End If
End If
' Return metrics array
GetZwiftWorkout = array(nSpeed, nIncline)
Set objWMIService = Nothing
Set colProcessList = Nothing
End Function
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment