Skip to content

Android SBeacon Framework Event Handling

AigarsM edited this page Jan 27, 2026 · 3 revisions

Event Handling

The SBeacon SDK provides an event-based system to notify your application of state changes, data arrivals, and real-time alerts.


EventsReceiver Pattern

Subscribe to SDK events by extending IBFHelper.EventsReceiver and overriding callback methods for the events you need.

Basic Setup

import com.scope.ibeacons.IBFHelper
import com.scope.common.models.*

class MyFragment : Fragment() {

    private val beaconEventsReceiver = object : IBFHelper.EventsReceiver() {

        override fun onBeaconConnected(mac: String) {
            Log.d("Beacon", "Connected: $mac")
        }

        override fun onBeaconDisconnected(mac: String) {
            Log.d("Beacon", "Disconnected: $mac")
        }

        override fun onTripStatusChanged(status: String) {
            Log.d("Trip", "Status changed: $status")
        }

        override fun onDrivingBehaviorEventsReceived(
            beaconMac: String,
            events: ArrayList<DrivingBehaviorEvent>
        ) {
            Log.d("DB", "Received ${events.size} driving behavior events")
            events.forEach { event ->
                Log.d("DB", "${event.eventType}: ${event.averageMagnitude}g")
            }
        }

        override fun onRealTimeDrivingBehaviorEvent(
            beaconMac: String,
            eventType: DrivingBehaviorType,
            timestamp: Long
        ) {
            Log.w("DB", "Real-time alert: $eventType")
            showDrivingAlert(eventType)
        }
    }

    override fun onResume() {
        super.onResume()
        IBFHelper.registerEventsReceiver(requireContext(), beaconEventsReceiver)
    }

    override fun onPause() {
        super.onPause()
        IBFHelper.unregisterEventsReceiver(requireContext(), beaconEventsReceiver)
    }
}

Available Event Callbacks

Connection Events

override fun onBeaconConnected(mac: String) {
    // Beacon established BLE connection
}

override fun onBeaconDisconnected(mac: String) {
    // Beacon lost BLE connection
}

override fun onBeaconPaired(name: String) {
    // Beacon successfully paired
}

override fun onBeaconUnpaired(name: String) {
    // Beacon was unpaired
}

override fun onBeaconUpdated(name: String) {
    // Beacon data updated
}

Trip Events

override fun onTripStatusChanged(status: String) {
    // Trip lifecycle changed (IDLE, START, ACTIVE, END, TIMEOUT)
    when (status) {
        "IDLE" -> Log.d("Trip", "No active trip")
        "START" -> Log.d("Trip", "Trip starting")
        "ACTIVE" -> Log.d("Trip", "Trip in progress")
        "END" -> Log.d("Trip", "Trip ending")
        "TIMEOUT" -> Log.d("Trip", "Trip timed out")
    }
}

override fun onUnsyncedDataCounts(
    beaconMac: String,
    tripCount: Int,
    incidentCount: Int,
    dbCount: Int
) {
    // Number of unsynced records on the beacon
    Log.d("Sync", "Pending: $tripCount trips, $incidentCount incidents, $dbCount DB events")
}

override fun onTripRecordsReceived(
    beaconMac: String,
    trips: ArrayList<TripRecord>
) {
    // Stored trip records retrieved from beacon
    trips.forEach { trip ->
        val duration = (trip.endTimestamp - trip.startTimestamp) / 1000 / 60
        Log.d("Trip", "Trip ${trip.recordIndex}: $duration minutes")
    }
}

override fun onTripIncidentsReceived(
    beaconMac: String,
    incidents: ArrayList<IncidentRecord>
) {
    // Stored collision/incident records
    incidents.forEach { incident ->
        Log.d("Incident", "Incident at ${Date(incident.timestamp)}")
    }
}

Driving Behavior Events

override fun onDrivingBehaviorEventsReceived(
    beaconMac: String,
    events: ArrayList<DrivingBehaviorEvent>
) {
    // Stored driving behavior events (up to 80)
    events.forEach { event ->
        Log.d("DB", """
            Type: ${event.eventType}
            Duration: ${event.durationMs}ms
            Avg G-force: ${event.averageMagnitude}g
            Max G-force: ${event.maxMagnitude}g
        """.trimIndent())
    }
}

override fun onRealTimeDrivingBehaviorEvent(
    beaconMac: String,
    eventType: DrivingBehaviorType,
    timestamp: Long
) {
    // Real-time harsh driving alert (during active trip)
    when (eventType) {
        DrivingBehaviorType.HARSH_ACCELERATION -> {
            showWarning("Hard acceleration detected!")
        }
        DrivingBehaviorType.HARSH_BRAKING -> {
            showWarning("Hard braking detected!")
        }
        DrivingBehaviorType.HARSH_CORNERING -> {
            showWarning("Sharp turn detected!")
        }
        DrivingBehaviorType.ACCIDENT -> {
            triggerEmergencyAlert()
        }
    }
}

override fun onDrivingBehaviorConfigReceived(
    beaconMac: String,
    config: DrivingBehaviorConfig
) {
    // Configuration settings received
    Log.d("DB", "Enabled: ${config.enabled}")
    Log.d("DB", "Corner threshold: ${config.corneringThreshold}mg")
    Log.d("DB", "Brake threshold: ${config.brakingThreshold}mg")
    Log.d("DB", "Accel threshold: ${config.accelerationThreshold}mg")
}

override fun onDrivingBehaviorEventsClearConfirmed(
    beaconMac: String,
    clearedIndex: Int
) {
    // Event clear operation confirmed
    if (clearedIndex == 0xFF) {
        Log.d("DB", "All events cleared successfully")
    } else {
        Log.d("DB", "Event $clearedIndex cleared")
    }
}

Calibration Events

override fun onAutoCalibrationReceived(mac: String) {
    // Accelerometer calibration completed
    Log.d("Calibration", "Beacon $mac calibration complete")
}

override fun onCalibrationResetReceived(mac: String) {
    // Calibration was reset
    Log.d("Calibration", "Calibration reset for $mac")
}

override fun onUpdateBeaconCalibrationStateReceived(
    mac: String,
    isCalibrated: Boolean
) {
    // Calibration state update
    if (isCalibrated) {
        Log.d("Calibration", "Beacon is calibrated")
    } else {
        Log.d("Calibration", "Beacon needs calibration")
    }
}

Other Events

override fun onEmergencyButtonPressed() {
    // Physical emergency button on beacon was pressed
    triggerEmergencyResponse()
}

override fun onAccidentThresholdReceived(mac: String, value: Int) {
    // Accident detection threshold value
    Log.d("Threshold", "Accident threshold: ${value}mg")
}

Complete Event Reference

Callback Method Parameters Description
onBeaconConnected mac: String Beacon BLE connected
onBeaconDisconnected mac: String Beacon BLE disconnected
onBeaconPaired name: String Beacon paired successfully
onBeaconUnpaired name: String Beacon unpaired
onBeaconUpdated name: String Beacon data updated
onTripStatusChanged status: String Trip status changed
onUnsyncedDataCounts beaconMac, tripCount, incidentCount, dbCount Pending upload counts
onTripRecordsReceived beaconMac, trips Stored trip records
onTripIncidentsReceived beaconMac, incidents Stored incident records
onDrivingBehaviorEventsReceived beaconMac, events Stored DB events
onRealTimeDrivingBehaviorEvent beaconMac, eventType, timestamp Real-time harsh driving
onDrivingBehaviorConfigReceived beaconMac, config DB configuration
onDrivingBehaviorEventsClearConfirmed beaconMac, clearedIndex Clear confirmation
onAutoCalibrationReceived mac: String Calibration completed
onCalibrationResetReceived mac: String Calibration reset
onUpdateBeaconCalibrationStateReceived mac, isCalibrated Calibration state
onEmergencyButtonPressed - Emergency button pressed
onAccidentThresholdReceived mac, value Accident threshold

Best Practices

1. Register/Unregister with Lifecycle

Always pair registration with the appropriate lifecycle methods:

// Fragment
override fun onResume() {
    super.onResume()
    IBFHelper.registerEventsReceiver(requireContext(), beaconEventsReceiver)
}

override fun onPause() {
    super.onPause()
    IBFHelper.unregisterEventsReceiver(requireContext(), beaconEventsReceiver)
}

// Activity
override fun onStart() {
    super.onStart()
    IBFHelper.registerEventsReceiver(this, beaconEventsReceiver)
}

override fun onStop() {
    super.onStop()
    IBFHelper.unregisterEventsReceiver(this, beaconEventsReceiver)
}

2. Register Before Requesting Data

Always register your receiver before requesting data from the beacon:

// CORRECT
override fun onResume() {
    super.onResume()
    IBFHelper.registerEventsReceiver(requireContext(), beaconEventsReceiver)
    bleManager.requestDrivingBehaviorEvents(beaconMac)
}

// WRONG - may miss the response
override fun onResume() {
    super.onResume()
    bleManager.requestDrivingBehaviorEvents(beaconMac)
    IBFHelper.registerEventsReceiver(requireContext(), beaconEventsReceiver) // Too late!
}

3. Override Only Needed Callbacks

You don't need to override all methods - only the ones you care about:

private val eventsReceiver = object : IBFHelper.EventsReceiver() {
    override fun onTripStatusChanged(status: String) {
        // Only handling trip status changes
        updateTripUI(status)
    }
}

4. Handle Events on Main Thread

Event callbacks are delivered on the main thread, so you can update UI directly:

override fun onRealTimeDrivingBehaviorEvent(
    beaconMac: String,
    eventType: DrivingBehaviorType,
    timestamp: Long
) {
    // Safe to update UI directly
    binding.alertText.text = "Alert: $eventType"
    binding.alertText.visibility = View.VISIBLE
}

For heavy processing, move to background thread:

override fun onDrivingBehaviorEventsReceived(
    beaconMac: String,
    events: ArrayList<DrivingBehaviorEvent>
) {
    lifecycleScope.launch(Dispatchers.IO) {
        // Process events on background thread
        val processed = processEvents(events)

        withContext(Dispatchers.Main) {
            // Update UI on main thread
            updateUI(processed)
        }
    }
}

5. Filter by Beacon MAC

When managing multiple beacons, filter events by MAC address:

private val currentBeaconMac = "AA:BB:CC:DD:EE:FF"

override fun onDrivingBehaviorEventsReceived(
    beaconMac: String,
    events: ArrayList<DrivingBehaviorEvent>
) {
    if (beaconMac == currentBeaconMac) {
        // Only process events from our beacon
        processEvents(events)
    }
}

Complete Example

class BeaconMonitorFragment : Fragment() {

    private var _binding: FragmentBeaconMonitorBinding? = null
    private val binding get() = _binding!!

    private val bleManager by lazy { BLEManager.retrieveInstance(requireContext()) }
    private var currentBeaconMac: String? = null

    private var lastTripStatus: String? = null

    private val beaconEventsReceiver = object : IBFHelper.EventsReceiver() {

        override fun onBeaconConnected(mac: String) {
            binding.connectionStatus.text = "Connected"
            binding.connectionStatus.setTextColor(Color.GREEN)
        }

        override fun onBeaconDisconnected(mac: String) {
            binding.connectionStatus.text = "Disconnected"
            binding.connectionStatus.setTextColor(Color.RED)
        }

        override fun onTripStatusChanged(status: String) {
            binding.tripStatus.text = "Trip: $status"
            when (status) {
                "ACTIVE" -> startTripMonitoring()
                "END" -> stopTripMonitoring()
                "IDLE" -> {
                    // Only sync when transitioning from END to IDLE
                    if (lastTripStatus == "END") {
                        syncTripData()
                    }
                }
            }
            lastTripStatus = status
        }

        override fun onUnsyncedDataCounts(
            beaconMac: String,
            tripCount: Int,
            incidentCount: Int,
            dbCount: Int
        ) {
            binding.unsyncedCount.text =
                "Unsynced: $tripCount trips, $incidentCount incidents, $dbCount events"

            if (tripCount > 0 || incidentCount > 0 || dbCount > 0) {
                binding.syncButton.isEnabled = true
            }
        }

        override fun onDrivingBehaviorEventsReceived(
            beaconMac: String,
            events: ArrayList<DrivingBehaviorEvent>
        ) {
            lifecycleScope.launch {
                // Upload to backend
                uploadEventsToServer(events)

                // Clear after successful upload
                bleManager.clearAllDrivingBehaviorEvents(beaconMac)
            }
        }

        override fun onRealTimeDrivingBehaviorEvent(
            beaconMac: String,
            eventType: DrivingBehaviorType,
            timestamp: Long
        ) {
            val message = when (eventType) {
                DrivingBehaviorType.HARSH_ACCELERATION -> "Hard Acceleration!"
                DrivingBehaviorType.HARSH_BRAKING -> "Hard Braking!"
                DrivingBehaviorType.HARSH_CORNERING -> "Sharp Turn!"
                DrivingBehaviorType.ACCIDENT -> "COLLISION DETECTED!"
            }

            showAlert(message, eventType == DrivingBehaviorType.ACCIDENT)
        }

        override fun onUpdateBeaconCalibrationStateReceived(
            mac: String,
            isCalibrated: Boolean
        ) {
            if (isCalibrated) {
                binding.calibrationStatus.text = "Calibrated"
                binding.calibrateButton.visibility = View.GONE
                binding.enableDbButton.isEnabled = true
            } else {
                binding.calibrationStatus.text = "Not Calibrated"
                binding.calibrateButton.visibility = View.VISIBLE
                binding.enableDbButton.isEnabled = false
            }
        }

        override fun onAutoCalibrationReceived(mac: String) {
            Snackbar.make(binding.root, "Calibration completed!", Snackbar.LENGTH_LONG).show()
        }

        override fun onDrivingBehaviorEventsClearConfirmed(
            beaconMac: String,
            clearedIndex: Int
        ) {
            Snackbar.make(binding.root, "Events cleared from beacon", Snackbar.LENGTH_SHORT).show()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentBeaconMonitorBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        currentBeaconMac = arguments?.getString("beaconMac")

        setupButtons()
    }

    override fun onResume() {
        super.onResume()
        IBFHelper.registerEventsReceiver(requireContext(), beaconEventsReceiver)

        // Request current state
        currentBeaconMac?.let { mac ->
            bleManager.requestTripInformation(mac)
            bleManager.requestAccelerometerConfig(mac)
            bleManager.requestDrivingBehaviorConfig(mac)
        }
    }

    override fun onPause() {
        super.onPause()
        IBFHelper.unregisterEventsReceiver(requireContext(), beaconEventsReceiver)
    }

    private fun setupButtons() {
        binding.calibrateButton.setOnClickListener {
            currentBeaconMac?.let { bleManager.requestAccelerometerCalibration(it) }
        }

        binding.enableDbButton.setOnClickListener {
            currentBeaconMac?.let { bleManager.enableDrivingBehavior(it) }
        }

        binding.syncButton.setOnClickListener {
            syncTripData()
        }
    }

    private fun syncTripData() {
        currentBeaconMac?.let { mac ->
            bleManager.requestTripRecords(mac)
            bleManager.requestTripIncidents(mac)
            bleManager.requestDrivingBehaviorEvents(mac)
        }
    }

    private fun startTripMonitoring() {
        // Enable real-time monitoring UI
    }

    private fun stopTripMonitoring() {
        // Disable real-time monitoring UI
    }

    private suspend fun uploadEventsToServer(events: ArrayList<DrivingBehaviorEvent>) {
        // Upload to your backend
    }

    private fun showAlert(message: String, isCritical: Boolean) {
        val color = if (isCritical) Color.RED else Color.parseColor("#FF5722")
        Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG)
            .setBackgroundTint(color)
            .show()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

Related Pages

Clone this wiki locally