Custom algorithms (legacy)
NOTE: This document describes the older (outdated) version of custom algorithms. Click here for the currently recommended documentation.
While Blank-it's out-of-the-box motion detection algorithms are sufficient for most environments, we also allow IT administrators to design their own algorithms in order to customize how Blank-it will blank the screen in response to input from the various sensors.
These algorithms are built in to the configuration file, and executed by the Blank-it software. Custom algorithms can contain arbitrary code.
Programming language
Custom algorithms are written in the C# programming language (opens in a new tab)
Algorithm structure
All algorithms must implement the following interface:
interface ISensorAlgorithm
{
public SensorAlgorithmOutput Apply(SensorData sensorData);
}They take the following structure as an input:
class SensorData
{
public string GpsSource { get; set; }
public SensorState GpsState { get; set; }
public SensorDataQuality GpsQuality { get; set; }
public Speed GpsSpeed { get; set; }
public GeoCoordinate GpsLocation { get; set; }
public string AccelerometerSource { get; set; }
public SensorState AccelerometerState { get; set; }
public double AccelerometerX { get; set; }
public double AccelerometerY { get; set; }
public double AccelerometerZ { get; set; }
public string GyrometerSource { get; set; }
public SensorState GyrometerState { get; set; }
public double GyrometerX { get; set; }
public double GyrometerY { get; set; }
public double GyrometerZ { get; set; }
public string Obd2Source { get; set; }
public SensorState Obd2State { get; set; }
public Speed Obd2Speed { get; set; }
public string BL205Source { get; set; }
public SensorState BL205State { get; set; }
public double BL205Vibration { get; set; }
public double BL205Motion { get; set; }
public string BL360Source { get; set; }
public SensorState BL360State { get; set; }
public SensorDataQuality BL360Quality { get; set; }
public string BL360Version { get; set; }
public bool? BL360IsMoving { get; set; }
public Speed? BL360Speed { get; set; }
public int? BL360PointCount { get; set; }
}They return the following structure as an output:
class SensorAlgorithmOutput
{
public MotionState MotionState { get; }
public SensorState SensorState { get; }
public string SensorType { get; }
public string SensorDescription { get; }
public SensorAlgorithmOutput(MotionState motionState, SensorState sensorState, string sensorType, string sensorDescription)
{
MotionState = motionState;
SensorState = sensorState;
SensorType = sensorType;
SensorDescription = sensorDescription;
}
}The follow structures are also useful to be aware of:
enum MotionState
{
Unknown,
Moving,
Stationary,
}
enum SensorState
{
Unknown,
SearchingForSensor,
WaitingForData,
Active,
Disabled,
}
public enum SensorDataQuality
{
None,
Low,
Medium,
High,
}
struct Speed
{
public double MetresPerSecond { get; }
public double KilometresPerHour { get; }
public double MilesPerHour { get; }
public double Knots { get; }
}
struct GeoCoordinate
{
public double Latitude { get; }
public double Longitude { get; }
}Thus, the basic structure of a custom algorithm is as per the following (very simple) example:
class CustomAlgorithm : ISensorAlgorithm
{
public SensorAlgorithmOutput Apply(SensorData sensorData)
{
var sensorState = sensorData.GpsState;
var motionState = MotionState.Stationary;
if (sensorData.GpsState is SensorState.Active && sensorData.GpsSpeed.KilometresPerHour > 5)
{
motionState = MotionState.Moving;
}
return new SensorAlgorithmOutput(motionState, sensorState, "GPS", description);
}
}Here is a more complex example:
class CustomAlgorithm : ISensorAlgorithm
{
private static bool IsMetric { get; } = RegionInfo.CurrentRegion.IsMetric;
private readonly SlidingWindow _accelerometerDeltaSlidingWindow = new SlidingWindow(10);
public SensorAlgorithmOutput Apply(SensorData sensorData)
{
var sources = new List<string>();
var descriptions = new List<string>();
var gpsMotionState = MotionState.Unknown;
var obdMotionState = MotionState.Unknown;
var bld205MotionState = MotionState.Unknown;
var accelerometerMotionState = MotionState.Unknown;
if (sensorData.GpsState is SensorState.Active)
{
gpsMotionState = sensorData.GpsSpeed.KilometresPerHour > 5
? MotionState.Moving
: MotionState.Stationary;
sources.Add("GPS");
descriptions.Add($"{sensorData.GpsSource} ({GetSpeedDescription(sensorData.GpsSpeed)})");
}
if (sensorData.Obd2State is SensorState.Active)
{
obdMotionState = sensorData.Obd2Speed.KilometresPerHour > 5
? MotionState.Moving
: MotionState.Stationary;
sources.Add("OBD-II");
descriptions.Add($"{sensorData.Obd2Source} ({GetSpeedDescription(sensorData.Obd2Speed)})");
}
if (sensorData.BL205State is SensorState.Active)
{
bld205MotionState = sensorData.BL205Vibration > 50
? MotionState.Moving
: MotionState.Stationary;
sources.Add("BL205");
descriptions.Add($"{sensorData.BL205Source} ({sensorData.BL205Vibration})");
}
if (sensorData.AccelerometerState is SensorState.Active)
{
var rms = MathHelper.RootMeanSquare(sensorData.AccelerometerX, sensorData.AccelerometerY, sensorData.AccelerometerZ);
var rmsMg = rms * 1000;
_accelerometerDeltaSlidingWindow.Add(rmsMg);
var rmsDelta = _accelerometerDeltaSlidingWindow.Delta();
accelerometerMotionState = rmsDelta > 80
? MotionState.Moving
: MotionState.Stationary;
sources.Add("Accelerometer");
descriptions.Add($"{sensorData.AccelerometerSource} ({rmsDelta:N0}mg)");
}
var type = sources.Any()
? $"Custom sensor (sources: {string.Join(" | ", sources)})"
: "Custom sensor (no sources present)";
var description = descriptions.Any()
? string.Join(" | ", descriptions)
: "No data available";
var combinedSensorState = SensorStateHelper.CombineSensorStates(
sensorData.BL205State,
sensorData.GpsState,
sensorData.Obd2State,
sensorData.AccelerometerState);
var combinedMotionState = MotionStateHelper.CombineMotionStatesByPriority(
gpsMotionState,
obdMotionState,
bld205MotionState,
accelerometerMotionState);
return new SensorAlgorithmOutput(combinedMotionState, combinedSensorState, type, description);
}
private static string GetSpeedDescription(Speed speed)
{
return IsMetric
? $"{speed.KilometresPerHour:N2} km/h"
: $"{speed.MilesPerHour:N2} mph";
}
private sealed class SlidingWindow
{
private readonly int _size;
private readonly Queue<double> _queue;
public SlidingWindow(int size)
{
_size = size;
_queue = new Queue<double>(_size);
}
public void Add(double value)
{
if (_queue.Count >= _size)
{
_queue.Dequeue();
}
_queue.Enqueue(value);
}
public double Delta()
{
return _queue.Max() - _queue.Min();
}
}
}