ColinPitrat / kalenji-gps-watch-reader

A tool to import data from some Kalenji running watches
GNU General Public License v3.0
30 stars 11 forks source link

Geonaute (B'twin) Count 14+ #70

Open elegos opened 6 years ago

elegos commented 6 years ago

Hello!

I received an old Geonaute Count 14+ device and I wanted to transfer the data on Linux, instead of running a windows box just for this.

lsusb shows me the following:

Bus 002 Device 027: ID 10c4:ea61 Cygnal Integrated Products, Inc. CP210x UART Bridge

when I try to run the application though it says: error on USB operation code -7 - 11 LIBUSB_ERROR_TIMEOUT

If you tell me how to sniff data, I'd use a Windows machine and download the data, in order to understand the communication protocol.

Thanks :)

ColinPitrat commented 6 years ago

The deviceId/vendorId corresponds to Navbike400 device so it should try to dialog with it. I see two potential issues:

If running as root doesn't work, can you try to build in debug and run with -v ? You can then attach the output as well as the content of the log directory under the output dir. If it turns out to be a different device, we'll need dumps (details are in the wiki, 'produce dumps' page).

elegos commented 6 years ago

Hello @ColinPitrat

Thanks for the reply :)

I've tried compiling in debug mode (make debug), but it fails warning about an undefined reference to "filter::ReducePoints::filter(Session*, std::__debug::map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >)"

My linux box is a Fedora 27. Any clue?

P.S. Yep, I tried running it as root. Resolved the need to run as root adding the udev rules (adding GROUP="plugdev" at each line)

EDIT: Mystery of the code: cloning it separately let me compile the whole thing. This is the output:

src/main.cc:292: Configuration parsed
src/main.cc:296: Create output directory '/tmp/kalenji_import'
src/main.cc:312: Source is USB
src/main.cc:316: With transaction logger
src/main.cc:336: Auto-detecting device
src/main.cc:365: Auto-detected PylePGSPW1
src/main.cc:374: Registering device
src/main.cc:381: Attaching source to device
src/main.cc:384: Initializing device
PylePGSPW1::init() - init device
PylePGSPW1::init() - vendor device 1
PylePGSPW1::init() - vendor device 2
PylePGSPW1::init() - vendor device 3 (in)
PylePGSPW1::init() - vendor device 4
PylePGSPW1::init() - vendor device 5 (in)
PylePGSPW1::init() - vendor device 6
PylePGSPW1::init() - vendor device 7
PylePGSPW1::init() - vendor device 8
PylePGSPW1::init() - send hello message
PylePGSPW1::readMessage()
error on USB operation code -7 - 11 LIBUSB_ERROR_TIMEOUT

And this is the output forcing the device to Navbike400:

./kalenji_reader -D Navbike400 -v
src/main.cc:292: Configuration parsed
src/main.cc:296: Create output directory '/tmp/kalenji_import'
src/main.cc:312: Source is USB
src/main.cc:316: With transaction logger
src/main.cc:374: Registering device
src/main.cc:381: Attaching source to device
src/main.cc:384: Initializing device
src/device/Navbike400.cc:33: Navbike400::init() - init device
src/device/Navbike400.cc:40: Navbike400::init() - step 0 (frame 18 (empty))
src/device/Navbike400.cc:43: Navbike400::init() - step 1 (frame 2)
src/device/Navbike400.cc:46: Navbike400::init() - step 2 (frame 4)
src/device/Navbike400.cc:49: Navbike400::init() - step 3 (frame 6)
src/device/Navbike400.cc:52: Navbike400::init() - step 4 (frame 9)
src/device/Navbike400.cc:55: Navbike400::init() - step 5 (frame 12)
src/device/Navbike400.cc:58: Navbike400::init() - step 6 (frame 15)
src/device/Navbike400.cc:77: Navbike400::init() - device init attempt 1
src/device/Navbike400.cc:84: Navbike400::init() - step 13 (retry device init)
src/main.cc:386: Device initialized
src/main.cc:388: Get sessions list
src/device/Navbike400.cc:123: Navbike400: Get sessions list !
src/device/Navbike400.cc:126: Navbike400: Read sessions list !
error on USB operation code -7 - 11 LIBUSB_ERROR_TIMEOUT

EDIT 2: here is the USBPcap dump. I'd really like to know how to read this file information (via Wireshark), as I never debugged / reverse engineered USB / serial protocols :) count14.zip

It should contain 2 tracks (speed + rpm), one of ~10 and another of ~20 minutes

ColinPitrat commented 6 years ago

Running make clean before make debug would have helped.

I'll have a look at the dump and give you more info in the coming days.

elegos commented 6 years ago

Hello!

I'm trying to figure out the protocol.

As far as I debugged it, this is the series of control commands that are being sent, that seem to be the handshake part:

bmRequestType: 0x40, bRequest: 0  , wValue: 0xffff, wIndex: 0x0000
bmRequestType: 0x40, bRequest: 1  , wValue: 0x2000, wIndex: 0x0000
bmRequestType: 0xc0, bRequest: 255, wValue: 0x370b, wIndex: 0x0000
bmRequestType: 0xc0, bRequest: 255, wValue: 0x370b, wIndex: 0x0000
bmRequestType: 0x40, bRequest: 30 , wValue: 0x0000, wIndex: 0x0000, payload: 00 4b 00 00
bmRequestType: 0x40, bRequest: 18 , wValue: 0x370b, wIndex: 0x0000

Then it seems it starts the data transfer... the first BULK out has always the payload 5a:5a:40:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00, but I have no idea about what those number represent.

Any idea?

EDIT: sending that URB_BULK packet out, the device started "blinking", i.e. sending loads of data! Now we only need to decode that data :package:

This is the pcapng sniffed running a little prototype of mine. Please note that the last packet sent by my application is packet 21, the rest of the packets are being transferred by the device and libusb (I'm not an USB master, I don't even know how to grab all that data!)

count14_linux_result.zip

ColinPitrat commented 6 years ago

My wild guess so far is:

Looking at the list of sessions (non-zero part): 02 12 02 0e 15 02 12 02 0f 13 39 The first 02 could be the number of sessions. Then in hexadecimal we have: 12 = 18 0e = 14 15 = 21 Another wild guess, this would just contain date & hour of the session: 2018 Feb 14 - 21:02 And then the following session: 2018 Feb 15 - 19:39 Does this match the sessions ?

Looking at the two requests for session header:

5a 5a 50 00 00 00 00 00 00 00 00 00 0c 0f 7a 78 03 00 00 00 02 00 00 00 35 00 41 00 00 00 00 00
5a 5a 50 00 01 00 00 00 00 00 00 00 0c 0f 7a 78 03 00 00 00 02 00 00 00 35 00 41 00 00 00 00 00

It looks like everything is magic except the session number on byte 4.

Looking at the two session header:

5a 5a 50 00 00 01 05 92 01 00 18 0a 00 e9 00 1d 01 4c 57 2a 00 00 06 00 ... 00 99
5a 5a 50 00 01 01 05 cf 02 00 04 14 00 d7 00 f3 00 3a 40 00 00 00 00 00 ... 00 38

Looking at the requests for session content, there's really just session number on byte 3 (which shows that it's stored on a single byte) and 'packet' number on byte 4.

5a 5a 60 00 00 00...
5a 5a 60 00 01 00...
5a 5a 60 00 02 00...
5a 5a 60 00 03 00...
5a 5a 60 01 00 00...
5a 5a 60 01 01 00...
5a 5a 60 01 02 00...
5a 5a 60 01 03 00...
5a 5a 60 01 04 00...

Looking at a single session content, we immediately see some regularity:

0000  5a 5a 60 00 00 00 ef 00  3c 00 12 01 3d 00 0e 01   ZZ`..... <...=...
0010  43 00 18 01 44 00 1c 01  45 00 1b 01 4b 00 19 01   C...D... E...K...
0020  4d 00 17 01 4e 00 16 01  4d 00 0d 01 4b 00 13 01   M...N... M...K...
0030  4c 00 10 01 4b 00 0d 01  49 00 0d 01 4b 00 f5 00   L...K... I...K...
0040  46 00 e5 00 55 00 e3 00  56 00 e0 00 55 00 f3 00   F...U... V...U...
0050  53 00 f0 00 51 00 eb 00  4e 00 ec 00 50 00 ec 00   S...Q... N...P...
0060  4f 00 ea 00 4f 00 ea 00  4e 00 e7 00 4f 00 eb 00   O...O... N...O...
0070  4f 00 e6 00 4d 00 e6 00  4d 00 e7 00 4e 00 e7 00   O...M... M...N...
0080  4e 00 e3 00 4d 00 ea 00  4e 00 e6 00 4e 00 e3 00   N...M... N...N...
0090  4c 00 e2 00 4c 00 dd 00  4b 00 ea 00 4d 00 ea 00   L...L... K...M...
00A0  50 00 e6 00 4e 00 e5 00  4d 00 e2 00 4d 00 ea 00   P...N... M...M...
00B0  4d 00 e5 00 4d 00 e6 00  4d 00 e5 00 4d 00 e5 00   M...M... M...M...
00C0  4d 00 e5 00 4d 00 ea 00  4e 00 ea 00 4f 00 ee 00   M...M... N...O...
00D0  50 00 e6 00 4e 00 e3 00  4d 00 e1 00 4b 00 e3 00   P...N... M...K...
00E0  4d 00 e7 00 4e 00 ee 00  4f 00 ee 00 50 00 e9 00   M...N... O...P...
00F0  4f 00 e7 00 4e 00 00 00  00 00 00 00 00 00 00 ad   O...N... ........

This is varying too fast to be direct encoding of GPS coordinates though. There's clearly a continuity with next packet though:

0000  5a 5a 60 00 01 00 ec 00  4f 00 e1 00 4c 00 e5 00   ZZ`..... O...L...
0010  4d 00 e2 00 4d 00 e1 00  4b 00 de 00 4b 00 de 00   M...M... K...K...
0020  4b 00 e1 00 4c 00 e2 00  4c 00 e5 00 4d 00 e0 00   K...L... L...M...
0030  4c 00 d9 00 4a 00 e2 00  4b 00 e2 00 4c 00 e6 00   L...J... K...L...
0040  4d 00 e0 00 4b 00 e5 00  4c 00 e0 00 4c 00 dd 00   M...K... L...L...
0050  4a 00 da 00 4a 00 e0 00  4b 00 e0 00 4b 00 e3 00   J...J... K...K...
0060  4d 00 e0 00 4b 00 eb 00  4f 00 e5 00 4d 00 de 00   M...K... O...M...
0070  4b 00 e1 00 4c 00 e7 00  4d 00 e7 00 4e 00 e1 00   K...L... M...N...
0080  4e 00 e9 00 4f 00 ea 00  4f 00 e1 00 4d 00 e5 00   N...O... O...M...
0090  4c 00 e5 00 4c 00 e3 00  4d 00 e6 00 4d 00 de 00   L...L... M...M...
00A0  4c 00 e2 00 4b 00 e0 00  4b 00 e5 00 4c 00 eb 00   L...K... K...L...
00B0  4e 00 e6 00 4e 00 e0 00  4c 00 dd 00 4b 00 da 00   N...N... L...K...
00C0  4b 00 dd 00 4a 00 d9 00  49 00 de 00 4a 00 d9 00   K...J... I...J...
00D0  49 00 dc 00 4a 00 e1 00  4b 00 d8 00 49 00 dc 00   I...J... K...I...
00E0  4a 00 d7 00 4a 00 e5 00  4c 00 dc 00 4b 00 da 00   J...J... L...K...
00F0  49 00 e0 00 4b 00 00 00  00 00 00 00 00 00 00 94   I...K... ........

The last packet is emtpy and the one before is not full which confirms there's probably no indication of the number of packets in the session header:

0000  5a 5a 60 00 02 00 e0 00  4b 00 de 00 4b 00 dd 00   ZZ`..... K...K...
0010  4a 00 dd 00 49 00 00 00  00 00 00 00 00 00 00 00   J...I... ........
0020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00A0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00B0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00C0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00D0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00E0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00F0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 b7   ........ ........
0000  5a 5a 60 00 03 00 00 00  00 00 00 00 00 00 00 00   ZZ`..... ........
0010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
0090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00A0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00B0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00C0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00D0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00E0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
00F0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 17   ........ ........

There are 60 patterns of 4 bytes in each message. For the first session, there are 2 full message plus two patterns in the third one. For the second session there are 4 full messages. So 124 4-bytes patterns in the first session and 240 in the second session. If the first session is ~10 minutes and the second one is ~20 minutes, this is probably one point every 30 seconds, each point containing speed and rpm, each on 2 bytes. I would suppose RPM first and speed second, in tenth of km/h probably.

ColinPitrat commented 6 years ago

Just repeating the questions of the previous comment to be sure you see it (it's burried in the middle of the message):

Ideally, if you have the GPX of the sessions (or another readable format) can you attach them ?

elegos commented 6 years ago

Hello @ColinPitrat!

Thanks for the response :)

This is the gc2log, which is essentially an XML file format saved by the application. Giacomo.zip

Thank you very much :)

ColinPitrat commented 6 years ago

It looks like you got the duration wrong in your numbers (doesn't match distance vs. avg speed). The rest matches (see comment below).

If you have some code that goes up to the bulk data transfer, could you please send a pull request ? This is a part that can take multiple round-trips without the watch so it could save me some time.

Also, the gc2log fileformat is not ideal: the TrackData field is encoded. It looks like base64 encoding of binary data. Would it be possible to export a single session as a GPX file ?

ColinPitrat commented 6 years ago

From your information, it looks like I got the average speed right.

From your files, it seems that I got the date, timing, avg & max cadence right: 2018-02-14T21:02:00Z totalTime="624" totalDistance="4020" averageCadence="76" maximumCadence="87"

2018-02-15T19:57:00Z totalTime="1204" totalDistance="7190" averageCadence="58" maximumCadence="64"

I was also half correct for the duration: Byte 10 corresponds to the second part of the duration (04 = 4 - 18 = 24 respectively). Byte 11 corresponds to the minutes part of the duration (0A = 10 - 14 = 20 respectively). Byte 12 being 0, we could assume it would be the hours part. A session of more than an hour could confirm this.

For the distance: Byte 7, 8 & 9 corresponds to the distance (2CF = 719 -> 7190m - 192 = 402 -> 4020m)

elegos commented 6 years ago

Mmm... after tinkering with USB packets, I think I messed up the data transfer of the device: it doesn't respond to the Geonaute software too. I tried unplugging the watch battery and wait a couple of minutes, but still the Geonaute software hangs waiting for the data (data packets arrive, but the BULK IN doesn't reply correctly).

Do you have any suggestion on how to reset the device, maybe without loosing the data? In the mean while, I'll try to think about an idea about the problem.

ColinPitrat commented 6 years ago

Sorry about that, I never encountered such an issue. With some devices, it happened that it would become stuck but using Geonaute software and restarting the device would always fixed it.

I looked at the manualt but there doesn't seem to be a reset button (or combination): http://www.btwin.com/notices/wp-content/uploads/pdf/notices/count/count14+_en.pdf

From the manual, it seems removing the battery looses the data. I'm surprised any other state would be conserved. Maybe you can retry removing the battery and switching on the device (maybe it will help discharge eventual capacitors) and/or waiting longer.

elegos commented 6 years ago

Thanks for the help :+1: I've tried removing the battery long enough to let it ask me again for the settings, but the data seems to be preserved. I think I'll try unplugging the battery for a couple of hours, maybe resetting the data, too, and spinning on the trainer (bad weather this month D:)