Protokol
Hardware
On modern computers you will usually find no serial port, so you need an USB<->serial converter. Make sure to buy a good one, which also works on linux systems. According to many web-sources an converter with FTDI chipsets are the best. I bought a digitus usb-serial adaptor for ca. 11.00 EUR.
For a wired connection you need a RS-232 extension cable with a male and female D-sub-9 connector. NOT a Null modem cable! Also a three-wire connection with TxD (pin 3), RxD (pin 2) and GND (pin 5) is sufficient.In my first step I needed to discover the protocol for ergometer data communication. For this task I used some software serial sniffer and an oscilloscope. Finally I discovered following serial connection specifications:
- Bitrate: 9600 (According to user Unknown, Kettler World Tours changes it into 57600 when using Software)
- Data bits: 8
- Stop bits: 1
- No handshake
Protocol
The next step is to discover the protocol. First of all I found out that it is possible to communicate with ergometer using a normal terminal for example Cutecom on Linux or Hyperterminal on windows sending strings ending with "\r\n" to the ergometer and getting "ERROR\r\n" response.Notation: I use a escape sequences \r, \n and \t to describe special characters for carriage return, new line and tab.
Playing with different commands I was unable to get more information, so I decided to use brute-force search methods to get the protocol specification.I wrote a small program which sends all possible strings consisting of one or two ascii symbols from "a" to "z" and ending with "\r\n" until I get a response different from "ERROR".
I was able to retrieve following commands*:
Notation: [request without "\r\n"] -> [response without "\r\n"]. User midway112 found out, that the command work only when used in uppercase. That means,for example, use "CA" instead of "ca".
- ca -> 041
- cd -> ACK
- cm -> ACK
- cp -> ACK
- id -> FX1S
- pd -> 000\t000\t000\t000\t025\t0000\t00:00\t000
- pe -> 000\t000\t000\t000\t025\t0000\t00:00\t000
- pt -> 000\t000\t000\t000\t025\t0000\t00:00\t000
- pw -> 000\t000\t000\t000\t025\t0000\t00:00\t000
- rd -> [long output]
- rp -> [long output]
- st -> 000\t000\t000\t000\t025\t0000\t00:00\t000
- ve -> 165
- vs -> 00
- PS -> [response unknwon] set speed on treadmil Task9 (Remark from User Eva).
- PI -> [response unknwon] set titl on treadmil Task9 (Remark from User Eva).
- Make an exercise on ergometer remember the values.
- Go to the computer check the output.
- Go to the step 1.
Some command can be used with an argument, which is maximal a 3- or 4- digit number. The argument and the command are separated by one blank character. No leading zeros are required. Missing argument considered to be 0 and large arguments are stripped.
In order to use cp, pd, pe, pt and pw commands one must first call cd or cm commands first.
cd: Maybe put the ergometer into manual mode ? After the call a serial port socket appears on the ergometer screen.
cm: Seems to be the same as cd.
cp: Reset (?).
id: Get device type, which in my case is KETTLER FX1.
pd: put distance. The argument is the distance in 0.1km. The values are between 0 and 999.
pt: put time. The argument is a time. The values are between 0 and 9959. First two digits describe minutes last two digits describes seconds. If the number of seconds is greater than 59 it will be reduced to 59
pp: put power. The argument is power in Watt. The values are between 25 and 400 in 5 steps.
The values less than 25 are converted to 25. The values greater than 400 are converted to 400. The values are round down until the becomes multiple of 5.
rp: read programs. Output consists of the ergometer standard programs written as a sequence of 2-digits or 3-digits blocks. The format of each program is [number of minutes] [watt in the first minute] [watt in the second minute ]...
st: It is an important command for monitoring purpose. It can mean "current status" or "current state".
It does not need any arguments and its output consists of 8 fields separated by tab-characters, where each fields means [pulse in Hz][rpm][speed in 0.1 km/h ][distance in 0.1 km][requested power][energy in kJ][time in minutes:seconds][actual power (?)]
The fictional response 088\t072\t324\t009\t150\t0024\t10:02\t140 means: pulse 88 beats per minute, 72 RPM, speed 32.4 km/h, distance 0.9 km, requested 150 Watt, burned energy 24kJ, time 10 minutes 02 seconds, actual power 140 Watt.
For your information: The Kettler ErgoRace II uses default also 9600 Baudrate, but Kettler World Tours changes it into 57600 when using the software..
AntwortenLöschenThank you for the comment. Did you try to use the same commands as in this article in ErgoRace II?
Löschen@Unknown. I added your remarkt to the original post.
Löschenvery useful article, but how to assign the distance or speed, or any other value is not specified.
AntwortenLöschenThank you. For the distance it could be pd123 for 12.3 km. But I do not think that it is possible to assign the speed.
LöschenI can image that settings speed makes sense in a treadmill but how it should work for stationary bicycle ;)?
Dieser Kommentar wurde vom Autor entfernt.
LöschenNow I try to understand how to operate in the same way with the help of bluetooth. While no results.
LöschenDieser Kommentar wurde vom Autor entfernt.
LöschenI figured:
LöschenCM - a command mode
Only after it is possible to send commands in the form of
PS10 - set the speed to the value of 1.0
PI20 - set the tilt angle is set to 2.0
PS! I forgot to say that this model treadmill Trask 9
Thank you for the comments.
LöschenIt apears to work very similar to my Kettler bycicle. My Kettler requires "cd"/"cm" commands before using the "p*-commands" too. Maybe I had to stress it better in my article.
PI -- cound mean "put inclination".
How do you want to connect it with bluetooth?
I built a test device to transmit data from the serial serial to bluetooth several years ago. But now I am thinking to rebuild it with more nice and cheap modern circuits.
I tried to connect it to the bluetooth using the Arduino and HC-05, but came across a problem, Arduino itself is not sent to the port of TRACK-9 commands. I do not know how to win. I think the use of 'USB Host Shield v2', but do not know the code for this program.
LöschenDieser Kommentar wurde vom Autor entfernt.
LöschenI faced once a similar problem because I used a device with TTL-Serial. That is 0/3.3V. In contrast, for RS-232 in Ergometer you need more: -5V/5V. I used an MAX232 ic and it worked.
LöschenMay be I will a HC-05 to test wireless communication with Android and other SPP-BT devices.
I did some experiments with SparkFun ESP32 Thing. It has nice specification, it is small but important sofware is missing. In particular there no SPP-BT support. I was not able to connect it to my Android devices.
@Eva I added your remarks to the original post.
LöschenDieser Kommentar wurde vom Autor entfernt.
AntwortenLöschenDieser Kommentar wurde vom Autor entfernt.
AntwortenLöschenDieser Kommentar wurde vom Autor entfernt.
AntwortenLöschenThanks for writing this!
AntwortenLöschenI am working on a project to store the training data on my Synology NAS (php, mysql) with an ESP32 NODEMCU development board.
I found out after a while that only UPPECASE commands work. ST, ID etc. (maybe you want to update the post?)
Also I found some strange behavior of my MAX3232: It would not send any data if I connected the Kettler CTR3 / CX3 RS232 first and then power up the ESP32.
I added a 4.7k resistor in the out line of the RS232 side of the max - now it works.
Also strange: The first command I send always returns ERROR, no matter what I send. All succeeding commands work as described.
Hope that may help others who try the same.
Cheers,
Henrik
Hi Hendrik,
Löschencould you link your solution / Nodemcu. Im very interested and would like to log via Nodemcu and upload the data to mysql
Thanks
> Also strange: The first command I send always returns ERROR
LöschenDid you tried to use some kind of terminal, to send data from a desktop computer? It helps to distinguish between Hardware and Software problems.
Dieser Kommentar wurde vom Autor entfernt.
Löschen@midway11 I added your remarks to the original post.
Löschen> Thanks for writing this!
Löschen:). I am glad that this information is still useful.
> I found out after a while that only UPPECASE commands work. ST, ID etc. (maybe you want to update the post?)
I cannot test it now with my device, but I will add your update. Thak you for sharing this information!
Dieser Kommentar wurde vom Autor entfernt.
AntwortenLöschenDusty
AntwortenLöschenI did not publish anything yet but I can share what I have. Let me know how to contact you
Thanks for your answer, can you please contact me at "mydustydustATgmail.com"
AntwortenLöschenSorry, I just saw, your posts :)
LöschenIt would be better if you publish your results first and then other developers could join.
Thanks to the former work i was able to make a small programm for my ctr3.
AntwortenLöschenJust one small correction: the hartbeat is in beat per minute.
Here is my vb.net code. You just need a Form with a Serialport, a timer (I have it on 1000 ms) and 8 labels for the live values. for the graf you need a chart with two series of lines. And last but not least a save botton.
Hello,
Löschenam not it geek, but am owner of Kettler AXIOM (usb and BT connections possible).
Would like to have an app, that at the same time records all training data and stores it to my PC (or via PC to the cloud) in database (favourable) or excel file.
I have HR stripe attached to AXIOM Kettler.
I have Windows 10 Pro 64 it.
I am keen to purchase such an app (PayPal etc).
Awaiting response.
Dieser Kommentar wurde vom Autor entfernt.
LöschenHi @EasyCoder,
LöschenI am glad that you could communicate with your Kettler Device.
thank you for you code and for the correction - 88 Beats per seconds was too much 😅.
@ Bee Man.
LöschenI do not development apps for Kettler anymore but if someone will do it, here are some my hints. Probably both: USB and BT will use some sort of virtual serial Port on a Windows System. For this kind of communication the ports settings like bitrate, data bits, stop bits, and handshake, are not important. Only the port name is important.
If Bluetooth uses serial profile then it is also possible to connect an Android Phone directly to it with the Android Bluetooth Sockets API: https://developer.android.com/reference/android/bluetooth/BluetoothSocket
Public Class Form1
AntwortenLöschenDim FlagDatenEmpfangen As Boolean = True
Dim StopUhr As New Stopwatch
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
SerialPort1.BaudRate = 9600
SerialPort1.DataBits = 8
SerialPort1.PortName = "COM3"
SerialPort1.StopBits = IO.Ports.StopBits.One
SerialPort1.Parity = IO.Ports.Parity.None
SerialPort1.NewLine = vbLf
Try
SerialPort1.Open()
Timer1.Start()
StopUhr.Start()
Catch
MessageBox.Show("Fehler: SerialPort konnte nicht geöffnet werden.")
End Try
End Sub
Dim ReceivedText As String
Dim Values As String()
Dim Messwerte As New List(Of String)
Dim Index As Integer
Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
If FlagDatenEmpfangen Then
ReceivedText = SerialPort1.ReadLine
DelegateTextBox1(ReceivedText)
ReceivedText = ReceivedText.Replace(vbCr, "")
Messwerte.Add(StopUhr.ElapsedMilliseconds & vbTab & ReceivedText)
If ReceivedText.Length = 34 Then
Values = ReceivedText.Split(vbTab)
DelegateLblHerzschlag(CInt(Values(0)))
DelegateSerie1(CInt(Values(0)))
DelegateLblDrehzahl(CInt(Values(1)))
DelegateLblGeschwindigkeit(CInt(Values(2)) / 10)
DelegateLblDistanz(CInt(Values(3)) / 10)
DelegateLblLeistungSoll(CInt(Values(4)))
DelegateLblVerbrauchteEnergie(Math.Round(CInt(Values(5)) / 4.1868, 0))
DelegateLbLZeit(Values(6))
DelegateLblLeistungIst(CInt(Values(7)))
DelegateSerie2(CInt(Values(7)))
End If
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SerialPort1.Write(TextBox2.Text & vbCr)
End Sub
#Region "Delegates"
Delegate Sub TextBox1Delegate(text As String)
Sub DelegateTextBox1(ByVal text As String)
If TextBox1.InvokeRequired Then
Dim d As New TextBox1Delegate(AddressOf DelegateTextBox1)
Invoke(d, New Object() {text})
Else
TextBox1.Text = text & vbLf & TextBox1.Text
End If
End Sub
Delegate Sub Serie1Delegate(value As Double)
Sub DelegateSerie1(ByVal value As Double)
If Chart1.InvokeRequired Then
Dim d As New Serie1Delegate(AddressOf DelegateSerie1)
Invoke(d, New Object() {value})
Else
Chart1.Series(0).Points.AddY(value)
End If
End Sub
Delegate Sub Serie2Delegate(value As Double)
Sub DelegateSerie2(ByVal value As Double)
If Chart1.InvokeRequired Then
Dim d As New Serie2Delegate(AddressOf DelegateSerie2)
Invoke(d, New Object() {value})
Else
Chart1.Series(1).Points.AddY(value)
End If
End Sub
Delegate Sub LblHerzschlagDelegate(text As String)
Sub DelegateLblHerzschlag(ByVal text As String)
If LblHerzschlag.InvokeRequired Then
Dim d As New LblHerzschlagDelegate(AddressOf DelegateLblHerzschlag)
Invoke(d, New Object() {text})
Else
LblHerzschlag.Text = "Herzschlag: " & text & " bpm"
End If
End Sub
Delegate Sub LblDrehzahlDelegate(text As String)
Sub DelegateLblDrehzahl(ByVal text As String)
If LblDrehzahl.InvokeRequired Then
Dim d As New LblDrehzahlDelegate(AddressOf DelegateLblDrehzahl)
Invoke(d, New Object() {text})
Else
LblDrehzahl.Text = "Drehzahl: " & text & " rpm"
End If
End Sub
Delegate Sub LblGeschwindigkeitDelegate(text As String)
AntwortenLöschenSub DelegateLblGeschwindigkeit(ByVal text As String)
If LblHerzschlag.InvokeRequired Then
Dim d As New LblGeschwindigkeitDelegate(AddressOf DelegateLblGeschwindigkeit)
Invoke(d, New Object() {text})
Else
LblGeschwindigkeit.Text = "Geschwindigkeit: " & text & " km/h"
End If
End Sub
Delegate Sub LblDistanzDelegate(text As String)
Sub DelegateLblDistanz(ByVal text As String)
If LblDistanz.InvokeRequired Then
Dim d As New LblDistanzDelegate(AddressOf DelegateLblDistanz)
Invoke(d, New Object() {text})
Else
LblDistanz.Text = "Distanz: " & text & " km"
End If
End Sub
Delegate Sub LblLeistungSollDelegate(text As String)
Sub DelegateLblLeistungSoll(ByVal text As String)
If LblLeistungSoll.InvokeRequired Then
Dim d As New LblLeistungSollDelegate(AddressOf DelegateLblLeistungSoll)
Invoke(d, New Object() {text})
Else
LblLeistungSoll.Text = "Leistung Soll: " & text & " W"
End If
End Sub
Delegate Sub LblVerbrauchteEnergieDelegate(text As String)
Sub DelegateLblVerbrauchteEnergie(ByVal text As String)
If LblVerbrauchteEnergie.InvokeRequired Then
Dim d As New LblVerbrauchteEnergieDelegate(AddressOf DelegateLblVerbrauchteEnergie)
Invoke(d, New Object() {text})
Else
LblVerbrauchteEnergie.Text = "Verbrauchte Energie: " & text & " kcal"
End If
End Sub
Delegate Sub LbLZeitDelegate(text As String)
Sub DelegateLbLZeit(ByVal text As String)
If LbLZeit.InvokeRequired Then
Dim d As New LbLZeitDelegate(AddressOf DelegateLbLZeit)
Invoke(d, New Object() {text})
Else
LbLZeit.Text = "Zeit: " & text
End If
End Sub
Delegate Sub LblLeistungIstDelegate(text As String)
Sub DelegateLblLeistungIst(ByVal text As String)
If LblLeistungIst.InvokeRequired Then
Dim d As New LblLeistungIstDelegate(AddressOf DelegateLblLeistungIst)
Invoke(d, New Object() {text})
Else
LblLeistungIst.Text = "Leistung Ist: " & text & " W"
End If
End Sub
#End Region
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
Timer1.Stop()
FlagDatenEmpfangen = False
SerialPort1.Write("id" & vbCr)
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
SerialPort1.Write("st" & vbCr)
End Sub
Dim WerTrainiert As String
AntwortenLöschenPrivate Sub RdbPerson1_CheckedChanged(sender As Object, e As EventArgs) Handles RdbPerson1.CheckedChanged
If RdbPerson1.Checked Then
WerTrainiert = "Person1"
End If
End Sub
Private Sub RdbPerson2_CheckedChanged(sender As Object, e As EventArgs) Handles RdbPerson2.CheckedChanged
If RdbPerson2.Checked Then
WerTrainiert = "Person2"
End If
End Sub
Private Sub CmdSpeichern_Click(sender As Object, e As EventArgs) Handles CmdSpeichern.Click
Speichern()
End Sub
Sub Speichern()
Dim FileName As String = WerTrainiert & Format(Date.Now, "yyyyMMdd_HHmmss") & ".txt"
Dim fs As New IO.FileStream(My.Computer.FileSystem.SpecialDirectories.Desktop & "\" & FileName, IO.FileMode.OpenOrCreate)
Dim sw As New IO.StreamWriter(fs)
sw.WriteLine("Timer [ms]" & vbTab & "Herzschlag [bpm]" & vbTab & "Drehzahl [rpm]" & vbTab & "Geschwindigkeit [0.1 km/h]" & vbTab & "Distanz [0.1 km]" & vbTab & "Leistung Soll [W]" & vbTab & "Verbrauchte Energie [kJ]" & vbTab & "Trainingszeit [mm:ss]" & vbTab & "Leistung Ist [W]")
For Each Linie As String In Messwerte
sw.WriteLine(Linie)
Next
sw.Close()
End Sub
End Class