Kevin Posted September 8, 2015 Share Posted September 8, 2015 I have a BLE sniffer that I've used to capture some packets going between a IPS XIMA LHOTZ wheel and my iPhone. My goal is to reverse engineer them with the following goals:1) Better understand the existing app and whether some of its bugs are fixable or not2) Develop a more user friendly app for iOs, possibly add features like map/GPS tracking in the distant future3) Investigate the possibility of writing a HUD app for some kind of smart glassesI'll be looking at this in my spare time, but if anyone else is interested in taking a look I can provide what data I capture **UPDATE: I've created an iOs app based on the findings in this thread here:I would like to release the source code at some point in the future, but need to do some cleanup first and I'm feeling lazy right now Link to comment Share on other sites More sharing options...
esaj Posted September 8, 2015 Share Posted September 8, 2015 I could take a look once I have a little more time, just drop me the captured data. Also if there's an Android-app available, I could take a look at that too, if it isn't obfuscated, it usually helps with deciphering the data. The downside on "raw" data captures is that the captured data also contains the BT-protocol frame data, which must be cleaned / picked out to find out the real data moving between the wheel and the app, Wireshark can do this, but I didn't find an option to export the payload only. Easier if you can connect directly to the wheel from you computer with an USB-dongle and custom software just to record the payload, or write a simple app for the phone that does that. Link to comment Share on other sites More sharing options...
Kevin Posted September 8, 2015 Author Share Posted September 8, 2015 These are the capture files I got from WireShark. When I view it in wireshark it parses out the frame data nicely, though I think I have a plugin installed with the sniffer dongle that may be responsible for that.Unfortunately I don't see an android app So far I've been able to determine the following:The 'service' UUID is FF00There are only 2 BLE characteristics, with UUID's FF01 and FF02, neither of which is readable. FF01 is subscribable for notifications.In the traces, it appears there are a lot of writes of value 0x100 to handle 0x000c, I believe this is the handle for the 'client characteristic configuration' descriptor on the FF01 characteristic (the characteristic handle is 0x000a and value handle is 0x000b). So although it is spamming this characteristic, I don't think it's having any effect after the first one.The important bit seems to be a write to handle 0x000e, which appears to be the value handle for FF02. This is always followed by a notification on handle 0x000b, i.e. the value-handle for FF01.I suspect the format of the write to 0x000e is 0x9000[1-byte op code][some payload]. The response on 0x000b always begins with the same 0x9000xx code as the write to 0x000e, however sometimes it appears multiple 0x9000xx responses are encoded in a single notification.0x900001 probably corresponds to "read speed", as it appears at about 0.5 second intervals, the same speed as the speedometer updates in the app. However I haven't been able to pick out with certainty how to parse the payload of the response, as 0kph appears to be 0x[opcode]0000010b.The information that the app displays are:Firmware versionBattery %Speed (kph)Speed limit (it starts at '--' until a wheel connects)'Full riding mileage' xima_hispeed.pcapng xima_lowspeed.pcapng xima1.pcapng Link to comment Share on other sites More sharing options...
esaj Posted September 8, 2015 Share Posted September 8, 2015 These are the capture files I got from WireShark. When I view it in wireshark it parses out the frame data nicely, though I think I have a plugin installed with the sniffer dongle that may be responsible for that.Yup, for anyone else wanting to give a try, one is available here for Windows: https://www.nordicsemi.com/eng/Products/Bluetooth-Smart-Bluetooth-low-energy/nRF-Sniffer Extract, grab the plugin-dll under Sniffer\plugins\1.12\windows\<x86 or x64>\ and place it under Wireshark\plugins\<versionnumber>\ Unfortunately I don't see an android app It appears there isn't one yet available.So far I've been able to determine the following:The 'service' UUID is FF00There are only 2 BLE characteristics, with UUID's FF01 and FF02, neither of which is readable. FF01 is subscribable for notifications.In the traces, it appears there are a lot of writes of value 0x100 to handle 0x000c, I believe this is the handle for the 'client characteristic configuration' descriptor on the FF01 characteristic (the characteristic handle is 0x000a and value handle is 0x000b). So although it is spamming this characteristic, I don't think it's having any effect after the first one.The important bit seems to be a write to handle 0x000e, which appears to be the value handle for FF02. This is always followed by a notification on handle 0x000b, i.e. the value-handle for FF01.I suspect the format of the write to 0x000e is 0x9000[1-byte op code][some payload]. The response on 0x000b always begins with the same 0x9000xx code as the write to 0x000e, however sometimes it appears multiple 0x9000xx responses are encoded in a single notification.0x900001 probably corresponds to "read speed", as it appears at about 0.5 second intervals, the same speed as the speedometer updates in the app. However I haven't been able to pick out with certainty how to parse the payload of the response, as 0kph appears to be 0x[opcode]0000010b.Nice start, I'll see if I can dig out anything coherent about the values just by looking at the captures, although might not have time for that until maybe later tonight, if even then. The information that the app displays are:Firmware versionBattery %Speed (kph)Speed limit (it starts at '--' until a wheel connects)'Full riding mileage'xima_hispeed.pcapngxima_lowspeed.pcapngxima1.pcapngIt would help to know what sort of values to look for, "hispeed" and "lowspeed" don't really tell much, what sort of speeds were there, what about the battery voltages (or percentages), should we look for values closer to full (say, above 64V) or empty (lower than 60V), what was the mileage when the captures were taken..? Link to comment Share on other sites More sharing options...
Kevin Posted September 8, 2015 Author Share Posted September 8, 2015 It would help to know what sort of values to look for, "hispeed" and "lowspeed" don't really tell much, what sort of speeds were there, what about the battery voltages (or percentages), should we look for values closer to full (say, above 64V) or empty (lower than 60V), what was the mileage when the captures were taken..? Haha right, sorry it was super late when I posted.Here's a couple screenshots of what the app shows, the values should be basically the same as they were last night since the unit was on full charge and I was just pushing it back and forth with my hands. I'm not 100% sure that the mileage will correspond to the figure shown - when I've been riding for a while and connect, it first shows one number, then updates to a slightly higher number, then a much higher number - however when I reconnect it will then show one of the two lower numbers again.'lowspeed' would have been with me pushing it back and forth around 0.1 or 0.2kph, whereas with 'hispeed' I tried to yank it as hard as I could, so there should be intervals of low-speed alternating with higher spikes up to 4kph-8kph ish. When I get a chance I'll try recording a video of the app while repeating this test so that I can get a record of the actual values displayed. Link to comment Share on other sites More sharing options...
esaj Posted September 8, 2015 Share Posted September 8, 2015 Here's a quick strip of data from xima_hispeed.pcapng, as reading it via Wireshark isn't exactly easy. I first filtered & exported the "Rcvd Handle Value Notification, Handle: 0x000b"-data packets plain text and then wrote a quick & dirty piece of program to strip out Value-fields, sort them by the first 3 bytes (like 900001), parse hex strings, both as Big-Endian (BE) and Little-Endian (LE) -formats and then output them in the order they occurred for that "id" (the first three bytes), hope it'll give some clues. There are a few "outliers", where the value-field has contained two values (like 900001000000010b900011d956683c49), didn't bother to fix them now, as I made it in like 15 minutes :900010:Hex: c800f04233, 8-bit BE: 51, 16-bit BE:16947, 32-bit BE: 15745587, 40-bit BE: 859009204787 Hex: 3342f000, 8-bit LE: 51, 16-bit LE: 13122, 32-bit LE: 860024832900011:Hex: 636a153b38, 8-bit BE: 56, 16-bit BE:15160, 32-bit BE: 1779776312, 40-bit BE: 426981538616 Hex: 383b156a, 8-bit LE: 56, 16-bit LE: 14395, 32-bit LE: 943396202Hex: 630d723b38, 8-bit BE: 56, 16-bit BE:15160, 32-bit BE: 225590072, 40-bit BE: 425427352376 Hex: 383b720d, 8-bit LE: 56, 16-bit LE: 14395, 32-bit LE: 943419917Hex: b2b0ce3b4b, 8-bit BE: 75, 16-bit BE:15179, 32-bit BE: -1328661685, 40-bit BE: 767470484299 Hex: 4b3bceb0, 8-bit LE: 75, 16-bit LE: 19259, 32-bit LE: 1262210736Hex: b9c9153c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: -921355191, 40-bit BE: 797942561865 Hex: 493c15c9, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 1228674505Hex: d957673c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: 1466383433, 40-bit BE: 933474286665 Hex: 493c6757, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 1228695383Hex: d956683c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: 1449671753, 40-bit BE: 933457574985 Hex: 493c6856, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 1228695638Hex: d956683c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: 1449671753, 40-bit BE: 933457574985 Hex: 493c6856, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 1228695638Hex: d956683c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: 1449671753, 40-bit BE: 933457574985 Hex: 493c6856, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 1228695638Hex: 39f6683c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: -160940983, 40-bit BE: 248947162185 Hex: 493c68f6, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 12286957989000d1:Hex: 000000041b, 8-bit BE: 27, 16-bit BE:1051, 32-bit BE: 1051, 40-bit BE: 1051 Hex: 1b040000, 8-bit LE: 27, 16-bit LE: 6916, 32-bit LE: 4532469769000d0:Hex: 0000000218, 8-bit BE: 24, 16-bit BE:536, 32-bit BE: 536, 40-bit BE: 536 Hex: 18020000, 8-bit LE: 24, 16-bit LE: 6146, 32-bit LE: 40278425679063b:Hex: 38, 8-bit BE: 56920001:Hex: 0000008f21, 8-bit BE: 33, 16-bit BE:36641, 32-bit BE: 36641, 40-bit BE: 36641 Hex: 218f0000, 8-bit LE: 33, 16-bit LE: 8591, 32-bit LE: 563019776900001:Hex: 0000000913, 8-bit BE: 19, 16-bit BE:2323, 32-bit BE: 2323, 40-bit BE: 2323 Hex: 13090000, 8-bit LE: 19, 16-bit LE: 4873, 32-bit LE: 319356928Hex: 0000000610, 8-bit BE: 16, 16-bit BE:1552, 32-bit BE: 1552, 40-bit BE: 1552 Hex: 10060000, 8-bit LE: 16, 16-bit LE: 4102, 32-bit LE: 268828672Hex: 0000000913, 8-bit BE: 19, 16-bit BE:2323, 32-bit BE: 2323, 40-bit BE: 2323 Hex: 13090000, 8-bit LE: 19, 16-bit LE: 4873, 32-bit LE: 319356928Hex: 0000000e18, 8-bit BE: 24, 16-bit BE:3608, 32-bit BE: 3608, 40-bit BE: 3608 Hex: 180e0000, 8-bit LE: 24, 16-bit LE: 6158, 32-bit LE: 403570688Hex: 000000030d, 8-bit BE: 13, 16-bit BE:781, 32-bit BE: 781, 40-bit BE: 781 Hex: 0d030000, 8-bit LE: 13, 16-bit LE: 3331, 32-bit LE: 218300416Hex: 000000100b, 8-bit BE: 11, 16-bit BE:4107, 32-bit BE: 4107, 40-bit BE: 4107 Hex: 0b100000, 8-bit LE: 11, 16-bit LE: 2832, 32-bit LE: 185597952Hex: 000000130e, 8-bit BE: 14, 16-bit BE:4878, 32-bit BE: 4878, 40-bit BE: 4878 Hex: 0e130000, 8-bit LE: 14, 16-bit LE: 3603, 32-bit LE: 236126208Hex: 000000140f, 8-bit BE: 15, 16-bit BE:5135, 32-bit BE: 5135, 40-bit BE: 5135 Hex: 0f140000, 8-bit LE: 15, 16-bit LE: 3860, 32-bit LE: 252968960Hex: 000000230f, 8-bit BE: 15, 16-bit BE:8975, 32-bit BE: 8975, 40-bit BE: 8975 Hex: 0f230000, 8-bit LE: 15, 16-bit LE: 3875, 32-bit LE: 253952000Hex: 000000230f, 8-bit BE: 15, 16-bit BE:8975, 32-bit BE: 8975, 40-bit BE: 8975 Hex: 0f230000, 8-bit LE: 15, 16-bit LE: 3875, 32-bit LE: 253952000Hex: 0000002915, 8-bit BE: 21, 16-bit BE:10517, 32-bit BE: 10517, 40-bit BE: 10517 Hex: 15290000, 8-bit LE: 21, 16-bit LE: 5417, 32-bit LE: 355008512Hex: 0000000c16, 8-bit BE: 22, 16-bit BE:3094, 32-bit BE: 3094, 40-bit BE: 3094 Hex: 160c0000, 8-bit LE: 22, 16-bit LE: 5644, 32-bit LE: 369885184Hex: 000000020c, 8-bit BE: 12, 16-bit BE:524, 32-bit BE: 524, 40-bit BE: 524 Hex: 0c020000, 8-bit LE: 12, 16-bit LE: 3074, 32-bit LE: 201457664Hex: 0000001914, 8-bit BE: 20, 16-bit BE:6420, 32-bit BE: 6420, 40-bit BE: 6420 Hex: 14190000, 8-bit LE: 20, 16-bit LE: 5145, 32-bit LE: 337182720Hex: 0000015111, 8-bit BE: 17, 16-bit BE:20753, 32-bit BE: 86289, 40-bit BE: 86289 Hex: 11510100, 8-bit LE: 17, 16-bit LE: 4433, 32-bit LE: 290521344Hex: 0000016415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 91157, 40-bit BE: 91157 Hex: 15640100, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875392Hex: 000000b81d, 8-bit BE: 29, 16-bit BE:47133, 32-bit BE: 47133, 40-bit BE: 47133 Hex: 1db80000, 8-bit LE: 29, 16-bit LE: 7608, 32-bit LE: 498597888Hex: 000000bd22, 8-bit BE: 34, 16-bit BE:48418, 32-bit BE: 48418, 40-bit BE: 48418 Hex: 22bd0000, 8-bit LE: 34, 16-bit LE: 8893, 32-bit LE: 582811648Hex: 000001f01a, 8-bit BE: 26, 16-bit BE:61466, 32-bit BE: 127002, 40-bit BE: 127002 Hex: 1af00100, 8-bit LE: 26, 16-bit LE: 6896, 32-bit LE: 451936512Hex: 000001220f, 8-bit BE: 15, 16-bit BE:8719, 32-bit BE: 74255, 40-bit BE: 74255 Hex: 0f220100, 8-bit LE: 15, 16-bit LE: 3874, 32-bit LE: 253886720Hex: 0000016011, 8-bit BE: 17, 16-bit BE:24593, 32-bit BE: 90129, 40-bit BE: 90129 Hex: 11600100, 8-bit LE: 17, 16-bit LE: 4448, 32-bit LE: 291504384Hex: 000001d21a, 8-bit BE: 26, 16-bit BE:53786, 32-bit BE: 119322, 40-bit BE: 119322 Hex: 1ad20100, 8-bit LE: 26, 16-bit LE: 6866, 32-bit LE: 449970432Hex: 000000ae22, 8-bit BE: 34, 16-bit BE:44578, 32-bit BE: 44578, 40-bit BE: 44578 Hex: 22ae0000, 8-bit LE: 34, 16-bit LE: 8878, 32-bit LE: 581828608Hex: 000000d81f, 8-bit BE: 31, 16-bit BE:55327, 32-bit BE: 55327, 40-bit BE: 55327 Hex: 1fd80000, 8-bit LE: 31, 16-bit LE: 8152, 32-bit LE: 534249472Hex: 0000021613, 8-bit BE: 19, 16-bit BE:5651, 32-bit BE: 136723, 40-bit BE: 136723 Hex: 13160200, 8-bit LE: 19, 16-bit LE: 4886, 32-bit LE: 320209408Hex: 0000014d1c, 8-bit BE: 28, 16-bit BE:19740, 32-bit BE: 85276, 40-bit BE: 85276 Hex: 1c4d0100, 8-bit LE: 28, 16-bit LE: 7245, 32-bit LE: 474808576Hex: 0000002f1b, 8-bit BE: 27, 16-bit BE:12059, 32-bit BE: 12059, 40-bit BE: 12059 Hex: 1b2f0000, 8-bit LE: 27, 16-bit LE: 6959, 32-bit LE: 456065024Hex: 000001e720, 8-bit BE: 32, 16-bit BE:59168, 32-bit BE: 124704, 40-bit BE: 124704 Hex: 20e70100, 8-bit LE: 32, 16-bit LE: 8423, 32-bit LE: 552009984Hex: 0000013412, 8-bit BE: 18, 16-bit BE:13330, 32-bit BE: 78866, 40-bit BE: 78866 Hex: 12340100, 8-bit LE: 18, 16-bit LE: 4660, 32-bit LE: 305398016Hex: 0000005817, 8-bit BE: 23, 16-bit BE:22551, 32-bit BE: 22551, 40-bit BE: 22551 Hex: 17580000, 8-bit LE: 23, 16-bit LE: 5976, 32-bit LE: 391643136Hex: 000001b319, 8-bit BE: 25, 16-bit BE:45849, 32-bit BE: 111385, 40-bit BE: 111385 Hex: 19b30100, 8-bit LE: 25, 16-bit LE: 6579, 32-bit LE: 431161600Hex: 000001a318, 8-bit BE: 24, 16-bit BE:41752, 32-bit BE: 107288, 40-bit BE: 107288 Hex: 18a30100, 8-bit LE: 24, 16-bit LE: 6307, 32-bit LE: 413335808Hex: 000000ab1f, 8-bit BE: 31, 16-bit BE:43807, 32-bit BE: 43807, 40-bit BE: 43807 Hex: 1fab0000, 8-bit LE: 31, 16-bit LE: 8107, 32-bit LE: 531300352Hex: 000001971b, 8-bit BE: 27, 16-bit BE:38683, 32-bit BE: 104219, 40-bit BE: 104219 Hex: 1b970100, 8-bit LE: 27, 16-bit LE: 7063, 32-bit LE: 462881024Hex: 0000025d1e900011f959453c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: 1497709641 Hex: 493c4559f91100901e5d0200, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 1228686681Hex: 000000c41a, 8-bit BE: 26, 16-bit BE:50202, 32-bit BE: 50202, 40-bit BE: 50202 Hex: 1ac40000, 8-bit LE: 26, 16-bit LE: 6852, 32-bit LE: 449052672Hex: 0000018619, 8-bit BE: 25, 16-bit BE:34329, 32-bit BE: 99865, 40-bit BE: 99865 Hex: 19860100, 8-bit LE: 25, 16-bit LE: 6534, 32-bit LE: 428212480Hex: 0000026113, 8-bit BE: 19, 16-bit BE:24851, 32-bit BE: 155923, 40-bit BE: 155923 Hex: 13610200, 8-bit LE: 19, 16-bit LE: 4961, 32-bit LE: 325124608Hex: 0000010e19, 8-bit BE: 25, 16-bit BE:3609, 32-bit BE: 69145, 40-bit BE: 69145 Hex: 190e0100, 8-bit LE: 25, 16-bit LE: 6414, 32-bit LE: 420348160Hex: 0000008618, 8-bit BE: 24, 16-bit BE:34328, 32-bit BE: 34328, 40-bit BE: 34328 Hex: 18860000, 8-bit LE: 24, 16-bit LE: 6278, 32-bit LE: 411435008Hex: 0000001b16, 8-bit BE: 22, 16-bit BE:6934, 32-bit BE: 6934, 40-bit BE: 6934 Hex: 161b0000, 8-bit LE: 22, 16-bit LE: 5659, 32-bit LE: 370868224Hex: 0000000e18, 8-bit BE: 24, 16-bit BE:3608, 32-bit BE: 3608, 40-bit BE: 3608 Hex: 180e0000, 8-bit LE: 24, 16-bit LE: 6158, 32-bit LE: 403570688Hex: 000000030d, 8-bit BE: 13, 16-bit BE:781, 32-bit BE: 781, 40-bit BE: 781 Hex: 0d030000, 8-bit LE: 13, 16-bit LE: 3331, 32-bit LE: 218300416Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b900011d956683c49, 8-bit BE: 73, 16-bit BE:15433, 32-bit BE: 1449671753 Hex: 493c6856d91100900b010000, 8-bit LE: 73, 16-bit LE: 18748, 32-bit LE: 1228695638Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912Hex: 000000030d, 8-bit BE: 13, 16-bit BE:781, 32-bit BE: 781, 40-bit BE: 781 Hex: 0d030000, 8-bit LE: 13, 16-bit LE: 3331, 32-bit LE: 218300416Hex: 000000020c, 8-bit BE: 12, 16-bit BE:524, 32-bit BE: 524, 40-bit BE: 524 Hex: 0c020000, 8-bit LE: 12, 16-bit LE: 3074, 32-bit LE: 201457664Hex: 000000010b, 8-bit BE: 11, 16-bit BE:267, 32-bit BE: 267, 40-bit BE: 267 Hex: 0b010000, 8-bit LE: 11, 16-bit LE: 2817, 32-bit LE: 184614912900002:Hex: 00000064159000010000000a1490001163, 8-bit BE: 99, 16-bit BE:4451, 32-bit BE: -1879043741 Hex: 63110090140a00000001009015640000, 8-bit LE: 99, 16-bit LE: 25361, 32-bit LE: 1662058640Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415, 8-bit BE: 21, 16-bit BE:25621, 32-bit BE: 25621, 40-bit BE: 25621 Hex: 15640000, 8-bit LE: 21, 16-bit LE: 5476, 32-bit LE: 358875136Hex: 0000006415900001000000020c, 8-bit BE: 12, 16-bit BE:524, 32-bit BE: 524 Hex: 0c0200000001009015640000, 8-bit LE: 12, 16-bit LE: 3074, 32-bit LE: 201457664900012:Hex: 0000040212, 8-bit BE: 18, 16-bit BE:530, 32-bit BE: 262674, 40-bit BE: 262674 Hex: 12020400, 8-bit LE: 18, 16-bit LE: 4610, 32-bit LE: 302121984900013:Hex: 0000001e1c, 8-bit BE: 28, 16-bit BE:7708, 32-bit BE: 7708, 40-bit BE: 7708 Hex: 1c1e0000, 8-bit LE: 28, 16-bit LE: 7198, 32-bit LE: 471728128The speed data could be in some weird format (like rpm, or revolutions / second, or meters/second), battery may be sent as a voltage-value, percentage or discrete steps (like 0...100% = 0...255 or 0...65535 or whatever), odometer can be represented as meters, or they could actually send float-values (but then we'd likely see less zeroes, as the mantissa and exponent should be something else than 0) etc. Let's see if we find any patterns here.There could be multiple values in some packets. Link to comment Share on other sites More sharing options...
Kevin Posted September 8, 2015 Author Share Posted September 8, 2015 Stroke of luck! I contacted the seller directly and they sent me the Android app directly (there are two versions, one which unlocks the 30kph immediately and the other which requires riding 100km first). XIMA-ver1.1.3-limit-30.apk Link to comment Share on other sites More sharing options...
esaj Posted September 8, 2015 Share Posted September 8, 2015 Stroke of luck! I contacted the seller directly and they sent me the Android app directly (there are two versions, one which unlocks the 30kph immediately and the other which requires riding 100km first). XIMA-ver1.1.3-limit-30.apkNice, I'll pick it apart, this should tell fairly easily how the data is read (unless it's obfuscated...) Link to comment Share on other sites More sharing options...
Kevin Posted September 8, 2015 Author Share Posted September 8, 2015 What tools do you use for that? I've never tried decompiling an app before. Link to comment Share on other sites More sharing options...
esaj Posted September 8, 2015 Share Posted September 8, 2015 What tools do you use for that? I've never tried decompiling an app before.The .apk is a zip-file, extract classes.dex from it, then use something like dex2jar to get a jar-package from that. Then use a decompiler (I've used jd-gui) on the jar-package and extract the decompiled classes. Here's the decompiled code (it won't be runnable or even compilable, some classes just don't decompile and the code will be "impossible" at parts, meaning it's doing things like reusing wrong type variables to store other info etc., but if you know Java, you can still read it): xima-classes.src.zipI've now been poking at it for about an hour, and this is how far I've got: Here are the commands I found so far: public static final String READ_MILEAGE_CMD_HEX = "90000700000000106FFFF8FFFFFFFFEF"; public static final String READ_POWER_CMD_HEX = "900002000000000B6FFFFDFFFFFFFFF4"; public static final String READ_VELOCITY_CMD_HEX = "900001000000000A6FFFFEFFFFFFFFF5"; So 900007 is odometer, 900002 power (so probably battery?) and 900001 is speed, as you suspected. Here are the Gatt-attributes, didn't find the Characteristics assigned number (2902) from the Bluetooth-site (although I don't have an account, so I don't see the whole list) from https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx : public static String CLIENT_CHARACTERISTIC_CONFIG; public static String RX; public static String SERVICE = "0000ff00-0000-1000-8000-00805f9b34fb"; public static String TX; private static HashMap<String, String> attributes; static { RX = "0000ff01-0000-1000-8000-00805f9b34fb"; TX = "0000ff02-0000-1000-8000-00805f9b34fb"; CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"; attributes = new HashMap(); attributes.put(SERVICE, "Type A Solowheel Service"); attributes.put(RX, "Receiver Attribute"); attributes.put(TX, "Transmission Attribute"); }There also seems to be also some WebService-class that contacts iamips.oicp.net, apparently to download something (maybe app autoupdate?). Found also the part which builds the command to sent to the wheel, unfortunately the CommandEnum-class didn't decompile, so the cases are all ???: public static byte[] compileCmd(CommandEnum paramCommandEnum) { byte[] arrayOfByte = new byte[8]; byte[] tmp6_5 = arrayOfByte; tmp6_5[0] = -112; byte[] tmp11_6 = tmp6_5; tmp11_6[1] = 0; byte[] tmp16_11 = tmp11_6; tmp16_11[2] = 1; byte[] tmp21_16 = tmp16_11; tmp21_16[3] = 0; byte[] tmp26_21 = tmp21_16; tmp26_21[4] = 0; byte[] tmp31_26 = tmp26_21; tmp31_26[5] = 0; byte[] tmp36_31 = tmp31_26; tmp36_31[6] = 0; byte[] tmp42_36 = tmp36_31; tmp42_36[7] = 0; tmp42_36; switch (paramCommandEnum) { default: return arrayOfByte; case ???: arrayOfByte[2] = 1; return arrayOfByte; case ???: arrayOfByte[2] = 2; return arrayOfByte; /** SNIP: lots of cases that tweak a byte or two in the original arrayOfByte **/ } The application seems to have firmware update-capabilities, but that's just a guess based on these: private static final int UPDATE_FIRMWARE_BYTES_AMOUNT = 64; private long firmwareFileSize; private FileInputStream firmwareUpdateFileInputStream; Then to the actual meat, BluetootLeService.class & DashBoardFragment (UI-class) that show how to read the data: public void onCharacteristicChanged(BluetoothGatt paramAnonymousBluetoothGatt, BluetoothGattCharacteristic paramAnonymousBluetoothGattCharacteristic) { paramAnonymousBluetoothGatt = paramAnonymousBluetoothGattCharacteristic.getValue(); paramAnonymousBluetoothGattCharacteristic = new StringBuilder(); int j = paramAnonymousBluetoothGatt.length; int i = 0; while (i < j) { paramAnonymousBluetoothGattCharacteristic.append(String.format("%02x", new Object[] { Integer.valueOf(paramAnonymousBluetoothGatt & 0xFF) })); i += 1; } Log.i(BluetoothLeService.TAG + ": bytes - ", paramAnonymousBluetoothGattCharacteristic.toString()); BluetoothLeService.this.parseReturnData(paramAnonymousBluetoothGatt); }BluetoothGatt & BluetoothGattCharacteristic are just Android-classes: https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html & https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html Here's the parsing, it looks weird because the class has been optimized by compiler during build time, and the decompilation is never perfect (it produces uncompilable code, for example the for-loop would complain of dead code in reality, as there's branching that cannot be ever reached, in reality the cases probably should be inside it, and the paramArrayOfByte couldn't be reused for the Event-objects, as it's wrong type): public void parseReturnData(byte[] paramArrayOfByte) { if (paramArrayOfByte.length != 8) { Log.e(TAG, "Return Data length is not 8"); return; } switch (paramArrayOfByte[2] & 0xFF) { default: paramArrayOfByte = new NullEvent(); case 1: case 2: case 16: case 17: case 18: case 19: case 208: case 209: case 80: case 81: for (;;) { EventBus.getDefault().post(paramArrayOfByte); return; paramArrayOfByte = new GetSpeedEvent(((paramArrayOfByte[5] & 0xF) * 256 + ((paramArrayOfByte[6] & 0xF0) >> 4) * 16 + (paramArrayOfByte[6] & 0xF)) / 100.0F); continue; paramArrayOfByte = new GetPowerEvent(paramArrayOfByte[6]); continue; paramArrayOfByte = new GetSumMileageEvent(ByteBuffer.wrap(new byte[] { paramArrayOfByte[3], paramArrayOfByte[4], paramArrayOfByte[5], paramArrayOfByte[6] }).order(ByteOrder.nativeOrder()).getFloat()); continue; paramArrayOfByte = new GetMileageEvent(ByteBuffer.wrap(new byte[] { paramArrayOfByte[3], paramArrayOfByte[4], paramArrayOfByte[5], paramArrayOfByte[6] }).order(ByteOrder.nativeOrder()).getFloat()); continue; paramArrayOfByte = new GetVernumEvent(String.valueOf(paramArrayOfByte[5]) + "." + String.valueOf(paramArrayOfByte[6])); continue; int j = paramArrayOfByte[6] & 0xFF; int i = j; if (j == 19) { i = 20; } paramArrayOfByte = new GetSpeedlimitEvent(i); continue; paramArrayOfByte = new GetLightStatusEvent(new LightStatus(CommandEnum.???????, paramArrayOfByte[6])); continue; paramArrayOfByte = new GetLightStatusEvent(new LightStatus(CommandEnum.??????, paramArrayOfByte[6])); continue; if (paramArrayOfByte[6] == 0) {} for (bool = true;; bool = false) { paramArrayOfByte = new SetSpeedLimitDoneEvent(bool); break; } paramArrayOfByte = new GetSumMileageEvent(0.0F); } } if (paramArrayOfByte[6] == 0) {} for (boolean bool = true;; bool = false) { paramArrayOfByte = new RenamingSucceeded(bool); break; } }Btw, the GetPowerEvent etc. classes are nothing but simple holders for values: public class GetPowerEvent{ private final int power; public GetPowerEvent(int paramInt) { this.power = paramInt; } public float getPower() { return this.power; }}public class GetSpeedEvent{ private final float speed; public GetSpeedEvent(float paramFloat) { this.speed = paramFloat; } public float getSpeed() { return this.speed; }} Skimming through the DashboardFragment & DigitVelocityFragment, they're just formatting strings and adding stuff like %-sign on the battery value, so the data is already there before the UI. They're also adding to the total mileage (so it looks like the mileage-data is sent as accumulated since last call, that's probably why it behaves weirdly?). Here's for example updating the speed in the DigitVelocityFragment: public void onEventMainThread(GetSpeedEvent paramGetSpeedEvent) { Log.i(TAG, "Received speed data(km/h):" + paramGetSpeedEvent.getSpeed()); this.semiCircleView.setAngle(paramGetSpeedEvent.getSpeed()); this.semiCircleView.invalidate(); this.speed.setText(String.format("%.1f", new Object[] { Float.valueOf(Math.abs(paramGetSpeedEvent.getSpeed())) })); } So, here's the actual parts which read the data:SPEED: paramArrayOfByte = new GetSpeedEvent(((paramArrayOfByte[5] & 0xF) * 256 + ((paramArrayOfByte[6] & 0xF0) >> 4) * 16 + (paramArrayOfByte[6] & 0xF)) / 100.0F);Don't know if that's done by the decompilation (unnecessarily complex, for example shifting right 4 bits to get the 4 uppermost bits and then multiplying by 16 cancel each other out?), but it seems basically what that computation does is same as:(((paramArrayOfByte[5] & 0xF) << 8) + (paramArrayOfByte[6] & 0xFF)) / 100.0F;So it takes the 4 lowermost bits from the 5th index and the whole value from the 6th index (casting it to int with & 0xFF without taking the sign into account, this is how you deal with binary data in Java due to the missing unsigned-types), and then divides by 100.0 (so the original value is "tens of meters per hour" before division).For example, taking one of the hex-strings from the earlier datacapture (Frame 1358 in the hispeed-capture):Value: 900001000000ab1fAt first I thought that [5] is 0xab and [6] is 1f => 0xab & 0x0f = bShifted left 8 bits (same as multiplying by 256) => 0x0b00Get the entire lower byte and add it => 0x0b1f = 2847Divide by 100, result is in km/h (according to DigitVelocityFragment, which just drops the last fractional digit and places the value into the UI) => 28.47km/h. Too high if the wheel was only being moved back and forth by hand...Then I tested the suspected "0" -speed, 900001000000010b0x01 & 0x0f = 0x01After shift => 0x0100Plus the lower part => 0x0101 = 257Divided by 100 results... 2.57km/h? That can't be right, there's something else going behind the scenes... Then I took a look at: POWER: paramArrayOfByte = new GetPowerEvent(paramArrayOfByte[6]); So it's just one byte from the value sent by the wheel, same position (6th index) as the latter byte used in speed, and the battery is supposed to be at 100%? 100 = 0x64, let's take a look at the battery data:9000020000006415Oh, look at that... there it is, 0x64... No idea what the last byte is for, checksum? So I was off-by-one with the speed data. Let's revisit that speed data:900001000000ab1fNow, if [5] = 0x00 and [6] = 0xab, it's simply0x00 & 0x00 = 0, shifting doesn't change itLower part is 0xab = 171, divide by 100 => 1.71km/h. Plausible, maybe..."Zero-speed": 900001000000010bIt's just "one", I'll skip explaining the calculation => 0.01km/h. Plausible, as the UI drops the last digit (so it shows just 0.0km/h)...And that's pretty much where I am now. TODO: TOTAL MILEAGE? paramArrayOfByte = new GetSumMileageEvent(ByteBuffer.wrap(new byte[] { paramArrayOfByte[3], paramArrayOfByte[4], paramArrayOfByte[5], paramArrayOfByte[6] }).order(ByteOrder.nativeOrder()).getFloat());ACCUMULATED MILEAGE? paramArrayOfByte = new GetMileageEvent(ByteBuffer.wrap(new byte[] { paramArrayOfByte[3], paramArrayOfByte[4], paramArrayOfByte[5], paramArrayOfByte[6] }).order(ByteOrder.nativeOrder()).getFloat()); VERSION NUMBER: paramArrayOfByte = new GetVernumEvent(String.valueOf(paramArrayOfByte[5]) + "." + String.valueOf(paramArrayOfByte[6]));EDIT: Actually the above are pretty self-explanatory, the mileages are 32bit floats and the version number is major version in one byte, and minor in the following... xima-classes.src.zip Link to comment Share on other sites More sharing options...
esaj Posted September 8, 2015 Share Posted September 8, 2015 Here's the speed data read from the hispeed-capture as above (reading the 12-bit value & dividing by 100, then formatting to show only one digit after the decimal point):SPEED: 0,1 km/hSPEED: 0,1 km/hSPEED: 0,1 km/hSPEED: 0,1 km/hSPEED: 0,0 km/hSPEED: 0,2 km/hSPEED: 0,2 km/hSPEED: 0,2 km/hSPEED: 0,3 km/hSPEED: 0,3 km/hSPEED: 0,4 km/hSPEED: 0,1 km/hSPEED: 0,0 km/hSPEED: 0,3 km/hSPEED: 3,4 km/hSPEED: 3,6 km/hSPEED: 1,8 km/hSPEED: 1,9 km/hSPEED: 5,0 km/hSPEED: 2,9 km/hSPEED: 3,5 km/hSPEED: 4,7 km/hSPEED: 1,7 km/hSPEED: 2,2 km/hSPEED: 5,3 km/hSPEED: 3,3 km/hSPEED: 0,5 km/hSPEED: 4,9 km/hSPEED: 3,1 km/hSPEED: 0,9 km/hSPEED: 4,3 km/hSPEED: 4,2 km/hSPEED: 1,7 km/hSPEED: 4,1 km/hSPEED: 6,1 km/hSPEED: 2,0 km/hSPEED: 3,9 km/hSPEED: 6,1 km/hSPEED: 2,7 km/hSPEED: 1,3 km/hSPEED: 0,3 km/hSPEED: 0,1 km/hSPEED: 0,0 km/hSPEED: 0,0 km/hSPEED: 0,0 km/hSPEED: 0,0 km/hSPEED: 0,0 km/h+ more 0km/h valuesDoes this seem about right to you? Link to comment Share on other sites More sharing options...
Kevin Posted September 8, 2015 Author Share Posted September 8, 2015 Wow, you sure don't mess around The values look entirely plausible! I don't have time to look at this until later tonight unfortunately... this is going to be distracting me all day Link to comment Share on other sites More sharing options...
esaj Posted September 8, 2015 Share Posted September 8, 2015 Wow, you sure don't mess around Well, having the original code decompiled made this pretty easy, otherwise I'd been taking wild guesses at the captured data for days I just fixed a couple of minor details in the above posts, looks like I can't count anymore (24-bit values from 2 bytes? nope... but at least I was reading 12-bit values in the code ). The values look entirely plausible! I don't have time to look at this until later tonight unfortunately... this is going to be distracting me all day When you have the time, you could try building a simple test-app to see if you can read the values directly from the wheel and if they still look alright, then the protocol-side can be pretty much considered solved... The hard part of course is building the actual app... I had never dealt with Bluetooth or even Android before making the GW/Kingsong-proto, and I have never dealt with BT LE before, based on this it works very differently from "classic" BT, kind of reminds me of Zigbee. Link to comment Share on other sites More sharing options...
Kevin Posted September 9, 2015 Author Share Posted September 9, 2015 When you have the time, you could try building a simple test-app to see if you can read the values directly from the wheel and if they still look alright, then the protocol-side can be pretty much considered solved... The hard part of course is building the actual app... I had never dealt with Bluetooth or even Android before making the GW/Kingsong-proto, and I have never dealt with BT LE before, based on this it works very differently from "classic" BT, kind of reminds me of Zigbee.Yeah, BT LE is more or less just a set of registers that the device defines and that the phone reads from/writes to... with small extensions to support notifications when a register value changes. Last night I whipped up a test app that replicated the 900001 command and verified that I got the expected response back (this is how I got the info about what characteristics were readable/writable/notifiable, actually). I had pre written code for something similar, so it was a bit easier Tonight I'd like to put in some parsing for what you've found and then go for a ride - I have a pretty good idea of the speed at which the wheel starts beeping, so I can use that to confirm the numbers at the upper end. Link to comment Share on other sites More sharing options...
WayneZengAU Posted September 9, 2015 Share Posted September 9, 2015 Alright Im gonna sound like a complete noob, but is it possible to create an app function to control the 'hardness' setting of the Lhotz? Link to comment Share on other sites More sharing options...
Kevin Posted September 9, 2015 Author Share Posted September 9, 2015 I doubt it There might be a way to affect beeping/tiltback speeds, though I'm not holding my breath for that either. Link to comment Share on other sites More sharing options...
esaj Posted September 9, 2015 Share Posted September 9, 2015 Alright Im gonna sound like a complete noob, but is it possible to create an app function to control the 'hardness' setting of the Lhotz?Don't think so, unless it's an hidden option (didn't notice anything like that when going through the app). It is possible to send commands to the wheel, but only those that the wheel firmware understands. Link to comment Share on other sites More sharing options...
Kevin Posted September 9, 2015 Author Share Posted September 9, 2015 @esajI'm slowly crawling through the decompiled source now. I see what you mean now by assigning to the wrong type - I thought it was a decompiler bug at first, but it actually makes sense after thinking about it. I think from now on in my code I am going to follow the compiler's example and just use Object for every variable and cast to the actual type only when I want to access methods/fields Command-building:public static byte[] sendRequestByType(CommandEnum paramCommandEnum)This seems to basically take an enum value, uses it to lookup a fixed sequence of 7 bytes (that's more or less what compileCmd does that you pasted above), then passes it through getResultBytes() which (1) adds a 1-byte checksum to the end, and (2) inverts the bits (including checksum) and appends the inverted result to the end. So as far as I can tell each command enum value maps 1:1 to a fixed sequence of 16 bytes. **I just doublechecked from the packet trace, 90:00:01:00:00:00:00:0a:6f:ff:fe:ff:ff:ff:ff:f5 -> the last 8 bytes are just the first 8 inverted.So the good news is that it's a lot simpler than I expected and we can just use a packet sniffer & replay the packet payload to replicate the behavior exactly, but the bad news is that there are only 20 commands that we can execute with no ability to parameterize or similar. Based on the switch-case logic, the 3rd byte gives the actual command code as expected and in a few specific cases the 7th byte specifies a parameter, but the parameter is always just some integer from 1-5. And.... err, I guess that actually basically covers it. So it seems there isn't a lot that can be done with the app other than minor tweaks/bugfixes, unless adding non-wheel-related features like GPS tracking etc... Link to comment Share on other sites More sharing options...
Kevin Posted September 12, 2015 Author Share Posted September 12, 2015 Finally got around to implementing some of this in-app. It took a little while because I'm a bit picky about how I structure my code A little snippet from my logging output:2015-09-12 01:36:23.981 XimaApp[495:398148] BLE: XIMA_UUID_SERIAL_RX received: <90000100 00003916> 2015-09-12 01:36:23.982 XimaApp[495:398148] XimaProtocol received packet: <90000100 00003916> 2015-09-12 01:36:23.982 XimaApp[495:398148] Speed: 0.6 2015-09-12 01:36:24.191 XimaApp[495:398148] BLE: XIMA_UUID_SERIAL_RX received: <90000100 00009417> 2015-09-12 01:36:24.192 XimaApp[495:398148] XimaProtocol received packet: <90000100 00009417> 2015-09-12 01:36:24.192 XimaApp[495:398148] Speed: 1.5 2015-09-12 01:36:24.370 XimaApp[495:398148] BLE: XIMA_UUID_SERIAL_RX received: <90000100 00012916> 2015-09-12 01:36:24.371 XimaApp[495:398148] XimaProtocol received packet: <90000100 00012916> 2015-09-12 01:36:24.372 XimaApp[495:398148] Speed: 3.0 2015-09-12 01:36:24.580 XimaApp[495:398148] BLE: XIMA_UUID_SERIAL_RX received: <90000100 0001dc24> 2015-09-12 01:36:24.581 XimaApp[495:398148] XimaProtocol received packet: <90000100 0001dc24> 2015-09-12 01:36:24.582 XimaApp[495:398148] Speed: 4.8 2015-09-12 01:36:24.760 XimaApp[495:398148] BLE: XIMA_UUID_SERIAL_RX received: <90000100 00017517> 2015-09-12 01:36:24.762 XimaApp[495:398148] XimaProtocol received packet: <90000100 00017517> 2015-09-12 01:36:24.762 XimaApp[495:398148] Speed: 3.7 2015-09-12 01:36:24.971 XimaApp[495:398148] BLE: XIMA_UUID_SERIAL_RX received: <90000100 0000da21> 2015-09-12 01:36:24.972 XimaApp[495:398148] XimaProtocol received packet: <90000100 0000da21>2015-09-12 01:46:31.842 XimaApp[502:399502] BLE: XIMA_UUID_SERIAL_RX received: <90000200 00006415> 2015-09-12 01:46:31.843 XimaApp[502:399502] XimaProtocol received packet: <90000200 00006415> 2015-09-12 01:46:31.843 XimaApp[502:399502] Battery: 100%Looking pretty good so far! Next step is to do some more packet captures doing various commands and see what app action each of these command strings (reconstructed by code inspection of android app) corresponds to: uint8_t cmdStrings[][16] = { {0x90,0x00,0x01,0x00,0x00,0x00,0x00}, // Speed request {0x90,0x00,0x02,0x00,0x00,0x00,0x00}, // Battery request {0x90,0x00,0xA0,0x00,0x00,0x00,0x02}, {0x90,0x00,0xA0,0x00,0x00,0x00,0x01}, {0x90,0x00,0xA1,0x00,0x00,0x00,0x01}, {0x90,0x00,0xA1,0x00,0x00,0x00,0x02}, {0x90,0x00,0xA1,0x00,0x00,0x00,0x03}, {0x90,0x00,0xA1,0x00,0x00,0x00,0x04}, {0x90,0x00,0xA1,0x00,0x00,0x00,0x05}, {0x90,0x00,0xD0,0x00,0x00,0x00,0x00}, {0x90,0x00,0xD1,0x00,0x00,0x00,0x00}, {0x90,0x00,0x16,0x00,0x00,0x00,0x00}, {0x90,0x00,0x17,0x00,0x00,0x00,0x00}, {0x90,0x00,0x18,0x00,0x00,0x00,0x00}, {0x90,0x00,0x19,0x00,0x00,0x00,0x00}, {0x90,0x00,0x80,0x00,0x00,0x00,0x00}, {0x90,0x00,0x81,0x00,0x00,0x00,0x00}, {0x90,0x00,0x82,0x00,0x00,0x00,0x00}, {0x90,0x00,0x83,0x00,0x00,0x00,0x00}, {0x90,0x00,0x84,0x00,0x00,0x00,0x00}, {0x90,0x00,0x0A,0x00,0x00,0x00,0x00}, };Some have the potential to be dangerous (firmware updates possibly), so I don't want to blindly throw packets at it. Link to comment Share on other sites More sharing options...
Kevin Posted September 13, 2015 Author Share Posted September 13, 2015 Managed to figure out most of the remaining commands by looking at the values in the receive logic, and then turning on/off the various light options and re-querying light status.typedef enum xima_cmd_e_ { XC_SPEED = 0x01, XC_BATTERY = 0x02, XC_SETLIGHT_FRONTBACK = 0xA0, XC_SETLIGHT_SIDES = 0xA1, XC_LIGHTSTATUS_FRONTBACK = 0xD0, XC_LIGHTSTATUS_SIDES = 0xD1, XC_SUM_MILEAGE = 0x10, XC_MILEAGE = 0x11, XC_VERSION = 0x12, XC_GETSPEEDLIMIT = 0x13, XC_SETSPEEDLIMIT_RESULT = 0x50, // not 100% sure XC_RENAME_RESULT = 0x51, // not 100% sure XC_UNK12 = 0x52, // no receive logic XC_UNK13 = 0x53, // no receive logic XC_UNK14 = 0x54, // no receive logic XC_UNK15 = 0x0A, // no receive logic } xima_cmd_e; typedef enum xima_lightstate_fb_ { XLSFB_OFF = 1, XLSFB_ON = 2 } xima_lightstate_fb; typedef enum xima_lightstate_sides_ { XLSS_OFF = 1, XLSS_LOW = 2, XLSS_HI = 3, XLSS_ROTATE = 4, XLSS_POWER = 5 } xima_lightstate_sides;- (NSData*)packetForCmd:(xima_cmd_e)cmd { return [self packetForCmd:cmd arg:0]; } - (NSData*)packetForCmd:(xima_cmd_e)cmd arg:(uint8_t)arg { uint8_t packet[16] = {0x90, 0x00, cmd, 0x00, 0x00, 0x00, arg}; [self completeCmdPacket:packet]; return [NSData dataWithBytes:packet length:sizeof(packet)]; } - (void)completeCmdPacket:(uint8_t*)packet { for (int i = 0; i < 7; ++i) { packet[7] += ((packet[i] & 0x0F) + (packet[i] >> 4)); } for (int i = 0; i < 8; ++i) { packet[8+i] = ~packet[i]; } } Just need to sniff the rename and set-speedlimit commands now.I could see some real potential for more serious hacking by doing firmware upgrade (kind of looks like the android app has something for that?), but at this point that's way too much work Link to comment Share on other sites More sharing options...
esaj Posted September 13, 2015 Share Posted September 13, 2015 Looking good, keep it up I could see some real potential for more serious hacking by doing firmware upgrade (kind of looks like the android app has something for that?), but at this point that's way too much work Be careful not to brick your wheel Explaining that in warranty repair might be hard Btw, I poked around the app when I was deciphering the protocol, it seems it loads the firmware-binary from some webservice (SOAP). Tried a couple requests with a REST-debugging tool, but got nothing out of there. Link to comment Share on other sites More sharing options...
Kevin Posted September 13, 2015 Author Share Posted September 13, 2015 Looking good, keep it up Be careful not to brick your wheel Explaining that in warranty repair might be hard Btw, I poked around the app when I was deciphering the protocol, it seems it loads the firmware-binary from some webservice (SOAP). Tried a couple requests with a REST-debugging tool, but got nothing out of there.What, bricking due to loading a poorly reverse-engineered firmware image over a 3rd party tool isn't covered under manufacturer defects? But yeah I am not going to touch firmware update. I can't really think of anything firmware related I would want to improve anyway. Link to comment Share on other sites More sharing options...
Sam Clegg Posted September 16, 2015 Share Posted September 16, 2015 Kevin, would you consider making you work open source? It could be used a base to build a general purpose open source app for wheels perhaps? github? The solowheel xtreme is also bluetooth LE so could there could be some shared code. Link to comment Share on other sites More sharing options...
Kevin Posted September 17, 2015 Author Share Posted September 17, 2015 I'd love to at some point. It will probably be some time though, as I haven't had much time recently and I want to sanitize it some - a lot of the code is copied and pasted from stuff I do for work. Link to comment Share on other sites More sharing options...
Kevin Posted September 28, 2015 Author Share Posted September 28, 2015 Karma caught up to me today for being lazy about wrapping this up My wheel factory reset itself (?!) and locked itself back to 20kph with aggressive tilt-back at 16kph. And of course I didn't realize it until this morning at the top of a mountain at the start of a 45km trail...If I'd noticed in the morning I could have loaded the no-limit android APK and unlocked it immediately... or if I'd finished writing my 'upgraded' iOs app I could have done the same. But I was lazy, and so ended up stuck with a long sloooooooow ride down, holding up the whole group of 15 other cyclists Kick in the butt, time to get back to work... Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.