Repository navigation
iOS SBeacon Framework Calibration
iOS SBeacon Framework v1.0.8
This guide covers accelerometer calibration for SBeacon devices. Calibration is critical for accurate driving behavior detection.
SBeacon uses a 3-axis accelerometer to detect driving events. Without calibration:
- G-force readings are inaccurate
- False positives/negatives in event detection
- Trip detection may fail
Calibration ensures the accelerometer knows "level" - establishing a baseline for detecting motion and impacts.
- Beacon Pairing - Beacon must be paired and connected
- Physical requirements (see below)
CRITICAL: Place beacon on a flat, stable, horizontal surface during calibration:
- ✅ Level table or desk
- ✅ Flat floor
- ✅ Dashboard of parked vehicle (engine off)
- ❌ Angled surface
- ❌ Moving vehicle
- ❌ Unstable surface
Follow ISO 8855 vehicle axis standard:
^ Z (up/down)
|
|
+----> Y (forward/backward)
/
/
X (left/right)
Mount beacon in vehicle with:
- X-axis: Side-to-side (lateral)
- Y-axis: Front-to-back (longitudinal)
- Z-axis: Up-down (vertical)
import BLEFramework
// Method 1: Check beacon property
if beacon.isCalibrated {
print("Beacon is already calibrated")
} else {
print("Beacon needs calibration")
}
// Method 2: Query beacon directly
SBeaconManager.shared.checkBeaconCalibration()
// Result updates beacon.isCalibrated property automaticallyfunc calibrateBeacon() {
// Set callback
SBeaconManager.shared.calibrationComplete = { [weak self] error in
if let error = error {
print("Calibration failed: \(error)")
self?.handleCalibrationFailure(error)
} else {
print("Calibration successful!")
self?.onCalibrationSuccess()
}
}
// Start calibration (beacon must be on flat surface)
SBeaconManager.shared.calibrateBeacon()
}import Foundation
import BLEFramework
class CalibrationManager {
private var calibrationTimer: Timer?
private let calibrationTimeout: TimeInterval = 15.0
func calibrateBeacon(completion: @escaping (Result<Void, CalibrationError>) -> Void) {
// Set callback
SBeaconManager.shared.calibrationComplete = { [weak self] error in
guard let self = self else { return }
// Cancel timeout
self.calibrationTimer?.invalidate()
self.calibrationTimer = nil
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
// Clean up callback
SBeaconManager.shared.calibrationComplete = nil
}
// Start timeout
calibrationTimer = Timer.scheduledTimer(withTimeInterval: calibrationTimeout, repeats: false) { [weak self] _ in
guard let self = self else { return }
SBeaconManager.shared.calibrationComplete = nil
completion(.failure(.timeout))
}
// Start calibration
SBeaconManager.shared.calibrateBeacon()
}
enum CalibrationError: Error {
case noBeacon
case timeout
case notOnFlatSurface
}
}import SwiftUI
import BLEFramework
struct CalibrationView: View {
@StateObject private var viewModel: CalibrationViewModel
let beacon: SCPBeacon
init(beacon: SCPBeacon) {
self.beacon = beacon
_viewModel = StateObject(wrappedValue: CalibrationViewModel(beacon: beacon))
}
var body: some View {
VStack(spacing: 24) {
// Status
if viewModel.isCalibrating {
ProgressView("Calibrating...")
.scaleEffect(1.5)
Text("Keep beacon on flat surface")
.font(.caption)
.foregroundColor(.secondary)
} else if viewModel.isCalibrated {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 64))
.foregroundColor(.green)
Text("Beacon Calibrated")
.font(.headline)
if let date = beacon.isCalibratedOn {
Text("Calibrated on \(date.formatted())")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
Image(systemName: "location.north.circle")
.font(.system(size: 64))
.foregroundColor(.orange)
Text("Calibration Required")
.font(.headline)
VStack(alignment: .leading, spacing: 8) {
InstructionRow(icon: "1.circle.fill", text: "Place beacon on flat surface")
InstructionRow(icon: "2.circle.fill", text: "Ensure surface is stable and level")
InstructionRow(icon: "3.circle.fill", text: "Tap 'Calibrate' button")
}
.padding()
Button("Calibrate Beacon") {
viewModel.startCalibration()
}
.buttonStyle(.borderedProminent)
}
if let error = viewModel.errorMessage {
Text(error)
.foregroundColor(.red)
.font(.caption)
}
}
.padding()
.navigationTitle("Beacon Calibration")
.onAppear {
viewModel.checkCalibration()
}
}
}
struct InstructionRow: View {
let icon: String
let text: String
var body: some View {
HStack {
Image(systemName: icon)
.foregroundColor(.blue)
Text(text)
.font(.callout)
}
}
}
class CalibrationViewModel: ObservableObject {
@Published var isCalibrating = false
@Published var isCalibrated = false
@Published var errorMessage: String?
private let beacon: SCPBeacon
private var calibrationTimer: Timer?
init(beacon: SCPBeacon) {
self.beacon = beacon
self.isCalibrated = beacon.isCalibrated
}
func checkCalibration() {
SBeaconManager.shared.checkBeaconCalibration()
// Update after check
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.isCalibrated = self?.beacon.isCalibrated ?? false
}
}
func startCalibration() {
isCalibrating = true
errorMessage = nil
SBeaconManager.shared.calibrationComplete = { [weak self] error in
guard let self = self else { return }
self.calibrationTimer?.invalidate()
self.isCalibrating = false
if let error = error {
self.errorMessage = "Calibration failed: \(error.localizedDescription)"
} else {
self.isCalibrated = true
self.beacon.isCalibrated = true
}
SBeaconManager.shared.calibrationComplete = nil
}
// Timeout protection
calibrationTimer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { [weak self] _ in
guard let self = self else { return }
if self.isCalibrating {
self.isCalibrating = false
self.errorMessage = "Calibration timeout. Please try again."
SBeaconManager.shared.calibrationComplete = nil
}
}
SBeaconManager.shared.calibrateBeacon()
}
deinit {
calibrationTimer?.invalidate()
SBeaconManager.shared.calibrationComplete = nil
}
}import UIKit
import BLEFramework
class CalibrationViewController: UIViewController {
private let beacon: SCPBeacon
private let statusLabel = UILabel()
private let instructionsLabel = UILabel()
private let calibrateButton = UIButton(type: .system)
private let activityIndicator = UIActivityIndicatorView(style: .large)
private var calibrationTimer: Timer?
init(beacon: SCPBeacon) {
self.beacon = beacon
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
checkCalibrationStatus()
}
private func setupUI() {
view.backgroundColor = .systemBackground
title = "Calibration"
statusLabel.textAlignment = .center
statusLabel.font = .systemFont(ofSize: 24, weight: .bold)
instructionsLabel.numberOfLines = 0
instructionsLabel.textAlignment = .center
instructionsLabel.font = .systemFont(ofSize: 16)
instructionsLabel.textColor = .secondaryLabel
calibrateButton.setTitle("Calibrate Beacon", for: .normal)
calibrateButton.addTarget(self, action: #selector(calibrateButtonTapped), for: .touchUpInside)
// Layout (simplified - use Auto Layout in production)
// Add subviews and constraints...
}
private func checkCalibrationStatus() {
if beacon.isCalibrated {
showCalibrated()
} else {
showNotCalibrated()
}
}
@objc private func calibrateButtonTapped() {
startCalibration()
}
private func startCalibration() {
calibrateButton.isEnabled = false
activityIndicator.startAnimating()
statusLabel.text = "Calibrating..."
instructionsLabel.text = "Keep beacon on flat surface"
SBeaconManager.shared.calibrationComplete = { [weak self] error in
self?.onCalibrationComplete(error: error)
}
calibrationTimer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { [weak self] _ in
self?.onCalibrationTimeout()
}
SBeaconManager.shared.calibrateBeacon()
}
private func onCalibrationComplete(error: CalibrationError?) {
calibrationTimer?.invalidate()
activityIndicator.stopAnimating()
calibrateButton.isEnabled = true
if let error = error {
showError("Calibration failed: \(error.localizedDescription)")
} else {
beacon.isCalibrated = true
showCalibrated()
}
SBeaconManager.shared.calibrationComplete = nil
}
private func onCalibrationTimeout() {
activityIndicator.stopAnimating()
calibrateButton.isEnabled = true
showError("Calibration timeout. Please try again.")
SBeaconManager.shared.calibrationComplete = nil
}
private func showCalibrated() {
statusLabel.text = "✓ Calibrated"
statusLabel.textColor = .systemGreen
instructionsLabel.text = "Beacon is ready for use"
calibrateButton.isHidden = true
}
private func showNotCalibrated() {
statusLabel.text = "Calibration Required"
statusLabel.textColor = .systemOrange
instructionsLabel.text = "Place beacon on flat surface and tap Calibrate"
calibrateButton.isHidden = false
}
private func showError(_ message: String) {
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
deinit {
calibrationTimer?.invalidate()
SBeaconManager.shared.calibrationComplete = nil
}
}- Calibrate on Flat Surface: Most common cause of calibration failure
- Keep Beacon Still: No movement during calibration (15 seconds)
- Use Timeout Protection: Prevent indefinite waiting
-
Verify Afterward: Check
beacon.isCalibratedproperty - Recalibrate When Needed: After firmware updates, battery replacement, or if detection seems off
- Store Calibration Date: Track when calibration was last performed
- User Instructions: Clearly guide users through process
Problem: calibrationComplete callback never fires
Solutions:
- Ensure beacon is connected
- Check timeout protection is implemented
- Verify beacon is on flat surface (tilt affects calibration)
- Try power cycling beacon (remove/reinstall battery)
Problem: calibrationComplete returns error
Solutions:
- Check
CalibrationError.noBeacon- beacon disconnected - Ensure service characteristic is available
- Reconnect beacon and retry
Problem: isCalibrated == true but detection is off
Solutions:
- Verify beacon mounting orientation in vehicle matches ISO8855
- Ensure beacon is securely mounted (not loose/vibrating)
- Check beacon isn't mounted on angled surface in vehicle
- Recalibrate in vehicle (parked, level surface)
Problem: isCalibrated resets to false
Solutions:
- CoreData may not be saving - call
BeaconCoreDataManager.shared.saveContext() - Check beacon object isn't being recreated/replaced
- Verify
isCalibratedOndate is being set
- Driving Behavior - Calibration enables accurate event detection
- Trip Management - Trip detection also requires calibration
- Beacon Pairing - Pair beacon before calibrating
- Event Handling - Calibration callback details
Consolidated SDK
SmartDrive
PortalApiBase
PortalApiClient
Wellness
SBeacon
- Overview
- Installation
- Initialization
- Beacon Scanning
- Beacon Pairing
- Trip Management
- Driving Behavior
- Calibration
- Event Handling
- Data Models
- API Reference
Consolidated SPM
SDApiManager
CCFrameworkOpen
SmartDrive
SBeacon