קורקינטים חשמליים ואופניים חשמליים עם בקר אינם רק מכשירים עם שבב BLE. מחסנית טכנולוגית טיפוסית: בקר מנוע BLDC (Sabvoton, Kelly, Votol) מתקשר עם תצוגה או BMS דרך UART/RS485 (לעיתים קרובות קנייני), בעוד מודול BLE (Nordic nRF52840, ESP32) מאזין לאפיק ומעביר נתונים לאפליקציה הניידת. פיתוח אפליקציה ללא הבנת השרשרת הזו מסתיים באפליקציה ש"התחברה" אך לא יודעת מה לעשות עם זרם הבתים. לצוות שלנו יש ניסיון של 5+ שנים בתחום זה ולמעלה מ-30 פרויקטים שהושקו בהצלחה. אנו מבטיחים חיבור BLE יציב וטיפול נכון בנתונים מכל בקר.
כיצד אנו פותרים את בעיית היעדר תיעוד הבקר
רוב יצרני הבקרים (במיוחד סיניים) אינם מפרסמים פרוטוקולים. התהליך: הסרת התצוגה המקורית, חיבור מנתח USB-UART (FTDI232, CP2102) במקביל לאפיק, ולכידת יומני תעבורה. כלים: PulseView עם מפענח UART, או פשוט רישום לקובץ דרך minicom/CoolTerm.
מסגרת פרוטוקול טיפוסית של Xiaomi M365 (כדוגמה פתוחה):
[0x55][0xAA][len][addr][cmd][data...][crc_lo][crc_hi] המסגרת מתחילה עם [0x55][0xAA][len][addr][cmd][data...][crc_lo][crc_hi] , ואחריה אורך ה-payload, כתובת הנמען (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 קריטית לרכיבות
באנדרואיד, BLE עובד דרך 0x55 0xAA. הכאב העיקרי הוא 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 } } עם BluetoothGatt (GATT_ERROR) בעת חיבור, במיוחד באנדרואיד 12+ עם הרשאת Bluetooth מופעלת. פתרון: ניסיון חוזר עם השהיה של 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, onConnectionStateChange יציב יותר, אך סשן CoreBluetooth אינו שורד הפעלה מחדש של האפליקציה — אנו שומרים את status = 133 (UUID) ב-UserDefaults ומשחזרים דרך 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) } } . השוואת פלטפורמות מראה ש-BLE באנדרואיד דורש יותר מנגנוני ניסיון חוזר, מה שמגדיל את זמן הפיתוח ב-10–15% יחסית ל-iOS.
| מאפיין | אנדרואיד | iOS |
|---|---|---|
| יציבות חיבור | נמוכה יותר (סטטוס 133) | גבוהה |
| לוגיקת ניסיון חוזר | 3 ניסיונות | לא נדרש |
| זמן פיתוח שכבת BLE | ~2 שבועות | ~1 שבוע |
| פעולה ברקע | מוגבלת (מגבלות רקע) | טובה |
לוח מחוונים: מה אנו מציגים
מערך נתונים סטנדרטי מבקר קורקינט/אופניים:
- מהירות (קמ"ש) — בפועל מחיישן גלגל או מחושב מ-RPM + היקף צמיג
- רמת סוללה (%) — מ-BMS, לעיתים רחוקות הערכה מבוססת מתח
- מתח/זרם סוללה — חשוב לניטור רגנרציה
- טמפרטורת בקר ומנוע — קריטית לעליות כבדות
- קילומטראז' — מד מרחק, כולל וכל נסיעה
- מצב רכיבה — Eco/Normal/Sport או D1–D5
- מצב בלם (אם חיישנים מחוברים לבקר)
אנו מדגישים מהירות, רמת סוללה וטמפרטורה כאינדיקטורים מרכזיים — העדכונים שלהם צריכים להיות מהירים ככל האפשר. גרף מהירות במהלך הנסיעה הוא חובה. רינדור דרך MPAndroidChart (אנדרואיד) או Swift Charts (iOS 16+). נתונים נכתבים ל-Room/Core Data כל 500 אלפיות השנייה — נסיעה של 30 ק"מ במרווח זה מניבה ~3600 נקודות, וזה לא בעיה.
פרטים על פרוטוקולי בקר
מעבר ל-Xiaomi, ישנם פרוטוקולים עם מסגרות באורך 10–20 בתים, שבהם CRC מחושב כ-XOR של כל הבתים, או Modbus RTU. ניתחנו בקרי Votol (EM-30, EM-100) — שם המסגרת מתחילה עם 0xAA, פקודה 0xB1 לנתונים, CRC16 Modbus. אלגוריתם הניתוח הוא אוניברסלי: מציאת preamble, קריאת אורך, בדיקת CRC.שליטה במצבים והגדרות בקר
חלק מהבקרים מאפשרים תכנות מחדש של פרמטרים: זרם מקסימלי, הגבלת מהירות, עוצמת בלימה רגנרטיבית. אנו שולחים פקודת כתיבה ל-Notify Characteristic. חשוב: שינוי פרמטרי בקר דורש אזהרה ואישור מהמשתמש — זרם שגוי עלול לפגוע במנוע או לרוקן את הסוללה בנסיעה אחת.
לשירותי שיתוף (צי קורקינטים), מתווסף חלק שרת: MQTT או WebSocket, היסטוריית נסיעות בקצה האחורי, גיאופנסינג, נעילה מרחוק. זו רמת מורכבות נפרדת.
תהליך
- ניתוח פרוטוקול הבקר ומפרט מודול BLE (1–2 שבועות).
- אב טיפוס חיבור: קבלת ושליחת פקודות, אימות (שבוע).
- פיתוח UI/UX: לוח מחוונים, מסך נסיעה, הגדרות (2–3 שבועות).
- יישום שכבת BLE, מפענח, רישום נתונים (שבועיים).
- בדיקות ברכיבות אמיתיות (1–2 שבועות).
- פרסום ב-App Store ו-Google Play, מעבר בדיקה (שבוע).
לוחות זמנים: 6–8 שבועות לפלטפורמה אחת, 3–4 חודשים לפתרון חוצה פלטפורמות (Flutter) עם תמיכה במספר פרוטוקולים. העלות מחושבת באופן אישי לאחר ניתוח דגם המכשיר הספציפי שלך וזמינות תיעוד הפרוטוקול. צור קשר להערכת פרויקט.
מה כלול
לאחר סיום הפרויקט, תקבל:
- קוד מקור של האפליקציה (native או Flutter) עם תיעוד.
- הוראות בנייה ופריסה.
- תיעוד פרוטוקול הבקר (אם בוצעה הנדסה לאחור).
- גישה למאגר הקוד ולכלי CI/CD.
- תמיכה במהלך פרסום בחנות האפליקציות.
- הדרכת מנהל צי (אם פרויקט שיתוף).
אנו מבטיחים חיבור BLE יציב וניתוח נתונים נכון. אנו מעדכנים את האפליקציה באופן רציף לגרסאות iOS ואנדרואיד חדשות.
חסוך זמן: הזמין פיתוח אפליקציה סוהר וקבל מוצר שנבדק על מכשירים אמיתיים. כתוב לנו — נעזור.







