Skip to content

Fall Detection Documentation

Oskar Öberg edited this page Jun 11, 2026 · 37 revisions

Fall Detection Guide

This guide explains how fall detection works on Flic Duo and how to use it from flic2lib.

Fall detection continuously monitors the Duo's built-in accelerometer for the broad pattern of a fall: a low-G period that indicates falling, followed by a high-G impact. When the configured conditions are met, the SDK reports state updates through the button delegate and delivers accelerometer data for the fall event.

For additional background, see https://github.com/50ButtonsEach/flic2-documentation/wiki/Fall-Detection-Documentation.

How the Algorithm Works

Fall detection is threshold-based. The firmware watches the acceleration magnitude and moves through these phases:

  1. The Duo enters the low-G state when the average acceleration magnitude stays below lowGThresholdMg for at least lowGDurationMs.
  2. Once in the low-G state, the Duo waits for an impact. The impact must happen within highGTimeoutMs.
  3. The impact is accepted when the acceleration magnitude stays at or above highGThresholdMg for highGTimeWindowMs. This smoothing window filters out very short spikes while still accepting impact-like motion.
  4. When the impact condition is met, fall detection is triggered and the SDK sends FLICButtonFallDetectionStateTriggered.
  5. The SDK then collects accelerometer data from before the impact and reports FLICButtonFallDetectionStatePreFallDataCollected when that pre-fall data is available.
  6. The Duo continues recording after the trigger for postEventRecordDurationMs, then the SDK reports FLICButtonFallDetectionStateCompleted when both pre-fall and post-fall data has been collected.

highGTimeoutMs is the transition window between the falling phase and impact. A longer timeout can detect falls where the impact is delayed or less abrupt, but it can also increase the probability of false positives.

Graph over the configuration parameters for Fall Detection

image

Running Fall Detection Indefinitely

To keep the Fall Detection feature running indefinitely it needs to be re-enabled in each buttonIsReady callback. Since the Fall Detection feature requires the connection to be active, combining it with alwaysReconnect=true and re-enabling the Fall Detection configuration on each buttonIsReady is how to keep the Fall Detection running indefinitely.

Wearing the Duo

Fall detection performs best when the Duo is worn on a lanyard around the neck. The lanyard should not be too loose, and the Duo should preferably be worn under the shirt. This keeps the device close to the body and reduces extra movement that can affect fall detection.

image

Battery Impact

Fall detection starts and uses the accelerometer continuously while it is active, so it affects battery performance. Using alwaysReconnect: true can also increase battery consumption because the Duo will keep advertising for reconnects after a lost connection. This is recommended for fall detection reliability, but apps should account for the battery tradeoff.

Enabling Fall Detection

Enable fall detection after the button is connected and ready, for example from buttonIsReady(_:). Do this every time the button becomes ready because the fall detection configuration is only valid for one session.

import flic2lib

func buttonIsReady(_ flicButton: FLICButton) {
    let config = FLICButtonFallDetectionConfig(
        lowGThresholdMg: 700,
        lowGDurationMs: 500,
        highGTimeoutMs: 650,
        highGThresholdMg: 3500,
        highGTimeWindowMs: 50,
        postEventRecordDurationMs: 2_000,
        fullScaleSelection: .fourG
    )

    flicButton.enableFallDetection(with: config, alwaysReconnect: true) { result in
        if result != .success {
            print("Failed to enable fall detection: \(result)")
        }
    }
}

Use alwaysReconnect: true for fall detection. This enables always-reconnect advertising, which helps the Duo reconnect after losing connection and keeps fall detection available if iOS disconnects the button. This is important because a fall detection event can happen without a button press, while a button press is normally the signal that causes the Duo to advertise and reconnect after a lost connection.

If enabling fall detection returns .firmwareUpdateNeeded, leave the Duo connected for a few minutes and make sure the host device has internet access. The firmware update will run automatically, and fall detection can be enabled after the update completes.

Suggested Configuration

This can be seen as a medium sensitive configuration to detect falls but reject everyday activities like jogging or light jumping.

Parameter Value
lowGThresholdMg 700
lowGDurationMs 500
highGTimeoutMs 650
highGThresholdMg 3500
highGTimeWindowMs 50
postEventRecordDurationMs 2000
fullScaleSelection .fourG
alwaysReconnect true

In Swift, these values map to FLICButtonFallDetectionConfig as:

let config = FLICButtonFallDetectionConfig(
    lowGThresholdMg: 700,
    lowGDurationMs: 500,
    highGTimeoutMs: 650,
    highGThresholdMg: 3500,
    highGTimeWindowMs: 50,
    postEventRecordDurationMs: 2_000,
    fullScaleSelection: .fourG
)

Configuration fields:

Swift property Meaning
lowGThresholdMg Acceleration magnitude threshold, in mg, used to enter the low-G state.
lowGDurationMs Minimum time the average acceleration magnitude must remain below lowGThresholdMg.
highGTimeoutMs Maximum time between entering the low-G state and detecting impact. Longer values allow more transition time but can increase false positives.
highGThresholdMg Acceleration magnitude threshold, in mg, used to detect impact.
highGTimeWindowMs Time window where acceleration must stay at or above highGThresholdMg for the impact to be accepted.
postEventRecordDurationMs Duration, in milliseconds, to keep recording accelerometer samples after a fall has been triggered.
fullScaleSelection Accelerometer full-scale range selection used while fall detection is active. Use .twoG, .fourG, .eightG, or .sixteenG.

Receiving Fall Detection Updates

Fall detection updates are reported to the button delegate:

func button(_ button: FLICButton, didUpdateFallDetection event: FLICButtonFallDetectionEvent)

FLICButtonFallDetectionEvent contains the current state and any accelerometer data collected so far:

class FLICButtonFallDetectionEvent: NSObject {
    var state: FLICButtonFallDetectionState { get }
    var preFallSampleRate: UInt16 { get }
    var preFallExpectedSampleCount: UInt16 { get }
    var preFallAccelerometerData: FLICButtonAccelerometerData { get }
    var postFallSampleRate: UInt16 { get }
    var postFallExpectedSampleCount: UInt16 { get }
    var postFallAccelerometerData: FLICButtonAccelerometerData { get }
}

The event state is one of:

enum FLICButtonFallDetectionState {
    case triggered
    case preFallDataCollected
    case completed
    case disabled
}

The states update as data is collected:

State Meaning
.triggered A high impact was detected after the low-G condition. Treat this as a potential fall.
.preFallDataCollected The accelerometer data up until the impact has been downloaded and is available in preFallAccelerometerData.
.completed Both pre-fall and post-fall accelerometer data has been collected.
.disabled Fall detection was disabled before the current collection finished.

Example delegate handling:

func button(_ button: FLICButton, didUpdateFallDetection event: FLICButtonFallDetectionEvent) {
    switch (event.state) {
    case .triggered:
        let notes = [
            FLICButtonBuzzerNote(hz: 2_200, duration: 0.2),
            FLICButtonBuzzerNote(hz: 0, duration: 0.1),
            FLICButtonBuzzerNote(hz: 2_200, duration: 0.2),
        ]
        button.playBuzzerSound(notes)

    case .preFallDataCollected:
        print(
            "Pre-fall samples: \(event.preFallAccelerometerData.points.count)/" +
            "\(event.preFallExpectedSampleCount) at \(event.preFallSampleRate) Hz"
        )

    case .completed:
        print(
            "Post-fall samples: \(event.postFallAccelerometerData.points.count)/" +
            "\(event.postFallExpectedSampleCount) at \(event.postFallSampleRate) Hz"
        )

    case .disabled:
        print("Fall detection disabled")

    @unknown default:
        break
    }
}

It is recommended to play a buzzer sound when .triggered is received. This alerts the user immediately and gives them an opportunity to cancel your app's alert or escalation flow before post-fall recording completes.

Disabling Fall Detection

Call disableFallDetection(_:) when the app no longer needs fall detection:

button.disableFallDetection(true)

Pass true if you also want to disable always-reconnect advertising that was enabled for fall detection. This is recommended for normal operation when not using Fall Detection.

Clone this wiki locally