Skip to content

Instantly share code, notes, and snippets.

@lostmsu
Last active April 10, 2024 06:54
Show Gist options
  • Save lostmsu/1b0d4a33e5ca2418c2b52797eb720ec7 to your computer and use it in GitHub Desktop.
Save lostmsu/1b0d4a33e5ca2418c2b52797eb720ec7 to your computer and use it in GitHub Desktop.
Control LifeSpan treadmill from C# (treadmill drops connection if not kept alive)
// License: MIT
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
public class LifeSpanTreadmill: IDisposable
{
readonly Stream toDevice, fromDevice;
readonly byte[] responseBuffer = new byte[8];
volatile bool stop;
readonly Task keepAliveLoop;
readonly ConcurrentQueue<Action> requests = new ConcurrentQueue<Action>();
public LifeSpanTreadmill(Stream toDevice, Stream fromDevice) {
this.toDevice = toDevice ?? throw new ArgumentNullException(nameof(toDevice));
this.fromDevice = fromDevice ?? throw new ArgumentNullException(nameof(fromDevice));
this.keepAliveLoop = Task.Run((Action)this.KeepAliveLoop);
}
public LifeSpanTreadmill(Stream deviceStream) {
this.toDevice = this.fromDevice = deviceStream ?? throw new ArgumentNullException(nameof(deviceStream));
this.keepAliveLoop = Task.Run((Action)this.KeepAliveLoop);
}
public void Start() => this.SendCommand(Command.Start);
public void Stop() => this.SendCommand(Command.Stop);
void KeepAliveLoop() {
while (!this.stop) {
while (this.requests.TryDequeue(out Action action))
action();
this.SendInitSequence();
long speed = this.SendPacket((uint)Command.GetSpeed);
}
}
void SendInitSequence() {
long[] resposes = new uint[] {
0x20000000,
0xC2000000,
0xE9FF0000,
0xE400F400,
}.Select(this.SendPacket).ToArray();
}
long SendCommand(Command command) => this.SendCommandAsync(command).Result;
Task<long> SendCommandAsync(Command command) {
var result = new TaskCompletionSource<long>();
this.requests.Enqueue(() => {
try {
this.SendInitSequence();
long response = this.SendPacket((uint)command);
result.TrySetResult(response);
} catch (Exception e) {
result.TrySetException(e);
}
});
return result.Task;
}
long SendPacket(uint command) {
byte[] bytes = BitConverter.GetBytes(command);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
this.toDevice.Write(bytes, 0, 4);
this.toDevice.WriteByte(0);
this.toDevice.Flush();
int read = this.fromDevice.Read(this.responseBuffer, 0, 6);
if (read < 6)
throw new NotSupportedException();
long result = BitConverter.ToInt64(this.responseBuffer, 0);
return result;
}
static decimal ToDecimal(long response) => response & 0xFF;
public void Dispose() {
this.stop = true;
this.keepAliveLoop.Wait();
}
enum Command: uint
{
Start = 0xE1000000,
Stop = 0xE0000000,
GetSteps = 0xA1880000,
GetCalories = 0xA1870000,
GetDistance = 0xA1850000,
GetTime = 0xA1890000,
GetSpeed = 0xA1820000,
}
}
@lukescott
Copy link

@lostmsu What model do you have?

@lostmsu
Copy link
Author

lostmsu commented Mar 11, 2022

@lukescott TR800

@lukescott
Copy link

@lostmsu Is it pretty much a standard RS-232? Do you have to have the console plugged in, or were you able to fully control it thru a serial port? Did you manage to to figure out how to increase/decrease speed?

@lostmsu
Copy link
Author

lostmsu commented Mar 14, 2022

@lukescott by now I don't really remember. Also, one really annoying thing is that I had to also send something to keep connection alive. And as soon as it disconnected, I had to do something on the treadmill to reconnect again.

@lukescott
Copy link

@lostmsu do you remember what serial protocol was used?

@lostmsu
Copy link
Author

lostmsu commented Mar 22, 2022

@lukescott no, I do not. Judging by the year, I might have used the new WinRT APIs to communicate with the Bluetooth device.

@lostmsu
Copy link
Author

lostmsu commented Mar 22, 2022

@lukescott well, guess what, you are in luck. I dug up the full project: https://github.com/lostmsu/Xrcise

@lukescott
Copy link

lukescott commented Mar 22, 2022

@lostmsu Thank you so much!

Oh I thought your program was serial. It's bluetooth. Dang it. My console is non-bluetooth version :(.

This is a serial version I stumbled across: https://gist.github.com/daeken/a3d3c4da11ca1c2d2b84

Trying to get data over Serial. It's not standard. (My model is TR5000). So far I have discovered pin 2 is COM ground. RS232 COM is pin 5, and RS422/RS485 is pin 1 (I think?). It appears 12 volts powers the console on pin 1. I don't have an Oscilloscope so guessing which pin is what beyond COM with a multimeter is tricky. I was able to find out COM was pin 2 because I took apart the console and did a continuity test on USB ground.

@lostmsu
Copy link
Author

lostmsu commented Mar 22, 2022

@lukescott this connects to the console via the Bluetooth. The console has to be plugged in. I believe the Bluetooth receiver is in the console.

@lukescott
Copy link

lukescott commented Mar 22, 2022

@lostmsu Yeah, sadly mine doesn't have bluetooth. The refreshed GlowUp versions is Retro without bluetooth or the new Omnihub with bluetooth. When I opened mine up, there is a spot for the bluetooth module that's empty. I got mine second hand, so I didn't really have much choice in what console I got. Good deal.

@icnocop
Copy link

icnocop commented Apr 3, 2022

@lukescott
Copy link

@icnocop sadly it’s non standard. Even if it was RS-232, the pins are not where they should be. I don’t have the tools needed to decode the signal. Sticking the wrong voltage on the wrong pins could cause damage. I was lucky to not cause any harm with the probing I did do. - I’m updating my retro console to the Omni hub and giving Bluetooth a shot.

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