Skip to content

API Reference: Sensors

Hannes Suhr edited this page Mar 9, 2026 · 24 revisions

API Reference: Sensors

The SensorThreshold library provides data containers with state channels and condition-dependent threshold rules. Sensors encapsulate time series data, machine state, and threshold logic in a single object.

Source location: libs/SensorThreshold/


Sensor

Data container with state channels and condition-dependent thresholds. Call resolve() to precompute threshold lines and violation points, then pass to FastPlot.addSensor().

Sensor is a handle class.

Constructor

s = Sensor(key);
s = Sensor(key, 'Name', 'Display Name');
s = Sensor(key, 'Name', 'Pressure', 'ID', 101, 'MatFile', 'data.mat', 'KeyName', 'p_chamber');

Constructor Parameters

Parameter Type Default Description
key char (required) Unique string identifier. Also used as the default for KeyName.
'Name' char '' Human-readable display name shown in legends and tooltips.
'ID' double [] Numeric sensor ID (e.g., from a database).
'Source' char '' Path to the original raw data file.
'MatFile' char '' Path to .mat file containing transformed data.
'KeyName' char key Field name inside the .mat file. Defaults to the value of key.

An error is thrown if an unrecognized option name is provided.

Properties

Property Type Default Description
Key char (from constructor) Unique string identifier for this sensor.
Name char '' Human-readable display name. When empty, FastPlot.addSensor() falls back to Key for the legend entry.
ID double [] Numeric sensor ID.
Source char '' Path to the original raw data file.
MatFile char '' Path to .mat file with transformed data.
KeyName char key Field name inside the .mat file. Defaults to Key at construction.
X 1xN double [] Time stamps (datenum values, monotonically increasing).
Y 1xN double (or MxN) [] Sensor values corresponding to each time stamp.
StateChannels cell array {} Attached StateChannel objects. Each channel contributes one field to the composite state struct evaluated during resolve().
ThresholdRules cell array {} Attached ThresholdRule objects. Each rule defines a condition-value pair.
ResolvedThresholds struct array struct() Precomputed threshold step-function lines. Populated by resolve(). Each element has fields: X, Y, Direction, Label, Color, LineStyle, Value.
ResolvedViolations struct array struct() Precomputed violation points. Populated by resolve(). Each element has fields: X, Y, Direction, Label.
ResolvedStateBands struct struct() Precomputed state region bands for shading. Populated by resolve().

Methods


load()

Load sensor data from an external source. This is a placeholder that throws an error by default -- override it in a subclass or set X and Y directly.

s.load();
% ERROR: 'load() is a wrapper for an external loading library.
%         Set X and Y directly or implement your loader.'

addStateChannel(sc)

Attach a StateChannel to this sensor. Multiple state channels can be attached (e.g., machine mode + process phase). During resolve(), each channel's Key becomes a field in the composite state struct used to evaluate ThresholdRule conditions.

s.addStateChannel(sc);
Parameter Type Description
sc StateChannel A StateChannel object with populated X and Y.

addThresholdRule(condition, value, ...)

Create a new ThresholdRule and append it to the sensor's ThresholdRules list. All additional name-value arguments are forwarded to the ThresholdRule constructor.

s.addThresholdRule(struct('machine', 1), 70, 'Direction', 'upper', 'Label', 'Run HI');
s.addThresholdRule(struct('machine', 2, 'phase', 3), 55, 'Direction', 'upper', 'Label', 'Boost Phase3 HI');
s.addThresholdRule(struct(), 100, 'Direction', 'upper', 'Label', 'Absolute Max');  % Always active
Parameter Type Default Description
condition struct (required) State key-value pairs that activate this rule. Field names must match StateChannel keys. An empty struct() means the rule is unconditional (always active).
value double (required) Threshold value.
'Direction' char 'upper' 'upper' (violation when y > value) or 'lower' (violation when y < value).
'Label' char '' Display label for plots and legends. Rules sharing the same Label+Direction are merged into a single step-function line during resolve().
'Color' 1x3 double [] RGB color override. Empty means the plotting layer's theme default is used.
'LineStyle' char '--' MATLAB line-style specifier (e.g., '--', ':', '-.').

resolve()

Precompute threshold step-functions, violation points, and state bands using an efficient segment-based algorithm. This method is idempotent: calling it again overwrites the previous resolved results.

Must be called before passing the sensor to FastPlot.addSensor().

s.resolve();

After resolve(), these properties are populated:

  • ResolvedThresholds -- struct array where each element contains:

    Field Type Description
    X 1xP double Step-function X coordinates (with NaN separators between non-contiguous regions).
    Y 1xP double Step-function Y coordinates.
    Value double The constant threshold value from the rule.
    Direction char 'upper' or 'lower'.
    Label char Display label.
    Color 1x3 double or [] RGB color (empty = use theme default).
    LineStyle char Line-style token (e.g., '--').
  • ResolvedViolations -- struct array where each element contains:

    Field Type Description
    X 1xK double Time stamps of violating data points, sorted chronologically.
    Y 1xK double Sensor values at the violating points.
    Direction char 'upper' or 'lower'.
    Label char Display label.
  • ResolvedStateBands -- struct with state region information for background shading.

When no rules are defined, all three resolved properties are set to [].


getThresholdsAt(t)

Evaluate which threshold rules are active at a specific time point. Builds the composite state struct at time t by querying each StateChannel, then tests every ThresholdRule against that state. This is a lightweight single-point query useful for tooltips, crosshair readouts, and debugging. For bulk precomputation over the full time range, use resolve().

active = s.getThresholdsAt(50.0);
% Returns struct array with Value, Direction, Label for each active rule
Parameter Type Description
t scalar double Query time (datenum).

Returns: struct array (possibly empty) where each element has:

Field Type Description
Value double Threshold value.
Direction char 'upper' or 'lower'.
Label char Display label.

Resolution Algorithm

The resolve() method uses an efficient segment-based approach instead of per-point evaluation:

  1. Collect segment boundaries from all StateChannel transition timestamps. The union of all transitions defines the segment grid. Boundaries are extended to cover the full sensor data range (sensorX(1) to sensorX(end)). When no state channels are attached, the entire time span is treated as a single segment.

  2. Evaluate composite state at each boundary via zero-order hold (StateChannel.valueAt()), producing a state struct per segment.

  3. Group rules by condition using conditionKey(), which serializes condition structs into canonical string keys (fields sorted alphabetically, joined with &; empty struct becomes '__empty__'). Rules sharing the same condition are batched so that matchesState() is called once per unique condition per segment, not once per rule.

  4. For each condition group: identify active segments (where the condition is satisfied), map them to [lo, hi] index ranges in the sensor's X vector using binary search.

  5. Batch-detect violations via compute_violations_batch(), which applies vectorized comparison within each active segment chunk. A pre-allocated buffer sized to the total number of active-segment data points avoids dynamic array growth. A MEX code path is available for maximum throughput.

  6. Merge results by Label+Direction via mergeResolvedByLabel(). Rules covering different state combinations but sharing the same label are overlaid into a single composite Y array, then converted to step-function format with NaN separators between non-contiguous active regions. Violation arrays from sibling entries are concatenated and time-sorted. Unlabeled entries (empty Label) are never merged.

Complexity: O(S x R) where S = number of state segments and R = number of rules. For 10M data points with 20 segments and 5 rules, this evaluates 100 condition checks instead of 50,000,000 per-point evaluations.

Private Helper Functions

These functions live in libs/SensorThreshold/private/ and are called internally by resolve():

Function Description
conditionKey(condStruct) Serializes a condition struct into a canonical string key for grouping. Fields are sorted alphabetically and joined with &. Empty struct yields '__empty__'.
compute_violations_batch(sensorY, segLo, segHi, thresholdValues, directions) Vectorized batch violation detection across active segments. Returns a 1xT cell array where each cell contains a vector of 1-based indices into sensorY where the threshold is violated. Uses > for upper and < for lower direction.
buildThresholdEntry(segBounds, thY, rule) Constructs a scalar struct with fields X, Y, Direction, Label, Color, LineStyle, Value from segment boundary data and a ThresholdRule.
mergeResolvedByLabel(resolvedTh, resolvedViol, segBounds, dataEnd) Merges threshold entries sharing the same Label+Direction into single step-function lines. Overlays Y values (fills NaN gaps), converts to step-function format via toStepFunction(), and concatenates/sorts violation arrays.
appendResults(resolvedTh, resolvedViol, th, viol) Appends a threshold entry and its companion violation entry to growing struct arrays. Seeds the arrays on first call.

Complete Example -- Single State Channel

% Create sensor with data
s = Sensor('pressure', 'Name', 'Chamber Pressure');
s.X = linspace(0, 100, 1e6);
s.Y = randn(1, 1e6) * 10 + 50;

% Machine mode: idle=0, run=1, boost=2
sc = StateChannel('machine');
sc.X = [0 30 60 80];
sc.Y = [0 1 2 1];
s.addStateChannel(sc);

% Thresholds depend on machine state
s.addThresholdRule(struct('machine', 0), 80, 'Direction', 'upper', 'Label', 'Idle HI');
s.addThresholdRule(struct('machine', 1), 70, 'Direction', 'upper', 'Label', 'Run HI');
s.addThresholdRule(struct('machine', 2), 55, 'Direction', 'upper', 'Label', 'Boost HI');
s.addThresholdRule(struct(), 100, 'Direction', 'upper', 'Label', 'Absolute Max');

% Resolve and plot
s.resolve();
fp = FastPlot('Theme', 'dark');
fp.addSensor(s, 'ShowThresholds', true);
fp.render();

Complete Example -- Multiple State Channels

s = Sensor('temperature', 'Name', 'Reactor Temp');
s.X = linspace(0, 200, 2e6);
s.Y = randn(1, 2e6) * 5 + 70;

% State channel 1: machine mode
sc1 = StateChannel('machine');
sc1.X = [0 50 100 150]; sc1.Y = [0 1 2 1];
s.addStateChannel(sc1);

% State channel 2: process phase
sc2 = StateChannel('phase');
sc2.X = [0 30 60 90 120 160]; sc2.Y = [1 2 3 1 2 3];
s.addStateChannel(sc2);

% Compound condition: machine=1 AND phase=2
s.addThresholdRule(struct('machine', 1, 'phase', 2), 75, ...
    'Direction', 'upper', 'Label', 'Run/Phase2 HI');
% Simple condition: any phase when machine=2
s.addThresholdRule(struct('machine', 2), 65, ...
    'Direction', 'upper', 'Label', 'Boost HI');

s.resolve();

Complete Example -- Lower Thresholds

s = Sensor('flow', 'Name', 'Coolant Flow');
s.X = linspace(0, 60, 5e5);
s.Y = randn(1, 5e5) * 3 + 20;

sc = StateChannel('machine');
sc.X = [0 20 40]; sc.Y = [0 1 2];
s.addStateChannel(sc);

% Lower thresholds: violation when value drops below limit
s.addThresholdRule(struct('machine', 1), 15, 'Direction', 'lower', 'Label', 'Run LO');
s.addThresholdRule(struct('machine', 2), 18, 'Direction', 'lower', 'Label', 'Boost LO');
% Upper threshold
s.addThresholdRule(struct(), 30, 'Direction', 'upper', 'Label', 'Absolute Max');

s.resolve();

Complete Example -- No State Channels (Unconditional Only)

s = Sensor('vibration', 'Name', 'Bearing Vibration');
s.X = linspace(0, 100, 1e6);
s.Y = randn(1, 1e6) * 2 + 5;

% No state channels attached -- only unconditional rules apply.
% The entire time span is treated as a single segment.
s.addThresholdRule(struct(), 10, 'Direction', 'upper', 'Label', 'HI');
s.addThresholdRule(struct(), 2, 'Direction', 'lower', 'Label', 'LO');

s.resolve();

StateChannel

Time-varying discrete state signal. Models machine modes, process phases, operating conditions, etc. Uses zero-order hold interpolation: the value stays constant until the next transition.

StateChannel is a handle class.

Constructor

sc = StateChannel(key);
sc = StateChannel(key, 'MatFile', 'states.mat');
sc = StateChannel(key, 'MatFile', 'states.mat', 'KeyName', 'machine_mode');

Constructor Parameters

Parameter Type Default Description
key char (required) Unique string identifier for this channel (e.g., 'machine', 'phase', 'mode'). Also used as the default for KeyName.
'MatFile' char '' Path to .mat file containing the state data.
'KeyName' char key Field name inside the .mat file. Defaults to key.

An error is thrown if an unrecognized option name is provided.

Properties

Property Type Default Description
Key char (from constructor) Unique string identifier. This key becomes the field name in the composite state struct built during Sensor.resolve().
MatFile char '' Path to .mat file containing the state data.
KeyName char key Field name inside the .mat file. Defaults to Key at construction.
X 1xN double [] Sorted datenum timestamps of state transitions (monotonically increasing).
Y 1xN double or 1xN cell array of char [] State values at each transition. Numeric values are compared with ==; char/string values are compared with strcmp() during ThresholdRule.matchesState().

Methods


load()

Load state data from an external source. This is a placeholder that throws an error by default -- override it in a subclass or set X and Y directly.

sc.load();
% ERROR: 'load() is a wrapper for an external loading library.
%         Set X and Y directly or implement your loader.'

valueAt(t)

Zero-order-hold lookup: returns the state value at time t. For a query before the first transition timestamp, the first state value is returned (clamp behavior). Supports both scalar and vectorized queries.

sc.X = [0 20 40 60];
sc.Y = [0 1 2 1];

sc.valueAt(10)          % Returns 0 (before second transition)
sc.valueAt(20)          % Returns 1 (at transition boundary, inclusive)
sc.valueAt(25)          % Returns 1
sc.valueAt(50)          % Returns 2
sc.valueAt([10 25 50])  % Returns [0 1 2]
Parameter Type Description
t scalar or 1xN double Query time(s) in datenum.

Returns:

  • When Y is numeric and t is scalar: a numeric scalar.
  • When Y is numeric and t is a vector: a 1xN numeric array.
  • When Y is a cell array of char and t is scalar: a char value.
  • When Y is a cell array of char and t is a vector: a 1xN cell array of char.

Implementation detail: Uses a right-biased binary search (bsearchRight, private method) that returns the largest index i such that X(i) <= t, clamped to [1, numel(X)]. Delegates to binary_search() with 'right' mode.

Example

% Machine operating mode (numeric)
sc = StateChannel('machine');
sc.X = [0 30 60 80 120];
sc.Y = [0 1 2 1 0];  % idle -> run -> boost -> run -> idle

% String-valued state channel
sc2 = StateChannel('recipe');
sc2.X = [0 50 100];
sc2.Y = {'setup', 'process', 'cooldown'};

ThresholdRule

Condition-value pair defining when a threshold is active and what value it takes. A rule is "active" when every field in its Condition struct matches the current system state (implicit AND). An empty condition means the rule is always active (unconditional).

ThresholdRule is a value class (not a handle class).

Constructor

rule = ThresholdRule(condition, value);
rule = ThresholdRule(struct('machine', 1), 70, 'Direction', 'upper', 'Label', 'Run HI');
rule = ThresholdRule(struct('machine', 1, 'phase', 3), 55, 'Direction', 'upper');
rule = ThresholdRule(struct(), 50);  % Empty condition = always active (unconditional)

Constructor Parameters

Parameter Type Default Description
condition struct (required) State key-value pairs. Field names must correspond to StateChannel keys. An empty struct() means unconditional (always active). Must be a struct or an error is thrown.
value double (required) Threshold value.
'Direction' char 'upper' 'upper' (violation when y > value) or 'lower' (violation when y < value). Must be one of these two values or an error is thrown.
'Label' char '' Display label for plots and legends.
'Color' 1x3 double [] RGB color triplet (e.g., [1 0 0] for red). Empty means the plotting layer's theme default is used.
'LineStyle' char '--' MATLAB line-style specifier (e.g., '--', ':', '-.', '-').

An error is thrown if an unrecognized option name is provided.

Properties

Property Type Default Description
Condition struct (from constructor) State key-value pairs. Field names are state channel keys; values are the required state values for activation. Empty struct = unconditional.
Value double (from constructor) Threshold value when the condition is satisfied.
Direction char 'upper' 'upper' (violation when sensor value exceeds threshold) or 'lower' (violation when sensor value falls below threshold).
Label char '' Human-readable display label for plots and legends.
Color 1x3 double [] RGB color triplet. Empty means defer to theme default.
LineStyle char '--' MATLAB line-style specifier for rendering.

Constant Properties

Property Type Value Description
DIRECTIONS cell array of char {'upper', 'lower'} Allowed values for the Direction property. Used for input validation in the constructor.

Methods


matchesState(st)

Check whether a state struct satisfies this rule's condition. Returns true if every field in the Condition struct exists in st and has a matching value (implicit AND logic). An empty Condition always returns true.

Comparison uses strcmp() for char/string values and == for numeric values. If a required field is missing from st, the result is false (fail-closed).

rule = ThresholdRule(struct('machine', 1, 'phase', 2), 70);

rule.matchesState(struct('machine', 1, 'phase', 2))              % true
rule.matchesState(struct('machine', 1, 'phase', 3))              % false (phase mismatch)
rule.matchesState(struct('machine', 2, 'phase', 2))              % false (machine mismatch)
rule.matchesState(struct('machine', 1))                           % false (phase field missing)
rule.matchesState(struct('machine', 1, 'phase', 2, 'extra', 5))  % true  (extra fields ignored)

Empty condition matches any state:

rule = ThresholdRule(struct(), 100);
rule.matchesState(struct('machine', 1))  % true (always)
rule.matchesState(struct())              % true (always)
Parameter Type Description
st struct Current system state with field names corresponding to StateChannel keys.

Returns: logical scalar (true if the condition is satisfied).


SensorRegistry

Singleton catalog of predefined sensor configurations for quick access. Sensor definitions are specified in the private catalog() method and cached in a persistent variable so that repeated lookups incur no construction overhead.

Static Methods


SensorRegistry.get(key)

Retrieve a fully configured Sensor object by its string key. Throws an error if the key is not found in the catalog.

s = SensorRegistry.get('pressure');
s.X = myTimeData;
s.Y = myPressureData;
s.resolve();
Parameter Type Description
key char Unique identifier for the desired sensor.

Returns: Sensor object corresponding to the key.

Error: 'No sensor defined with key ''<key>''. Use SensorRegistry.list() to see available sensors.'


SensorRegistry.getMultiple(keys)

Retrieve multiple sensors at once by providing a cell array of keys.

sensors = SensorRegistry.getMultiple({'pressure', 'temperature'});
% sensors{1} is the 'pressure' Sensor
% sensors{2} is the 'temperature' Sensor
Parameter Type Description
keys cell array of char Sensor identifier strings.

Returns: 1xN cell array of Sensor objects.


SensorRegistry.list()

Print a formatted table of all available sensor keys and their display names to the command window. Keys are sorted alphabetically. When a sensor has no Name set, '(no name)' is displayed as the fallback.

SensorRegistry.list();
% Output:
%   Available sensors:
%     pressure                   Chamber Pressure
%     temperature                Chamber Temperature

SensorRegistry.printTable()

Print a detailed table of all registered sensors to the command window. Includes columns for Key, Name, ID, Source, MatFile, number of state channels, number of threshold rules, and number of data points. Long strings are truncated to fit column widths.

SensorRegistry.printTable();
% Output:
%   Key                  Name                      ID  Source               MatFile              #States #Rules  #Points
%   ----------------------------------------------------------------------------------------------------------------------
%   pressure             Chamber Pressure          101                                                 0      0        0
%   temperature          Chamber Temperature       102                                                 0      0        0
%
%   2 sensor(s) total.

If no sensors are registered, prints No sensors registered.


SensorRegistry.viewer()

Open a GUI figure window displaying all registered sensors in a uitable. The figure uses a dark theme (matching EventViewer's style) and shows the same columns as printTable(): Key, Name, ID, Source, MatFile, #States, #Rules, and #Points.

hFig = SensorRegistry.viewer();

Returns: Figure handle to the viewer window.

UI Details:

  • Window size: 900x400 pixels
  • Dark background ([0.15 0.15 0.18])
  • Title bar shows sensor count
  • Alternating row colors for readability
  • No menu bar or toolbar (clean display)

Adding Sensors to the Registry

Edit the private catalog() method in SensorRegistry.m to add predefined sensor configurations. The catalog uses a containers.Map keyed by string identifier:

% Inside the catalog() method, after 'cache = containers.Map();':

s = Sensor('flow', 'Name', 'Gas Flow Rate', 'ID', 103, 'MatFile', 'data/flow.mat');
sc = StateChannel('machine');
s.addStateChannel(sc);
s.addThresholdRule(struct('machine', 1), 100, 'Direction', 'upper', 'Label', 'Flow HH');
s.addThresholdRule(struct('machine', 1), 20, 'Direction', 'lower', 'Label', 'Flow LL');
cache('flow') = s;

Note: The catalog is cached in a persistent variable. Changes take effect after clearing the persistent variable (e.g., clear SensorRegistry or restarting MATLAB).

Built-in Sensor Definitions

The default catalog ships with:

Key Name ID
'pressure' Chamber Pressure 101
'temperature' Chamber Temperature 102

Integration with FastPlot

The FastPlot.addSensor() method accepts a resolved Sensor and renders its data line and threshold overlays:

fp.addSensor(sensor);                            % Data + thresholds (default)
fp.addSensor(sensor, 'ShowThresholds', true);     % Explicit: show thresholds
fp.addSensor(sensor, 'ShowThresholds', false);    % Data line only, no thresholds
Parameter Type Default Description
sensor Sensor (required) A Sensor object with X, Y, and populated ResolvedThresholds (via resolve()).
'ShowThresholds' logical true Whether to render threshold lines and violation markers.

Behavior:

  • The sensor's Name (or Key if Name is empty) is used as the DisplayName for the legend.
  • The sensor's data is added as a line via addLine().
  • Each entry in ResolvedThresholds is added via FastPlot.addThreshold() with its resolved color, line style, direction, label, and 'ShowViolations', true.
  • Unlabeled thresholds receive an auto-generated label ('Threshold 1', 'Threshold 2', etc.).
  • Must be called before render(). An error is thrown if called after rendering.

Typical Workflow

% 1. Create sensor
s = Sensor('pressure', 'Name', 'Chamber Pressure', 'ID', 101);

% 2. Set data
s.X = linspace(0, 100, 1e6);
s.Y = randn(1, 1e6) * 10 + 50;

% 3. Attach state channels
sc = StateChannel('machine');
sc.X = [0 30 60 80];
sc.Y = [0 1 2 1];
s.addStateChannel(sc);

% 4. Define threshold rules
s.addThresholdRule(struct('machine', 0), 80, 'Direction', 'upper', 'Label', 'Idle HI');
s.addThresholdRule(struct('machine', 1), 70, 'Direction', 'upper', 'Label', 'Run HI');
s.addThresholdRule(struct('machine', 2), 55, 'Direction', 'upper', 'Label', 'Boost HI');
s.addThresholdRule(struct(), 100, 'Direction', 'upper', 'Label', 'Absolute Max');

% 5. Resolve
s.resolve();

% 6. Render
fp = FastPlot('Theme', 'dark');
fp.addSensor(s, 'ShowThresholds', true);
fp.render();

See Also

Clone this wiki locally