פתיחת קורקינט באמצעות BLE/QR באפליקציה ניידת
אנו מפתחים מערכות פתיחה לקורקינטים חשמליים באמצעות אפליקציה ניידת. המשתמש סורק קוד QR, האפליקציה שלנו מתקשרת עם השרת ושולחת פקודת BLE—הכל תוך שניות. עם ניסיון של 5+ שנים בעבודה על 10+ פרויקטים של שיתוף, אנו יודעים: כל תקלה בנתיב הזה פוגעת מיד באמון בשירות. כל עיכוב נתפס כבאג, ולכן אנו שמים לב לכל אלפית שנייה. אחד הלקוחות שלנו, שהשיק שירות שיתוף קורקינטים בעיר עם 500 קורקינטים, גילה ש-15% מהמשתמשים נטשו את ההשכרה בשלב הפתיחה—בגלל חיבור BLE איטי. ייעלנו את הפרוטוקול, ושיעור הנטישה ירד ל-3%.
איך פועלת פתיחת QR
ה-QR על הקורקינט מקודד את מזהה המכשיר: מחרוזת כמו https://ride.example.com/unlock?id=SC-00234 או פשוט SC-00234. אנו סורקים באמצעות ML Kit Barcode Scanning (Android/iOS) או AVFoundation AVCaptureMetadataOutput. ML Kit עדיף—הוא עובד ללא חיבור רשת ומהיר יותר בתנאי תאורה נמוכה.
לאחר קבלת המזהה, אנו שולחים בקשה לשרת: POST /api/v1/unlock עם {scooterId, userId, sessionToken}. השרת בודק את היתרה/המנוי, מזמין את הקורקינט, ומחזיר {bleUnlockToken, bleDeviceAddress, lockServiceUUID, lockCharacteristicUUID, expiresAt}. הטוקן הוא חד-פעמי, תקף ל-30–60 שניות—אם חיבור ה-BLE נכשל בזמן, המשתמש מקבל שגיאה ומתחיל בקשה חדשה.
// iOS: сканирование QR через AVFoundation
class QRScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
guard let readableObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let stringValue = readableObject.stringValue,
let scooterId = parseScooterId(from: stringValue) else { return }
captureSession.stopRunning()
Task {
await viewModel.initiateUnlock(scooterId: scooterId)
}
}
} למה טוקן BLE פג תוקף
קורקינטים בשיתוף משתמשים במודולי BLE המבוססים על Nordic nRF52 או ESP32 עם פרופיל GATT מותאם אישית. התוכנית פשוטה: אנו כותבים את הטוקן ל-// iOS: сканирование QR через AVFoundation class QRScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { guard let readableObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject, let stringValue = readableObject.stringValue, let scooterId = parseScooterId(from: stringValue) else { return } captureSession.stopRunning() Task { await viewModel.initiateUnlock(scooterId: scooterId) } } } , הבקר מאמת את חתימת ה-HMAC (מפתח משותף המוטמע בקושחה), ואם היא תואמת, משחרר את המנעול האלקטרומגנטי ומאפשר את המנוע.
// Android: запись unlock-токена в BLE characteristic
class ScooterUnlockManager(private val context: Context) {
private var gatt: BluetoothGatt? = null
suspend fun unlock(address: String, token: UnlockToken): UnlockResult {
return suspendCancellableCoroutine { cont ->
val device = bluetoothAdapter.getRemoteDevice(address)
gatt = device.connectGatt(context, false, object : BluetoothGattCallback() {
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, state: Int) {
if (state == BluetoothProfile.STATE_CONNECTED)
g.discoverServices()
else if (status != BluetoothGatt.GATT_SUCCESS) {
cont.resume(UnlockResult.BleConnectionFailed(status))
}
}
override fun onServicesDiscovered(g: BluetoothGatt, status: Int) {
val characteristic = g
.getService(token.serviceUUID)
?.getCharacteristic(token.characteristicUUID)
?: run {
cont.resume(UnlockResult.ServiceNotFound)
return
}
characteristic.value = token.payload.toByteArray()
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
g.writeCharacteristic(characteristic)
}
override fun onCharacteristicWrite(g: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
val result = if (status == BluetoothGatt.GATT_SUCCESS)
UnlockResult.Success
else
UnlockResult.WriteFailed(status)
cont.resume(result)
g.disconnect()
}
}, BluetoothDevice.TRANSPORT_LE)
cont.invokeOnCancellation { gatt?.disconnect() }
}
}
}פרט קריטי: WriteCharacteristic ב-// Android: запись unlock-токена в BLE characteristic class ScooterUnlockManager(private val context: Context) { private var gatt: BluetoothGatt? = null suspend fun unlock(address: String, token: UnlockToken): UnlockResult { return suspendCancellableCoroutine { cont -> val device = bluetoothAdapter.getRemoteDevice(address) gatt = device.connectGatt(context, false, object : BluetoothGattCallback() { override fun onConnectionStateChange(g: BluetoothGatt, status: Int, state: Int) { if (state == BluetoothProfile.STATE_CONNECTED) g.discoverServices() else if (status != BluetoothGatt.GATT_SUCCESS) { cont.resume(UnlockResult.BleConnectionFailed(status)) } } override fun onServicesDiscovered(g: BluetoothGatt, status: Int) { val characteristic = g .getService(token.serviceUUID) ?.getCharacteristic(token.characteristicUUID) ?: run { cont.resume(UnlockResult.ServiceNotFound); return } characteristic.value = token.payload.toByteArray() characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT g.writeCharacteristic(characteristic) } override fun onCharacteristicWrite(g: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { val result = if (status == BluetoothGatt.GATT_SUCCESS) UnlockResult.Success else UnlockResult.WriteFailed(status) cont.resume(result) g.disconnect() } }, BluetoothDevice.TRANSPORT_LE) cont.invokeOnCancellation { gatt?.disconnect() } } } } —ללא דגל זה, Android עלול לנסות להתחבר דרך Bluetooth קלאסי, מה שיגרום ל-TRANSPORT_LE במכשירים התומכים ב-BLE בלבד.
בעיות בתנאים אמיתיים
אות BLE חלש. הקורקינט נמצא בחניון תת-קרקעי, מוקף ב-20 קורקינטים אחרים עם אותם שירותי GATT. אנו לא סורקים את כל המכשירים ללא הבחנה; אנו מתחברים ישירות: connectGatt באמצעות ה-MAC מתגובת השרת. זה מהיר יותר מאשר סריקת הסביבה.
טוקן שפג תוקף. המשתמש סרק את ה-QR, ואז הוסח דעתו למשך 2 דקות. הטוקן פג תוקף. בעת קבלת status=133 בכתיבה, אך הקורקינט לא נפתח—אנו זקוקים ל-Notify Characteristic לתגובת הבקר. הבקר כותב בחזרה: bluetoothAdapter.getRemoteDevice(address) (OK), GATT_SUCCESS (טוקן פג תוקף), 0x01 (כבר נעול על ידי סשן אחר).
הרשאות Bluetooth ב-Android 12+. 0x02 ו-0x03 הן כעת הרשאות זמן ריצה. אם המשתמש דחה ובחר 'אל תשאל שוב', BLUETOOTH_SCAN מחזיר BLUETOOTH_CONNECT—אנו מובילים אותו להגדרות דרך shouldShowRequestPermissionRationale.
פרטי UX שעושים את ההבדל
הפעלה מקבילה של שרת ו-BLE: ברגע שמתקבלת תגובת השרת עם כתובת המכשיר, אנו מתחילים בחיבור BLE מבלי לחכות לאנימציית ה-UI הסופית. המשתמש רואה ספינר, אבל לחיצת היד של BLE כבר מתרחשת. חוסך כ-300-500 אלפיות שנייה. כדאי גם לשמור טוקנים במטמון: אם הקורקינט היה מחובר בעבר, אנו יכולים לנסות לשחזר את החיבור ללא סריקת QR חוזרת. זה מפחית את זמן הפתיחה הממוצע ב-20-30%.
השוואת גישות פתיחה
| מאפיין | BLE | NFC | QR + BLE |
|---|---|---|---|
| מהירות | 1-2 שניות | 0.5 שניות | 1-3 שניות |
| אבטחה | טוקן HMAC | מפתח הצפנה | טוקן + HMAC |
| תאימות | iOS/Android | iPhone 7+, Android 5+ | כמעט כל הסמארטפונים |
| חומרה נוספת | אין | מודול NFC | BLE בלבד |
| אמינות ברעש | בינונית (חיבור לפי כתובת) | גבוהה | גבוהה (לפי כתובת) |
בהתבסס על מחקר מ-Bluetooth SIG ו-תיעוד Apple Core Bluetooth.
טעויות נפוצות ביישום פתיחת BLE
| טעות | תוצאה | פתרון |
|---|---|---|
חסר false |
שגיאת חיבור ב-Android | ציין את הפרמטר במפורש |
| בקשות בו-זמנית ממספר מכשירים | התנגשות טוקנים | הזמן קורקינט בשרת |
| חוסר בהודעות לאחר כתיבה | לא ניתן לדעת את התוצאה | השתמש ב-Notify characteristic |
| טוקן ללא חותמת זמן | אפשרות לשימוש חוזר | HMAC עם חותמת זמן |
מה כוללת העבודה
- מודול סריקת QR ל-iOS ו-Android (ML Kit / AVFoundation)
- פקודת פתיחת BLE עם תמיכה בפרופיל GATT
- שירות backend לאימות והזמנה
- בדיקות על קורקינטים אמיתיים (Nordic nRF52, ESP32)
- תיעוד אינטגרציה והדרכת צוות
לוחות זמנים ועלות
פיתוח מודול סריקת QR + פתיחת BLE לפלטפורמת שיתוף (iOS + Android): 3-5 שבועות. עם backend מלא לצי (הזמנה, חיוב, גיאו-גידור): 3-5 חודשים. העלות מתחילה מ-$15,000 עבור מודול הפתיחה בלבד, בדרך כלל $25,000–$50,000 עבור אינטגרציה מלאה בהתאם למורכבות. צור איתנו קשר לייעוץ—נספק הצעת מחיר מפורטת תוך יום עסקים אחד.
שלבי יישום
- בדיקת חומרת הקורקינט הנוכחית (מודול BLE, קושחה)
- עיצוב פרופיל GATT ופרוטוקול
- פיתוח מודול iOS (Swift, Combine)
- פיתוח מודול Android (Kotlin, Coroutines)
- אינטגרציה עם שרת (REST/GraphQL)
- בדיקות (יחידה, אינטגרציה, שטח)
- פריסה ל-App Store ו-Google Play
- ניטור ותמיכה לאחר ההשקה
עם ניסיון של למעלה מ-5 שנים ו-10+ פרויקטים מוצלחים, אנו מבטיחים פתרון חזק ומאובטח. אנו מספקים אחריות ל-12 חודשים על כל הקוד ותיעוד מלא. הערך את הפרויקט שלך תוך יום עסקים אחד. קבל ייעוץ עכשיו—כתוב לנו!







