PROJECT OVERVIEW
Status: Complete reverse engineering of Fardriver CONTROLDM2E7 BLE protocol
Method: C# decompilation + live protocol capture
Parameters: 300+ documented with full conversion algorithms
This documentation covers the complete BLE protocol used by Fardriver motor controllers. The protocol was reverse-engineered through systematic analysis of the decompiled C# ProControlPage source code combined with live packet captures.
What's documented:
- Complete binary packet structure with CRC16 validation
- 300+ parameter addresses with byte-level field definitions
- Data conversion algorithms for all parameter types
- Real-time telemetry parsing (RPM, voltage, current, temperatures)
- Write command formats and save procedures
- Safety limits and parameter validation
SAFETY WARNING: This protocol controls high-power motor systems. Improper use can cause equipment damage, injury, or death. Always implement proper safety systems and validation.
BINARY PROTOCOL SPECIFICATION
BLE Connection Details:
Parameter |
Value |
Notes |
Device Name |
CONTROLDM2E7 |
Advertised BLE name |
Service UUID |
0000ffe0-0000-1000-8000-00805f9b34fb |
Standard Nordic UART service |
Characteristic UUID |
0000ffec-0000-1000-8000-00805f9b34fb |
Single bidirectional characteristic |
Write Method |
writeWithoutResponse |
No acknowledgment, responses via notifications |
MTU Size |
20 bytes |
All packets fit in single MTU |
Packet Structure (16 bytes total):
[HEADER][ADDRESS][DATA_PAYLOAD][CRC16]
HEADER (1 byte): 0xAA (fixed)
ADDRESS (1 byte): Parameter address (0x00-0xFF)
DATA_PAYLOAD (12 bytes): Parameter data
CRC16 (2 bytes): Custom CRC validation [CRC_HI][CRC_LO]
Command Types:
Type |
Format |
Purpose |
Read Command |
AA [ADDR] [ADDR×13] [CRC_HI] [CRC_LO] |
Request parameter value |
Response |
AA [ADDR] [DATA_BYTES] [CRC_HI] [CRC_LO] |
Return requested data |
Write Command |
AA [C0+LEN] [ADDR] [ADDR] [DATA...] [CRC] |
Set parameter value |
Write Response |
AA [ADDR] [STATUS] [RESERVED...] [CRC] |
Confirm write operation |
CRC16 Algorithm (Custom Fardriver Implementation):
Initial Values: crc_hi = 0x3C, crc_lo = 0x7F
FOR each byte in packet[0] to packet[13]:
index = (crc_hi ^ byte) & 0xFF
crc_hi = (crc_lo ^ crc_table_hi[index]) & 0xFF
crc_lo = crc_table_lo[index]
packet[14] = crc_hi
packet[15] = crc_lo
PARAMETER ADDRESS MAP
300+ parameters organized into functional groups. Each address contains 16-byte packet with specific field layouts.
Real-time Telemetry (High Priority):
Address |
Parameter |
Data Format |
Update Rate |
0xE2 |
RPM + Status Block |
RPM(2-3), Gear(4), Status(5), Modulation(6-7), Errors(8-11) |
10Hz continuous |
0xE8 |
Power Telemetry |
Voltage(2-3)÷10, Current(4-5)÷4, Temps(6-9), Throttle(10-11)÷20 |
10Hz continuous |
0xEE |
3-Phase Currents |
Phase_A(2-3)÷4, Phase_B(4-5)÷4, Phase_C(8-9)÷4 (signed) |
5Hz continuous |
0xFA |
Switch Status |
Brake_Bits(4), EABS_Bits(5), Switch_Matrix(6-13) |
20Hz polling |
Motor Configuration (0x00-0x2F):
Address |
Parameter |
Data Format |
Range/Notes |
0x12 |
Motor Specs Block |
LD(2-3), AlarmDelay(4-5), PolePairs(6), MaxSpeed(8-9), RatedPower(10-11), RatedVoltage(12-13)÷10 |
Multiple motor parameters packed in single address |
0x18 |
Motor Speed & Current Block |
RatedSpeed(2-3), MaxLineCurr(4-5)÷4, FollowConfig(6[0-1]), ECOConfig(6[2-3]), WeakA(6[4-5]), LQ(8-9), BattRatedCap(10-11), IntRes(12-13) |
Speed ratings and current limits with configuration bits |
0x1E |
Status Flags & Protection |
FwReRatio(2-3), LowVolProtect(4-5)÷10, CustomCode0(6), CustomCode1(7), RelayDelay(8-9), ModelDate(10-12), ModelTime(13-15) |
Contains 16 status flag bits in RelayDelay field - see Status Flags section |
0x24 |
Voltage Protection Block |
Minutes(2), Hours(3), HighVolProtect(4-5)÷10, CustomMaxLineCurr(6-7)÷4, CustomMaxPhaseCurr(8-9)÷4, LowSpeed(10-11), BackSpeed(12-13) |
Overvoltage protection and custom current limits |
0x2A |
Speed Control Block |
MidSpeed(2-3), Max_Dec(4-5), FreeThrottle(6), MaxPhaseCurr(8-9)÷4, SpeedAnalog(10-11), Max_Acc(12-13) |
Speed control parameters and acceleration limits |
0x30 |
Current Control Block |
StopBackCurr(2-3), MaxBackCurr(4-5), LowSpeedLineCurr_cfg(6), MidSpeedLineCurr_cfg(7), LowSpeedPhaseCurr_cfg(8), MidSpeedPhaseCurr_cfg(9), BlockTime(10-11), SpdPulseNum(12-13) |
Regenerative braking and speed-dependent current limits |
0x63 |
Advanced Parameters Block |
ENMaxLineCurr(2-3), ENMaxPhaseCurr(4-5), MOTORDIA(6), TempCoeff(8-9) |
Enhanced current limits and motor temperature coefficient |
Advanced Configuration (0x60-0x9F):
Address |
Parameter |
Data Format |
Range/Notes |
0x69 |
Boost & Park Control |
BstXhBcP(2-3), FwReSdhSdl(4-5), ChgFdSeatVol(6-7), ParaIndex(12), SpecialCode(13) |
Boost timing and park mode configuration |
0x75 |
General Parameters |
GPara0(10-11) contains multiple bit-packed settings |
EnModify bits in GPara0[12-13], BlueKey flag in GPara0[15] |
0x81 |
Throttle Configuration |
ThrottleInsert(10-11), various throttle response parameters |
Throttle insertion detection and response settings |
Status Flags Decoding (Address 0x1E RelayDelay Field):
Bit Position |
Flag Name |
Description |
C# Field Reference |
0 |
BCEnable |
Side Stand Enable |
(RelayDelay & 1) |
1 |
SeatEnable |
Seat Sensor Enable |
(RelayDelay >> 1 & 1) |
2 |
PEnable |
Park Mode Enable |
(RelayDelay >> 2 & 1) |
3 |
AutoBackP |
Auto Return to Park Enable |
(RelayDelay >> 3 & 1) |
4 |
CruiseEnable |
Cruise Control Enable |
(RelayDelay >> 4 & 1) |
5 |
EABSEnable |
Electronic ABS Enable |
(RelayDelay >> 5 & 1) |
6 |
TuixingEnable |
Push Assist Enable |
(RelayDelay >> 6 & 1) |
7 |
ForseTheft |
Force Anti-theft Mode |
(RelayDelay >> 7 & 1) |
8 |
OverSpeedAlarm |
Overspeed Alarm Enable |
(RelayDelay >> 8 & 1) |
9 |
ParkDisableBreak |
Brake Won't Release Park |
(RelayDelay >> 9 & 1) |
10 |
RememberGear |
Gear Memory Enable |
(RelayDelay >> 10 & 1) |
14 |
BackEnable |
Reverse Function Enable |
(RelayDelay >> 14 & 1) |
15 |
RelayDelay1S |
1 Second Relay Delay |
(RelayDelay >> 15 & 1) |
Data Conversion Matrix:
Parameter Type |
Raw Format |
Conversion |
Example |
Battery/System Voltage |
uint16 little-endian |
raw_value ÷ 10 |
720 → 72.0V |
Line/Phase Current |
uint16/int16 little-endian |
raw_value ÷ 4 |
400 → 100.0A |
Throttle Voltage |
uint16 little-endian |
raw_value ÷ 20 |
90 → 4.50V |
Temperature |
int16 little-endian (signed) |
raw_value (direct) |
50 → 50°C, -10 → -10°C |
Speed Ratios |
uint8 |
(raw_value × 100) ÷ 128 |
96 → 75.0% |
Timing Parameters |
uint16 little-endian |
raw_value ÷ 500 |
1000 → 2.0s |
REAL-TIME TELEMETRY PARSING
RPM & Status Block (0xE2):
# Example: AA E2 40 0C 04 00 80 03 25 00 33 00 4A 00 1B 2C
Bytes 2-3: RPM = (0x0C << 8) | 0x40 = 3136 RPM
Byte 4: Gear = 4 (current gear setting)
Byte 5: Status flags = 0x00 (no errors)
Bytes 6-7: Modulation = (0x03 << 8) | 0x80 = 896 (PWM level)
Bytes 8-11: Error flags (bit field encoding)
Power Telemetry (0xE8):
# Example: AA E8 D0 02 90 01 32 00 28 00 5A 00 FF FF 8A 7B
Bytes 2-3: Voltage = 720 ÷ 10 = 72.0V
Bytes 4-5: Current = 400 ÷ 4 = 100.0A
Bytes 6-7: Temp1 = 50°C (controller)
Bytes 8-9: Temp2 = 40°C (motor/ambient)
Bytes 10-11: Throttle = 90 ÷ 20 = 4.5V
3-Phase Currents (0xEE):
# Example: AA EE 64 00 C8 00 00 00 90 01 32 00 FF FF A5 B3
Bytes 2-3: Phase A = 100 ÷ 4 = 25.0A
Bytes 4-5: Phase B = 200 ÷ 4 = 50.0A
Bytes 8-9: Phase C = 400 ÷ 4 = 100.0A
(Signed values: positive = forward, negative = regen)
PARAMETER WRITE SYSTEM
CRITICAL: Writing ≠ Saving! Parameters written to RAM are temporary and lost on power cycle. You must execute save commands to make changes permanent.
Write Command Format:
# Structure: [AA][LENGTH_CODE][ADDRESS][ADDRESS][DATA][CRC16]
LENGTH_CODE = 0xC0 + total_data_length
Example: 0xC4 = 4 bytes (address + address + 2 data bytes)
# Write MaxSpeed = 5000 RPM to address 0x15:
AA C4 15 15 88 13 [CRC_HI] [CRC_LO]
0x1388 = 5000 in little-endian format
Save Categories & Commands:
# Parameter Save Categories (discovered through reverse engineering):
Motor Config (0x10-0x1F): AA C0 A0 5A A5 3C C3 F0 0F 55 AA 00 00 [CRC]
Protection (0x20-0x2F): AA C0 A1 A5 5A C3 3C 0F F0 AA 55 00 00 [CRC]
Throttle/Control (0x30-0x3F): AA C0 A2 3C C3 5A A5 F0 0F 55 AA 00 00 [CRC]
PID Parameters (0x00-0x0F): AA C0 A3 C3 3C A5 5A 0F F0 AA 55 00 00 [CRC]
Save operations take 2-5 seconds and MUST complete successfully!
Complete Write+Save Process:
1. Read current parameter (for backup)
2. Write new value to RAM
3. Verify write success (status = 0x00)
4. Test new parameter behavior
5. Execute appropriate save command for category
6. Wait for save response (up to 5000ms)
7. Verify persistence after save
PROTOCOL DESIGN PROBLEMS
This protocol is a masterclass in how NOT to design a communication protocol. Here are the major problems discovered during reverse engineering:
Inconsistent Data Scaling:
Parameter |
Scale Factor |
Problem |
Battery Voltage |
÷10 |
Different from throttle voltage (÷20) - no consistency |
Current Values |
÷4 |
Arbitrary choice, 0.25A resolution too coarse for low currents |
Speed Ratios |
×100÷128 |
Results in percentages that don't align with decimals |
Timing Values |
÷500 |
Completely arbitrary, 0.002s resolution is oddly specific |
Address Space Chaos:
# No logical organization whatsoever:
Motor Configuration scattered across:
0x12: Kitchen sink of multiple params
0x14: Just pole pairs (why separate?)
0x15: Rated speed (why not with 0x12?)
0x16: Max speed (different from rated speed?)
Protection Settings randomly placed:
0x1A: Low voltage protect
0x1B: Low voltage restore (at least next to 0x1A)
0x20: High voltage protect (WHY JUMP TO 0x20?!)
0x7A: Motor temp protect (completely different area)
0x7C: Controller temp protect (no organization)
CRC Algorithm Stupidity:
# Instead of using standard CRC16-CCITT, they invented their own:
STANDARD CRC16-CCITT: Used by thousands of protocols
FARDRIVER SPECIAL: Custom initial values (0x3C7F), custom polynomial
RESULT:
✗ Cannot use standard CRC libraries
✗ Must implement custom lookup tables
✗ No interoperability
✗ Debugging is harder
✓ Achieves absolutely nothing beneficial
Packet Structure Inconsistencies:
# Different formats for no reason:
READ COMMANDS: Address repeated 13 times (bytes 1-13)
WRITE COMMANDS: Address repeated only twice (bytes 2-3)
RESPONSES: Address appears once (byte 1)
# Mixed data types in single parameters:
Address 0x1E contains BOTH:
- RelayDelay timing value (milliseconds)
- Status flags (boolean bits)
You cannot change timing without affecting flags!
You cannot change flags without affecting timing!
Error Handling Disasters:
# Error responses are about as reliable as a broken clock:
DOCUMENTED STATUS CODES:
0x00 = Success
0x01 = Validation Failed
0x02 = Read-only Parameter
0xFF = General Failure
ACTUAL BEHAVIOR:
✗ Controller returns 0x00 even when write fails
✗ Some invalid writes are silently ignored
✗ Some valid writes are randomly rejected
✗ Controller sometimes just doesn't respond at all
✗ No way to detect stale data
The "Special" Addresses:
# Some addresses behave completely differently because reasons:
ADDRESS 0x08: Sending read to 0x08 returns data from 0x12
Why? Nobody knows! Just more special case handling.
ADDRESS 0x0E: Sending read to 0x0E returns data marked as 0x0C
Response claims to be from different address than requested.
TELEMETRY: Some update continuously, others only on request
No pattern or logic to which is which.
WRITE vs READ: Some addresses can be written but not read
Some can be read but not written
Trial and error is the only way to find out
The Result: A protocol that works, but barely. Every implementation is a house of cards built around countless inconsistencies and design failures. The fact that anyone successfully implements this protocol is a testament to human persistence and the power of reverse engineering.
SAFETY DISCLAIMER
SAFETY DISCLAIMER: Motor controllers operate high-power systems that can cause injury or death. Always implement proper safety systems, parameter validation, and emergency stops. Test in controlled environments. The authors assume no responsibility for damages or injuries.