Configuration.kt

This is a container, presenting a common interface from potential multiple configurations. UserConfig is an example of a configuration creator.
package my.package.ble

import fr.maxcom.beacon.configuration.ScanFrequency
import fr.maxcom.beacon.configuration.ScanMode
import fr.maxcom.beacon.manager.listeners.AScanStatusListener
import fr.maxcom.beacon.plugin.IExplorerPlugin
import fr.maxcom.beacon.plugin.IParserPlugin
import fr.maxcom.beacon.plugin.IProcessorPlugin
import fr.maxcom.beacon.processor.IProcessor
import fr.maxcom.beacon.rssi.IRssiCalculator
import java.util.UUID

class Configuration {
    // general settings ...

    var verbose = false

    // dedicated to ScanManager ...

    var scanStatusListener: AScanStatusListener? = null

    // dedicated to the scan processing ...

    var scanFrequency: ScanFrequency? = null
    var scanMode: ScanMode? = null
    var rssiCalculator: IRssiCalculator? = null
    var preventDoze = false
    var reminderIntervalSeconds: Long = 0
    var devicesUpdateEventSlowdown: Long = -1 // special value meaning 'not set', for the default value to apply

    // dedicated to feed a IContextRegistrar (typically a ScanContext.Builder) ...

    private var mProcessors: MutableList<IProcessor>? = null
    private var mProcessorPlugins: MutableList<IProcessorPlugin>? = null
    private var mExplorerPlugins: MutableList<IExplorerPlugin>? = null
    private var mParserPlugins: MutableList<IParserPlugin>? = null

    // setters dedicated to the main app, thus concentrating all library accesses to only a ble subpackage ...

    @Suppress("unused")
    fun setRangingScanFrequency() {
        scanFrequency = ScanFrequency.RANGING
    }
    @Suppress("unused")
    fun setMonitoringScanFrequency() {
        scanFrequency = ScanFrequency.MONITORING
    }

    // collections management for processors and plugins ...

    fun addProcessor(processor: IProcessor): Configuration {
        if (mProcessors == null) mProcessors = ArrayList()
        mProcessors!!.add(processor)
        return this
    }

    val processors: List<IProcessor>?
        get() = mProcessors

    @Suppress("unused")
    fun addProcessorPlugin(plugin: IProcessorPlugin): Configuration {
        if (mProcessorPlugins == null) mProcessorPlugins = ArrayList()
        mProcessorPlugins!!.add(plugin)
        return this
    }

    val processorPlugins: List<IProcessorPlugin>?
        get() = mProcessorPlugins

    @Suppress("unused")
    fun addExplorerPlugin(plugin: IExplorerPlugin): Configuration {
        if (mExplorerPlugins == null) mExplorerPlugins = ArrayList()
        mExplorerPlugins!!.add(plugin)
        return this
    }

    val explorerPlugins: List<IExplorerPlugin>?
        get() = mExplorerPlugins

    @Suppress("unused")
    fun addParserPlugin(plugin: IParserPlugin): Configuration {
        if (mParserPlugins == null) mParserPlugins = ArrayList()
        mParserPlugins!!.add(plugin)
        return this
    }

    val parserPlugins: List<IParserPlugin>?
        get() = mParserPlugins

    // cleanup of a possible previous run
    fun clear() {
        rssiCalculator?.clear()
        mProcessors?.forEach { it.clear() }
        mProcessorPlugins?.forEach { it.clear() }
        mExplorerPlugins?.forEach { it.clear() }
    }

    companion object {
        // the default iBeacon proximity UUID of interest for the app
        val APP_UUID: UUID = UUID.fromString("01020304-0506-0708-090a-0b0c0d0e0f10") // TODO set your own one
    }
}

UserConfig.kt

A configuration creator, for a manual usage, that is with a user facing an Activity.

How screens and layouts are implemented (XML, Jetpack Compose, ...) is out of scope of the samples. An (UI) marker is used in the code comments to denote this point.

package my.package.ble

import android.util.Log
import fr.maxcom.beacon.configuration.DeviceFilter
import fr.maxcom.beacon.configuration.ScanMode
import fr.maxcom.beacon.device.BeaconDevice
import fr.maxcom.beacon.device.BeaconRegion
import fr.maxcom.beacon.device.EddystoneDevice
import fr.maxcom.beacon.device.EddystoneNamespace
import fr.maxcom.beacon.error.ScanError
import fr.maxcom.beacon.manager.listeners.IDeviceListener
import fr.maxcom.beacon.manager.listeners.AScanStatusListener
import fr.maxcom.beacon.manager.listeners.ISpaceListener
import fr.maxcom.beacon.plugin.IExplorerPlugin
import fr.maxcom.beacon.plugin.IPluginDeviceListener
import fr.maxcom.beacon.plugin.IProcessorPlugin
import fr.maxcom.beacon.plugin.manufacturer.AccSysParserPlugin
import fr.maxcom.beacon.plugin.manufacturer.ElaParserPlugin
import fr.maxcom.beacon.plugin.manufacturer.InvirtusProcessorPlugin
import fr.maxcom.beacon.plugin.manufacturer.MinewExplorerPlugin
import fr.maxcom.beacon.processor.BeaconProcessor
import fr.maxcom.beacon.processor.EddystoneProcessor
import fr.maxcom.beacon.processor.IProcessor
import fr.maxcom.beacon.rssi.CappedAverageRssiCalculator
import my.package.BuildConfig
import java.util.UUID

object UserConfig {
    private val TAG = UserConfig::class.java.simpleName

    fun create(): Configuration {
        val cfg = Configuration()
        with(cfg) {
            //scanFrequency = ScanFrequency.MONITORING // default is ScanFrequency.RANGING
            scanMode = ScanMode.LOW_LATENCY // default is BALANCED
            rssiCalculator = CappedAverageRssiCalculator(5)
            reminderIntervalSeconds = 10 * 60
            devicesUpdateEventSlowdown = 14 * 1000

            scanStatusListener = object : AScanStatusListener {
                override fun onScanStart() {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onScanStart")
                    // ... (UI) say "onScanStart"
                }

                override fun onScanStop() {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onScanStop")
                    // ... (UI) say "onScanStop"
                }

                override fun onScanFailed(errorCode: Int, errorReason: String) {
                    // is already in logcat
                    // ... (UI) say "onScanFailed: $errorReason"
                }

                override fun onScanError(scanError: ScanError) {
                    // is already in logcat
                    // ... (UI) say "onScannerError: $scanError"
                }

                // onCycle*() are called only if ScanFrequency is set to something other than RANGING

                override fun onCycleResume() {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onCycleResume")
                    // ... (UI) say "onCycleResume"
                }

                override fun onCyclePause() {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onCyclePause")
                    // ... (UI) say "onCyclePause"
                }
            }
        }

        // may be recycled to serve more than one time
        val filters: MutableList<DeviceFilter> = ArrayList()

        // iBeacon settings
        val beaconProcessor = BeaconProcessor(
            object : IDeviceListener<BeaconDevice, BeaconRegion> {
                override fun onDiscovered(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onDiscovered: $device, region: $space")
                    // ... (UI) say "+ Mm=${device.major}.${device.minor}, n=${device.name}, 1m=${device.txPower}, rs=${device.rssi} ${device.address}"
                }

                override fun onKnown(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.v(TAG, "onKnown: $device, region: $space")
                    // ... (UI) say "= Mm=${device.major}.${device.minor}, n=${device.name}, 1m=${device.txPower}, rs=${device.rssi} ${device.address}"
                }

                override fun onKnownReminder(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onKnownReminder: $device, region: $space")
                    // ... (UI) say ". Mm=${device.major}.${device.minor}, n=${device.name}, 1m=${device.txPower}, rs=${device.rssi} ${device.address}"
                }

                override fun onUpdated(devices: List<BeaconDevice>, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onUpdated: $devices, region: $space")
                }

                override fun onLost(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onLost: $device, region: $space")
                    // ... (UI) say "- Mm=${device.major}.${device.minor}, n=${device.name} ${device.address}"
                }
            },
            object : ISpaceListener<BeaconRegion> {
                override fun onEntered(space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onEntered: $space")
                }

                override fun onExited(space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onExited: $space")
                }
            }
        )
        // if the processor has eventually an empty collection, BeaconRegion.EVERYWHERE will be assumed
        val regions: MutableCollection<BeaconRegion> = HashSet()
        regions.add(BeaconRegion.Builder()
            .name("MyApp") // TODO set your own value
            .proximity(Configuration.APP_UUID)
            .build())
        // optionally add some regions, for example to observe a few extra development beacon tags
        /*
        Example for an 'Ultra-Thin Beacon U1' tag from Meeblue, Inc.:
        regions.add(BeaconRegion.Builder()
            .name("Meeblue")
            .proximity(UUID.fromString("d35b76e2-e01c-9fac-ba8d-7ce20bdba0c6"))
            .build())
        Example for a tag from Minew Technologies Co.:
        regions.add(BeaconRegion.Builder()
            .name("Minew")
            .proximity(UUID.fromString("e2c56db5-dffb-48d2-b060-d0f5a71096e0"))
            .build())
        */
        beaconProcessor.setBeaconRegions(regions)

        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceName("L ID 003D4F")) // Example of a 'BLUE LITE ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceName("S ID 002A3B")) // Example of a 'BLUE SLIM ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceAddress("AC:23:3F:A1:B2:C3")) // Example of a tag from Minew Technologies
        (beaconProcessor as IProcessor).setDeviceFilters(filters)

        cfg.addProcessor(beaconProcessor)

        // Eddystone settings
        val eddystoneProcessor = EddystoneProcessor(
            object : IDeviceListener<EddystoneDevice, EddystoneNamespace> {
                override fun onDiscovered(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onDiscovered: $device, namespace: $space")
                    // ... (UI) say "+ id=${device.namespace}.${device.instance}, 0m=${device.txPower}, rs=${device.rssi}\n  n=${device.name}"
                }

                override fun onKnown(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.v(TAG, "onKnown: $device, namespace: $space")
                    // ... (UI) say "= id=${device.namespace}.${device.instance}, 0m=${device.txPower}, rs=${device.rssi}\n  n=${device.name}"
                }

                override fun onKnownReminder(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onKnownReminder: $device, namespace: $space")
                    // ... (UI) say ". id=${device.namespace}.${device.instance}, 0m=${device.txPower}, rs=${device.rssi}\n  n=${device.name}"
                }

                override fun onUpdated(devices: List<EddystoneDevice>, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onUpdated: $devices, namespace: $space")
                }

                override fun onLost(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onLost: $device, namespace: $space")
                    // ... (UI) say "- id=${device.namespace}.${device.instance}, n=${device.name}"
                }
            },
            object : ISpaceListener<EddystoneNamespace> {
                override fun onEntered(space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onEntered: $space")
                }

                override fun onExited(space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onExited: $space")
                }
            }
        )
        // if the processor has eventually an empty collection, EddystoneNamespace.EVERYWHERE will be assumed
        val namespaces: MutableCollection<EddystoneNamespace> = HashSet()
        namespaces.add(EddystoneNamespace.Builder()
            .name("MyApp") // TODO set your own value
            .namespace("00112233445566778899") // TODO set your own value
            .build())
        eddystoneProcessor.setEddystoneNamespaces(namespaces)

        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceName("L ID 003D4F")) // Example of a 'BLUE LITE ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceName("S ID 002A3B")) // Example of a 'BLUE SLIM ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceAddress("AC:23:3F:A1:B2:C3")) // Example of a tag from Minew Technologies
        (eddystoneProcessor as IProcessor).setDeviceFilters(filters)

        cfg.addProcessor(eddystoneProcessor)

        // Plugins settings, if wanted ...

        // Processor Plugin
        val invirtusPlugin = InvirtusProcessorPlugin(
            object : IPluginDeviceListener<InvirtusProcessorPlugin.Device> {
                override fun onDiscovered(device: InvirtusProcessorPlugin.Device) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onDiscovered: $device")
                }

                override fun onUpdated(device: InvirtusProcessorPlugin.Device) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onUpdated: $device")
                }

                override fun onLost(device: InvirtusProcessorPlugin.Device) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onLost: $device")
                }
            }
        )
        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceAddress("F2:18:FF:A1:B2:C3"))
        (invirtusPlugin as IProcessorPlugin).setDeviceFilters(filters)
        cfg.addProcessorPlugin(invirtusPlugin)

        // Explorer Plugin
        val minewPlugin = MinewExplorerPlugin()
        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceAddress("AC:23:3F:A1:B2:C3"))
        (minewPlugin as IExplorerPlugin).setDeviceFilters(filters)
        cfg.addExplorerPlugin(minewPlugin)

        // Parser Plugins
        val elaPlugin = ElaParserPlugin()
        val accSysPlugin = AccSysParserPlugin()
        cfg.addParserPlugin(elaPlugin)
            .addParserPlugin(accSysPlugin)

        return cfg
    }
}