שילוב BLE יציב לאפליקציות מובייל

שילוב Bluetooth Low Energy באפליקציה ניידת הופך לעיתים קרובות לכאוס בשל מצבי חיבור ושגיאות רבים שמתעוררים בכל שלב. אנחנו בונים פתרונות BLE עבור iOS ו-Android, ומספקים את הפרויקט במפתח מלא—מביקורת ועד הטמעה. הצוות המומחה שלנו מבטיח פעילות יציבה של המכשירים ותמיכה מתמשכת, כך שהאפליקציה שלך מתקשרת באופן אמין עם ציוד היקפי.

פיתוח ותמיכה בכל סוגי היישומים הניידים:

יישומים ניידים למידע ובידור
אפליקציות חדשות, משחקים, מדריכים, קטלוגים מקוונים, אפליקציות מזג אוויר, אפליקציות כושר ובריאות, אפליקציות נסיעות, אפליקציות חינוכיות, רשתות חברתיות ומסנג'רים, חידונים, בלוגים ופודקאסטים, פורומים, אגרגטורים
יישומים ניידים למסחר אלקטרוני
חנויות מקוונות, אפליקציות B2B, מרקטפלייסים, בורסות מקוונות, שירותי קאשבק, בורסות, פלטפורמות דרופשיפינג, תוכניות נאמנות, משלוחי מזון ומוצרים, מערכות תשלום.
יישומים ניידים לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, ניהול פרויקטים, כלים לצוותי מכירות, ניהול פיננסי, ניהול ייצור, ניהול לוגיסטיקה ומשלוחים, ניהול משאבי אנוש, מערכות ניטור נתונים
יישומים ניידים לשירותים אלקטרוניים
פלטפורמות מודעות מסווגות, בתי ספר מקוונים, בתי קולנוע מקוונים, פלטפורמות שירות אלקטרוניות, פלטפורמות קאשבק, אירוח וידאו, פורטלים נושאיים, פלטפורמות הזמנות ותזמון מקוונות, פלטפורמות מסחר מקוונות

אלה הם רק חלק מסוגי היישומים הניידים שאנו עובדים איתם, ולכל אחד מהם עשויים להיות מאפיינים ופונקציונליות ספציפיים משלו, המותאמים לצרכים ולמטרות הספציפיות של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 1734 השירותים
שילוב BLE יציב לאפליקציות מובייל
מורכב
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית מובייל עבור FEEDME
    פיתוח אפליקציית מובייל עבור FEEDME
    971
  • פיתוח אפליקציית מובייל עבור XOOMER
    פיתוח אפליקציית מובייל עבור XOOMER
    869
  • פיתוח אפליקציית מובייל עבור RHL
    פיתוח אפליקציית מובייל עבור RHL
    1289
  • פיתוח אפליקציית מובייל עבור ZIPPY
    פיתוח אפליקציית מובייל עבור ZIPPY
    1143
  • פיתוח אפליקציית מובייל עבור Affhome
    פיתוח אפליקציית מובייל עבור Affhome
    1076
  • פיתוח אפליקציית מובייל עבור חברת FLAVORS
    פיתוח אפליקציית מובייל עבור חברת FLAVORS
    677

שילוב BLE: פעולה יציבה עם מכשירים

לעתים קרובות אנו רואים פרויקטים שבהם חיבור BLE מיושם כ"הפעל סריקה — התחבר — תהנה." במציאות מדובר במכונת מצבים עם תריסר מצבים, שכל אחד מהם יכול להיכשל: מתאם כבוי, מכשיר מחוץ לטווח, שירות לא נמצא, מאפיין לא תומך בכתיבה. הצוות שלנו הם מומחים עם ניסיון של 5+ שנים בפיתוח פתרונות BLE ל-iOS ואנדרואיד. אנו מבטיחים שהאפליקציה שלך תטפל ביציבות בכל מקרי הקצה.

מדוע שילוב BLE דורש גישה מקצועית?

BLE פועל על מודל GATT (Generic Attribute Profile). המכשיר ההיקפי מספק Services — קבוצות פונקציות לוגיות. כל Service מכיל Characteristics — ערכים ספציפיים לקריאה, כתיבה או הרשמה (notify/indicate). שגיאה בכל אחת מהרמות הללו — והאפליקציה לא תקבל נתונים. ראינו פרויקטים שבהם מפתחים שכחו להירשם ל-descriptor של CCCD — כתוצאה מכך, המאפיין לא שלח התראות.

מכונת מצבי חיבור

קבוצה מינימלית של מצבים שחובה לטפל בהם במפורש:

IDLE → SCANNING → DISCOVERED → CONNECTING → CONNECTED → DISCOVERING_SERVICES → SERVICES_READY → SUBSCRIBING → READY 

ושגיאות בכל מעבר: פסק זמן סריקה, ניתוק חיבור ב-CONNECTING, gattStatus != GATT_SUCCESS בגילוי, אובדן חיבור ב-READY. אנו בודקים את כל התרחישים באמצעות סימולטורים ומכשירים אמיתיים.

מצב iOS (CoreBluetooth) אנדרואיד (BluetoothGatt)
IDLE IDLE → SCANNING → DISCOVERED → CONNECTING → CONNECTED → DISCOVERING_SERVICES → SERVICES_READY → SUBSCRIBING → READY .poweredOn
SCANNING BluetoothAdapter.state_on scanForPeripherals
DISCOVERED startScan didDiscover
CONNECTING onScanResult connect
CONNECTED connectGatt didConnect (STATE_CONNECTED)
DISCOVERING_SERVICES onConnectionStateChange discoverServices
SERVICES_READY discoverServices didDiscoverServices
SUBSCRIBING onServicesDiscovered setNotifyValue (CCCD)
READY writeDescriptor didUpdateValue

מה לעשות אם החיבור נופל?

השגיאה הנפוצה ביותר היא התעלמות מ-onCharacteristicChanged (iOS) או didDisconnectPeripheral עם non-GATT_SUCCESS (אנדרואיד). ללא חיבור מחדש אוטומטי, המשתמש צריך להפעיל מחדש את האפליקציה. אנו מיישמים לוגיקה עם backoff אקספוננציאלי: בניתוק נמתין 1s, 2s, 4s, 8s, ואז נאפס. ב-iOS לפעולה ברקע אנו משתמשים ב-onConnectionStateChange — המערכת מעירה את האפליקציה כשהמכשיר מופיע שוב.

iOS: CoreBluetooth

import CoreBluetooth
class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    var centralManager: CBCentralManager!
    var targetPeripheral: CBPeripheral?
    
    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: DispatchQueue(label: "ble.queue"))
    }
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        guard central.state == .poweredOn else {
            // обрабатываем .poweredOff, .unauthorized, .unsupported
            return
        }
        startScanning()
    }
    
    func startScanning() {
        let serviceUUID = CBUUID(string: "180D")
        centralManager.scanForPeripherals(withServices: [serviceUUID], options: [
            CBCentralManagerScanOptionAllowDuplicatesKey: false
        ])
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        guard RSSI.intValue > -80 else {
            return
        }
        // фильтр по сигналу
        centralManager.stopScan()
        targetPeripheral = peripheral
        centralManager.connect(peripheral, options: nil)
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheral.delegate = self
        peripheral.discoverServices([CBUUID(string: "180D")])
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard error == nil, let services = peripheral.services else {
            return
        }
        for service in services {
            peripheral.discoverCharacteristics([CBUUID(string: "2A37")], for: service)
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else {
            return
        }
        for char in characteristics where char.properties.contains(.notify) {
            peripheral.setNotifyValue(true, for: char)
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard let data = characteristic.value else {
            return
        }
        // парсим data согласно GATT-спецификации характеристики
    }
}

אנדרואיד: BluetoothGatt

val gattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
        if (status != BluetoothGatt.GATT_SUCCESS) {
            // status содержит код ошибки, например 133 (GATT_ERROR) - нужен реконнект
            gatt.close()
            return
        }
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverServices()
        }
    }

    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        val characteristic = gatt
            .getService(UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb"))
            ?.getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb"))
            ?: return
        gatt.setCharacteristicNotification(characteristic, true)
        val descriptor = characteristic.getDescriptor(
            UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") // Client Characteristic Configuration
        )
        descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
        gatt.writeDescriptor(descriptor)
    }

    override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
        val data = characteristic.value
        // парсим
    }
}

device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)

בעיות שאנו פותרים בכל פרויקט

בעיה פתרון
CBCentralManagerOptionRestoreIdentifierKey ללא אזהרה חיבור מחדש אוטומטי עם backoff, מזהה שחזור (iOS)
סטטוס 133 import CoreBluetooth class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate { var centralManager: CBCentralManager! var targetPeripheral: CBPeripheral? override init() { super.init() centralManager = CBCentralManager(delegate: self, queue: DispatchQueue(label: "ble.queue")) } func centralManagerDidUpdateState(_ central: CBCentralManager) { guard central.state == .poweredOn else { // обрабатываем .poweredOff, .unauthorized, .unsupported return } startScanning() } func startScanning() { let serviceUUID = CBUUID(string: "180D") centralManager.scanForPeripherals(withServices: [serviceUUID], options: [ CBCentralManagerScanOptionAllowDuplicatesKey: false ]) } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { guard RSSI.intValue > -80 else { return } // фильтр по сигналу centralManager.stopScan() targetPeripheral = peripheral centralManager.connect(peripheral, options: nil) } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { peripheral.delegate = self peripheral.discoverServices([CBUUID(string: "180D")]) } func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard error == nil, let services = peripheral.services else { return } for service in services { peripheral.discoverCharacteristics([CBUUID(string: "2A37")], for: service) } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { guard let characteristics = service.characteristics else { return } for char in characteristics where char.properties.contains(.notify) { peripheral.setNotifyValue(true, for: char) } } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { guard let data = characteristic.value else { return } // парсим data согласно GATT-спецификации характеристики } } באנדרואיד val gattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { if (status != BluetoothGatt.GATT_SUCCESS) { // status содержит код ошибки, например 133 (GATT_ERROR) - нужен реконнект gatt.close() return } if (newState == BluetoothProfile.STATE_CONNECTED) { gatt.discoverServices() } } override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { val characteristic = gatt .getService(UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb")) ?.getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")) ?: return gatt.setCharacteristicNotification(characteristic, true) val descriptor = characteristic.getDescriptor( UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") // Client Characteristic Configuration ) descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) } override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { val data = characteristic.value // парсим } } device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) , didDisconnectPeripheral באמצעות reflection, ניסיון חוזר אחרי 2s
סריקה מרוקנת סוללה GATT_ERROR — הפעל רק בעת מדידת RSSI
שחזור מצב לא עובד הגדר gatt.close() ב-scheme
הרשאות אנדרואיד 12+ בקש refresh() ו-allowDuplicates: false באופן דינמי

מה כלול בעבודת שילוב BLE

  • תיעוד של סכמת GATT של המכשיר (services, characteristics, פורמטי נתונים)
  • יישום של סריקה, חיבור, הרשמה, חיבור מחדש
  • טיפול בכל המצבים הרגילים והחירום
  • אופטימיזציית MTU: בקשת 512 בתים במקום 23 להעברה מהירה יותר
  • אינטגרציה עם מכשיר בדיקה (מסופק על ידי הלקוח או שלנו)
  • בדיקות יחידה ותרחישי בדיקת עומס (עד 10 ציוד היקפי BLE בו-זמנית)
  • תמיכה לאחר מסירה: אחריות של 3 חודשים על יציבות החיבור

איך אנחנו עובדים: מניתוח ועד פריסה

  1. ניתוח — לימוד תיעוד המכשיר, הסכמה על סכמת GATT ופורמט נתונים.
  2. עיצוב — יצירת מכונת מצבים, הגדרת מדיניות חיבור מחדש.
  3. יישום — כתיבת קוד ב-Swift/Kotlin באמצעות מסגרות מודרניות.
  4. בדיקות — בדיקה על סימולטורים, מכשירים אמיתיים, עם אמולציית הפרעות.
  5. פריסה — פרסום ל-TestFlight / Firebase Distribution, סיוע ב-App Review (בדוק סעיפים 4.2 ו-5.1 של הנחיות סקירת App Store).

ביצועים וסוללה

ברירת המחדל של MTU היא 23 בתים. אנו מבקשים דרך CBCentralManagerOptionRestoreIdentifierKey / BLUETOOTH_SCAN. בהעברות גדולות (OTA של קושחה, נתוני חיישנים) זה ההבדל בין 30 שניות ל-3 דקות. חיסכון בזמן באמצעות הספרייה שלנו מגיע ל-40%. היעילות הפיננסית של שילוב BLE מפתח-ביד ברורה — אתה מקבל פתרון מוכן בלי להתעמק במורכבויות GATT.

עוד על MTU

כברירת מחדל, BLE משתמש ב-MTU של 23 בתים, מתוכם 3 הם תקורה. אנו מבקשים 512 בתים, שזה פי 22 יותר. להעברת 1 KB של נתונים, זה מפחית את מספר החבילות מ-50 ל-3. במכשירים עם רוחב פס מוגבל (למשל, צמידי כושר), זה חוסך עד 60% אנרגיה.

לוח זמנים ותנאים

אינטגרציה בסיסית (שירות notify אחד) — 3–5 ימים. תרחישים מורכבים (OTA, ריבוי ציוד היקפי, מצב רקע) — 1–2 שבועות. העלות מחושבת באופן אישי לאחר ניתוח המכשיר שלך. צור קשר להערכת הפרויקט שלך. הזמן שילוב BLE היום — נציע אפשרויות יישום.

אנו ממליצים לקרוא את התיעוד הרשמי עבור CoreBluetooth ו-Android BluetoothGatt להבנה מפורטת יותר של ה-API. למד עוד על GATT ב-ויקיפדיה.