Advanced IT information
This section provides information to IT teams who are supporting Blank-it within their organization.
File system
Blank-it stores files in three main directories.
- The baseline configuration directory. This directory should be read-only. It stores some baseline configuration file that should never be able to be edited by unprivileged users.
- The shared data directory. Users should have read and write access. It stores several transient data files and configuration files.
- The logs directory. Users should have read and write access. It stores the application log files.
By default, these directories are as follows:
| Directory | Directory path | Permissions |
|---|---|---|
| Baseline configuration | C:\ProgramData\Blank-it | Read-only |
| Shared data | C:\Users\Public\Documents\Blank-it\Data | Read + Write |
| Logs | C:\Users\Public\Documents\Blank-it\Logs | Read + Write |
Firewall configuration
The Blank-it software will occasionally make web requests to the Blank-it servers. It is recommended that an organization allows the following traffic through its firewall:
| Hostname | Port | Comments |
|---|---|---|
gateway.blank-it.com | 443 | Required for general operation of the Blank-it software. |
portal.blank-it.com | 443 | Required to allow administrators to use the built-in configuration manager. |
Distributing a configuration to a fleet of PCs
In order to accommodate the widest variety of organizational software deployment tools, Blank-it uses the simplest possible configuration deployment strategy. To roll out a new Blank-it configuration, all an organization has to do is overwrite the existing Blank-it configuration file with a new Blank-it configuration file. This can be done while the Blank-it software is running; Blank-it will continue to run as it was, the new configuration will be applied next time the Blank-it software starts.
Advanced installation options
Blank-it has a number of advanced installation options that can be used by passing custom arguments to msiexec during the installation process.
| Argument | Description |
|---|---|
| BASE_DIR | The path to use as the baseline configuration directory (described above). |
| SHARED_DIR | The path to use as the shared configuration directory (described above). |
| LOGS_DIR | The path to use as the logs directory (described above). |
| CONFIG_PATH | The path of a config file to be automatically deployed to the relevant locations. |
| KEEP_LOGS | (Uninstall only) If set to true, retain the log files when uninstalling. |
In addition, Blank-it can be installed in 'quiet' mode by passing the /qn flag to msiexec.
Here is an example installation command that will install Blank-it in quiet mode, with a custom directory structure, and automatic deployment of a configuration file:
msiexec.exe /i blankit.msi /qn BASE_DIR="C:\BL_BASE" SHARED_DIR="C:\BL_SHARED" LOGS_DIR="C:\BL_LOGS" CONFIG_PATH="config.bl6"Here is an example uninstallation command that will uninstall Blank-it in quiet mode, but retain the log files.
msiexec.exe /x blankit.msi /qn KEEP_LOGS=TRUECustom algorithms
Blank-it supports custom algorithms, allowing IT administrators 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 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; }
}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,
}
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();
}
}
}Algorithm encoding
Once an algorithm has been designed, it must be encoded as UTF-8 Base64 or UTF-8 Base32 (Crockman) before being saved to the Blank-it Portal.
The custom algorithms generated by the Blank-it Assistant tool will automatically be encoded in this manner. Likewise, algorithms created or saved via the Blank-it Assistant tool's Algorithm Editor will also automatically be encoded in this manner.