Разработка мобильного приложения для электросамоката/электровелосипеда
Электросамокаты и велосипеды с контроллером — это не просто устройства с BLE-чипом. Типичный стек: контроллер BLDC-мотора (Sabvoton, Kelly, Votol) общается с дисплеем или BMS по протоколу UART/RS485 (часто проприетарный), BLE-модуль (Nordic nRF52840, ESP32) слушает шину и транслирует данные в мобильное приложение. Разработка приложения без понимания этой цепочки заканчивается тем, что приложение «подключилось», но не знает, что делать с потоком байт.
Реверс-инжиниринг протокола контроллера
Большинство производителей контроллеров (особенно китайских) не публикуют протоколы. Процесс: снять родной дисплей, подключить USB-UART анализатор (FTDI232, CP2102) параллельно шине и записать трафик в логи. Инструмент — PulseView + декодер UART, либо просто запись в файл через minicom/CoolTerm.
Типичный фрейм протокола Xiaomi M365 (как пример открытого):
[0x55][0xAA][len][addr][cmd][data...][crc_lo][crc_hi]
Фрейм начинается с 0x55 0xAA, затем длина полезной нагрузки, адрес получателя (0x20 — контроллер, 0x21 — BMS, 0x3E — дисплей), команда, данные, CRC16. Для менее популярных брендов CRC считается по-разному — XOR, Modbus CRC, иногда просто сумма байт с маской.
class ScooterFrameParser {
private val buffer = ByteArrayOutputStream()
fun feed(byte: Byte): ScooterFrame? {
buffer.write(byte.toInt())
val bytes = buffer.toByteArray()
// Ищем начало фрейма
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-коммуникация и стабильность соединения
На Android BLE работает через BluetoothGatt. Главная боль — onConnectionStateChange с status = 133 (GATT_ERROR) при подключении, особенно на Android 12+ с включённым Bluetooth Permission. Лечение: retry с задержкой 500-1000 мс, максимум 3 попытки, после — показываем пользователю инструкцию переподключить 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)
}
}
На iOS CBPeripheral стабильнее, но сессия CoreBluetooth не выживает после перезапуска приложения — сохраняем peripheral.identifier (UUID) в UserDefaults и восстанавливаем через retrievePeripherals(withIdentifiers:).
Дашборд: что показываем
Стандартный набор данных с контроллера самоката/велосипеда:
- Скорость (км/ч) — реальная, с датчика колеса или расчётная из RPM + длина окружности
- Заряд батареи (%) — из BMS, реже — расчётный по напряжению
- Напряжение/ток батареи — важно для мониторинга рекуперации
- Температура контроллера и мотора — критично для тяжёлых подъёмов
- Пробег — одометр, суммарный и за поездку
- Режим езды — Eco/Normal/Sport или D1-D5
- Состояние тормозов (если датчики подключены к контроллеру)
Скоростной график за поездку — обязательный элемент. Renderим через MPAndroidChart (Android) или Swift Charts (iOS 16+). Данные пишем в Room/Core Data каждые 500 мс — поездка на 30 км при таком интервале = ~3600 точек, это не проблема.
Управление режимами и настройки контроллера
Ряд контроллеров позволяет перепрограммировать параметры: максимальный ток, ограничение скорости, мощность рекуперации. Отправляем write-команду в Notify Characteristic. Важно: изменения параметров контроллера требуют предупреждения пользователя и подтверждения — неправильный ток может вывести мотор из строя или разрядить батарею за поездку.
Для шеринговых сервисов (флот самокатов) добавляется серверная часть: MQTT или WebSocket, история поездок на бэкенде, геофенсинг, удалённая блокировка. Это отдельный уровень сложности.
Разработка приложения с BLE-подключением к самокату, дашбордом и записью поездок: 6-8 недель на одну платформу. Кросс-платформа на Flutter с поддержкой нескольких протоколов контроллеров: 3-4 месяца. Стоимость рассчитывается индивидуально после анализа конкретной модели устройства и наличия документации на протокол.







