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
}
}
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
}
}