Developing a Mobile App for E-scooter/E-bike
E-scooters and electric bikes with controller—not just devices with BLE chip. Typical stack: BLDC motor controller (Sabvoton, Kelly, Votol) communicates with display or BMS via UART/RS485 protocol (often proprietary), BLE module (Nordic nRF52840, ESP32) listens to bus and streams data to mobile app. Developing app without understanding this chain ends with app "connected" but not knowing what to do with byte stream.
Reverse-Engineering Controller Protocol
Most controller manufacturers (especially Chinese) don't publish protocols. Process: disconnect native display, connect USB-UART analyzer (FTDI232, CP2102) in parallel to bus, log traffic. Tool—PulseView + UART decoder, or just log via minicom/CoolTerm.
Typical frame from Xiaomi M365 protocol (as open example):
[0x55][0xAA][len][addr][cmd][data...][crc_lo][crc_hi]
Frame starts with 0x55 0xAA, then payload length, recipient address (0x20—controller, 0x21—BMS, 0x3E—display), command, data, CRC16. For less popular brands, CRC computed differently—XOR, Modbus CRC, sometimes just byte sum with mask.
class ScooterFrameParser {
private val buffer = ByteArrayOutputStream()
fun feed(byte: Byte): ScooterFrame? {
buffer.write(byte.toInt())
val bytes = buffer.toByteArray()
// Find frame start
val start = findStart(bytes) ?: return null
if (bytes.size - start < 4) return null
val len = bytes[start + 2].toInt() and 0xFF
val totalLen = len + 6 // header(2) + len(1) + addr(1) + cmd(1) + crc(2) - 1
if (bytes.size - start < totalLen) return null
val frame = bytes.copyOfRange(start, start + totalLen)
buffer.reset()
if (start + totalLen < bytes.size) {
buffer.write(bytes, start + totalLen, bytes.size - start - totalLen)
}
return if (verifyCRC(frame)) parseFrame(frame) else null
}
}
BLE Communication and Connection Stability
On Android, BLE via BluetoothGatt. Main pain—onConnectionStateChange with status = 133 (GATT_ERROR) on connect, especially Android 12+ with Bluetooth Permission enabled. Fix: retry with 500-1000 ms delay, max 3 tries, then show user instruction to reconnect Bluetooth.
class ScooterBLEManager(private val context: Context) {
private var gatt: BluetoothGatt? = null
private var retryCount = 0
fun connect(device: BluetoothDevice) {
gatt = device.connectGatt(context, false, object : BluetoothGattCallback() {
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) {
when {
newState == BluetoothProfile.STATE_CONNECTED -> {
retryCount = 0
g.discoverServices()
}
status == 133 && retryCount < 3 -> {
retryCount++
g.close()
Handler(Looper.getMainLooper()).postDelayed({ connect(device) }, 800)
}
else -> notifyConnectionFailed()
}
}
override fun onCharacteristicChanged(g: BluetoothGatt,
characteristic: BluetoothGattCharacteristic, value: ByteArray) {
frameParser.feed(value)
}
}, BluetoothDevice.TRANSPORT_LE)
}
}
On iOS, CBPeripheral more stable, but CoreBluetooth session doesn't survive app restart—save peripheral.identifier (UUID) to UserDefaults and restore via retrievePeripherals(withIdentifiers:).
Dashboard: What to Show
Standard data from e-scooter/e-bike controller:
- Speed (km/h)—real from wheel sensor or calculated from RPM + wheel circumference
- Battery (%)—from BMS, rarely calculated by voltage
- Battery voltage/current—important for regeneration monitoring
- Controller and motor temperature—critical on heavy uphill
- Mileage—odometer, total and per trip
- Ride mode—Eco/Normal/Sport or D1-D5
- Brake state (if sensors connected to controller)
Speed graph per trip—mandatory. Render via MPAndroidChart (Android) or Swift Charts (iOS 16+). Log data in Room/Core Data every 500 ms—30 km trip at this interval = ~3600 points, no problem.
Mode Control and Controller Settings
Some controllers allow parameter reconfiguration: max current, speed limit, regeneration power. Send write command to Notify Characteristic. Important: changing controller parameters requires user warning and confirmation—wrong current can damage motor or drain battery during trip.
For sharing services (scooter fleet), add server layer: MQTT or WebSocket, trip history on backend, geofencing, remote lock. Separate complexity level.
Developing app with BLE connection to scooter, dashboard, trip logging: 6-8 weeks per platform. Cross-platform Flutter supporting multiple controller protocols: 3-4 months. Cost individually quoted after specific device model and protocol documentation analysis.







