Custom algorithms
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 JavaScript.
Existing custom algorithms from older versions of the software may be written in C#, but this is no longer recommended. The documentation for these legacy algorithms is available here.
Defining a custom algorithm
Custom algorithms must implement the following function:
function run(sensorData, appState) {
// ...
}The function has two parameters:
sensorDataprovides access to the sensor data from the currently connected sensors (GPS / Accelerometer / Gyrometer / OBD-II / BL-205 / BL-360 / etc.)appStateprovides access to the current app state (sensor state, motion state, etc.). This state can be updated by the function in order to control how Blank-it should respond to the sensor data.
Here is a very simple example, that will set the motion state in response to the speed from GPS being above a certain threshold:
function run(sensorData, appState) {
appState.sensorType = "GPS";
appState.sensorDescription = `State: ${sensorData.gpsState} / Speed: ${Math.round(sensorData.gpsSpeed.milesPerHour)}mph`
appState.sensorState = sensorData.gpsState;
appState.motionState = (sensorData.gpsState == sensorState.Active && sensorData.gpsSpeed.milesPerHour > 5)
? motionState.moving
: motionState.stationary;
}Built-in objects and functions
Most of the standard Javascript built-in objects and functions are available to use when building your custom algorithm.
Here is a link to the standard built-in objects and functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects (opens in a new tab)
Custom algorithm classes and structures
The sensorData object is mapped to the following C# class in the Blank-it app:
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; }
}The appState structure is mapped to the following C# class in the Blank-it app:
class AppState
{
public MotionState MotionState { get; set; }
public SensorState SensorState { get; set; }
public string SensorType { get; set; }
public string SensorDescription { get; set; }
}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; }
}Custom algorithm additional functions
A number of other functions are made available:
log(message, arguments)writes a log message.error(message, arguments)writes an error message.formatSpeed(speed)formats the specifiedSpeedvalue as a localized string, using miles-per-hour in imperial locales and kilometres-per-hour in metric locales.algorithmName(name)sets the algorithm name for display in the Blank-it UI.requireVersion(version)ensures that the algorithm can only run in a specific version (or later) of Blank-it.
Examples
A basic algorithm that checks for GPS speed over a certain threshold:
function run(sensorData, appState) {
appState.sensorType = "GPS";
appState.sensorDescription = `State: ${sensorData.gpsState} / Speed: ${Math.round(sensorData.gpsSpeed.milesPerHour)}mph`
appState.sensorState = sensorData.gpsState;
appState.motionState = (sensorData.gpsState == sensorState.Active && sensorData.gpsSpeed.milesPerHour > 5)
? motionState.moving
: motionState.stationary;
}A more advanced algorithm that looks at GPS speed and also a sliding window of BL-360 speed:
// Set the algorithm name, and any Blank-it version restrictions:
algorithmName('QuickStart (BL-360 + GPS)');
requireVersion('25.5'); // v25.5.x or above.
// These constants can be edited to customize how your algorithm works:
const bl360WindowSize = 25; // Sliding window size for BL-360 values. We run 20 times per second, so this is one second worth of data.
const bl360WindowSuccessRequired = 13; // Number of successes required in the BL-360 sliding window before we blank the screen.
const bl360SpeedThreshold = 0.01; // BL-360 speed threshold in metres per second. Roughly equivalent to 0.02 mph or 0.03 km/h.
const gpsSpeedThreshold = 1.4; // GPS speed threshold in metres per second. Roughly equivalent to 3 mph or 5 km/h.
// Define a sliding window class to keep track of the BL-360 values:
class SlidingWindow {
constructor(size) {
this.size = size;
this.queue = [];
}
add(value) {
if (this.queue.length >= this.size) {
this.queue.shift();
}
this.queue.push(value);
}
clear() {
this.queue.length = 0;
}
movingCount() {
return this.queue.filter(x => x === MotionState.Moving).length;
}
stationaryCount() {
return this.queue.filter(x => x === MotionState.Stationary).length;
}
}
// Define some helper function to combine sensor states and motion states:
function combineSensorStates(states) {
if (states.some(x => x === SensorState.Active)) {
return SensorState.Active;
}
if (states.some(x => x === SensorState.WaitingForData)) {
return SensorState.WaitingForData;
}
if (states.some(x => x === SensorState.SearchingForSensor)) {
return SensorState.SearchingForSensor;
}
return SensorState.Unknown;
}
function combineMotionStates(states) {
if (states.some(x => x === MotionState.Moving)) {
return MotionState.Moving;
}
if (states.some(x => x === MotionState.Stationary)) {
return MotionState.Stationary;
}
return MotionState.Unknown;
}
// Set up any variables that need to persist between runs:
const bl360Window = new SlidingWindow(bl360WindowSize);
// The most important part of the code is the 'run' function.
// This function is called periodically by the Blank-it app to check the sensor data and update the app state.
function run(sensorData, appState) {
// Handle the BL-360 sensor data.
let bl360MotionState = MotionState.Unknown;
let bl360MovingCount = 0;
const hasBL360 = sensorData.BL360State == SensorState.Active && sensorData.BL360Speed != null;
if (hasBL360) {
const absoluteSpeed = Math.abs(sensorData.BL360Speed.MetresPerSecond);
const motionStateForThisReading = (absoluteSpeed > bl360SpeedThreshold)
? MotionState.Moving
: MotionState.Stationary;
bl360Window.add(motionStateForThisReading);
bl360MovingCount = bl360Window.movingCount();
bl360MotionState = bl360MovingCount >= bl360WindowSuccessRequired
? MotionState.Moving
: MotionState.Stationary;
}
// Handle the GPS sensor data.
let gpsMotionState = MotionState.Unknown;
const hasGps = sensorData.GpsState == SensorState.Active && sensorData.GpsSpeed != null;
if (hasGps) {
const absoluteSpeed = Math.abs(sensorData.GpsSpeed.MetresPerSecond);
gpsMotionState = (absoluteSpeed > gpsSpeedThreshold) ? MotionState.Moving : MotionState.Stationary;
}
// Update the app state based on the sensor data.
appState.SensorState = combineSensorStates([sensorData.BL360State, sensorData.GpsState]);
appState.MotionState = combineMotionStates([bl360MotionState, gpsMotionState]);
// Finally, we can set the sensor type and description.
// This isn't required for app functionality, but it can help the user understand what the app is doing.
appState.SensorType = (hasBL360 && hasGps)
? 'QuickStart (BL-360 + GPS)'
: (hasBL360) ? 'QuickStart (BL-360)'
: (hasGps) ? 'QuickStart (GPS)'
: 'QuickStart (no data)';
appState.SensorDescription = (hasBL360 && hasGps)
? `BL-360: ${Math.round((bl360MovingCount / bl360WindowSuccessRequired) * 100)}% | GPS: ${formatSpeed(sensorData.GpsSpeed)}`
: (hasBL360) ? `BL-360: ${Math.round((bl360MovingCount / bl360WindowSuccessRequired) * 100)}%`
: (hasGps) ? `GPS: ${formatSpeed(sensorData.GpsSpeed)}`
: 'No sensor data available';
}