Electric Scooter BLE/QR Unlock via Mobile App

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1 servicesAll 1735 services
Electric Scooter BLE/QR Unlock via Mobile App
Medium
~1-2 weeks
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Implementing E-scooter Unlock via BLE/QR in Mobile App

Scooter unlock in sharing—several seconds of user flow that either work invisibly or break experience completely. User scans QR, app must: verify token on server, get unlock command, deliver to scooter controller via BLE—all in 1-2 seconds. Any delay perceived as bug.

QR Code: From Scan to BLE Command

QR on scooter encodes device identifier: string like https://ride.example.com/unlock?id=SC-00234 or just SC-00234. Scan via ML Kit Barcode Scanning (Android/iOS) or AVFoundation AVCaptureMetadataOutput. ML Kit preferable—works offline, faster in poor light.

After ID received—server request: POST /api/v1/unlock with {scooterId, userId, sessionToken}. Server checks balance/subscription, reserves scooter, returns {bleUnlockToken, bleDeviceAddress, lockServiceUUID, lockCharacteristicUUID, expiresAt}. Token one-time, lives 30-60 seconds—if BLE connection didn't succeed, user gets error and initiates new request.

// iOS: QR scanning via 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 Unlock: Implementation Details

Sharing scooters use BLE modules on Nordic nRF52 or ESP32 with custom GATT profile. Scheme simple: write token to WriteCharacteristic, controller verifies HMAC signature of token (shared secret hardcoded in firmware), on match—releases electromagnetic lock and enables motor.

// Android: write unlock token to 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() }
        }
    }
}

Critical detail: TRANSPORT_LE in connectGatt flag—without it, Android may attempt Classic Bluetooth connection, giving status=133 on BLE-only devices.

Real-World Problems

Poor BLE signal. Scooter in underground parking, 20 other scooters nearby with same GATT services. Don't scan all devices; scan targeted: bluetoothAdapter.getRemoteDevice(address) by MAC from server response. Faster than environment scan.

Token expired. User scanned QR, then distracted 2 minutes. Token expired. After GATT_SUCCESS on write, but scooter didn't unlock—need Notify Characteristic for controller response. Controller writes to response characteristic: 0x01 (OK), 0x02 (token expired), 0x03 (already locked by another session).

Android 12+ Bluetooth permissions. BLUETOOTH_SCAN and BLUETOOTH_CONNECT—now runtime permissions. If user rejected with "Don't ask again", shouldShowRequestPermissionRationale returns false—lead to Settings via Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).

UX Details That Make a Difference

Parallel server and BLE: as soon as server response with device address received, start BLE connection without waiting for final UI animation. User sees spinner, BLE handshake already happening. Saves ~300-500 ms.

Developing QR scan + BLE unlock module for sharing platform (iOS + Android): 3-5 weeks. With full fleet backend (reservation, billing, geofencing): 3-5 months. Cost individually quoted.