-
Notifications
You must be signed in to change notification settings - Fork 0
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/
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.
s = Sensor(key);
s = Sensor(key, 'Name', 'Display Name');
s = Sensor(key, 'Name', 'Pressure', 'ID', 101, 'MatFile', 'data.mat', 'KeyName', 'p_chamber');| 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. |
'Units' |
char | '' |
Measurement unit string (e.g., 'degC', 'bar'). |
An error is thrown if an unrecognized option name is provided.
| 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. |
Units |
char | '' |
Measurement unit string (e.g., 'degC', 'bar', 'rpm'). Used by DashboardEngine widgets for auto-labeling. |
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(). |
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.'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. |
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., '--', ':', '-.'). |
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 X1xP double Step-function X coordinates (with NaN separators between non-contiguous regions). Y1xP double Step-function Y coordinates. Valuedouble The constant threshold value from the rule. Directionchar 'upper'or'lower'.Labelchar Display label. Color1x3 double or []RGB color (empty = use theme default). LineStylechar Line-style token (e.g., '--'). -
ResolvedViolations-- struct array where each element contains:Field Type Description X1xK double Time stamps of violating data points, sorted chronologically. Y1xK double Sensor values at the violating points. Directionchar 'upper'or'lower'.Labelchar Display label. -
ResolvedStateBands-- struct with state region information for background shading.
When no rules are defined, all three resolved properties are set to [].
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. |
Count the total number of violation points across all resolved threshold rules.
n = s.countViolations();Returns: scalar double, total count of violating data points.
The resolve() method uses an efficient segment-based approach instead of per-point evaluation:
-
Collect segment boundaries from all
StateChanneltransition timestamps. The union of all transitions defines the segment grid. Boundaries are extended to cover the full sensor data range (sensorX(1)tosensorX(end)). When no state channels are attached, the entire time span is treated as a single segment. -
Evaluate composite state at each boundary via zero-order hold (
StateChannel.valueAt()), producing a state struct per segment. -
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 thatmatchesState()is called once per unique condition per segment, not once per rule. -
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. -
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. -
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.
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. |
% 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();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();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();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();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.
sc = StateChannel(key);
sc = StateChannel(key, 'MatFile', 'states.mat');
sc = StateChannel(key, 'MatFile', 'states.mat', 'KeyName', 'machine_mode');| 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.
| 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(). |
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.'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
Yis numeric andtis scalar: a numeric scalar. - When
Yis numeric andtis a vector: a 1xN numeric array. - When
Yis a cell array of char andtis scalar: a char value. - When
Yis a cell array of char andtis 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.
% 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'};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).
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)| 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.
| 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. |
| Property | Type | Value | Description |
|---|---|---|---|
DIRECTIONS |
cell array of char | {'upper', 'lower'} |
Allowed values for the Direction property. Used for input validation in the constructor. |
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).
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.
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.'
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.
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 TemperaturePrint 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.
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)
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).
The default catalog ships with:
| Key | Name | ID |
|---|---|---|
'pressure' |
Chamber Pressure | 101 |
'temperature' |
Chamber Temperature | 102 |
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(orKeyifNameis empty) is used as theDisplayNamefor the legend. - The sensor's data is added as a line via
addLine(). - Each entry in
ResolvedThresholdsis added viaFastPlot.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.
% 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();-
API Reference: FastPlot --
addSensor()andaddThreshold()methods - API Reference: Event Detection -- Detecting events from sensor violations
-
MEX Acceleration -- MEX code paths for
compute_violations_batchandbinary_search - Examples -- Sensor examples
FastSense Wiki
API Reference
Guides
Use Cases
Internals
Resources