Greetings. This is kind of long...
I went to Daytona during Bike Week on my 2015 SR.
I found out that the bike would make it from Orlando to Daytona without charging, which was a surprise.
This was a surprise because I stop to charge the bike far more often than I need to, since I don't really have a mental handle on the range versus distance to the things I'm interested in.
The range *says* I can get to Daytona. Does it mean it? Can I trust it? It would be nice if I had real-time feedback.
I figured I needed to write an Android app to display a map with a range circle continuously updated from the bike.
This meant I needed to buckle down and decode the CANBUS data, if possible.
I managed to decode not only the range, but also the speed, mode selected (sport/eco/custom), motor temp, motor RPM, and the error number(s) on dash.
Here are my notes on that.
CANBUS is available on the OBD-II connector located above and behind the motor next to the accessory charging port, on the right-hand side.
On my 2015, it's buried deep in there. It's like birthing a cow. On the newer models, it's under the seat.
Installation:
* Bolt PiCAN 2 Duo board to Raspberry Pi
$70 from
http://copperhilltech.com/pican2-duo-can-bus-board-for-raspberry-pi-2/I should have actually used a plain PiCan 2 for $47 and had a nice DB-9 connector
http://copperhilltech.com/pican-2-can-interface-for-raspberry-pi/* I'm not going to go into much detail on the Raspberry Pi stuff because there's tons of tutorials that explain things far better than I can.
Also, since my desktop environment is Debian Linux, I don't how you would do things like ssh or scp in Windows.
* Install Raspbian Jessie Lite on the SD card.
Note that ssh is now disabled by default, as per
https://www.raspberrypi.org/blog/a-security-update-for-raspbian-pixel/This kills me because I usually use my Raspberry Pis headless (without a keyboard and monitor) so I can't just run raspi-config and enable ssh.
To enable ssh, loopback-mount the image on your Linux box, and put a file named "ssh" in the /boot directory on the VFAT partition. This enables it for the next boot only, so be sure to immediately enable ssh permanently through raspi-config.
* Hook the Raspberry Pi up to USB power and your ethernet, boot it up and enable ssh through raspi-config.
* Edit /boot/config.txt and add:
dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25
dtoverlay=mcp2515-can1,oscillator=16000000,interrupt=24
dtoverlay=spi-bcm2835-overlay
* Bring the interfaces up in /etc/rc.local
ip link set can0 up type can bitrate 500000
ip link set can1 up type can bitrate 500000
* Edit /etc/wpa_supplicant/wpa_supplicant.conf and add the wi-fi info for your network.
See
https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.mdThis is so you can ssh into it when it's in your saddlebag and not physically connected to your ethernet.
I also added an entry for the portable hotspot on my Android phone, so I could communicate with it when I was away from my home network.
I use the ConnectBot app to ssh to the Raspberry Pi from the phone.
Note that when you're using the Android portable hotspot, you can use the "arp" command on the phone (or look at /proc/net/arp) to see the IP addresses of attached devices like the Pi, so you know where to ssh.
* Fetch test software
wget
http://www.skpang.co.uk/dl/can-test_pi2.zipUnzip it, set the executable bits, and copy to /usr/local/bin
You probably want to just delete "cansend" for safety's sake.
Actually to say it stronger: DON'T EVER TRANSMIT DATA USING CANSEND
* Install Python 3.x CANBUS module
apt-get install python3-pip
pip3 install python-can
* Connect the PiCAN2 to the CANBUS via screw terminal and an OBD-II cable.
You can get the SparkFun OBD-II to DB-9 Cable p/n CAB-10087 for $10
I just cut the DB-9 off the cable and toned the wires with the DMM continuity function to find pins 6 (red wire) & 14 (yellow wire)
* Throw the Raspberry Pi in a saddlebag, and connect the USB power either to a portable battery, or to a USB converter in the "cigarette lighter" jack.
If you use the bike power jack, then you must ssh to the Raspberry Pi and shut it down gracefully using "poweroff" before turning the bike off, or you can corrupt the SD card.
* Store traffic for analysis by using:
candump -e can0 >dump.txt
You get output like:
can0 1C0 [8] 20 00 10 07 17 48 72 00
can0 408 [8] 00 1A 19 01 00 61 00 FF
can0 080 [0]
can0 701 [1] 05
can0 181 [8] 00 00 00 00 FD FF 06 00
can0 481 [8] 00 00 29 02 00 00 00 00
can0 501 [8] 00 00 B2 02 93 02 00 74
can0 381 [8] 18 07 20 00 00 23 07 00
can0 281 [8] 00 00 00 00 77 00 2F 00
can0 506 [8] 08 68 42 02 0F 00 FF FF
can0 506 [8] 09 68 40 02 0F 00 FF FF
can0 240 [7] 91 0F 00 00 00 00 61
can0 340 [8] 24 04 00 00 00 00 00 00
can0 440 [8] BC E2 01 00 CC 2B 5C 12
can0 540 [8] 00 00 00 00 67 16 CA 1B
can0 3C0 [6] 85 55 02 00 00 00
can0 189 [8] 61 00 42 56 00 02 00 01
Note that everything is in hexidecimal (base 16)
Each line is a "message"
1st column is the interface name.
2nd column is the PID (Parameter ID)
3rd column is the number of data bytes
4th column is data byte #0
5th column is data byte #1
...
11th column is data byte #7
I've not seen more than 8 data bytes.
* candump tricks:
Only show messages with PID 0x123:
candump can0,0x123:0x7FF
Only show messages with PID 0x123 or PID 0x456:
candump can0,0x123:0x7FF,0x456:0x7FF
* You can also visually monitor data in real time with cansniffer.
* The command "awk '{ print $2 }' dump.txt | sort | uniq" shows the following PIDs repeat continously:
0x080, 0x181, 0x188, 0x189, 0x1C0, 0x240, 0x281, 0x288, 0x289, 0x2C0, 0x308, 0x309, 0x340, 0x381, 0x388, 0x389, 0x3C0, 0x408, 0x409, 0x440, 0x481, 0x488, 0x489, 0x501, 0x506, 0x508, 0x509, 0x540, 0x701
* The brake switches do not appear to be connected to the CANBUS. Nothing seems to change in candump/cansniffer.
* Note that when you spin the rear wheel, the speed & motor RPM both update on the dashboard.
Speed is probably calculated from RPM using a formula based on the sprocket sizes (belt reduction) and nominal tire circumference.
* I wrote "print_range" to run on the Raspberry Pi from the phone, so I could display the range from CANBUS while I rode, and confirm it matched the dashboard.
That is, I enabled the Android portable wi-fi hotspot, ssh-ed into the Raspberry Pi from the phone, and ran print_range. This continuously prints output, and I could then mount the phone on the bike with the RAM mount and ride down the road.
This is also an example of how to fetch and decode CANBUS data in a Python script.
Known info for each PID:
--------------------------------------------------------------------------------
0x080
No data bytes. Some sort of timing/sync?
--------------------------------------------------------------------------------
0x240
Current mode == byte 0
0x85 (133) (binary 1000 0101) Sport
0x89 (137) (binary 1000 1001) Eco
0x91 (145) (binary 1001 0001) Custom
I noticed this with cansniffer.
Speed (km/hr) == (byte 2+256*byte 3)/100
Used same procedure as range.
--------------------------------------------------------------------------------
0x281
Motor temp (degrees C) == byte 6
Used same procedure as range.
Good thing I realized it's metric, because the C=(F-32)x5/9 crap would have killed me.
--------------------------------------------------------------------------------
0x340
Motor revolutions (RPM) == byte 4+256*byte 5
Used same procedure as range.
Error readout on dash == byte 6
0x00 (0) No faults
0x2C (44) Kill switch on
0x2D (45) Kickstand down
(See manual for complete list)
One transmission for every error, so if there's 2 errors, there will be 2 0x340 frames.
I noticed this with cansniffer.
--------------------------------------------------------------------------------
0x440
This is a two-byte "word" value least-significant-byte first, in units of "10 meters"
This tells me the architecture is metric and little-endian, which is valuable information.
range (km) == (byte 4+256*byte 5)/100
range (miles) == (byte 4+256*byte 5)/160.9
The procedure I used to decode it:
1. I filmed the dashboard range display with my phone on several rides. I also ran candump.
2. I pulled an image every second from the videos: ffmpeg -i input.mp4 -vf fps=1 out%05d.png
3. I reduced this by hand to a text file with the range and the frame it changed using geeqie and emacs. As you can imagine, this was a lot of work, dealing with over 1200 frames on one ride.
78.2,1
78.3,46
78.2,56
78.1,61
78.2,66
78.4,71
78.5,81
78.7,86
...
4. I imported this into a spreadsheet and charted it.
5. I wrote a Python script (gen_graphs) to graph all the single and both double-byte representations of every possible slice of the data for each PID from the candump output.
6. I compared the chart with all of the generated graphs until I found one where the shape matched.
7. I experimented until I realized the unit was metric to make the numbers match. This took a while because my original data was in miles. When I divided it by 161 and it matched, I was wondering why the weird constant, then I remembered that there's 1,609 meters in a mile and figured out it was metric.
--------------------------------------------------------------------------------
0x508
bytes 0-2, LSB first.
Some sort of up-counter incremented every second.
Total time running? (Hobbs meter?)
--------------------------------------------------------------------------------
0x509
bytes 0-2, LSB first.
Some sort of up-counter incremented every second.
Total time running? (Hobbs meter?)
--------------------------------------------------------------------------------
0x701
Always one data byte of 0x05. Some sort of timing/sync?
--------------------------------------------------------------------------------
There is different traffic when the bike is charging, but I didn't look at that much.
I have the Android range app written and working, except it needs me to type in the range number. Next up is getting that data via Bluetooth from the Raspberry Pi, so I have to figure out the Python Bluetooth libraries and the Android Bluetooth environment. That is not going to be fun.
I also want to mount the Raspberry Pi filesystems read-only to avoid SD card corruption at power-off. Other people have done this.
I considered switching to an Arduino, as an Arduino is a heck of a lot smaller than a Raspberry Pi, and it can live on straight +12VDC.
However, I'd need to code in C, which would suck, and there's confusion about what Bluetooth and CANBUS "shields" are compatible with which flavor of Arduino. Maybe some day when I get bored again.
Well, I hope this is interesting and/or useful to somebody out there. Enjoy.