Tunturi Protocol

From EQUIS Lab Wiki

Jump to: navigation, search

(back to Will's Notes.)

I'm currently working on encapsulating all the functionality provided by our specific bike into one class which manages all data and settings and handles all communications with the actual hardware. I wrote up a little demo app in .NET using Visual C++ that uses this interface.

Here's a recent screenshot:

twin.png

Contents

Serial Port Library

We have optimised the Tunturi interface library to be fast; it uses a Win32 implementation of the pthreads library for threaded asynchronous operation. Read more about setting up and building pthreads on the CAX Dependencies page

Doxygen Documentation

The doxygen docs for this library can be found here.

Lag Issues

From starting to pedal to registration of nonzero speed at the computer's COM port is about 3000 ms; from stopping to pedal until the time when the computer registers zero speed is about 6250 ms.

See User Interaction Tuning for how we are trying to deal with this problem.

Serial Port References

Here is a site that rather thoroughly describes the serial bus used on PCs.

This page contains instructions for getting serial port communication to work under Win NT/2000, etc. Even has instructions for how to set up the library under MinGW.

This page has a large number of links to serial port resources.

My housemate found this Python library, which lets you open serial and parallel ports on Windows, *NIX and BSD systems.

This is Microsoft's Windows SDK reference on serial communications. Very good. I used this to hack together my first Win32 COM Port interface in Dev-C++.

Old Implementation Notes

Version 4: I've reimplemented threading in the Tunturi protocol library behind the top-level object to reduce the latency for reading and writing data off the bike (latency is down to 5-20 ms). This also makes the main program loop independent of low-level I/O errors that occur relating to the COM port. Instead of Win32 threads, this time we're using a WinXP port of Posix threads library. A (Linux-oriented) tutorial for pthreads is available here. Version 4 also rips out all the explicit console I/O, replacing this with a configurable logger utility which writes messages to a log file.

Version 3: I cut out the multithreading and reimplemented the COM port library using polling instead. Data transfer is now down to 15-30ms for a round trip. I've also put together a small Tunturi protocol library that sits on top and implements all the bike functions that are supported by our hardware.

Version 2: The COM port library I've been working on over the past few days is progressing well. I've rewritten the interface to it, and rewritten the Win32 backend, which is now multithreaded with some locking code to protect the input and output buffers. I've altered the interface so that the com port stream is stored in an object, the way I started writing the library the first time. This should make it easy to have multiple Tunturi bikes hooked up to the same computer, using a separate COM port for each bicycle. The largest problem I'm facing right now is the high latency on data transfer: it takes somewhere in the neighbourhood of 1100ms to send a message to the bike and receive a response. Also, some messages are dropped. I think I'll take out the multithreading code and reimplement the library using polling to see if I can reduce this.

Version 1: my first attempt at writing a com port library was successful. The stream object was encapsulated in an object, and the library used polling to do I/O (not multithreaded).

Tunturi Protocol Fundamentals

Tunturi's bikes all interface to PCs using a serial cable and a proprietary data transmission format (T-protocol). Data transmission uses standard settings and is performed via a RS232 serial bus (9600bps, 8 bits, no parity, 1 stop bit).

el Cinqo de Mayo: Our new computers didn't have com ports, so I went out and bought an add-in PCI card with a single serial port on it for my box. I also hooked the bike up to the PC and confirmed that it's working using the included software.

The T-protocol works by sending messages. Messages are packaged with a start byte (0xF1) and an end byte (0xF2). The first byte inside a message is an opcode that specifies what the message contains; the last byte is always a checksum byte, which is computed by XORing all the other bytes inside the message (not the start or end markers).

Because of the "specialness" of the start and end markers 0xF1 and 0xF2, the T-protocol defines an escape character, 0xF3 to allow these values to be passed inside a message. The escape rewrite rules are:

0xF1 -> 0xF3, 0x01
0xF2 -> 0xF3, 0x02
0xF3 -> 0xF3, 0x03

All rewriting takes place after packaging the data into messages and computing checksums (i.e., escape the characters immediately before writing them to the serial port).

Multibyte data are transmitted in big-endian format (i.e., most-significant byte first).

Several Tunturi functions start with Get- and allow access to data; these messages cause the bike to send back an AnswerAck message, containing the requested data. Many of these Get-functions have a corresponding Set- message with an opcode one greater, whose internal message layout is an exact copy of the data returned by the Get- message.

For example, the following is a GetUserData message (opcode 0x04).

Input: F1 04 04 F2
Output: F1 04 31 DD 01 02 EB F2 (AnswerAck)

The corresponding SetUserData (which has an opcode of 0x05) message would be

F1 05 31 DD 01 02 EA F2

Note the changed checksum byte. Set-messages tend to cause the bike to return a copy of the Set-message as acknowledgement. The Set-messages supported by the E6R are:

  • SetUserData (5, 0x05)
  • SetRemStatus (7, 0x07)
  • SetTargetData (9, 0x09)

Functions Supported by the Tunturi Bicycle

I've tested the following functions and know for certain that they are supported on our hardware (Tunturi E6R recumbent exercise bicycle).

Controls or Getter-/Setter-Functions

SetReset reset the bike controller
Get-/SetUserData retrieve or change the user settings
Get-/SetRemStatus control the remote control status bits
Get-/SetTargetData set the target heartrate or similar
KeyCmd send a software keypress to the bike

Data Retrieval Functions

GetCurrentData immediate data
GetCumulatedData total trip distance in this session
GetTotalData data since manufacture
GetSwVersion software version
GetErrorStatus ERROR and WARNING status

Streaming Data Retrieval Functions

GetCurrentData streaming information about bike data
KeyInfo information about keyboard presses on bike

SerialTTY

I've put together a small console program to allow the user to communicate in raw byte format with the Tunturi bicycle controller chip in order to test the protocol and determine which functions are supported by our hardware. Below is some sample output from this program.

Tunturi Protocol debugger
Will Roberts, May 2005

This program allows the user to send an arbitrary byte string
to the Tunturi bicycle using the COM port interface.

Bytes may be space-delimited (e.g. "F1 06 06 f2"), or not
(e.g. "f10606F2").  Case is not important.

Opening COM port ...
Enter input in hexadecimal format (Q to quit): f10202f2
Writing data to COM port ...
< < <   F1 02 02 F2
Reading from COM port ...
> > >   F1 00 02 01 03 F2
Enter input in hexadecimal format (Q to quit): f10a000af2
Writing data to COM port ...
< < <   F1 0A 00 0A F2
Reading from COM port ...
> > >   F1 0A 00 00 00 00 00 0A F2
Enter input in hexadecimal format (Q to quit): q
Closing COM port ...

Supported Functions

I'm basically reverse-engineering the Tunturi protocol, going through it function by function and documenting the response I get and if the function is supported on the E6R bicycle. Below, I've listed the functions I've looked at in the bike; mostly, they're query functions, not commands that actually set data or do anything. Next to each heading I've listed the Tunturi operation code in integer and hex format. I've just pasted in inputs and outputs verbatim, and I'm working on indicating how these values can be interpreted.

SetReset (1, 0x01)

Input: F1 01 01 F2

Output: No response.

Bike controller and display are instantly reset.

This function is fully implemented and tested in the TBicycle class.

GetUserData (4, 0x04)

Input: F1 04 04 F2

Output: F1 04 31 DD 01 02 EB F2 (AnswerAck)

BeatLo is 31 (49=off),
BeatHi is DD (221=off),
sound is 01 (1=on),
units are 02 (2=rpm/miles/kcal/lbs/ft).

The bike might only support setting the sound and units parameters, or it could allow setting all four of them.

This function is not yet implemented in the TBicycle class.

GetRemStatus (6, 0x06)

Input: F1 06 06 F2

Output: F1 06 00 00 00 06 F2 (AnswerAck)

Control is 00,
keylock1 is 00,
keylock2 is 00

This function is not yet implemented in the TBicycle class.

GetTargetData (8, 0x08)

Input: F1 08 08 F2

Output: F1 08 00 00 00 00 00 08 F2 (AnswerAck)

Mode is 00,
Target Torque is 00 Nm,
Target Heart Rate is 00 per min,
Target Power Output is 0000 watt.

This function is fully implemented and tested in the TBicycle class.

SetTargetData (9, 0x09)

This message seems to be the only way of setting the tension on the bicycle, apart from scripting in button-press events (e.g., press RESET-MANUAL-ENTER-ENTER-UP-UP-UP-UP or similar). Button-pressing also has the disadvantage that the bike's current tension cannot be determined, since GetTargetData just returns all zeroes. The control software would likely get out of synch with the bike fairly quickly. Unfortunately, the SetTargetData message doesn't seem to give the really fine-level control over bike tension that we want. Ideally, there would be a command to set the braking tension on the bike, which would take effect immediately. However, the only way that I've found so far to actually alter tension is using watt control, which operates over a span of several seconds. I believe this delay is because the bicycle integrated electronics must sample the bike speed and power output before calculating what power to apply to the electric brake.

  • Mode = 00 doesn't work, although this is the form of message returned by the bike when the user has entered a program on the console. The following message has Mode = 00, Watt = 25, and resembles the data returned from GetCurrentTarget when the user has chosen one of the first four programs on the bike (the first four are governed by watt control; the last four by heart rate).
f1 09 00 00 00 00 19 10 f2
  • Mode = 01 should set the target torque on the bike, according to the Tprotocol, but it hasn't worked yet. Torque is supposed to vary from 1 to 32 or 40, but the following message (torque = 25) has no effect:
f1 09 01 19 00 00 00 11 f2
  • Mode = 02 should set the target heart rate. As far as I know, this works like watt control, but I haven't been testing it because the heart rate monitor is a bit finicky. The following sets target heart rate to 140 bpm:
f1 09 02 00 8c 00 00 87 f2
  • Mode = 03 sets target power output in watts. This works fine, but there is a considerable latency between the time when the tension is change in software and when the bike becomes more difficult to pedal (several full seconds). Watts may range from 25-400. This example uses 200:
f1 09 03 00 00 00 c8 c2 f2

This function is implemented and tested (using Watt Control) in the TBicycle class.

GetCurrentData (10, 0x0A)

Input: F1 0A 00 0A F2 (send once)

Output: F1 0A 00 00 03 0F 00 06 F2 (AnswerAck)

Heart rate is 00 per min,
Power output is 0003 watt,
Speed is 0F rpm,
keynumber is 00.

The parameter p to this message (the byte following opcode 0x0A) is specified in the Tunturi protocol to make the bike stream current data information. Messages are supposed to be sent every p x 50 milliseconds. However, sustained output is not working yet (unknown reason).

Input: F1 0A 05 0F F2 (send once every 250ms)

Output: F1 0A 00 00 0A 29 00 29 F2

The bike sends a single response but then doesn't send anything else. This can't be a stream error because I/O still works several seconds later, and for now the COM port library is configured to hard fail on any kind of error.

From some work I did with the SerialTTY program this morning, I've put together a list of keynumber values for the bike (these weren't listed in the Tunturi protocol):

RESET           = 16  (0x10)
DOWN            = 32  (0x20)
UP              = 48  (0x30)
SCAN HOLD       = 64  (0x40)
MANUAL          = 80  (0x50)
ENTER           = 96  (0x60)
MEMORY          = 112 (0x70)
WATT CONTROL    = 128 (0x80)
PROGRAMS        = 144 (0x90)
TARGET HR       = 160 (0xa0)

This function is fully implemented and tested in the TBicycle class.

GetCumulatedData (11, 0x0B)

Input: F1 0B 0B F2

Output: F1 0B 00 00 00 00 00 00 0A 01 F2 (AnswerAck)

Energy expended is 0000 kcal,
Distance travelled is 0000 x 0.1 km,
timehr is 00,
timemn is 00,
timesec is 0A

This function is fully implemented and tested in the TBicycle class.

GetTotalData (12, 0x0C)

Input: F1 0C 0C F2

Output: F1 0C 00 03 00 03 0C F2 (AnswerAck)

Distance is 0003 x km,
time is 0003 x 5 min

This function is not yet implemented in the TBicycle class.

KeyCmd (13, 0x0D)

Input: F1 0D 0B 06 F2

Output: F1 0D 0B 06 F2 (NormalAck)

The bike beeped loudly and the display reset itself.

Thank you, SerialTTY! The KeyCmd values are slightly different from the ones used in GetCurrentData, and they also aren't documented anywhere in the Tunturi protocol:

DOWN            = 2   (0x02)
UP              = 3   (0x03)
SCAN HOLD       = 4   (0x04)
MANUAL          = 5   (0x05)
ENTER           = 6   (0x06)
MEMORY          = 7   (0x07)
WATT CONTROL    = 8   (0x08)
PROGRAMS        = 9   (0x09)
TARGET HR       = 10  (0x0a)
RESET           = 11  (0x0b)

This function is fully implemented and tested in the TBicycle class.

GetSwVersion (14, 0x0E)

Input: F1 0E 0E F2

Output: F1 0E 45 36 20 20 20 20 01 0A 76 F2 (AnswerAck)

ID is 453620202020 ("E6 "),
version is 01,
revision is 0A (1.10)

This function is fully implemented and tested in the TBicycle class.

GetErrorStatus (20, 0x14)

Input: F1 14 14 F2

Output: f1 14 00 00 14 f2 (AnswerAck)

Error is 00,
Warning is 00.

This function is not yet implemented in the TBicycle class.

Unsupported Functions

GetCfg (2, 0x02)

Input: F1 02 02 F2

Output: F1 00 02 01 03 F2 (ErrorAck)

Error code is 01 (unsupported command)

GetCalibrationData (16, 0x10)

Input: F1 10 10 F2

Output: f1 00 10 01 11 f2 (ErrorAck)

Error code 01 indicates unsupported function.

GetSerialNo (18, 0x12)

Input: F1 12 12 F2

Output: f1 00 12 01 13 f2 (ErrorAck)

Error code 01 indicates unsupported function.

GetClockMode (22, 0x16)

Input: F1 16 16 F2

Output: f1 00 16 01 17 f2 (ErrorAck)

Error code 01 indicates unsupported function.

GetAllData (24, 0x18)

Input: F1 18 18 F2

Output: f1 00 18 01 19 f2 (ErrorAck)

Error code 01 indicates unsupported function.