Fardriver controller serial protocol reverse engineering

Hello! Has anyone figured out the CRC16 calculation in the latest versions of the controller? I found this information: Poly: 0x8005, Init: 0x7F3C, RefIn: true, RefOut: true, XorOut: false. But it doesn't work for my controller. Here is one of the parcels, you can check:
AA 80 85 07 11 20 00 00 00 00 00 00 00 00 1F FF
AA 81 74 00 79 00 00 00 00 00 00 00 00 00 A2 5D
AA 82 00 00 00 00 00 00 00 00 00 00 00 00 C5 2A
 
Last edited:
Making some progress with reading the UART from Fardriver controller. I am using an ESP32-S3, Visual Studio Code with PlatformIO extension and the ESP-IDF. I think i will have a working software at the end of summer.

20240623_215403944_iOS.jpg
 
Hello! Has anyone figured out the CRC16 calculation in the latest versions of the controller? I found this information: Poly: 0x8005, Init: 0x7F3C, RefIn: true, RefOut: true, XorOut: false. But it doesn't work for my controller. Here is one of the parcels, you can check:
AA 80 85 07 11 20 00 00 00 00 00 00 00 00 1F FF
AA 81 74 00 79 00 00 00 00 00 00 00 00 00 A2 5D
AA 82 00 00 00 00 00 00 00 00 00 00 00 00 C5 2A

This github repository has the implementation of the CRC in one of its files with some comments in russian. -> google translate.


But i think it´s only needed for sending data to the controller not for reading the data from it for a display.
 
The data there is outdated, I checked. The Chinese changed the algorithm again.
I just need a CRC for sending.
But I picked up the data!
 
I've started documenting my findings on the serial protocol in a Github repo - I've been able to decode most of the status messages received from the controller, and have started to figure out how to interact with the controller through sending commands, etc. I'd like to fill the fardriver.hpp with comments about what each variable does/how it works. The two CRC methods are described there are well.
 
Someone messaged me asking about details on what I've found, so I thought I'd describe the decoding algorithm a bit here, in addition to the readme I've been working on: fardriver-controllers/README.md at main · jackhumbert/fardriver-controllers - I also have the full decoding implemented in a 010 Editor template file: fardriver-controllers/fardriver.bt at main · jackhumbert/fardriver-controllers - it can decode a binary file of the data received.

The 16 byte status messages look like this (the c++ version is here):

Code:
0xAA <0x80 + index> <data[12]> <crc[2]>

Where the index (after removing the highest two bits) can be converted to a flash address via this lookup table:

Code:
const uint8_t flash_read_addr[55] = {
  0xE2, 0xE8, 0xEE, 0x00, 0x06, 0x0C, 0x12, 
  0xE2, 0xE8, 0xEE, 0x18, 0x1E, 0x24, 0x2A, 
  0xE2, 0xE8, 0xEE, 0x30, 0x5D, 0x63, 0x69, 
  0xE2, 0xE8, 0xEE, 0x7C, 0x82, 0x88, 0x8E, 
  0xE2, 0xE8, 0xEE, 0x94, 0x9A, 0xA0, 0xA6, 
  0xE2, 0xE8, 0xEE, 0xAC, 0xB2, 0xB8, 0xBE, 
  0xE2, 0xE8, 0xEE, 0xC4, 0xCA, 0xD0,
  0xE2, 0xE8, 0xEE, 0xD6, 0xDC, 0xF4, 0xFA
};

Based on that address, the data section can be mapped to a struct in the fardriver.hpp file, named via the address in hex: fardriver-controllers/fardriver.hpp at main · jackhumbert/fardriver-controllers - I tried to use a lot of the names from the app, but am still working on describing things/adding notes.

I'm also working on a template for HEB files that works the same way as the one mentioned earlier (HEB files are just pure data though, and also include CAN info): fardriver-controllers/HEB.bt at main · jackhumbert/fardriver-controllers
 
Hey,
Here's what I have been able to figure out about the serial communications protocol for the Fardriver controller.
I have only looked at the data FROM the controller, as I wanted to use it for a custom display.
My thread with my EKSR build that use that display, is here.
If you have anything to add to this and/or can help decode this further, please let me know.

Questions welcome, of course.

Here is a short Python program you can run to visualize the messages on the serial link and what bytes are being changed:
(change the serial port in the last line to suit your setup)

import serial
import datetime, threading, time

alist = [b'\x00' * 32] * 32

def open_serial_port(port, baudrate):
try:
global ser
ser = serial.Serial(port, baudrate, timeout=1)
print(f"Connected to {port} at {baudrate} baud.")
return ser
except Exception as e:
print(f"Error opening serial port: {e}")
return None

def clear_screen():
print('\x1B[2J')

def goto_line(line):
print('\x1B[' + str(line) + ';1H')

def set_color(inverse):
if (inverse):
print('\x1B[7m', end='')
else:
print('\x1B[0m', end='')
return

def read_from_serial(ser):
while ser.in_waiting:
try:
global alist
# read 16 bytes total : 0xAA, address, 12 bytes data and 2 bytes checksum
data = ser.read(16)
index = data[1]
if (index < 24):
goto_line(index + 2)
set_color(False)
print('{: >2}'.format(index) + ' : ', end='')
old = alist[index]
for i in range(2,14):
if (old != data):
set_color(True)
else:
set_color(False)
print(format(data,'02x') + ' ', end='')
set_color(False)
print('')
alist[index] = data


except Exception as e:
print(f"\nError reading from serial: {e}")


def write_to_serial(ser):
try:
data = input("Enter data to send: ")
ser.write(data.encode('utf-8'))
except Exception as e:
print(f"Error writing to serial: {e}")

def foo():
global ser
if (foo.first):
keep_alive = [ 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 ] # size 8
else:
keep_alive = [ 0xAA, 0x13, 0xec, 0x07, 0x01, 0xF1, 0xA2, 0x5D ] # size 8
foo.first = False
ser.write(keep_alive);
#print (datetime.datetime.now())
threading.Timer(2, foo).start()

foo.first = True

def run_serial_terminal(port, baudrate):
ser = open_serial_port(port, baudrate)
clear_screen()
goto_line(1)
print(' 0 1 2 3 4 5 6 7 8 9 10 11')
print('\x1B[?25l')

timerThread = threading.Thread(target=foo)
timerThread.daemon = True
timerThread.start()

if ser:
try:
while True:
read_from_serial(ser)
time.sleep(0.1) # Adjust the polling rate if needed
except KeyboardInterrupt:
print("\nClosing serial connection.")
finally:
ser.close()
else:
print("Unable to open serial port.")

if __name__ == "__main__":
run_serial_terminal("/dev/tty.usbserial-834440", 19200)
 

Attachments

  • FarDriver serial messages.pdf
    157.3 KB · Views: 60
Someone messaged me asking about details on what I've found, so I thought I'd describe the decoding algorithm a bit here, in addition to the readme I've been working on: fardriver-controllers/README.md at main · jackhumbert/fardriver-controllers - I also have the full decoding implemented in a 010 Editor template file: fardriver-controllers/fardriver.bt at main · jackhumbert/fardriver-controllers - it can decode a binary file of the data received.

The 16 byte status messages look like this (the c++ version is here):

Code:
0xAA <0x80 + index> <data[12]> <crc[2]>

Where the index (after removing the highest two bits) can be converted to a flash address via this lookup table:

Code:
const uint8_t flash_read_addr[55] = {
  0xE2, 0xE8, 0xEE, 0x00, 0x06, 0x0C, 0x12,
  0xE2, 0xE8, 0xEE, 0x18, 0x1E, 0x24, 0x2A,
  0xE2, 0xE8, 0xEE, 0x30, 0x5D, 0x63, 0x69,
  0xE2, 0xE8, 0xEE, 0x7C, 0x82, 0x88, 0x8E,
  0xE2, 0xE8, 0xEE, 0x94, 0x9A, 0xA0, 0xA6,
  0xE2, 0xE8, 0xEE, 0xAC, 0xB2, 0xB8, 0xBE,
  0xE2, 0xE8, 0xEE, 0xC4, 0xCA, 0xD0,
  0xE2, 0xE8, 0xEE, 0xD6, 0xDC, 0xF4, 0xFA
};

Based on that address, the data section can be mapped to a struct in the fardriver.hpp file, named via the address in hex: fardriver-controllers/fardriver.hpp at main · jackhumbert/fardriver-controllers - I tried to use a lot of the names from the app, but am still working on describing things/adding notes.

I'm also working on a template for HEB files that works the same way as the one mentioned earlier (HEB files are just pure data though, and also include CAN info): fardriver-controllers/HEB.bt at main · jackhumbert/fardriver-controllers
Wow! Really nice work man! This is going to help my project a lot!
 
Someone messaged me asking about details on what I've found, so I thought I'd describe the decoding algorithm a bit here, in addition to the readme I've been working on: fardriver-controllers/README.md at main · jackhumbert/fardriver-controllers - I also have the full decoding implemented in a 010 Editor template file: fardriver-controllers/fardriver.bt at main · jackhumbert/fardriver-controllers - it can decode a binary file of the data received.

The 16 byte status messages look like this (the c++ version is here):

Code:
0xAA <0x80 + index> <data[12]> <crc[2]>

Where the index (after removing the highest two bits) can be converted to a flash address via this lookup table:

Code:
const uint8_t flash_read_addr[55] = {
  0xE2, 0xE8, 0xEE, 0x00, 0x06, 0x0C, 0x12,
  0xE2, 0xE8, 0xEE, 0x18, 0x1E, 0x24, 0x2A,
  0xE2, 0xE8, 0xEE, 0x30, 0x5D, 0x63, 0x69,
  0xE2, 0xE8, 0xEE, 0x7C, 0x82, 0x88, 0x8E,
  0xE2, 0xE8, 0xEE, 0x94, 0x9A, 0xA0, 0xA6,
  0xE2, 0xE8, 0xEE, 0xAC, 0xB2, 0xB8, 0xBE,
  0xE2, 0xE8, 0xEE, 0xC4, 0xCA, 0xD0,
  0xE2, 0xE8, 0xEE, 0xD6, 0xDC, 0xF4, 0xFA
};

Based on that address, the data section can be mapped to a struct in the fardriver.hpp file, named via the address in hex: fardriver-controllers/fardriver.hpp at main · jackhumbert/fardriver-controllers - I tried to use a lot of the names from the app, but am still working on describing things/adding notes.

I'm also working on a template for HEB files that works the same way as the one mentioned earlier (HEB files are just pure data though, and also include CAN info): fardriver-controllers/HEB.bt at main · jackhumbert/fardriver-controllers
Have you been able to figure out the login process via Bluetooth? I'm connecting to the the controller via BLE through my Mac and the controller seems to only send me dummy default values.
 
Have you been able to figure out the login process via Bluetooth? I'm connecting to the the controller via BLE through my Mac and the controller seems to only send me dummy default values.
Nevermind, messed up Little Endian with Big Endian!
 
Nevermind, messed up Little Endian with Big Endian!
Hi @Dexion I saw you made an app "GD Fardriver Speed & Trip Info". Did you manage to connect to the fardriver's ble dongle?
I've been getting data from the fardrivers for more than an year now but via the SIF (one line) output using an esp32 and seding it to my stock motorcycle display and to an extra 4" stats display. However the protocol is quite limited and I was thinking of switching to serial to get more stable (no EMI) and more complete data. However if I were to do it clasically I guess that would require a 4 channel relay module to switch the serial input from esp32 to the bluetooth dongle and disconnect the 5v from the dongle when using esp32. If it were to work via BLE that would be amazing as no hardware mods would be necessary, all just software.

On my ebike with votol I use serial to get controller data and pass it to my displays and classic bluetooth serial to passtrhrough the connection when I want to change controller settings.

Also on my BMS', both JK and JBD I get my data via BLE.

Aside from what's in the well put document here GitHub - jackhumbert/fardriver-controllers: Information on Nanjing Fardriver Controllers , is there anything else I should take into account when connecting via ble? Do I need to login for read only data?

Thanks in advance.
 
Hi @Dexion I saw you made an app "GD Fardriver Speed & Trip Info". Did you manage to connect to the fardriver's ble dongle?
I've been getting data from the fardrivers for more than an year now but via the SIF (one line) output using an esp32 and seding it to my stock motorcycle display and to an extra 4" stats display. However the protocol is quite limited and I was thinking of switching to serial to get more stable (no EMI) and more complete data. However if I were to do it clasically I guess that would require a 4 channel relay module to switch the serial input from esp32 to the bluetooth dongle and disconnect the 5v from the dongle when using esp32. If it were to work via BLE that would be amazing as no hardware mods would be necessary, all just software.

On my ebike with votol I use serial to get controller data and pass it to my displays and classic bluetooth serial to passtrhrough the connection when I want to change controller settings.

Also on my BMS', both JK and JBD I get my data via BLE.

Aside from what's in the well put document here GitHub - jackhumbert/fardriver-controllers: Information on Nanjing Fardriver Controllers , is there anything else I should take into account when connecting via ble? Do I need to login for read only data?

Thanks in advance.
I'm weaker on the hardware side, but I do know no login is necessary for read only data via BLE.
 
I am able to get data via ble and decode the packages.
The useful data rate is about 5.2 frames / s (for frame types EE, E8 and E2) and that is exactly what I got with SIF.
Note to anyone else wanting to connect via BLE is that the fardriver changes its address, so you need to scan it by name each time you want to connect to it. I used nRF Connect to get the service and characteristics.
I did not need to request the packages, the notifications just arrive upon subscribing to them, at least on my nd72680.
I only tested it lightly to see if it works, but I will implement it on my motorcycle soon.
Many thanks to @jackhumbert for putting together all that information and to everyone else involved.
 
I implemented this on my motorcycle, I'm able to get all the relevant data, except the cruise control flag. I'll probably need to put the bike on a stand and inspect the bits that change once cruise is enabled. I expect it to be hidden somewhere in E2 .
 
Back
Top