diff --git a/wiki/Architecture.md b/wiki/Architecture.md index 3f90ff9b..699951de 100644 --- a/wiki/Architecture.md +++ b/wiki/Architecture.md @@ -4,162 +4,115 @@ ## Overview -FastPlot uses a render-once, re-downsample-on-zoom architecture. Instead of pushing millions of points to the GPU, it maintains a lightweight cache and re-downsamples only the visible range on every interaction. +FastPlot uses a **render‑once, re‑downsample‑on‑zoom** architecture. Instead of pushing millions of points to the GPU, it maintains lightweight caches and re‑downsamples only the visible range on every interaction. This provides fluid pan/zoom performance for datasets from 1K to 100M+ points. -## Project Structure +The library is organised into several interconnected subsystems: + +- **Core plotting engine** (`FastSense`, `FastSenseGrid`, `FastSenseDock`) – time‑series rendering with dynamic downsampling, linked zoom, theme support, and live polling. +- **Dashboard framework** (`DashboardEngine`, `DashboardWidget` hierarchy) – full‑widget dashboards with gauges, numbers, status indicators, tables, timelines, and an edit mode. +- **Tag‑based data domain** (`SensorTag`, `StateTag`, `MonitorTag`, `CompositeTag`, `TagRegistry`) – a unified data model for raw sensors, derived signals, and event generation. +- **Event detection** (`LiveEventPipeline`, `EventDetector`, `EventStore`, `EventViewer`) – threshold‑based real‑time violation monitoring with persistence and notifications. +- **MEX acceleration** – optional C compilations with SIMD intrinsics for the hottest loops; pure‑MATLAB fallbacks are always available. + +## Class Hierarchy (Core Plotting) ``` -FastPlot/ -├── install.m # Path install + MEX compilation -├── libs/ -│ ├── FastSense/ # Core plotting engine -│ │ ├── FastSense.m # Main class -│ │ ├── FastSenseGrid.m # Dashboard layout -│ │ ├── FastSenseDock.m # Tabbed container -│ │ ├── FastSenseToolbar.m # Interactive toolbar -│ │ ├── FastSenseTheme.m # Theme system -│ │ ├── FastSenseDataStore.m # SQLite-backed chunked storage -│ │ ├── SensorDetailPlot.m # Sensor detail view with state bands -│ │ ├── NavigatorOverlay.m # Minimap zoom navigator -│ │ ├── ConsoleProgressBar.m # Progress indication -│ │ ├── binary_search.m # Binary search utility -│ │ ├── build_mex.m # MEX compilation script -│ │ └── private/ # Internal algorithms + MEX sources -│ ├── SensorThreshold/ # Sensor and threshold system -│ │ ├── Sensor.m -│ │ ├── StateChannel.m -│ │ ├── ThresholdRule.m -│ │ ├── SensorRegistry.m -│ │ ├── ExternalSensorRegistry.m -│ │ └── private/ # Resolution algorithms -│ ├── EventDetection/ # Event detection and viewer -│ │ ├── Event.m -│ │ ├── EventDetector.m -│ │ ├── EventViewer.m -│ │ ├── LiveEventPipeline.m -│ │ ├── NotificationService.m -│ │ ├── EventStore.m -│ │ ├── EventConfig.m -│ │ ├── IncrementalEventDetector.m -│ │ ├── DataSource.m # Abstract data source -│ │ ├── MatFileDataSource.m # File-based data source -│ │ ├── MockDataSource.m # Test data generation -│ │ ├── NotificationRule.m # Email notification rules -│ │ └── private/ # Event grouping algorithms -│ ├── Dashboard/ # Dashboard engine (serializable) -│ │ ├── DashboardEngine.m -│ │ ├── DashboardBuilder.m -│ │ ├── DashboardLayout.m -│ │ ├── DashboardSerializer.m -│ │ ├── DashboardTheme.m -│ │ ├── DashboardToolbar.m -│ │ ├── DashboardWidget.m # Abstract widget base -│ │ ├── FastSenseWidget.m -│ │ ├── GaugeWidget.m -│ │ ├── NumberWidget.m -│ │ ├── StatusWidget.m -│ │ ├── TextWidget.m -│ │ ├── TableWidget.m -│ │ ├── RawAxesWidget.m -│ │ ├── EventTimelineWidget.m -│ │ ├── GroupWidget.m # Collapsible/tabbed widget groups -│ │ ├── MultiStatusWidget.m # Grid of status indicators -│ │ ├── BarChartWidget.m -│ │ ├── ScatterWidget.m -│ │ ├── HeatmapWidget.m -│ │ ├── HistogramWidget.m -│ │ ├── ImageWidget.m -│ │ └── MarkdownRenderer.m # HTML conversion for info panels -│ └── WebBridge/ # TCP server for web visualization -│ ├── WebBridge.m -│ └── WebBridgeProtocol.m -├── examples/ # 40+ runnable examples -└── tests/ # 30+ test suites +FastSense – single time‑series plot +FastSenseGrid – tiled grid of FastSense instances (or raw axes) +FastSenseDock – tabbed container for multiple FastSenseGrid dashboards +FastSenseToolbar – interactive toolbar (cursors, crosshair, export, live) +FastSenseTheme – theme struct builder (light/dark presets) +FastSenseDataStore – SQLite‑backed chunked storage for large datasets ``` +`FastSense` is the primary unit of rendering. `FastSenseGrid` manages a grid of such plots; `FastSenseDock` wraps multiple grids as tabs. Each can be themed independently and supports linked zoom/pan via `LinkGroup` strings. + ## Render Pipeline -1. User calls `render()` -2. Create figure/axes if not parented -3. Validate all data (X monotonic, dimensions match) -4. Switch to disk storage mode if data exceeds `MemoryLimit` -5. Allocate downsampling buffers based on axes pixel width -6. For each line: initial downsample of full range, create graphics object -7. Create threshold, band, shading, marker objects -8. Install XLim PostSet listener for zoom/pan events -9. Set axis limits, disable auto-limits -10. `drawnow` to display +When `fp.render()` is called (source: `FastSense.render` docstring): + +1. Create a figure and axes (or use `ParentAxes`). +2. Apply the current `Theme` (background, grid, fonts, colours). +3. Render bands, shaded regions, and area fills (back layer). +4. Render area‑fills from lines to a baseline. +5. For each line: downsample the full data range to screen pixel resolution using the configured algorithm (MinMax or LTTB) and create the graphics object. +6. Draw threshold lines, violation markers, and threshold labels. +7. Draw custom event markers (front layer). +8. Set axis limits with 5% padding, disable auto‑limits. +9. Install XLim and resize listeners for dynamic re‑downsample on zoom/pan. +10. Schedule async refinement for very large datasets (optional background pyramid build). +11. Register in any active `LinkGroup` for synchronised zoom. +12. Call `drawnow` to display. + +All calls to `addLine`, `addThreshold`, `addBand`, `addShaded`, etc. must be made **before** `render()`. After rendering, only `updateData()` can replace an existing line’s data without destroying graphics objects. ## Zoom/Pan Callback -When the user zooms or pans: +FastSense relies on an XLim listener (not continuous redraw). When the user zooms or pans: + +- A PostSet listener on the axes’ `XLim` property fires. +- For each line, the visible X range is located with a binary search (**O(log n)**) via `binary_search`. +- The coarsest pre‑built pyramid level that provides sufficient resolution is selected. +- Visible data is downsampled to roughly `DownsampleFactor × pixel width` points (typically ~4,000). +- Graphics object properties (`XData`, `YData`) are updated directly using MATLAB dot notation for speed. +- Violation markers are recomputed (optionally fused with pixel culling). +- If a `LinkGroup` is active, the new XLim is propagated to all linked plots. +- `drawnow limitrate` caps the display rate at ~20 FPS. -1. XLim listener fires -2. Compare new XLim to cached value (skip if unchanged) -3. For each line: - - Binary search visible X range — O(log N) - - Select pyramid level with sufficient resolution - - Build pyramid level lazily if needed - - Downsample visible range to ~4,000 points - - Update hLine.XData/YData (dot notation for speed) -4. Recompute violation markers (fused SIMD with pixel culling) -5. If LinkGroup active: propagate XLim to linked plots -6. `drawnow limitrate` (caps display at 20 FPS) +This approach keeps the UI responsive, even with hundreds of millions of points. ## Downsampling Algorithms -### MinMax (default) -For each pixel bucket, keep the minimum and maximum Y values. Preserves signal envelope and extreme values. Fast O(N/bucket) per bucket. +FastSense supports two algorithms, selectable per‑line or globally via `DefaultDownsampleMethod`. -### LTTB (Largest Triangle Three Buckets) -Visually optimal downsampling that preserves signal shape by maximizing triangle area between consecutive buckets. Better visual fidelity but slightly slower. +### MinMax (default) +For each pixel bucket, the minimum and maximum Y values are retained. This preserves the signal envelope and extreme values. Complexity is **O(N / bucket)**. -Both algorithms handle NaN gaps by segmenting contiguous non-NaN regions independently. +### LTTB (Largest‑Triangle‑Three‑Buckets) +Visually optimal downsampling that preserves signal shape by maximising triangle area between consecutive buckets. Better visual fidelity at a slightly higher computational cost. -## Lazy Multi-Resolution Pyramid +Both algorithms handle NaN gaps by segmenting contiguous non‑NaN regions independently. -Problem: At full zoom-out with 50M+ points, scanning all data is O(N). +## Lazy Multi‑Resolution Pyramid -Solution: Pre-computed MinMax pyramid with configurable reduction factor (default 100x per level): +A multi‑level **MinMax pyramid** is constructed lazily to accelerate zoom: ``` -Level 0: Raw data (50,000,000 points) -Level 1: 100x reduction ( 500,000 points) -Level 2: 100x reduction ( 5,000 points) +Level 0 → Raw data (e.g. 50,000,000 points) +Level 1 → 100× reduction ( 500,000 points) +Level 2 → 100× reduction ( 5,000 points) ``` -On zoom, the coarsest level with sufficient resolution is selected. Full zoom-out reads level 2 (5K points) and downsamples to ~4K in under 1ms. +The reduction factor is controlled by the `PyramidReduction` property (default 100). When a zoom level changes, the coarsest level that still provides enough pixel buckets is used for re‑downsampling. Full zoom‑out reads only level 2 (5K points) and downsamples that to the target resolution in under 1 ms. -Levels are built lazily on first access — the first zoom-out pays a one-time build cost (~70ms with MEX), subsequent queries are instant. +Levels are built on first access. The one‑time build cost (if done with MEX) is ~70 ms for 50 M points; subsequent zooms are instant. ## MEX Acceleration -Optional C MEX functions with SIMD intrinsics (AVX2 on x86_64, NEON on arm64): +Performance‑critical routines can be compiled to C with SIMD intrinsics (AVX2 on x86_64, NEON on ARM64). `build_mex` handles compilation of all MEX sources. -| Function | Speedup | Description | -|----------|---------|-------------| -| binary_search_mex | 10-20x | O(log n) visible range lookup | -| minmax_core_mex | 3-10x | Per-pixel MinMax reduction | -| lttb_core_mex | 10-50x | Triangle area computation | -| violation_cull_mex | significant | Fused detection + pixel culling | -| compute_violations_mex | significant | Batch violation detection for resolve() | -| resolve_disk_mex | significant | SQLite disk-based sensor resolution | -| build_store_mex | 2-3x | Bulk SQLite writer for DataStore init | -| to_step_function_mex | significant | SIMD step-function conversion for thresholds | +The following MEX functions exist (each with a pure‑MATLAB fallback): -All share a common `simd_utils.h` abstraction layer. If MEX is unavailable, pure-MATLAB implementations are used with identical behavior. +- `binary_search_mex` – visible range lookup (O(log n)) +- `minmax_core_mex` – per‑pixel MinMax reduction +- `lttb_core_mex` – triangle area computation for LTTB +- `violation_cull_mex` – fused threshold violation detection + pixel culling +- `compute_violations_mex` – batch violation detection for legacy `resolve()` +- `resolve_disk_mex` – SQLite‑backed sensor resolution +- `build_store_mex` – bulk SQLite writer for `FastSenseDataStore` initialisation +- `to_step_function_mex` – SIMD step‑function conversion for time‑varying thresholds + +Availability is checked once per session (`exist(…, 'file')`). If a MEX file is not found, the identical algorithm runs in pure MATLAB. This ensures the library works out‑of‑the‑box without a compiler, but reaps speed gains when MEX is available. ## Data Flow Architecture ### Core Data Path ``` -Raw Data (X, Y arrays) +Raw Data (X, Y arrays, Tag objects) ↓ FastSenseDataStore (optional, for large datasets) ↓ -Downsampling Engine (MinMax/LTTB) - ↓ -Pyramid Cache (lazy multi-resolution) +Downsampling Engine (MinMax / LTTB) + Pyramid Cache ↓ Graphics Objects (line handles) ↓ @@ -167,159 +120,164 @@ Interactive Display ``` ### Storage Modes -- **Memory mode**: X/Y arrays held in MATLAB workspace -- **Disk mode**: Data chunked into SQLite database via `FastSenseDataStore` -- **Auto mode**: Switches to disk when data exceeds `MemoryLimit` (default 500MB) +`FastSense` can operate in three modes, controlled by the `StorageMode` property: -## Sensor Threshold Resolution +- **Memory mode** (`'memory'`) – X/Y arrays stay in the MATLAB workspace. +- **Disk mode** (`'disk'`) – data is chunked into an SQLite database via `FastSenseDataStore`. Only chunks overlapping the visible range are loaded on zoom/pan. Suitable for 100 M+ points. +- **Auto mode** (`'auto'`) – switches to disk mode when the total byte size of all lines exceeds `MemoryLimit` (default 500 MB). -The `Sensor.resolve()` algorithm is segment-based: +When using disk mode, a pre‑computed L1 MinMax pyramid is stored for instant zoom‑out, and bulk writes are accelerated by `build_store_mex`. -1. Collect all state-change timestamps from all StateChannels -2. For each segment between state changes: - - Evaluate which ThresholdRules match the current state - - Group rules with identical conditions -3. Assign threshold values per segment -4. Detect violations using SIMD-accelerated comparison +## Tag‑Based Data Domain -Complexity: O(S × R) where S = state segments and R = rules, instead of O(N × R) per-point evaluation. +The `Tag` hierarchy replaces the legacy `Sensor`/`StateChannel` paradigm: -## Disk-Backed Data Storage +``` +Tag (abstract base) +├── SensorTag – raw sensor time series (X, Y) +├── StateTag – discrete state transitions (X, Y numeric or cellstr) +├── MonitorTag – derived 0/1 binary alarm signal from a condition +└── CompositeTag – aggregation of multiple MonitorTags (AND, OR, worst‑case, etc.) +``` -For datasets exceeding available memory (100M+ points), `FastSenseDataStore` provides SQLite-backed chunked storage: +All tags are registered in `TagRegistry`, a global singleton. `FastSense.addTag()` accepts any `Tag` subclass; for `SensorTag` and `MonitorTag` it creates a standard line plot, for `StateTag` a stepped line. -1. Data is split into chunks (~10K-500K points each, auto-tuned) -2. Each chunk stored as a pair of typed BLOBs (X and Y) with X range metadata -3. On zoom/pan, only chunks overlapping the visible range are loaded -4. Pre-computed L1 MinMax pyramid for instant zoom-out +### Derived Signals and Event Detection (New Pipeline) -The bulk write path uses `build_store_mex` — a single C call that writes all chunks with SIMD-accelerated Y min/max computation, replacing ~20K mksqlite round-trips. +**MonitorTag** is the core of derived alarm signals: +- It watches its `Parent` tag (any `Tag` with `getXY()`). +- Evaluates a user‑supplied `ConditionFn` on the parent’s data to produce a 0/1 time series. +- Supports hysteresis (`AlarmOffConditionFn`), minimum duration debouncing (`MinDuration`), and opt‑in persistence to `FastSenseDataStore`. +- Streaming updates are handled by `appendData()`, which extends the cached result incrementally and emits events only when a violation run closes. -If SQLite is unavailable, a binary file fallback is used automatically. +**LiveEventPipeline** orchestrates real‑time event detection: +- It holds a `DataSourceMap` that maps sensor keys to `DataSource` instances (e.g. `MatFileDataSource`). +- Each tick polls the sources, updates parent sensor data, then calls `MonitorTag.appendData()`. +- New events are stored atomically in an `EventStore` (persistent `.mat` file). +- `NotificationService` can fire email alerts with embedded plot snapshots. -## Theme Inheritance +**EventBinding** maintains a many‑to‑many mapping between events (by `Id`) and tag `Key`s, enabling O(1) query “all events for a given tag”. -``` -Element override > Tile theme > Figure theme > 'default' preset -``` +## Legacy Sensor Threshold Resolution -Each level fills in only the fields it specifies; unspecified fields cascade from the next level. +*(The legacy system based on `Sensor`, `StateChannel`, and `ThresholdRule` is being superseded by the Tag model. Existing code using `Sensor.resolve()` still works but new projects should adopt `MonitorTag` + `EventStore`.)* -## Dashboard Architecture +In the older approach, a `Sensor`’s `resolve()` algorithm used a segment‑based state evaluation: +- Collect all state‑change timestamps from attached `StateChannel`s. +- For each segment, determine which `ThresholdRule`s match the current state and group rules with identical conditions. +- Assign threshold values per segment. +- Detect violations using SIMD‑accelerated comparison (if MEX available). -### FastSenseGrid vs DashboardEngine +This gave **O(S × R)** complexity where S = number of state segments and R = number of rules, instead of evaluating every point individually. -- **[[Dashboard|FastSenseGrid]]**: Simple tiled grid of FastSense instances with synchronized live mode -- **[[Dashboard Engine Guide|DashboardEngine]]**: Full widget-based dashboard with gauges, numbers, status indicators, tables, timelines, and edit mode +## Disk‑Backed Data Storage (`FastSenseDataStore`) -### DashboardEngine Components +`FastSenseDataStore` provides SQLite‑based storage for datasets that exceed available RAM: -``` -DashboardEngine -├── DashboardToolbar — Top toolbar (Live, Edit, Save, Export, Sync) -├── DashboardLayout — 24-column responsive grid with scrollable canvas -├── DashboardTheme — FastSenseTheme + dashboard-specific fields -├── DashboardBuilder — Edit mode overlay (drag/resize, palette, properties) -├── DashboardSerializer — JSON save/load and .m script export -└── Widgets (DashboardWidget subclasses) - ├── FastSenseWidget — FastSense instance (Sensor/DataStore/inline) - ├── GaugeWidget — Arc/donut/bar/thermometer gauge - ├── NumberWidget — Big number with trend arrow - ├── StatusWidget — Colored dot indicator - ├── TextWidget — Static label or header - ├── TableWidget — uitable display - ├── RawAxesWidget — User-supplied plot function - ├── EventTimelineWidget — Colored event bars on timeline - ├── GroupWidget — Collapsible panels, tabbed containers - └── MultiStatusWidget — Grid of sensor status dots -``` +1. Data is split into chunks (~100K points each, auto‑tuned). +2. Each chunk is stored as two typed BLOBs (X and Y) with indexed X range metadata. +3. On zoom/pan, only chunks overlapping the visible range are loaded, then trimmed to the exact view window. +4. A pre‑computed L1 MinMax pyramid is stored for instant zoom‑out. +5. Extra data columns (cell, string, categorical, logical, etc.) can be added and retrieved by X‑range. -### Render Flow +The bulk write path (`build_store_mex`) performs a single C call that writes all chunks with SIMD‑accelerated Y min/max computation, replacing thousands of individual SQLite round‑trips. -1. `DashboardEngine.render()` creates the figure -2. `DashboardTheme(preset)` generates the full theme struct -3. `DashboardToolbar` creates the top toolbar panel -4. Time control panel (dual sliders) is created at the bottom -5. `DashboardLayout.createPanels()` computes grid positions, creates viewport/canvas/scrollbar, and creates a uipanel per widget -6. Each widget's `render(parentPanel)` is called to populate its panel -7. `updateGlobalTimeRange()` scans widgets for data bounds and configures the time sliders +If the MEX SQLite interface (`mksqlite`) is not available, a pure binary file fallback is used automatically. -### Live Mode +## Theme Inheritance -When `startLive()` is called, a timer fires at `LiveInterval` seconds: -1. `updateLiveTimeRange()` expands time bounds from new data -2. Each widget's `refresh()` is called (sensor-bound widgets re-read `Sensor.Y(end)`) -3. The toolbar timestamp label is updated -4. Current slider positions are re-applied to the updated time range +Theming follows a stacking rule: -### Edit Mode +``` +Element override → Tile theme → Figure theme → 'default' preset +``` -Clicking "Edit" in the toolbar creates a `DashboardBuilder` instance: -1. A palette sidebar (left) shows widget type buttons -2. A properties panel (right) shows selected widget settings -3. Drag/resize overlays are added on top of each widget panel -4. The content area narrows to accommodate sidebars -5. Mouse move/up callbacks handle drag and resize interactions -6. Grid snap rounds positions to the nearest column/row +Each level specifies only the fields it needs; missing fields cascade from the next level. `FastSenseTheme` and `DashboardTheme` provide presets (`'light'` and `'dark'`) that can be partially overridden. -### JSON Persistence +## Dashboard Architecture -`DashboardSerializer` handles round-trip serialization: -- **Save:** each widget's `toStruct()` produces a plain struct with type, title, position, and source. The struct is encoded to JSON with heterogeneous widget arrays assembled manually (MATLAB's `jsonencode` cannot handle cell arrays of mixed structs). -- **Load:** JSON is decoded, widgets array is normalized to cell, and `configToWidgets()` dispatches to each widget class's `fromStruct()` static method. An optional `SensorResolver` function handle re-binds Sensor objects by name. -- **Export script:** generates a `.m` file with `DashboardEngine` constructor calls and `addWidget` calls for each widget. +`DashboardEngine` is the top‑level orchestrator for widget‑based dashboards. It manages: + +- A grid layout (`DashboardLayout` – 24 columns, responsive, scrollable). +- A theme (`DashboardTheme`, extending `FastSenseTheme` with dashboard‑specific colours and font sizes). +- A toolbar (`DashboardToolbar`) with live toggle, edit mode, export, config, and info buttons. +- A time‑range selector (`TimeRangeSelector`) with an aggregate data envelope and event‑marker overlay. +- Optional multi‑page support (`DashboardPage`). + +Widgets (`DashboardWidget` subclasses) include: + +| Widget | Purpose | +|--------|---------| +| `FastSenseWidget` | a `FastSense` instance bound to a `Tag` or data source | +| `GaugeWidget` | arc, donut, bar, or thermometer gauge | +| `NumberWidget` | big number with trend arrow | +| `StatusWidget` | colored dot indicator | +| `TextWidget` | static label or section header | +| `TableWidget` | `uitable` display | +| `RawAxesWidget` | user‑supplied plot function on raw axes | +| `EventTimelineWidget` | colored event bars on a timeline | +| `GroupWidget` | collapsible panels or tabbed containers | +| `MultiStatusWidget` | grid of sensor status dots | +| `SparklineCardWidget` | KPI card with a mini sparkline | +| `IconCardWidget` | compact icon + value + label | +| `ChipBarWidget` | horizontal row of status chips | +| `HeatmapWidget`, `BarChartWidget`, … | additional chart types | + +### Render Flow (Dashboard) + +1. `DashboardEngine.render()` creates the figure. +2. A theme struct is built (cached until `Theme` changes). +3. `DashboardToolbar` is created at the top; time‑control panel at the bottom. +4. `DashboardLayout.createPanels()` computes grid positions, creates viewport/canvas/scrollbar, and allocates a `uipanel` per widget. +5. Each widget’s `render(parentPanel)` is called. +6. Global time range is computed from all widget data bounds and configured on the time sliders. +7. If a `LiveInterval` is set, a timer is started for live updates. + +Live mode uses a timer that polls data sources, calls `refresh()` on each widget, and updates the global time range selector. + +Edit mode (`DashboardBuilder`) adds drag/resize overlays, a widget palette sidebar, and a properties panel, allowing interactive layout without coding. ## Event Detection Architecture -The event detection system provides real-time threshold violation monitoring with configurable notifications and data persistence. - -### Core Components +The new event detection pipeline revolves around `MonitorTag` and `EventStore`: ``` LiveEventPipeline -├── DataSourceMap — Maps sensor keys to data sources -├── IncrementalEventDetector — Tracks per-sensor state and open events -├── EventStore — Thread-safe .mat file persistence -├── NotificationService — Rule-based email alerts -└── EventViewer — Interactive Gantt chart + filterable table +├── DataSourceMap – maps sensor keys to data sources +├── MonitorTargets – containers.Map of key → MonitorTag +├── EventStore – atomic .mat file persistence +├── NotificationService – rule‑based email alerts +└── EventViewer – interactive Gantt chart + filterable table ``` -### Data Sources - -- **MatFileDataSource**: Polls .mat files for new data -- **MockDataSource**: Generates realistic test signals with violations -- **Custom sources**: Implement `DataSource.fetchNew()` interface +**Flow:** +1. `LiveEventPipeline.runCycle()` polls all data sources. +2. New data is pushed to the parent sensor tags. +3. For each monitor, `MonitorTag.appendData(newX, newY)` extends the cached 0/1 signal and emits closed violation events into the `EventStore`. +4. `EventStore.save()` atomically writes all events to disk. +5. `NotificationService` sends email alerts (optional snapshots) using configurable rules. +6. Active `EventViewer` instances auto‑refresh to show new events. -### Event Detection Flow +Events are now identified by a unique `Id` and can carry multiple `TagKeys` (via `EventBinding`), enabling cross‑sensor event queries. -1. `LiveEventPipeline.runCycle()` polls all data sources -2. New data is passed to `IncrementalEventDetector.process()` -3. Sensor state is evaluated via `Sensor.resolve()` -4. Violations are grouped into events with debouncing (`MinDuration`) -5. Events are stored via `EventStore.append()` (atomic .mat writes) -6. `NotificationService` sends rule-based email alerts with plot snapshots -7. Active `EventViewer` instances auto-refresh to show new events +## Interactive Features -### Escalation Logic +### Toolbars and Navigation +- **FastSenseToolbar**: Data cursor (snaps to nearest point), crosshair, grid/legend toggle, Y autoscale, PNG/data export, refresh, live mode, metadata display, violation visibility. +- **DashboardToolbar**: Live toggle, events toggle, edit mode, save/export, config dialog, info panel. +- **NavigatorOverlay**: Minimap with draggable zoom rectangle, used by `SensorDetailPlot`. -When `EscalateSeverity` is enabled, events are promoted to the highest violated threshold: -- A violation starts at "Warning" level -- If "Alarm" threshold is also crossed, the event is escalated to "Alarm" -- The event retains the highest severity level encountered +### Link Groups +Multiple `FastSense` instances (even across different grids or docks) can share synchronised zoom/pan by assigning the same `LinkGroup` string. When one plot’s XLim changes, all others in the group update automatically. ## Progress Indication -`ConsoleProgressBar` provides hierarchical progress feedback: -- Single-line ASCII/Unicode bars with backspace-based updates -- Indentation support for nested operations (e.g., dock → tabs → tiles) -- Freeze/finish modes for permanent status lines +`ConsoleProgressBar` provides a single‑line progress bar with indentation support. It is used hierarchically – for example, a dock’s `renderAll()` prints a header for each tab, then nested per‑tile progress bars. Bars can be frozen permanently to stack multiple stages. -## Interactive Features +## See Also -### Toolbars and Navigation -- **[[API Reference: FastPlot|FastSenseToolbar]]**: Data cursor, crosshair, grid toggle, autoscale, export, live mode -- **DashboardToolbar**: Live toggle, edit mode, save/export, name editing -- **NavigatorOverlay**: Minimap with draggable zoom rectangle for `SensorDetailPlot` - -### Link Groups -Multiple FastSense instances can share synchronized zoom/pan via `LinkGroup` strings. When one plot's XLim changes, all plots in the same group update automatically. +- [[MEX Acceleration]] – detailed MEX performance and compilation +- [[Performance]] – tuning tips and benchmarks +- [[API Reference: FastPlot]] – comprehensive API for `FastSense` +- [[Dashboard Engine Guide]] – building dashboards programmatically +- [[Live Mode Guide]] – live data polling patterns diff --git a/wiki/Dashboard-Engine-Guide.md b/wiki/Dashboard-Engine-Guide.md index 2fff83a1..133ae1c4 100644 --- a/wiki/Dashboard-Engine-Guide.md +++ b/wiki/Dashboard-Engine-Guide.md @@ -2,7 +2,7 @@ # Dashboard Engine Guide -Build rich, interactive dashboards with mixed widget types, sensor bindings, JSON persistence, and a visual editor. +Build rich, interactive dashboards with mixed widget types, JSON persistence, a visual editor, multi-page navigation, and live data mirroring. --- @@ -12,18 +12,21 @@ FastSense provides two dashboard systems: | Feature | FastSenseGrid | DashboardEngine | |---------|---------------|-----------------| -| Grid | Fixed rows x cols | 24-column responsive | -| Tile content | FastSense instances only | 8 widget types (plots, gauges, numbers, tables, etc.) | -| Persistence | None | JSON save/load + .m script export | -| Visual editor | No | Yes (drag/resize, palette, properties panel) | -| Scrolling | No | Auto-scrollbar when content overflows | +| Grid | Fixed rows × cols | 24‑column responsive | +| Tile content | FastSense instances only | 20+ widget types (plots, gauges, KPIs, tables, bars, sparklines, …) | +| Persistence | None | JSON save/load + `.m` script export | +| Visual editor | No | Yes (drag, resize, palette, properties) | +| Scrolling | No | Auto‑scrollbar when content overflows | | Global time | No | Dual sliders controlling all widgets | -| Sensor binding | Via addSensor per tile | Direct widget property (auto-title, auto-units) | -| Live mode | Per-figure timer | Engine-level timer refreshing all widgets | +| Multi‑page | No | Named pages with toolbar tab switching | +| Console preview | No | ASCII render of layout and data | +| Image export | Figure copy | High‑resolution PNG/JPEG export | +| Widget pop‑out | No | `detachWidget()` into a live mirror window | +| Stale data detection | None | Banner for widgets with no new data in live mode | **When to use FastSenseGrid:** You need a simple tiled grid of FastSense time series plots with linked axes and a toolbar. -**When to use DashboardEngine:** You need mixed widget types (gauges, KPIs, tables, timelines), JSON persistence, or the visual editor. +**When to use DashboardEngine:** You need mixed widget types, JSON persistence, the visual editor, multi‑page dashboards, or any of the features listed above. --- @@ -55,15 +58,15 @@ d.render(); ## Grid System -DashboardEngine uses a **24-column grid**. Widget positions are specified as: +DashboardEngine uses a **24‑column grid**. Widget positions are specified as: ``` Position = [col, row, width, height] ``` -- `col`: column (1-24), left to right +- `col`: column (1–24), left to right - `row`: row (1+), top to bottom -- `width`: number of columns to span (1-24) +- `width`: number of columns to span (1–24) - `height`: number of rows to span Examples: @@ -83,7 +86,7 @@ If a new widget overlaps an existing one, it is automatically pushed down to the ### FastSense (time series) ```matlab -% Sensor-bound (recommended) +% Sensor‑bound (recommended) d.addWidget('fastsense', 'Position', [1 1 12 8], 'Sensor', mySensor); % Inline data @@ -99,7 +102,7 @@ d.addWidget('fastsense', 'Title', 'Store', 'Position', [1 15 24 6], ... 'DataStore', myDataStore); ``` -When bound to a Sensor, threshold rules apply automatically (resolved violations are shown). The widget title, X-axis label (`'Time'`), and Y-axis label (sensor Units or Name) are auto-derived. +When bound to a Sensor, threshold rules apply automatically. The widget title, X‑axis label (`'Time'`), and Y‑axis label (sensor Units or Name) are auto‑derived. ### Number (big value display) @@ -108,18 +111,18 @@ d.addWidget('number', 'Title', 'Temperature', ... 'Position', [1 1 6 2], ... 'Sensor', sTemp, 'Units', 'degF', 'Format', '%.1f'); -% Or with static value +% Static value d.addWidget('number', 'Title', 'Total Count', ... 'Position', [7 1 6 2], ... 'StaticValue', 1234, 'Units', 'pcs', 'Format', '%d'); -% Or with function callback +% Callback d.addWidget('number', 'Title', 'CPU Load', ... 'Position', [13 1 6 2], ... 'ValueFcn', @() getCpuLoad(), 'Units', '%', 'Format', '%.0f'); ``` -Shows a large number with a trend arrow (up/down/flat) computed from recent sensor data. Layout: `[Title | Value+Trend | Units]`. +Shows a large number with a trend arrow (up/down/flat). Layout: `[Title | Value+Trend | Units]`. ### Status (health indicator) @@ -128,13 +131,13 @@ d.addWidget('status', 'Title', 'Pump', ... 'Position', [7 1 5 2], ... 'Sensor', sTemp); -% Legacy static status +% Static status d.addWidget('status', 'Title', 'System', ... 'Position', [12 1 5 2], ... 'StaticStatus', 'ok'); % 'ok', 'warning', 'alarm' ``` -Shows a colored dot (green/amber/red) and the sensor's latest value. Status is derived automatically from threshold rules. +Displays a colored dot (green/amber/red) and the sensor’s latest value. Status is derived from threshold rules automatically. ### Gauge (arc/donut/bar/thermometer) @@ -151,9 +154,7 @@ d.addWidget('gauge', 'Title', 'Efficiency', ... 'Style', 'arc'); ``` -Styles: `'arc'` (default), `'donut'`, `'bar'`, `'thermometer'`. - -When Sensor-bound, range and units are auto-derived from threshold rules and sensor properties. +Styles: `'arc'` (default), `'donut'`, `'bar'`, `'thermometer'`. When Sensor‑bound, range and units are auto‑derived. ### Text (labels and headers) @@ -178,7 +179,7 @@ d.addWidget('table', 'Title', 'Recent Data', ... 'Position', [1 9 12 4], ... 'Sensor', sTemp, 'N', 15); -% Dynamic data via callback +% Dynamic callback d.addWidget('table', 'Title', 'Live Log', ... 'Position', [1 13 12 4], ... 'DataFcn', @() getRecentAlarms(), ... @@ -199,14 +200,14 @@ d.addWidget('rawaxes', 'Title', 'Temperature Distribution', ... 'PlotFcn', @(ax) histogram(ax, tempData, 50, ... 'FaceColor', [0.31 0.80 0.64], 'EdgeColor', 'none')); -% Sensor-bound with time range +% Sensor‑bound with time range d.addWidget('rawaxes', 'Title', 'Custom Analysis', ... 'Position', [9 5 8 4], ... 'Sensor', mySensor, ... 'PlotFcn', @(ax, sensor, tRange) plotCustom(ax, sensor, tRange)); ``` -The `PlotFcn` receives MATLAB axes as the first argument. When Sensor-bound, it also receives the Sensor object and optionally a time range. +The `PlotFcn` receives axes as first argument. When Sensor‑bound, it also receives the Sensor object and optionally a time range. ### Event Timeline @@ -231,6 +232,124 @@ d.addWidget('timeline', 'Title', 'Temp Events', ... 'FilterSensors', {'T-401', 'T-402'}); ``` +### Sparkline Card (KPI + mini chart) + +```matlab +d.addWidget('sparkline', 'Title', 'CPU', ... + 'Position', [1 1 6 3], ... + 'StaticValue', 42.0, 'SparkData', cpuHistory, ... + 'Units', '%', 'ShowDelta', true); +``` + +A compact card with a large value, a sparkline chart, and an optional delta indicator. Can also be bound to a Sensor for value & sparkline data. + +### Icon Card (mushroom card) + +```matlab +d.addWidget('iconcard', 'Title', 'Pump 3', ... + 'Position', [1 1 4 3], ... + 'Sensor', sPump, 'Units', 'psi'); +``` + +Shows a colored icon (circle) reflecting the sensor’s threshold state, a large numeric value, and a secondary label. + +### Chip Bar (multi‑sensor status strip) + +```matlab +chips = { + struct('label', 'Pump', 'sensor', sPump), + struct('label', 'Tank', 'statusFcn', @() 'ok'), + struct('label', 'Fan', 'statusFcn', @() 'alarm') +}; +d.addWidget('chipbar', 'Title', 'System Health', ... + 'Position', [1 1 24 1], 'Chips', chips); +``` + +A horizontal row of colored status dots, ideal for a dense summary of many items. + +### Bar Chart + +```matlab +d.addWidget('barchart', 'Title', 'Production', ... + 'Position', [1 1 12 6], ... + 'DataFcn', @() struct('categories', {{'A','B','C'}}, ... + 'values', [120, 85, 200]), ... + 'Orientation', 'vertical'); +``` + +Uses `DataFcn` that returns a struct with `categories` and `values`. Supports `'vertical'` / `'horizontal'` orientation and stacked mode. + +### Heatmap + +```matlab +d.addWidget('heatmap', 'Title', 'Correlation', ... + 'Position', [1 1 12 6], ... + 'DataFcn', @() rand(5), ... + 'Colormap', 'parula', 'ShowColorbar', true); +``` + +Accepts a matrix from `DataFcn`. Axes labels, colormap, and colorbar are configurable. + +### Histogram + +```matlab +d.addWidget('histogram', 'Title', 'Values', ... + 'Position', [1 1 8 4], ... + 'DataFcn', @() randn(1,1000), ... + 'NumBins', 30, 'EdgeColor', [0.2 0.2 0.5]); +``` + +### Scatter + +```matlab +d.addWidget('scatter', 'Title', 'X vs Y', ... + 'Position', [1 1 8 6], ... + 'SensorX', sX, 'SensorY', sY); +``` + +Plots two sensors against each other. An optional third sensor can be used for point coloring. + +### Image + +```matlab +d.addWidget('image', 'Title', 'Layout', ... + 'Position', [1 1 8 6], ... + 'File', 'plant_layout.png', 'Scaling', 'fit'); +``` + +Or with a function: `'ImageFcn', @() imread('file')`. Scaling modes: `'fit'`, `'fill'`, `'stretch'`. + +### Divider (horizontal line) + +```matlab +d.addWidget('divider', 'Position', [1 5 24 1], 'Thickness', 2); +``` + +A static visual separator that respects the theme’s border color. + +### Group Widget (panel / collapsible / tabbed) + +```matlab +% Collapsible group +w1 = d.addWidget('fastsense', ...); +w2 = d.addWidget('number', ...); +d.addWidget('group', 'Title', 'Sensors', ... + 'Position', [1 1 12 8], ... + 'Mode', 'collapsible', ... + 'Label', 'Temperature Bundle', ... + 'Children', {w1, w2}); + +% Or via convenience method +d.addCollapsible('Details', {w1, w2}, 'Collapsed', false); +``` + +`GroupWidget` supports three modes: +- `'panel'` — simple container with a sub‑grid. +- `'collapsible'` — header bar that toggles visibility. +- `'tabbed'` — multiple named tabs, each holding a set of widgets. + +Children automatically flow inside the group’s local grid. Nesting depth is limited (source code enforces a max depth). + --- ## Sensor Binding @@ -254,7 +373,7 @@ sTemp.addThresholdRule(struct('machine', 1), 85, ... 'Direction', 'upper', 'Label', 'Hi Alarm'); sTemp.resolve(); -% All of these auto-derive from the Sensor: +% All of these auto‑derive from the Sensor: d.addWidget('fastsense', 'Sensor', sTemp, 'Position', [1 1 12 8]); d.addWidget('number', 'Sensor', sTemp, 'Position', [13 1 6 2], 'Units', 'degF'); d.addWidget('status', 'Sensor', sTemp, 'Position', [19 1 6 2]); @@ -262,12 +381,11 @@ d.addWidget('gauge', 'Sensor', sTemp, 'Position', [13 3 12 6]); ``` Benefits of Sensor binding: -- **Title:** auto-derived from `Sensor.Name` or `Sensor.Key` -- **Units:** auto-derived from `Sensor.Units` +- **Title:** auto‑derived from `Sensor.Name` or `Sensor.Key` +- **Units:** auto‑derived from `Sensor.Units` - **Value:** uses `Sensor.Y(end)` for number, gauge, and status widgets -- **Thresholds:** FastSenseWidget renders resolved thresholds and violations -- **Status:** StatusWidget checks the latest value against all threshold rules -- **Live refresh:** calling `refresh()` re-reads the sensor data +- **Thresholds:** FastSenseWidget renders resolved thresholds and violations; StatusWidget compares against all rules +- **Live refresh:** `refresh()` re‑reads sensor data --- @@ -279,8 +397,6 @@ Benefits of Sensor binding: d.save('dashboard.json'); ``` -The JSON file contains the dashboard name, theme, live interval, grid settings, and each widget's type, title, position, and data source. - ### Load from JSON ```matlab @@ -288,7 +404,7 @@ d2 = DashboardEngine.load('dashboard.json'); d2.render(); ``` -To re-bind Sensor objects on load, provide a resolver function: +To re‑bind Sensor objects on load, provide a resolver function: ```matlab d2 = DashboardEngine.load('dashboard.json', ... @@ -302,21 +418,21 @@ d2.render(); d.exportScript('rebuild_dashboard.m'); ``` -Generates a readable `.m` file with `DashboardEngine` constructor and `addWidget` calls that recreates the dashboard. +Generates a readable `.m` file with `DashboardEngine` constructor and `addWidget` calls that recreates the dashboard. Multi‑page dashboards are fully supported. --- ## Theming -DashboardEngine uses `DashboardTheme`, which extends `FastSenseTheme` with dashboard-specific fields (widget backgrounds, border colors, status indicator colors, etc.). +DashboardEngine uses `DashboardTheme`, which extends `FastSenseTheme` with dashboard‑specific fields (widget backgrounds, border colors, status indicator colors, etc.). ```matlab d = DashboardEngine('My Dashboard'); -d.Theme = 'dark'; % or 'light', 'industrial', 'scientific', 'ocean' +d.Theme = 'dark'; % or 'light' d.render(); ``` -Available presets: `'default'`, `'dark'`, `'light'`, `'industrial'`, `'scientific'`, `'ocean'`. +Available presets: `'dark'` and `'light'`. Legacy preset names (`'default'`, `'industrial'`, …) are aliased to `'light'` for backward compatibility. You can also override specific theme properties: @@ -345,7 +461,17 @@ d.startLive(); % start periodic refresh d.stopLive(); % stop ``` -You can also toggle live mode from the toolbar's Live button. The toolbar shows the last update timestamp when live mode is active. +You can also toggle live mode from the toolbar’s **Live** button. The toolbar shows a blue border when live mode is active and displays the last update timestamp. + +### Stale Data Detection + +During live mode, if a widget’s latest timestamp ceases to advance, a warning banner appears below the toolbar listing the stale widgets. The banner can be dismissed by the user; it remains hidden until data resumes. + +```matlab +% The banner is automatically created and updated by the engine. +% You can control its visibility indirectly via: +d.ProgressMode = 'auto'; % 'auto', 'on', 'off' — render progress bar visibility +``` --- @@ -355,9 +481,13 @@ The time panel at the bottom of the dashboard has two sliders that control the v - **FastSenseWidget:** sets xlim on the FastSense axes - **EventTimelineWidget:** sets xlim on the timeline axes -- **RawAxesWidget:** passes the time range to the PlotFcn +- **RawAxesWidget:** passes the time range to the `PlotFcn` -If a user manually zooms a specific widget, that widget detaches from global time (`UseGlobalTime = false`). Click the **Sync** button in the toolbar to re-attach all widgets. +If a user manually zooms a specific widget, that widget detaches from global time (`UseGlobalTime = false`). Click the **Sync** button in the toolbar to re‑attach all widgets. + +### Time Range Envelope + +The slider panel displays an aggregate down‑sampled preview of all FastSense widget data as a shaded envelope behind the selection rectangle. This gives an overview of data density before scrubbing. --- @@ -365,20 +495,85 @@ If a user manually zooms a specific widget, that widget detaches from global tim Click the **Edit** button in the toolbar to enter edit mode: -1. A **palette sidebar** appears on the left with buttons for each widget type -2. A **properties panel** appears on the right showing the selected widget's settings -3. **Drag handles** let you reposition widgets on the grid -4. **Resize handles** let you change widget dimensions -5. Click **Apply** to save property changes -6. Click **Done** to exit edit mode +1. A **palette sidebar** appears on the left with buttons for each widget type. +2. A **properties panel** appears on the right showing the selected widget’s settings. +3. **Drag handles** let you reposition widgets on the grid. +4. **Resize handles** let you change widget dimensions. +5. Click **Apply** to save property changes. +6. Click **Done** to exit edit mode. -The editor snaps to the 24-column grid. You can change the widget's title, position, axis labels, and data source directly in the properties panel. +The editor snaps to the 24‑column grid. You can change the widget’s title, position, axis labels, and data source directly in the properties panel. Certain widget types have specialized add‑methods in the builder (e.g., `addIconCard`, `addChipBar`, `addSparkline`) that streamline creation. Widget management functions: -- `addWidget(type)` - add a new widget of the specified type -- `deleteWidget(idx)` - remove widget by index -- `selectWidget(idx)` - select a widget for property editing -- `setWidgetPosition(idx, pos)` - move/resize widget programmatically +- `addWidget(type)` – add a new widget of the specified type +- `deleteWidget(idx)` – remove widget by index +- `selectWidget(idx)` – select a widget for property editing +- `setWidgetPosition(idx, pos)` – move/resize widget programmatically + +--- + +## Multi‑Page Dashboards + +Dashboards can be organized into multiple named pages, each holding its own set of widgets. The toolbar automatically shows navigation tabs when pages are defined. + +```matlab +d = DashboardEngine('Plant Monitor'); +d.Theme = 'dark'; + +% Create first page (active by default after first addPage) +pgOverview = d.addPage('Overview'); +d.addWidget('fastsense', 'Sensor', sTemp, 'Position', [1 1 12 8]); +d.addWidget('number', 'Sensor', sTemp, 'Position', [13 1 6 2]); + +% Create second page +d.switchPage(2); % explicitly switch before adding widgets +pgAlarms = d.addPage('Alarms'); +d.addWidget('table', 'Title', 'All Alarms', 'Position', [1 1 24 6], ...) + +d.render(); +``` + +`addPage(name)` creates a `DashboardPage`, appends it to `Pages`, and makes it the active page for subsequent `addWidget` calls. You can switch between pages with `switchPage(n)` or by clicking the tabs in the toolbar. + +Multi‑page configuration is fully serialized in JSON and in exported scripts. + +--- + +## Console Preview + +To quickly inspect a dashboard’s layout and widget summaries without rendering a figure, use `preview()`: + +```matlab +d.preview(); % default 120‑char width +d.preview('Width', 80); % custom width +``` + +This prints an ASCII representation to the console, with each widget’s type, title, and a basic data indicator. + +--- + +## Export Image + +Save the rendered dashboard as a high‑resolution PNG or JPEG from code: + +```matlab +d.exportImage('dashboard.png'); % PNG at 150 DPI +d.exportImage('dashboard.jpg', 'jpeg'); % JPEG +``` + +The toolbar’s **Image** button opens a save dialog with the same functionality. + +--- + +## Detached Mirror Pop‑outs + +Any widget can be detached into its own standalone figure window that continues to receive live updates: + +```matlab +d.detachWidget(w); % w is a widget handle +``` + +The mirror is a cloned copy. It is ticked by the same live timer. Dead mirrors are automatically cleaned up. Use `removeDetached(obj)` to manually prune stale mirrors. --- @@ -388,17 +583,17 @@ Dashboards can link to external Markdown documentation files: ```matlab d = DashboardEngine('My Dashboard'); -d.InfoFile = 'dashboard_help.md'; % path to Markdown file +d.InfoFile = 'dashboard_help.md'; d.render(); ``` -An **Info** button appears in the toolbar. Clicking it renders the Markdown file as HTML and opens it in the system browser. Supports basic Markdown syntax including headers, lists, code blocks, and tables. +An **Info** button appears in the toolbar. Clicking it renders the Markdown file as HTML and opens it in the system browser. Supports basic Markdown syntax (headings, lists, code, tables). When no `InfoFile` is set, a placeholder page is shown describing how to attach one. --- ## Complete Example -This example creates a process monitoring dashboard with sensor-bound widgets: +This example creates a process monitoring dashboard with sensor‑bound widgets, a sparkline card, and a collapsible group: ```matlab install; @@ -434,12 +629,15 @@ sPress.addThresholdRule(struct(), 65, 'Direction', 'upper', 'Label', 'Hi Warn'); sPress.addThresholdRule(struct(), 70, 'Direction', 'upper', 'Label', 'Hi Alarm'); sPress.resolve(); +% CPU load history for sparkline +cpuHistory = 30 + 20*rand(1,60); + %% Build dashboard d = DashboardEngine('Process Monitoring — Line 4'); d.Theme = 'light'; d.LiveInterval = 5; -% Header row: text + numbers + status +% Header row d.addWidget('text', 'Title', 'Overview', 'Position', [1 1 4 2], ... 'Content', 'Line 4 — Shift A', 'FontSize', 16); d.addWidget('number', 'Title', 'Temperature', 'Position', [5 1 5 2], ... @@ -451,16 +649,23 @@ d.addWidget('status', 'Title', 'Temp', 'Position', [15 1 5 2], ... d.addWidget('status', 'Title', 'Press', 'Position', [20 1 5 2], ... 'Sensor', sPress); -% Plot row: sensor-bound FastSense widgets +% Main plot row d.addWidget('fastsense', 'Position', [1 3 12 8], 'Sensor', sTemp); d.addWidget('fastsense', 'Position', [13 3 12 8], 'Sensor', sPress); -% Bottom row: gauge + custom plot +% Bottom row: gauge, sparkline, and collapsible details d.addWidget('gauge', 'Title', 'Pressure', 'Position', [1 11 8 6], ... 'Sensor', sPress, 'Range', [0 100], 'Units', 'psi'); -d.addWidget('rawaxes', 'Title', 'Temp Distribution', 'Position', [9 11 8 6], ... - 'PlotFcn', @(ax) histogram(ax, sTemp.Y, 50, ... - 'FaceColor', [0.31 0.80 0.64], 'EdgeColor', 'none')); +d.addWidget('sparkline', 'Title', 'CPU Load', 'Position', [9 11 6 3], ... + 'StaticValue', cpuHistory(end), 'SparkData', cpuHistory, ... + 'Units', '%'); + +% Collapsible group for additional sensors +wAlarm = d.addWidget('table', 'Title', 'Alarms', ... + 'Position', [1 14 12 4], 'Sensor', sTemp, 'N', 10); +wEvent = d.addWidget('timeline', 'Title', 'Events', ... + 'Position', [1 18 12 3], 'EventStoreObj', myEventStore); +d.addCollapsible('More Details', {wAlarm, wEvent}, 'Collapsed', true); d.render(); @@ -472,7 +677,7 @@ d.save(fullfile(tempdir, 'process_dashboard.json')); ## See Also -- [[API Reference: Dashboard]] -- Full API reference for all dashboard classes -- [[API Reference: Sensors]] -- Sensor, StateChannel, ThresholdRule -- [[Live Mode Guide]] -- Live data polling -- [[Examples]] -- `example_dashboard_engine`, `example_dashboard_all_widgets` +- [[API Reference: Dashboard]] — Full API reference for all dashboard classes +- [[API Reference: Sensors]] — Sensor, StateChannel, ThresholdRule +- [[Live Mode Guide]] — Live data polling +- [[Examples]] — `example_dashboard_engine`, `example_dashboard_all_widgets` diff --git a/wiki/Event-Detection-Guide.md b/wiki/Event-Detection-Guide.md index 7e1ff841..1bf43762 100644 --- a/wiki/Event-Detection-Guide.md +++ b/wiki/Event-Detection-Guide.md @@ -2,255 +2,253 @@ # Event Detection Guide -The Event Detection system in FastSense provides comprehensive threshold-based monitoring with live detection, notification services, and visual event management. It bridges the [[Sensors]] library for threshold analysis with real-time event pipelines, storage, and notifications. +The Event Detection system provides threshold‑based monitoring, live detection pipelines, persistent storage, interactive visualisation, and notification services. It integrates with the [[Sensors]] library for threshold configuration and with the MonitorTag infrastructure for streaming, incremental alerting. ## When to Use Event Detection -- **Real-time monitoring**: Detect threshold violations as they occur in live data streams -- **Historical analysis**: Analyze events from recorded sensor data with statistical summaries -- **Alert systems**: Configure rule-based notifications with email and snapshot generation -- **Event visualization**: View events in Gantt timelines and filterable tables -- **Data archival**: Store events with automatic backup rotation and atomic file operations +- **Real‑time monitoring** – detect threshold violations in live data streams +- **Historical analysis** – review events from recorded sensor data with statistical summaries +- **Alerting** – configure rule‑based notifications with email and graphical snapshots +- **Visualisation** – view events in a Gantt timeline and filterable table +- **Data archival** – store events with automatic backup rotation and atomic file operations -## Core Workflow +## Core Workflow (Modern Approach) -The event detection workflow follows these steps: +1. **Create MonitorTag** objects with thresholds (see [[Sensors]] for threshold setup). +2. **Connect data sources** (`DataSource`) that supply new timestamps and values. +3. **Configure a pipeline** (`LiveEventPipeline`) that polls data, feeds `MonitorTag.appendData`, and detects events via `EventDetector`. +4. **Persist events** with `EventStore` (atomic saves, backups). +5. **Visualise and alert** with `EventViewer` and `NotificationService`. -1. **Configure sensors** with thresholds using the [[Sensors]] library -2. **Set up data sources** to fetch new sensor data (live files, mock data, etc.) -3. **Configure event detection** with minimum duration, callbacks, and escalation -4. **Run detection** to find threshold violations and generate Event objects -5. **Store and visualize** events using EventStore and EventViewer +For batch processing of already‑resolved `Sensor` data, the legacy `EventConfig` class can still be used by directly populating its `Sensors` property. -## Basic Event Detection +## Basic Event Detection (Batch with Sensors) -### Quick Start Example +`EventConfig` runs detection across an array of `Sensor` objects that already hold data and thresholds. The deprecated `addSensor` method is a no‑op; assign the `Sensors` property directly. ```matlab -% Create a sensor with threshold +% Create a sensor with data and a threshold sensor = Sensor('temperature'); sensor.X = 1:100; sensor.Y = 70 + 10*sin((1:100)/10) + randn(1,100); sensor.addThresholdRule(struct(), 85, 'Direction', 'upper', 'Label', 'temp high'); -% Configure and run detection +% Configure batch detection cfg = EventConfig(); -cfg.MinDuration = 2; % 2-second minimum -cfg.addSensor(sensor); +cfg.Sensors = {sensor}; % <-- direct assignment (do NOT use cfg.addSensor) +cfg.MinDuration = 2; % ignore violations shorter than 2 seconds +cfg.EscalateSeverity = true; % promote to higher thresholds if peak exceeds + events = cfg.runDetection(); -% Print summary +% Quick summary printEventSummary(events); ``` -### EventConfig - Central Configuration +### EventConfig Properties + +| Property | Purpose | +|----------|---------| +| `Sensors` | Cell array of `Sensor` objects (set directly). | +| `MinDuration` | Minimum event duration in seconds. | +| `MaxCallsPerEvent` | Limit callback invocations per event. | +| `OnEventStart` | Function handle called on each new event. | +| `ThresholdColors` | `containers.Map` from threshold label to RGB triple. | +| `AutoOpenViewer` | Open `EventViewer` automatically after detection. | +| `EscalateSeverity` | Escalate events to higher thresholds if peak exceeds (default `true`). | +| `EventFile` | Path for automatic event storage (empty = disabled). | +| `MaxBackups` | Number of backup files to keep (default 5). | + +## Event Objects -The [[Event Detection|EventConfig]] class orchestrates all event detection: +Each detected event is represented by an `Event` object: ```matlab -cfg = EventConfig(); -cfg.MinDuration = 1.5; % Debounce short violations -cfg.MaxCallsPerEvent = 2; % Limit callback invocations -cfg.EscalateSeverity = true; % H -> HH when peak exceeds -cfg.AutoOpenViewer = true; % Open EventViewer after detection -cfg.OnEventStart = eventLogger(); % Console logging callback - -% Auto-save events to file with backup rotation -cfg.EventFile = 'my_events.mat'; -cfg.MaxBackups = 5; +e = Event(startTime, endTime, 'temperature', 'temp high', 85, 'upper'); +e.setStats(peakValue, numPoints, minVal, maxVal, meanVal, rmsVal, stdVal); +``` -% Add sensors -cfg.addSensor(temperatureSensor); -cfg.addSensor(pressureSensor); +**Core properties** (set at construction, read‑only): -% Set threshold colors for visualization -cfg.setColor('temp warning', [1 0.8 0]); -cfg.setColor('temp critical', [1 0.2 0]); +- `StartTime`, `EndTime`, `Duration` (days) +- `SensorName`, `ThresholdLabel`, `ThresholdValue`, `Direction` (`'upper'` or `'lower'`) -% Run detection -events = cfg.runDetection(); -``` +**Statistics** (populated by `setStats` or the detector): + +- `PeakValue`, `NumPoints`, `MinValue`, `MaxValue`, `MeanValue`, `RmsValue`, `StdValue` -### Event Objects +**Open/close lifecycle** (`IsOpen` flag, `close()` method): -Each detected event is represented by an [[Event Detection|Event]] object: +```matlab +e.IsOpen = true; % event still in progress +e.close(endTime, finalStats); % finalises event (EndTime, Duration, stats) +``` + +**Severity escalation** (`escalateTo`): ```matlab -% Event properties (read-only after creation) -event.StartTime % datenum of violation start -event.EndTime % datenum of violation end -event.Duration % duration in days -event.SensorName % sensor identifier -event.ThresholdLabel % threshold name -event.ThresholdValue % threshold numeric value -event.Direction % 'upper' or 'lower' - -% Statistical properties (set by detector) -event.PeakValue % most extreme value during violation -event.NumPoints % number of data points in violation -event.MinValue % minimum value during violation -event.MaxValue % maximum value during violation -event.MeanValue % mean value during violation -event.RmsValue % RMS value during violation -event.StdValue % standard deviation during violation +e.escalateTo('HH Alarm', 95) % changes label & threshold value in place ``` +**Tag‑binding** (`TagKeys`, `EventBinding`): events can be linked to any number of tags for advanced filtering. + ## Live Event Detection ### Data Sources -Data sources provide the interface between your data and the event detection system: +Data sources implement the abstract `DataSource` interface; they return new data since the last call. ```matlab -% Mock data source for testing +% Test‑data generator mockDS = MockDataSource('BaseValue', 100, 'NoiseStd', 2, ... 'ViolationProbability', 0.001, 'ViolationAmplitude', 25); -% File-based data source for live monitoring -fileDS = MatFileDataSource('sensors/temp.mat', 'XVar', 'time', 'YVar', 'temp'); +% File‑based source for a continuously‑updated .mat file +fileDS = MatFileDataSource('sensors/temp.mat', ... + 'XVar', 'time', 'YVar', 'temp'); +``` -% Map sensors to data sources +A `DataSourceMap` binds keys (sensor names) to data sources: + +```matlab dsMap = DataSourceMap(); dsMap.add('temperature', mockDS); dsMap.add('pressure', fileDS); ``` -### Live Pipeline +### LiveEventPipeline -The [[Event Detection|LiveEventPipeline]] orchestrates continuous monitoring: +`LiveEventPipeline` orchestrates continuous monitoring. It expects a `containers.Map` of tag keys to `MonitorTag` objects and a `DataSourceMap`. ```matlab -% Create pipeline -pipeline = LiveEventPipeline(sensors, dsMap, ... - 'EventFile', 'live_events.mat', ... - 'Interval', 15, ... % 15-second polling - 'MinDuration', 5, ... % 5-second minimum events - 'EscalateSeverity', true); % H -> HH escalation - -% Configure notifications -notifService = NotificationService('DryRun', true); -pipeline.NotificationService = notifService; - -% Start/stop live monitoring -pipeline.start(); % begins timer-driven cycles -pipeline.stop(); % stops timer -``` +% Suppose 'monitors' is a containers.Map with keys -> MonitorTag instances +pipeline = LiveEventPipeline(monitors, dsMap, ... + 'Interval', 15, ... % poll every 15 seconds + 'MinDuration', 5, ... % 5‑second minimum events + 'EscalateSeverity', true); -### Incremental Detection +% Attach optional storage and notification +pipeline.EventStore = EventStore('live_events.mat', 'MaxBackups', 3); +pipeline.NotificationService = NotificationService('DryRun', true); -For live scenarios, use [[Event Detection|IncrementalEventDetector]] to maintain state between updates: +% Start / stop +pipeline.start(); % begins timer‑driven cycles +pipeline.stop(); % halts timer +``` -```matlab -detector = IncrementalEventDetector('MinDuration', 2, ... - 'EscalateSeverity', true); +During each `runCycle()`, the pipeline: -% Process incremental updates -newEvents = detector.process('temp_01', sensor, newX, newY, [], []); +1. Fetches new data from every `DataSource`. +2. Calls `MonitorTag.Parent.updateData(newX, newY)` then `MonitorTag.appendData(newX, newY)` (order matters – see Pitfall Y in source comments). +3. Uses an internal `EventDetector` to find events from threshold crossings. +4. Appends new events to the `EventStore` and dispatches notifications. -% Check for ongoing events -if detector.hasOpenEvent('temp_01') - state = detector.getSensorState('temp_01'); - fprintf('Open event since %.2f\n', state.openEventStart); -end -``` +> **Legacy Note:** `IncrementalEventDetector.process` is a stub. For incremental detection use `MonitorTag.appendData` via `LiveEventPipeline`. ## Event Storage and Persistence -### EventStore - Atomic File Operations +### EventStore -The [[Event Detection|EventStore]] provides thread-safe event persistence: +`EventStore` provides atomic read/write of events to a shared `.mat` file with backup rotation. ```matlab -% Create event store -store = EventStore('events.mat', 'MaxBackups', 3); - -% Configure metadata for EventViewer -store.SensorData = cfg.SensorData; % for click-to-plot -store.ThresholdColors = cfg.ThresholdColors; % for color consistency +store = EventStore('events.mat', 'MaxBackups', 5); +store.append(newEvents); % atomic file operation +store.save(); % or call explicitly -% Append new events (atomic operation) -store.append(newEvents); -store.save(); +% Attach metadata for EventViewer +store.SensorData = cfg.SensorData; +store.ThresholdColors = cfg.ThresholdColors; -% Load from file (static method) -[events, metadata, changed] = EventStore.loadFile('events.mat'); +% Load events from file later +[events, meta, changed] = EventStore.loadFile('events.mat'); ``` -### Auto-Save Configuration +**Key methods:** -EventConfig can automatically save events to a file: +- `append(newEvents)` – appends events, auto‑assigns unique `Id`, updates timestamp. +- `getEvents()` – returns all events in memory. +- `closeEvent(eventId, endTime, finalStats)` – closes an open event (does **not** auto‑save). +- `save()` – writes to disk. +- `static loadFile(filePath)` – returns events, metadata, and a `changed` flag. -```matlab -cfg.EventFile = 'auto_events.mat'; % Enable auto-save -cfg.MaxBackups = 5; % Backup rotation +### Auto‑save in EventConfig -% Events saved automatically after cfg.runDetection() -events = cfg.runDetection(); -``` +Set `EventFile` and `MaxBackups`; `runDetection()` will save automatically. -## Event Visualization +```matlab +cfg.EventFile = 'auto_events.mat'; +cfg.MaxBackups = 5; +events = cfg.runDetection(); % events saved, backups rotated +``` -### EventViewer - Interactive Timeline +## Event Visualization – EventViewer -The [[Event Detection|EventViewer]] provides a Gantt timeline and filterable table: +`EventViewer` displays a Gantt timeline and a filterable table. ```matlab % Create viewer with full context viewer = EventViewer(events, sensorData, thresholdColors); -% Or load from saved file +% Or open from a saved EventStore file viewer = EventViewer.fromFile('events.mat'); -% Auto-refresh from file -viewer.startAutoRefresh(10); % refresh every 10 seconds +% Auto‑refresh from the same file (e.g., every 10 seconds) +viewer.startAutoRefresh(10); viewer.stopAutoRefresh(); -% Manual refresh -viewer.refreshFromFile(); - -% Update with new events +% Update with new events programmatically viewer.update(newEvents); ``` -The EventViewer features: -- **Gantt timeline**: Visual event bars colored by threshold -- **Filterable table**: Filter by sensor, threshold, date range -- **Click interaction**: Click Gantt bars to highlight table rows -- **Auto-refresh**: Polls the source file for live updates -- **Export**: Context menu options for data export +**Features:** + +- **Gantt timeline** – coloured bars, one row per sensor, bar width = event duration. +- **Filterable table** – filter by sensor name, threshold label, date range. +- **Click‑to‑highlight** – clicking a bar highlights the corresponding table row. +- **Auto‑refresh** – polls the source file for changes. +- **Export** – context menu options for data export. ## Notification System ### Notification Rules -Configure rule-based notifications with priority matching: +`NotificationRule` defines a matching condition, template, and delivery options. Rules are scored: 3 = exact match (sensor + threshold), 2 = sensor match only, 1 = default. ```matlab -% Default rule (catches all events) +% Default rule (catches anything) defaultRule = NotificationRule('Recipients', {{'ops@company.com'}}, ... 'Subject', 'Event: {sensor} - {threshold}', ... 'IncludeSnapshot', false); -% Sensor-specific rule (higher priority) +% Sensor‑specific rule tempRule = NotificationRule('SensorKey', 'temperature', ... 'Recipients', {{'thermal@company.com'}}, ... 'Subject', 'Temperature Event: {threshold}', ... 'IncludeSnapshot', true, ... 'ContextHours', 2); -% Exact match rule (highest priority) +% Exact match (sensor + threshold) criticalRule = NotificationRule('SensorKey', 'temperature', ... 'ThresholdLabel', 'critical', ... - 'Recipients', {{'safety@company.com', 'manager@company.com'}}, ... + 'Recipients', {{'safety@company.com','manager@company.com'}}, ... 'Subject', 'CRITICAL: {sensor} {threshold}!'); ``` +### Template Variables + +Use curly‑brace placeholders in `Subject` and `Message`: + +- `{sensor}`, `{threshold}`, `{direction}`, `{peak}` +- `{startTime}`, `{endTime}`, `{duration}` +- `{mean}`, `{std}`, `{min}`, `{max}`, `{rms}` + ### NotificationService -The [[Event Detection|NotificationService]] manages rule-based notifications: +The `NotificationService` holds rules, handles snapshot generation, and sends emails. ```matlab -notif = NotificationService('DryRun', true, ... % test mode +notif = NotificationService('DryRun', true, ... % test mode 'SnapshotDir', 'snapshots/', ... 'SmtpServer', 'mail.company.com'); @@ -258,153 +256,96 @@ notif.setDefaultRule(defaultRule); notif.addRule(tempRule); notif.addRule(criticalRule); -% Notify on event (called by pipeline) +% On each event (called by pipeline) notif.notify(event, sensorData); ``` -### Email Templates - -Notification templates support variable substitution: - -```matlab -rule = NotificationRule( ... - 'Subject', 'Alert: {sensor} exceeded {threshold}', ... - 'Message', ['Sensor: {sensor}\n' ... - 'Threshold: {threshold} ({direction})\n' ... - 'Time: {startTime} to {endTime}\n' ... - 'Duration: {duration}\n' ... - 'Peak: {peak}\n' ... - 'Statistics: mean={mean}, std={std}']); -``` - -Available template variables: -- `{sensor}`, `{threshold}`, `{direction}`, `{peak}` -- `{startTime}`, `{endTime}`, `{duration}` -- `{mean}`, `{std}`, `{min}`, `{max}`, `{rms}` - -### Event Snapshots - -Generate PNG snapshots showing event context: - -```matlab -% Generate detail and context plots -files = generateEventSnapshot(event, sensorData, ... - 'OutputDir', 'snapshots/', ... - 'SnapshotSize', [800, 400], ... - 'Padding', 0.1, ... % 10% padding around event - 'ContextHours', 2); % 2 hours before event - -% Returns: {detailFile, contextFile} -``` +**Snapshots** are generated by `generateEventSnapshot` – two PNG files per event (detail and context). ## Severity Escalation -Events can escalate to higher severity levels when peaks exceed multiple thresholds: +When a sensor has multiple thresholds (e.g., `'Warning'` at 85, `'Alarm'` at 95), enabling `EscalateSeverity` causes an event that starts at a lower threshold but peaks above a higher one to be escalated **in place**. The original time span is kept, but label and value change to the higher threshold. ```matlab -% Configure escalation -detector = EventDetector('EscalateSeverity', true); - -% Sensor with multiple thresholds -sensor.addThresholdRule(struct(), 85, 'Label', 'H Warning'); -sensor.addThresholdRule(struct(), 95, 'Label', 'HH Alarm'); - -% If violation starts at 87 (H Warning) but peaks at 97: -% 1. Initial event: "H Warning" -% 2. Escalated event: "HH Alarm" (same time span, higher severity) -events = detectEventsFromSensor(sensor, detector); +det = EventDetector('EscalateSeverity', true); +% … event starts at 87 (Warning) but peak is 97 → escalated to Alarm ``` +This behaviour can be controlled via `EventConfig.EscalateSeverity` or `EventDetector` constructor. + ## Utility Functions -### Event Logging +### `eventLogger()` – Console callback -Simple console logging for development: +Returns a function handle that prints a one‑line log: ```matlab cfg.OnEventStart = eventLogger(); - -% Logs: [EVENT] Temperature | temp high | UPPER | 123.45 -> 125.67 (dur=0.02) | peak=126.83 +% Prints: [EVENT] Temperature | temp high | UPPER | 123.45 -> 125.67 (dur=0.02) | peak=126.83 ``` -### Event Summary - -Formatted console output for analysis: +### `printEventSummary(events)` – Tabular summary ```matlab printEventSummary(events); - -% Outputs table with columns: -% Start | End | Duration | Sensor | Threshold | Dir | Peak | #Pts | Mean | Std +% Outputs a formatted table with Start, End, Duration, Sensor, Threshold, Dir, Peak, #Pts, Mean, Std ``` -### Bridging with Sensors - -Convert from sensor violations to events: +### `generateEventSnapshot(event, sensorData)` – Graphical snapshots ```matlab -% Uses sensor.ResolvedViolations and sensor.ResolvedThresholds -events = detectEventsFromSensor(sensor); -events = detectEventsFromSensor(sensor, customDetector); +files = generateEventSnapshot(event, sensorData, ... + 'OutputDir', 'snapshots/', ... + 'ContextHours', 2); +% Returns {detailFile, contextFile} ``` +### Bridging with Sensors + +If you have a `Sensor` with `ResolvedViolations` and `ResolvedThresholds`, the function `detectEventsFromSensor` (from the [[Sensors]] library) converts them directly to `Event` objects. + ## Performance Considerations -- **MinDuration**: Use appropriate debounce times to filter noise -- **MaxCallsPerEvent**: Limit callback overhead in high-frequency scenarios -- **Backup rotation**: Configure MaxBackups to manage disk usage -- **Incremental detection**: Use IncrementalEventDetector for live scenarios to avoid reprocessing -- **File polling**: Balance refresh intervals with system load -- **Snapshot generation**: PNG creation can be expensive; use sparingly +- **MinDuration** – debounce noise with a reasonable minimum (seconds). +- **MaxCallsPerEvent** – limit callback overhead in high‑frequency streams. +- **MaxBackups** – control disk usage. +- **EventStore** – atomic writes, but only as often as needed; call `save()` sparingly. +- **Snapshot generation** – PNG creation is expensive; use only for high‑severity events or throttled intervals. +- **Live pipeline** – balance polling interval (`Interval`) against system load. ## Common Patterns -### Multi-Sensor Dashboard with Events +### Multi‑sensor batch analysis ```matlab -% Configure multiple sensors cfg = EventConfig(); -cfg.addSensor(temperatureSensor); -cfg.addSensor(pressureSensor); -cfg.addSensor(vibrationSensor); +cfg.Sensors = {tempSensor, pressSensor, vibSensor}; cfg.AutoOpenViewer = true; - -% Run detection and view results events = cfg.runDetection(); ``` -### Live Monitoring with Notifications +### Live monitoring with persistence and alerts ```matlab -% Set up complete live pipeline -pipeline = LiveEventPipeline(sensors, dataSourceMap, ... - 'EventFile', 'monitoring.mat', ... - 'Interval', 30); - -% Configure notifications +pipeline = LiveEventPipeline(monitors, dsMap, ... + 'Interval', 30, 'EventStore', store); pipeline.NotificationService = notificationService; - -% Start monitoring pipeline.start(); ``` -### Event Analysis Workflow +### Retrospective analysis from saved store ```matlab -% Load saved events viewer = EventViewer.fromFile('historical_events.mat'); - -% Analyze programmatically [events, meta] = EventStore.loadFile('historical_events.mat'); -tempEvents = events(strcmp({events.SensorName}, 'Temperature')); -criticalEvents = events(strcmp({events.ThresholdLabel}, 'critical')); - -printEventSummary(criticalEvents); +% programmatic filtering +criticals = events(strcmp({events.ThresholdLabel}, 'critical')); ``` ## See Also -- [[Sensors]] - Configure thresholds and violations -- [[Live Mode Guide]] - Real-time data streaming patterns -- [[Dashboard Engine Guide]] - Multi-plot coordination -- [[Examples]] - Complete working examples +- [[Sensors]] – Configure thresholds and violations +- [[Live Mode Guide]] – Real‑time data streaming patterns +- [[Dashboard Engine Guide]] – Multi‑plot coordination +- [[Event Detection|API Reference: Event Detection]] – Detailed class and method documentation +- [[Examples]] – Complete working examples diff --git a/wiki/Live-Mode-Guide.md b/wiki/Live-Mode-Guide.md index 0e25d85a..aab96d68 100644 --- a/wiki/Live-Mode-Guide.md +++ b/wiki/Live-Mode-Guide.md @@ -2,15 +2,16 @@ # Live Mode Guide -FastSense supports live data visualization by polling a .mat file for updates and auto-refreshing the display. Live mode works with single plots, tiled dashboards, and tabbed docks. +FastSense supports live data visualization by continuously polling a `.mat` file for new data and auto-refreshing the display. Live mode works with single plots, tiled dashboards ([[Dashboard|API Reference: Dashboard]]), and tabbed docks ([[FastSenseDock|API Reference: FastSenseDock]]). It can also update per‑point metadata from a companion file. --- ## Basic Live Plot -```matlab -install; +1. **Create a plot** (add lines, thresholds, etc.) and call `render()`. +2. **Start live polling** with `startLive()`, providing a `.mat` file path and a callback that handles the newly loaded data. +```matlab % Create initial plot fp = FastSense('Theme', 'dark'); x = linspace(0, 10, 1e5); @@ -23,10 +24,12 @@ fp.render(); fp.startLive('data.mat', @(fp, s) fp.updateData(1, s.x, s.y)); ``` -The callback `@(fp, s) fp.updateData(1, s.x, s.y)` is called every poll cycle: -- `fp` — the FastSense instance -- `s` — struct loaded from the .mat file -- `fp.updateData(lineIdx, newX, newY)` — replaces line data and re-renders +The callback receives: + +- `fp` – the FastSense instance +- `s` – struct loaded from the `.mat` file + +In the callback you typically call `fp.updateData(lineIdx, newX, newY)` to replace the line data and re‑render. ### Stopping Live Mode @@ -34,25 +37,29 @@ The callback `@(fp, s) fp.updateData(1, s.x, s.y)` is called every poll cycle: fp.stopLive(); ``` -Or use the Live Mode button in the toolbar. +The live timer stops immediately. You can also use the **Live Mode** toggle button in the [[FastSenseToolbar|API Reference: FastSenseToolbar]]. --- ## View Modes -Control how the view updates when new data arrives: +Control how the X‑axis reacts when new data arrives: + +| Mode | Behavior | +|-------------|----------| +| `'preserve'` | Keep the user’s current zoom/pan position. The view is not disturbed. | +| `'follow'` | Scroll the X‑axis to show the latest data. Ideal for monitoring. | +| `'reset'` | Zoom out to show all data. Good for occasional overviews. | +| `''` (empty) | No automatic view adjustment. Equivalent to avoid changing limits. | -| Mode | Behavior | -|------|----------| -| `'preserve'` | Keep current zoom/pan position. User's view is not disturbed | -| `'follow'` | Scroll X-axis to show the latest data. Good for monitoring | -| `'reset'` | Zoom to show all data. Good for overview | +Set the view mode at startup: ```matlab fp.startLive('data.mat', @updateFcn, 'ViewMode', 'follow'); ``` -Change view mode while running: +Change it while live mode is running: + ```matlab fp.setViewMode('follow'); fp.setViewMode('preserve'); @@ -62,18 +69,32 @@ fp.setViewMode('preserve'); ## Polling Interval -Default is 2 seconds. Adjust with the 'Interval' option: +The default polling interval is **2 seconds**. Adjust with the `'Interval'` parameter (in seconds): + +```matlab +fp.startLive('data.mat', @updateFcn, 'Interval', 0.5); % every half‑second +fp.startLive('data.mat', @updateFcn, 'Interval', 5); % every 5 seconds +``` + +--- + +## Manual Refresh + +Trigger a one‑shot data reload without starting a timer: ```matlab -fp.startLive('data.mat', @updateFcn, 'Interval', 0.5); % Poll every 500ms -fp.startLive('data.mat', @updateFcn, 'Interval', 5); % Poll every 5 seconds +fp.refresh(); ``` +This loads the file set in `LiveFile`, calls `LiveUpdateFcn`, reloads metadata if configured, and updates the display once. The timestamp `LiveFileDate` is updated so that the next `refresh()` only pulls new data when the file has actually changed. + --- -## Live Dashboard +## Live Dashboard (FastSenseGrid) + +[[FastSenseGrid|API Reference: Dashboard]] orchestrates live mode across all tiles of a dashboard. -FastSenseGrid supports live mode across all tiles: +### Setting Up ```matlab fig = FastSenseGrid(2, 2, 'Theme', 'dark'); @@ -85,12 +106,13 @@ fp4 = fig.tile(4); fp4.addLine(x, y4, 'DisplayName', 'Vibration'); fig.renderAll(); -% Start live mode on the entire dashboard +% Start live mode on the whole dashboard fig.startLive('sensors.mat', @(fig, s) updateDashboard(fig, s), ... 'Interval', 2, 'ViewMode', 'follow'); ``` -Update callback for dashboard: +The update callback needs to push data to each tile: + ```matlab function updateDashboard(fig, s) fig.tile(1).updateData(1, s.t, s.pressure); @@ -100,73 +122,62 @@ function updateDashboard(fig, s) end ``` +`fig.startLive()` sets `LiveFile`, `LiveUpdateFcn`, and `LiveInterval` on the grid, starts a timer that calls the callback, and propagates `ViewMode` to all tiles. + +### Controlling the Dashboard + +- `fig.stopLive()` stops the timer. +- `fig.refresh()` – manual one‑shot update. +- `fig.setViewMode('follow')` – sets the view mode on every tile. + --- ## Live Mode with Metadata -Attach metadata that updates on each poll: +You can attach metadata (e.g., sensor units, calibration info) that updates together with the data. The metadata is loaded from a *separate* `.mat` file. -```matlab -fp.startLive('data.mat', @updateFcn, ... - 'MetadataFile', 'meta.mat', ... - 'MetadataVars', {'units', 'calibration'}); -``` - -The metadata is loaded from a separate file and attached to the specified line and tile: +### Per‑Plot Metadata ```matlab -fig.MetadataFile = 'metadata.mat'; -fig.MetadataVars = {'sensor_id', 'location', 'units'}; -fig.MetadataLineIndex = 1; % which line within the tile -fig.MetadataTileIndex = 1; % which tile to attach to +fp.LiveFile = 'data.mat'; +fp.LiveUpdateFcn = @updateFcn; +fp.MetadataFile = 'meta.mat'; +fp.MetadataVars = {'units', 'calibration'}; % variable names to extract +fp.MetadataLineIndex = 1; % which line to attach to + +fp.startLive(); ``` ---- +The metadata is attached to the specified line as a `struct` and can be queried with `fp.lookupMetadata()`. When a metadata file is configured, `refresh()` reloads it automatically. -## Live Event Detection +### Dashboard Metadata -Combine live mode with event detection for real-time monitoring using the LiveEventPipeline: +For a grid dashboard, set the properties before calling `startLive`: ```matlab -% Create sensors with thresholds -tempSensor = Sensor('temperature', 'Name', 'Temperature'); -tempSensor.addThresholdRule(struct(), 78, 'Direction', 'upper', 'Label', 'Hi Warn'); -tempSensor.addThresholdRule(struct(), 82, 'Direction', 'upper', 'Label', 'Hi Alarm'); -tempSensor.resolve(); - -sensors = containers.Map(); -sensors('temperature') = tempSensor; - -% Configure data sources -dsMap = DataSourceMap(); -dsMap.add('temperature', MockDataSource('BaseValue', 70, 'NoiseStd', 2)); - -% Set up pipeline with event store -pipeline = LiveEventPipeline(sensors, dsMap, ... - 'EventFile', 'events.mat', ... - 'Interval', 15, ... - 'MinDuration', 0.5); - -% Start live event detection -pipeline.start(); +fig.MetadataFile = 'metadata.mat'; +fig.MetadataVars = {'sensor_id', 'location', 'units'}; +fig.MetadataLineIndex = 1; % which line within the target tile +fig.MetadataTileIndex = 1; % which tile receives the metadata ``` +The metadata is forwarded to the tile’s `setLineMetadata()` on each poll. + --- ## Octave Compatibility -GNU Octave does not support MATLAB timers. Use `runLive()` for a blocking poll loop: +GNU Octave does not support MATLAB `timer` objects. Use `runLive()` as a blocking alternative: ```matlab fp.render(); - -% Blocking loop — press Ctrl+C to stop fp.LiveFile = 'data.mat'; fp.LiveUpdateFcn = @(fp, s) fp.updateData(1, s.x, s.y); -fp.runLive(); +fp.runLive(); % blocks until Ctrl+C or LiveIsActive becomes false ``` For dashboards: + ```matlab fig.renderAll(); fig.LiveFile = 'data.mat'; @@ -174,22 +185,13 @@ fig.LiveUpdateFcn = @myUpdateFcn; fig.runLive(); ``` ---- - -## Manual Refresh - -Trigger a one-shot data reload without starting continuous polling: - -```matlab -fp.refresh(); -fig.refresh(); -``` +`runLive()` loops internally at the configured `LiveInterval`, stopping when the figure is closed or `stopLive()` is called. --- ## Toolbar Integration -The [[FastSenseToolbar|API Reference: FastSenseToolbar]] provides a Live Mode button: +[[FastSenseToolbar|API Reference: FastSenseToolbar]] provides toolbar buttons for live mode: ```matlab tb = FastSenseToolbar(fp); @@ -198,43 +200,41 @@ tb = FastSenseToolbar(fp); tb.toggleLive(); ``` -The Refresh button triggers a manual one-shot reload: -```matlab -tb.refresh(); -``` +The **Refresh** button calls `tb.refresh()`, triggering a manual one‑shot reload of data and metadata. --- ## Console Progress Bars -Use ConsoleProgressBar for visual feedback during long operations: +During long render operations you may see a console progress bar. You can also use `ConsoleProgressBar` manually for feedback in custom loops: ```matlab -pb = ConsoleProgressBar(2); % 2-space indent +pb = ConsoleProgressBar(2); % 2‑space indent pb.start(); for k = 1:8 pb.update(k, 8, 'Tile 1'); pause(0.1); end -pb.freeze(); % becomes permanent line +pb.freeze(); % leave the final bar on the console ``` --- ## Tips -- Set `'ViewMode', 'follow'` for monitoring use cases where you always want to see the latest data -- Use `'preserve'` when users need to zoom into historical data while live updates continue -- Keep polling interval reasonable (1-5 seconds) to avoid overwhelming the file system -- The .mat file should be written atomically (write to temp file, then rename) to avoid partial reads -- Live mode works with linked axes — all linked plots update together -- Use `DeferDraw = true` to skip drawnow during batch render for better performance +- **Polling interval** – Keep it reasonable (1–5 s) to avoid filesystem stress. +- **Atomic writes** – Write the `.mat` file atomically (write to a temp file, then rename) to prevent partial reads. +- **View mode** – Use `'follow'` for real‑time monitoring, `'preserve'` when users are inspecting historical data while live updates continue. +- **Deferred rendering** – Set `DeferDraw = true` on the plot or grid to skip `drawnow` during batch updates, improving performance. +- **Metadata** – Metadata files can be updated independently from the main data file; they are reloaded on every poll or manual refresh. +- **Linked axes** – Live mode works with linked axes – all linked plots update together. --- ## See Also -- [[API Reference: FastPlot]] — startLive(), stopLive(), updateData() methods -- [[API Reference: Dashboard]] — Dashboard live mode -- [[API Reference: Event Detection]] — Live event detection -- [[Examples]] — example_dashboard_live.m, example_live_pipeline.m +- [[API Reference: FastPlot]] – `startLive()`, `stopLive()`, `refresh()`, `updateData()`, `setViewMode()` +- [[API Reference: Dashboard]] – dashboard live mode (`FastSenseGrid.startLive`) +- [[API Reference: FastSenseDock]] – tabbed dock live mode +- [[API Reference: FastSenseToolbar]] – Live Mode toggle and Refresh buttons +- [[Examples]] – `example_dashboard_live.m`, `example_live_pipeline.m` diff --git a/wiki/MEX-Acceleration.md b/wiki/MEX-Acceleration.md index 1d99b22c..8de1b0f5 100644 --- a/wiki/MEX-Acceleration.md +++ b/wiki/MEX-Acceleration.md @@ -2,110 +2,144 @@ # MEX Acceleration -FastSense includes optional C MEX functions with SIMD intrinsics for maximum performance. All MEX functions have pure-MATLAB fallbacks — behavior is identical. +FastSense includes optional C MEX functions that leverage SIMD intrinsics for maximum performance. Every MEX function has a pure‑MATLAB fallback — behavior and results are identical whether the MEX is available or not. + +Use MEX acceleration when you work with large datasets (millions of points) and need fluid zoom/pan in dashboards. On systems without a C compiler, the fallback still provides excellent performance for datasets under ~10M points. ## Building MEX Files +From the root of the FastSense library (the directory containing `install.m`), run: + ```matlab cd libs/FastSense build_mex(); ``` -The build script auto-detects your architecture and compiles all MEX functions with appropriate SIMD optimizations. +The build script auto-detects your CPU architecture and compiles all MEX source files with the appropriate SIMD flags. ### Requirements -| Platform | Compiler | -|----------|----------| -| macOS | Xcode Command Line Tools | -| Linux | GCC | -| Windows | MSVC | +| Platform | Compiler | +|------------|--------------------------------------------| +| macOS | Xcode Command Line Tools (Clang) | +| Linux | GCC (real GCC, not Clang‑as‑gcc) | +| Windows | Microsoft Visual C++ (MSVC) | -SQLite3 is bundled as an amalgamation and compiled directly into MEX files that need it — no system installation required. +SQLite3 is bundled as the [amalgamation](https://www.sqlite.org/amalgamation.html) (`sqlite3.c` + `sqlite3.h`) in `private/mex_src/` and compiled directly into the MEX files that require it — no system `libsqlite3` installation is necessary. ## Architecture Support -All MEX functions include a common SIMD abstraction layer that adapts to your CPU: +All MEX functions share a common SIMD abstraction layer that adapts to the detected CPU: -| Architecture | SIMD Instructions | Fallback | -|-------------|------------------|----------| -| x86_64 | AVX2 + FMA | SSE2 | -| ARM64 (Apple Silicon) | NEON | - | -| Other | Scalar operations | - | +| Architecture | SIMD Instructions | Fallback | +|-------------------------|-------------------|----------------| +| x86‑64 (Intel/AMD) | AVX2 + FMA | SSE2 | +| ARM64 (Apple Silicon) | NEON (implicit) | – | +| Other | Scalar operations | – | -If AVX2 compilation fails on x86_64, the build script automatically retries with SSE2. +If the initial AVX2 compilation fails on x86‑64 (e.g., on older hardware without AVX2 support), the build script automatically retries with SSE2 flags. On ARM64, NEON is enabled by default by both Clang (macOS) and GCC (Linux/Octave). ## Accelerated Functions +The following MEX files are compiled by `build_mex()`: + ### Core Downsampling -**binary_search_mex** — O(log n) binary search for visible data range -- **Speedup**: 10-20x over MATLAB's `find` -- **Used by**: Zoom/pan callbacks to locate visible indices +- **`binary_search_mex`** — `O(log n)` binary search for the visible data range. + Speedup: 10–20× over MATLAB’s `find`. + Used in: zoom/pan callbacks to locate visible indices. + See [[FastPlot|API Reference: FastPlot]] render pipeline. -**minmax_core_mex** — Per-pixel MinMax reduction with SIMD vectorization -- **Speedup**: 3-10x over pure MATLAB -- **SIMD**: Processes 4 doubles (AVX2) or 2 doubles (NEON) per cycle -- **Used by**: Default downsampling algorithm in [[FastPlot|API Reference: FastPlot]] +- **`minmax_core_mex`** — Per‑pixel MinMax reduction with SIMD vectorisation. + Speedup: 3–10× over pure MATLAB. + SIMD: Processes 4 doubles (AVX2) or 2 doubles (NEON) per cycle. + Used by: default MinMax downsampling in [[FastPlot|API Reference: FastPlot]]. -**lttb_core_mex** — Largest Triangle Three Buckets with SIMD triangle area computation -- **Speedup**: 10-50x over MATLAB implementation -- **Used by**: LTTB downsampling method +- **`lttb_core_mex`** — Largest‑Triangle‑Three‑Buckets kernel using SIMD triangle area computation. + Speedup: 10–50× over the MATLAB implementation. + Used by: LTTB downsampling method. ### Threshold Processing -**violation_cull_mex** — Fused threshold violation detection and pixel culling -- **Speedup**: Significant (single-pass vs two-pass MATLAB) -- **Used by**: Violation marker rendering during zoom/pan +- **`violation_cull_mex`** — Fused threshold violation detection and pixel culling in a single pass. + Speedup: Significant (single‑pass vs two‑pass MATLAB). + Used by: violation marker rendering during zoom/pan. + +- **`compute_violations_mex`** — Batch threshold violation detection for all thresholds. + Speedup: Significant over per‑point MATLAB comparisons. + Used by: [[Sensors|API Reference: Sensors]] resolution pipeline. + +### Data Storage (SQLite Backend) + +- **`build_store_mex`** — Bulk SQLite writer for `FastSenseDataStore` initialisation. + Speedup: 2–3× (eliminates ~20K MATLAB‑to‑MEX round‑trips). + SIMD: Accelerated Y min/max computation per chunk. + Used by: `FastSenseDataStore` constructor. -**compute_violations_mex** — Batch threshold violation detection -- **Speedup**: Significant over per-point MATLAB comparison -- **Used by**: [[Sensors|API Reference: Sensors]] resolution pipeline +- **`resolve_disk_mex`** — Disk‑based sensor resolution for large datasets. + Used by: `Sensor.resolve()` with disk‑backed storage. + Benefit: Reads chunks directly from the database without loading full arrays into memory. -### Data Storage +- **`mksqlite`** — SQLite3 MEX interface with typed BLOB support. + Used by: `DataStore`, disk‑backed sensor resolution. + Features: Serialises MATLAB arrays while preserving type and shape; supports WAL mode for concurrent reads. -**build_store_mex** — Bulk SQLite writer for DataStore initialization -- **Speedup**: 2-3x (eliminates ~20K MATLAB-to-MEX round-trips) -- **SIMD**: Accelerated Y min/max computation per chunk -- **Used by**: `FastSenseDataStore` construction +### Helpers -**resolve_disk_mex** — SQLite disk-based sensor resolution -- **Used by**: `Sensor.resolve()` with disk-backed storage -- **Benefit**: Reads chunks from database without loading full datasets +- **`to_step_function_mex`** — SIMD‑accelerated conversion of time‑varying thresholds into step functions. + Used by: threshold rendering. -**mksqlite** — SQLite3 MEX interface with typed BLOB support -- **Used by**: DataStore, disk-backed sensor resolution -- **Features**: Serializes MATLAB arrays preserving type and shape +All MEX functions have corresponding pure‑MATLAB implementations in `libs/FastSense/private/`. A runtime check selects MEX when available; otherwise the MATLAB fallback executes. ## Fallback Behavior -When MEX files are unavailable: +When MEX files are not available (compilation failed, platform without a C compiler, or MEX not on the path): -- Each function has a pure-MATLAB equivalent in `libs/FastSense/private/` -- Runtime auto-detection switches between MEX and MATLAB seamlessly -- Identical numerical results and API -- Performance remains excellent for datasets under ~10M points +- Each function uses its pure‑MATLAB equivalent. +- The runtime auto‑detection is done once per session (e.g., `binary_search.m` checks `exist('binary_search_mex', 'file')` and caches the result in a persistent variable). +- Numerical results and API are **identical** to the MEX versions. +- Performance remains excellent for datasets under ~10 million points. + +No user intervention is required; the library degrades gracefully. ## Compilation Process -The `build_mex()` function: +The `build_mex()` function performs the following steps: + +1. **Architecture detection** – normalises platform strings (`maca64`, `aarch64‑…`, `glnxa64`, etc.) into canonical labels: `x86_64`, `arm64`, or `unknown`. +2. **Compiler selection** – + - **Octave**: searches for a real GCC binary (preferring Homebrew `gcc‑15` down to `gcc‑10`) because GCC provides better auto‑vectorisation than Octave’s default Clang. Falls back to the system default if none is found. + - **MATLAB**: uses the configured default compiler (Xcode Clang on macOS, MSVC on Windows) because MATLAB passes compiler‑specific linker flags that GCC may reject. +3. **SIMD flag selection** – chooses instruction set flags based on the canonical architecture: + - `x86_64` → `-mavx2 -mfma -O3 -ffast-math` (MSVC: `/arch:AVX2 /fp:fast`) + - `arm64` → NEON is enabled implicitly by Clang; GCC gets `-mcpu=apple-m3` on Apple Silicon. + - `unknown` → scalar‑only `-O3 -ffast-math`. +4. **Compilation of individual MEX files** – iterates through the list of source files, skipping any that already exist (based on timestamp, with a backup mtime‑based skip gate; see `build_mex.m` for details). + - Each MEX is linked with the required extra sources (e.g., `sqlite3.c` for database‑related MEX files). + - If a compilation fails on x86‑64 with AVX2 flags, it is automatically retried with SSE2 flags. +5. **Compilation of `mksqlite`** – builds the SQLite3 MEX interface using the same SIMD flags and the bundled sqlite3 amalgamation. +6. **Copy shared MEX files** – distributes `violation_cull_mex`, `compute_violations_mex`, `resolve_disk_mex`, and `to_step_function_mex` to the `SensorThreshold/private/` directory so they are usable by the sensor resolution subsystem. -1. **Detects architecture** — normalizes platform strings (`maca64`, `aarch64`, etc.) into canonical labels -2. **Selects compiler** — prefers GCC on Octave for better auto-vectorization; uses MATLAB's default on MATLAB -3. **Sets SIMD flags** — chooses instruction sets based on detected CPU architecture -4. **Compiles sources** — builds all MEX files with bundled SQLite3 amalgamation -5. **Handles failures** — automatically retries x86_64 builds with SSE2 if AVX2 fails -6. **Copies shared files** — distributes MEX binaries to other library directories +The build system is self‑contained; it never relies on a system‑wide `libsqlite3`. Octave uses `mkoctfile` while MATLAB uses the `mex` command. Platform‑specific extensions (`.mexa64`, `.mexmaci64`, etc.) are handled automatically. ## Verifying Installation -Test that MEX functions produce identical results to MATLAB fallbacks: +After a successful build, you can run the built‑in tests to confirm that MEX functions produce identical results to the MATLAB fallbacks: ```matlab -install; +install; % add FastSense to the path addpath('tests'); -test_mex_parity; % Verify MEX matches MATLAB output -test_mex_edge_cases; % Test edge cases (empty arrays, NaN, etc.) +test_mex_parity; % numerical comparison +test_mex_edge_cases; % empty arrays, NaN, single point, etc. ``` -The test suite validates numerical accuracy across all MEX functions and handles edge cases like empty arrays, single points, and NaN values. +The test suite verifies numerical accuracy across all MEX functions and exercises edge cases like empty inputs, single‑element vectors, and NaN values to ensure the fallbacks are robust. + +If any MEX file failed to compile, a warning is printed during `build_mex()` and the section “× failed” tells you which functions will fall back to MATLAB code. + +## Related Pages + +- [[Installation]] – initial setup and MEX compilation as part of `install.m` +- [[FastPlot|API Reference: FastPlot]] – the plot class that benefits most from MEX acceleration +- [[Sensors|API Reference: Sensors]] – uses `compute_violations_mex` and `resolve_disk_mex` for resolution +- [[Performance]] – overall performance architecture and tuning diff --git a/wiki/Use-Case:-Multi-Sensor-Shared-Threshold.md b/wiki/Use-Case:-Multi-Sensor-Shared-Threshold.md index 6f4ebba4..8ef3cc6e 100644 --- a/wiki/Use-Case:-Multi-Sensor-Shared-Threshold.md +++ b/wiki/Use-Case:-Multi-Sensor-Shared-Threshold.md @@ -11,171 +11,228 @@ Plot multiple sensors on a single tile with one shared threshold, see violation ## Quick Example ```matlab -install; +% Add toolbox to path (adjust as needed) +addpath(genpath('libs')); -%% Create sensors with identical threshold rules -sensors = cell(1, 4); -names = {'Zone A', 'Zone B', 'Zone C', 'Zone D'}; -x = linspace(0, 60, 500000); +% Create three zone sensors with slightly different baselines +zones = {'North', 'Central', 'South'}; +t = linspace(0, 120, 50000); +sensors = cell(1, 3); +baseline = [22, 25, 23]; % zone baselines -for i = 1:4 - s = Sensor(sprintf('zone_%d', i), 'Name', names{i}); - s.X = x; - s.Y = sin(x * 2 * pi * i / 20) + 0.3 * randn(1, numel(x)) + 3; +for i = 1:3 + key = sprintf('zone_%s', zones{i}); + y = baseline(i) + 6*sin(2*pi*t/40) + 2*randn(1, numel(t)); + sensors{i} = SensorTag(key, 'X', t, 'Y', y, ... + 'Name', sprintf('Zone %s', zones{i}), 'Units', '°C'); +end - % Same threshold rule for every sensor - s.addThresholdRule(struct(), 4.5, 'Direction', 'upper', 'Label', 'Max Temp'); - s.resolve(); +% Shared threshold: identical condition function for all zones +threshold_value = 30; +condFn = @(x, y) y > threshold_value; - sensors{i} = s; +% Create a MonitorTag for each zone +monitors = cell(1,3); +for i = 1:3 + monitors{i} = MonitorTag(['mon_', sensors{i}.Key], sensors{i}, condFn, ... + 'MinDuration', 0.5); end -%% Plot all sensors on a single tile -fp = FastSense(); -for i = 1:numel(sensors) - % Show threshold line only for the first sensor to avoid duplicates - fp.addSensor(sensors{i}, 'ShowThresholds', (i == 1)); +% Plot each sensor and its binary alarm on the same axis +figure; +hold on; +colors = lines(3); +legEntries = []; +for i = 1:3 + [x, y] = sensors{i}.getXY(); + plot(x, y, 'DisplayName', sensors{i}.Name, 'Color', colors(i,:)); + % Binary alarm as shaded overlay or second y-axis? We'll use a second + % plot with offset to visualize. + + [~, bin] = monitors{i}.getXY(); + % Convert 0/1 to NaN/2 to show only alarm regions + alarmY = nan(size(bin)); + alarmY(bin==1) = 40 + i; % offset to separate visually + plot(x, alarmY, 'o', 'MarkerSize', 2, 'Color', colors(i,:), ... + 'HandleVisibility','off'); end -fp.render(); -title(fp.hAxes, 'Multi-Sensor — Shared Threshold'); -legend(fp.hAxes, 'show'); - -%% Detect events for all sensors -detector = EventDetector('MinDuration', 0.5); -allEvents = []; -for i = 1:numel(sensors) - evts = detectEventsFromSensor(sensors{i}, detector); - if ~isempty(evts) - allEvents = [allEvents, evts]; +yline(threshold_value, 'r--', 'Max Temp = 30 °C', 'LineWidth', 1.2); +xlabel('Time (s)'); ylabel('Temperature (°C)'); +title('Multi-Zone Shared Threshold'); +grid on; legend('show'); + +% Event detection: scan each monitor's binary series for contiguous runs +fprintf('Detected threshold violations:\n'); +for i = 1:3 + [x, bin] = monitors{i}.getXY(); + inEvent = false; + startT = NaN; + peakVal = -Inf; + for k = 1:length(bin) + if bin(k) == 1 + if ~inEvent + inEvent = true; + startT = x(k); + peakVal = sensors{i}.valueAt(x(k)); + else + peakVal = max(peakVal, sensors{i}.valueAt(x(k))); + end + else + if inEvent + inEvent = false; + fprintf(' %s: %.1f–%.1f s, peak %.2f°C\n', ... + sensors{i}.Name, startT, x(k), peakVal); + end + end + end + % Close event if still open at end + if inEvent + fprintf(' %s: %.1f–%.1f s (ongoing), peak %.2f°C\n', ... + sensors{i}.Name, startT, x(end), peakVal); end end -fprintf('Detected %d events across %d sensors.\n', numel(allEvents), numel(sensors)); ``` --- ## How It Works -### 1. Threshold on the Sensor, not just the plot - -Each `Sensor` gets the same `ThresholdRule`. When you call `resolve()`, each sensor independently computes: -- **ResolvedThresholds** — the threshold step-function line -- **ResolvedViolations** — the (X, Y) points that exceed the limit +### 1. One Condition, Many Monitors -This means every sensor carries its own violation data, which is required for event detection. - -### 2. Avoid duplicate threshold lines - -When calling `addSensor()`, pass `'ShowThresholds', true` only for the **first** sensor. All subsequent sensors use `'ShowThresholds', false`. This draws the threshold line once while still computing violations for every sensor. - -Alternatively, skip `ShowThresholds` entirely and add the threshold manually: +The threshold logic is captured in a single anonymous function (`y > 30`) that is reused identically for every `MonitorTag`. The `MonitorTag` constructor takes the parent `SensorTag` and the condition function, producing a binary 0/1 series aligned to the parent’s time grid. ```matlab -for i = 1:numel(sensors) - fp.addSensor(sensors{i}, 'ShowThresholds', false); -end -fp.addThreshold(4.5, 'Direction', 'upper', 'ShowViolations', true, ... - 'Label', 'Max Temp', 'Color', 'r'); +condFn = @(x, y) y > 30; +mon1 = MonitorTag('mon_North', sensorNorth, condFn); +mon2 = MonitorTag('mon_Central', sensorCentral, condFn); ``` -This approach draws a single shared threshold line and FastSense computes violation markers against **all lines** on the tile during rendering (see `updateViolations` in FastSense.m). +Each `MonitorTag` lazily memorizes its output. Calling `getXY()` returns the full binary vector. Because the condition is shared, the threshold semantics are identical — only the underlying sensor data differ. -### 3. Event detection works per-sensor +### 2. One Threshold Line, Multiple Violations -`detectEventsFromSensor()` reads each sensor's `ResolvedViolations` and groups consecutive violation points into `Event` objects. Since each sensor resolved independently, events are attributed to the correct sensor: +When plotting, draw the threshold line once (e.g., `yline()`). Each `MonitorTag` independently computes its own violation points. There is no need to toggle `ShowThresholds` as in the legacy system — the binary alarm data lives on the monitor. You can overlay violation markers from each monitor individually. -```matlab -% Each event knows which sensor it came from -for i = 1:numel(allEvents) - fprintf(' %s: %.1fs – %.1fs (peak %.2f)\n', ... - allEvents(i).SensorName, ... - allEvents(i).StartTime, allEvents(i).EndTime, ... - allEvents(i).PeakValue); -end -``` +### 3. Event Detection from Binary Series + +The binary output of each `MonitorTag` is a perfect source for event detection. In the example above, a simple state machine scans for runs of `1`s and reports start/end times and peak value. This can be enhanced by setting `MonitorTag.MinDuration` to merge short interruptions, or by binding an `EventStore` and callbacks (`OnEventStart`, `OnEventEnd`) for automatic event logging (requires the `EventDetection` package, not detailed here). + +**Key point:** Because each sensor has its own `MonitorTag`, events are automatically attributed to the correct sensor. --- ## With State-Dependent Thresholds -The shared threshold can also be state-dependent. Attach the same `StateChannel` and conditional `ThresholdRule` to each sensor: +A shared threshold that depends on operating mode can be implemented by injecting a `StateTag` into the condition function. The condition must evaluate the state at the current timepoint(s) using `StateTag.valueAt()`. ```matlab -for i = 1:numel(sensors) - sc = StateChannel('mode'); - sc.X = modeX; - sc.Y = modeValues; % same state for all sensors - sensors{i}.addStateChannel(sc); - - % Threshold only active during 'run' mode - sensors{i}.addThresholdRule(struct('mode', 'run'), 4.5, ... - 'Direction', 'upper', 'Label', 'Run HI'); - sensors{i}.resolve(); +% Create a state channel for machine mode +modeX = [0, 30, 60, 90, 120]; % transition times +modeY = {'idle', 'active', 'active', 'idle'}; % state values +stateTag = StateTag('machine_mode', 'X', modeX, 'Y', modeY); + +% Build a condition that raises alarm only in 'active' mode +threshold = 28; +condFn_active = @(x, y) y > threshold & ... + strcmp(stateTag.valueAt(x), 'active'); + +% Apply identical condition to all zones +for i = 1:3 + monitors{i} = MonitorTag(['mon_active_', sensors{i}.Key], ... + sensors{i}, condFn_active, 'MinDuration', 2.0); end ``` -Each sensor evaluates the same state conditions independently, so thresholds activate/deactivate synchronously across all sensors while maintaining individual violation tracking. +Because `stateTag.valueAt(x)` is vectorised, it works seamlessly with the vectorised condition function. Every monitor uses the same state tag to decide when the threshold is active, ensuring synchronised activation/deactivation. --- ## Complete Multi-Zone Example ```matlab -%% Multi-zone temperature monitoring with shared alarm level -sensors = cell(1, 3); -zones = {'North', 'Central', 'South'}; -t = linspace(0, 120, 50000); +%% Setup +t = linspace(0, 180, 60000); +zones = {'Extruder','Die','Chiller'}; +baseline = [200, 215, 180]; +noiseAmp = 3; + +% Create sensors +sensors = cell(1,3); +for i = 1:3 + y = baseline(i) + 10*sin(2*pi*t/50) + noiseAmp*randn(1,numel(t)); + sensors{i} = SensorTag(sprintf('temp_%s', zones{i}), ... + 'X', t, 'Y', y, 'Name', zones{i}, 'Units', '°C'); +end -% Create state channel for system mode -modeX = [0, 30, 60, 90]; -modeY = [0, 1, 1, 0]; % 0=idle, 1=active +% Shared high-limit +hi_limit = 210; +condHi = @(x,y) y > hi_limit; +% Mode state: maintenance [0–30, 120–150], otherwise production +modeX = [0, 30, 60, 120, 150, 180]; +modeY = [0, 1, 1, 0, 1, 0]; % 0=maintenance, 1=production +stateTag = StateTag('plant_mode', 'X', modeX, 'Y', modeY); + +% Conditional threshold: tighter limit during production +prod_limit = 205; +condProd = @(x,y) y > prod_limit & stateTag.valueAt(x) == 1; + +% Create monitors +monitorsHi = cell(1,3); +monitorsPr = cell(1,3); for i = 1:3 - s = Sensor(sprintf('temp_zone_%d', i), 'Name', sprintf('Zone %s', zones{i})); - s.X = t; - - % Each zone has different baseline but similar patterns - baseline = 20 + i * 2; - s.Y = baseline + 5*sin(2*pi*t/40) + 2*randn(1, numel(t)); - - % Add state channel - sc = StateChannel('system_mode'); - sc.X = modeX; - sc.Y = modeY; - s.addStateChannel(sc); - - % Shared threshold rules - s.addThresholdRule(struct(), 30, 'Direction', 'upper', 'Label', 'Max Temp (any mode)'); - s.addThresholdRule(struct('system_mode', 1), 28, 'Direction', 'upper', 'Label', 'Max Temp (active)'); - s.resolve(); - - sensors{i} = s; + monitorsHi{i} = MonitorTag(['hi_', sensors{i}.Key], sensors{i}, condHi, ... + 'MinDuration', 1.0); + monitorsPr{i} = MonitorTag(['prod_', sensors{i}.Key], sensors{i}, condProd, ... + 'MinDuration', 0.5); end -%% Plot with shared thresholds -fp = FastSense(); -for i = 1:numel(sensors) - fp.addSensor(sensors{i}, 'ShowThresholds', (i == 1)); +% Plot +figure; +subplot(2,1,1); hold on; +for i = 1:3 + plot(t, sensors{i}.Value, 'DisplayName', sensors{i}.Name); end -fp.render(); -title('Multi-Zone Temperature Monitoring'); -xlabel('Time (s)'); -ylabel('Temperature (°C)'); -legend('show'); +yline(hi_limit, 'r--', 'High Limit'); +yline(prod_limit, 'm--', 'Production Limit'); +ylabel('°C'); legend('show'); -%% Event detection -detector = EventDetector('MinDuration', 2.0); -allEvents = []; -for i = 1:numel(sensors) - evts = detectEventsFromSensor(sensors{i}, detector); - allEvents = [allEvents, evts]; +subplot(2,1,2); hold on; +for i = 1:3 + [~, bin] = monitorsProd{i}.getXY(); + stairs(t, bin + (i-1)*1.2, 'DisplayName', [sensors{i}.Name,' (prod alarm)']); end +ylim([-0.2 3.6]); xlabel('Time (s)'); ylabel('Alarm'); +legend('show'); -fprintf('\n=== Event Summary ===\n'); -for i = 1:numel(allEvents) - fprintf('%s: %s violation at %.1f-%.1fs (peak %.2f°C)\n', ... - allEvents(i).SensorName, allEvents(i).ThresholdLabel, ... - allEvents(i).StartTime, allEvents(i).EndTime, allEvents(i).PeakValue); +% Event detection (simple scan, production alarm only) +fprintf('Production-limit violations:\n'); +for i = 1:3 + [x, bin] = monitorsProd{i}.getXY(); + inEvent = false; + startT = NaN; + peakVal = -Inf; + for k = 1:length(bin) + if bin(k) == 1 + if ~inEvent + inEvent = true; + startT = x(k); + peakVal = sensors{i}.valueAt(x(k)); + else + peakVal = max(peakVal, sensors{i}.valueAt(x(k))); + end + else + if inEvent + fprintf(' %s: %.1f–%.1f s, peak %.1f°C\n', ... + sensors{i}.Name, startT, x(k), peakVal); + inEvent = false; + end + end + end + if inEvent + fprintf(' %s: %.1f–%.1f s (ongoing), peak %.1f°C\n', ... + sensors{i}.Name, startT, x(end), peakVal); + end end ``` @@ -185,14 +242,14 @@ end | Aspect | Behavior | |--------|----------| -| **Threshold line** | Drawn once (first sensor or manual `addThreshold`) | -| **Violation markers** | Computed for every line on the tile | -| **Event detection** | Per-sensor via `detectEventsFromSensor()` — requires `resolve()` on each sensor | -| **State-dependent thresholds** | Supported — attach identical `StateChannel` + `ThresholdRule` to each sensor | -| **EventViewer** | Shows events from all sensors, attributed by name | +| **Shared condition** | Reuse the same function handle when constructing `MonitorTag` per sensor | +| **Threshold line** | Drawn once with a single `yline()` (or `plot()`) | +| **Violation markers** | Binary 0/1 series from each `MonitorTag.getXY()` — overlapped on plot | +| **Event detection** | Simple state machine scanning binary series, or use `MonitorTag` built‑in event emission when `EventStore` is bound | +| **State‑dependent thresholds** | Use a `StateTag` inside the condition function; each monitor evaluates synchronised state | ## See Also -- [[Sensors|API Reference: Sensors]] — `Sensor`, `ThresholdRule`, `StateChannel` -- [[Event Detection|API Reference: Event Detection]] — `EventDetector`, `detectEventsFromSensor`, `EventViewer` -- [[Examples]] — `example_multi_sensor_linked`, `example_sensor_threshold`, `example_sensor_multi_state` +- [[Sensors | API Reference: Sensors]] — `SensorTag`, `MonitorTag`, `StateTag` +- [[Event Detection | API Reference: Event Detection]] — `EventDetector`, `detectEventsFromSensor`, `EventViewer` +- [[Examples]] — example scripts for multi‑sensor linked, sensor threshold, and multi‑state configurations. diff --git a/wiki/WebBridge-Guide.md b/wiki/WebBridge-Guide.md index f0c1e2df..47c7f5c4 100644 --- a/wiki/WebBridge-Guide.md +++ b/wiki/WebBridge-Guide.md @@ -2,241 +2,186 @@ # WebBridge Guide -WebBridge provides a powerful system for integrating FastPlot with web applications through a TCP-based communication protocol. It enables real-time data streaming, dashboard configuration synchronization, and remote control capabilities. +WebBridge provides a connectivity layer between MATLAB/FastPlot dashboards and external web applications. It streams signal data, synchronizes dashboard configuration, and allows remote code execution via a TCP-based protocol using NDJSON messages. ## Overview -WebBridge serves as a communication bridge between MATLAB/FastPlot and web clients, using NDJSON (Newline Delimited JSON) messages over TCP. This architecture allows: +WebBridge is a pure data relay – it does not render any UI. It exposes a TCP server inside MATLAB and launches a companion Python‑based bridge that translates the TCP stream into HTTP and WebSocket for web clients. -- Real-time streaming of sensor data to web applications -- Bidirectional dashboard configuration synchronization -- Remote action execution from web interfaces -- Multiple concurrent client connections +**When to use WebBridge** +- You need a live dashboard view in a web browser. +- You want to trigger MATLAB functions from a web interface. +- You need to stream sensor data to multiple remote consumers. ## Basic Usage -### Setting Up WebBridge +### Creating a Bridge and Starting the Server ```matlab -% Create a dashboard with some data +% Build a dashboard with one sensor dashboard = Dashboard(); -dashboard.addSensor('temperature', randn(1000, 1), 'units', '°C'); +dashboard.addSensor('temperature', randn(1000,1), 'units', '°C'); -% Create and start WebBridge +% Create the bridge and start all services bridge = WebBridge(dashboard); -bridge.serve(); % Starts both TCP server and data polling +bridge.serve(); % TCP server + Python HTTP/WS bridge on port 8080 ``` -### Configuration Options +The call to `serve()` is blocking only in the sense that it keeps the TCP server alive; you can continue to execute MATLAB commands in the same session while the bridge runs. -```matlab -% Customize polling interval for configuration changes -bridge = WebBridge(dashboard, 'ConfigPollInterval', 0.5); % Poll every 500ms +### Stopping the Bridge -% Manual control over TCP server -bridge = WebBridge(dashboard); -bridge.startTcp(); % Start TCP server only -% ... later ... -bridge.stop(); % Stop all services +```matlab +bridge.stop(); % clean shutdown of TCP server and Python bridge ``` ## Protocol Messages -WebBridge uses a structured message protocol for communication: +WebBridge uses **NDJSON** (Newline Delimited JSON). Internally the `WebBridgeProtocol` class encodes and decodes the following message types. + +### Initialization (`init`) -### Initialization Messages +Sent when a client first connects. It contains the list of signals (name, units, data type, …), the current dashboard configuration, and all registered actions. -When a client connects, WebBridge sends initialization data: +### Data Update (`data_changed`) + +Broadcast when data for one or more signals has changed. The message carries the identifiers of the affected signals. ```matlab -% The bridge automatically sends: -% - Signal definitions (names, units, data types) -% - Dashboard configuration (layouts, themes, etc.) -% - Available actions list +bridge.notifyDataChanged('temperature'); % single signal +bridge.notifyDataChanged({'temp','pressure','hum'}); % batch ``` -### Data Update Messages +### Action Change (`actions_changed`) -As data changes, WebBridge streams updates: +Sent whenever an action is registered or removed. The message lists the currently available action names. -```matlab -% Manually trigger data change notifications -bridge.notifyDataChanged('temperature'); % Single signal -bridge.notifyDataChanged({'temp', 'pressure'}); % Multiple signals -``` +### Action Result (`action_result`) -### Configuration Synchronization +Sent as a response after a client requests the execution of an action. It includes the request ID, the action name, a success flag, and any returned data or error message. -Dashboard configuration changes are automatically detected and broadcast: +### Shutdown (`shutdown`) -```matlab -% Any changes to dashboard properties trigger config updates -dashboard.Title = 'Updated Dashboard'; -% WebBridge automatically detects and broadcasts this change +Indicates the bridge is about to close. + +**Example JSON messages (client view)** +```json +// init: +{"type":"init","signals":[...],"dashboard":{...},"actions":["reset","recalc"]} + +// data_changed: +{"type":"data_changed","signalIds":["temp","pressure"]} + +// action_result (success): +{"type":"action_result","requestId":"42","name":"recalc","ok":true,"data":{"mean":23.4}} + +// action_result (error): +{"type":"action_result","requestId":"42","name":"recalc","ok":false,"error":"Something went wrong"} ``` ## Remote Actions -### Registering Actions +Actions are MATLAB callbacks that web clients can invoke. They are registered on the bridge and immediately broadcast to all connected clients. -Register callback functions that can be invoked from web clients: +### Registering an Action ```matlab -% Register a simple action -bridge.registerAction('reset_data', @() resetSensorData()); +% Simple action without parameters +bridge.registerAction('reset', @() resetSensorData()); -% Register an action with parameters -bridge.registerAction('set_threshold', @(params) setThreshold(params.value)); +% Action with a parameters struct +bridge.registerAction('set_threshold', @(p) setThreshold(p.value)); -% Register an action that returns results -bridge.registerAction('get_stats', @() struct('mean', mean(data), 'std', std(data))); +% Action that returns a result +bridge.registerAction('stats', @() struct('mean', mean(data), 'std', std(data))); ``` -### Action Management +The callback can accept zero or one argument. If a client sends a JSON payload, it is passed as a MATLAB struct. + +### Checking for an Action ```matlab -% Check if an action exists -if bridge.hasAction('reset_data') +if bridge.hasAction('reset') disp('Reset action is available'); end - -% Actions are automatically broadcast to clients when registered ``` -## Advanced Integration Patterns +### Error Handling in Actions -### Live Data Streaming +If an action throws an error, the bridge automatically catches it and sends a failure response to the client: ```matlab -dashboard = Dashboard(); -sensor = dashboard.addSensor('live_data', []); -bridge = WebBridge(dashboard); -bridge.serve(); - -% Simulate live data updates -timer_obj = timer('ExecutionMode', 'fixedRate', 'Period', 0.1, ... - 'TimerFcn', @(~,~) updateLiveData()); +bridge.registerAction('failing', @() error('Something went wrong')); +``` - function updateLiveData() - new_data = randn(10, 1); - sensor.appendData(new_data); - bridge.notifyDataChanged('live_data'); - end +The client will receive `{"ok":false, "error":"Something went wrong"}`. -start(timer_obj); -``` +## Advanced Integration Patterns -### Multi-Dashboard Broadcasting +### Live Data Streaming ```matlab -% WebBridge can handle complex dashboard configurations dashboard = Dashboard(); -dashboard.addSensor('sensor1', data1); -dashboard.addSensor('sensor2', data2); - -% All sensors and their configurations are automatically synchronized +sensor = dashboard.addSensor('live', []); bridge = WebBridge(dashboard); bridge.serve(); -``` -### Custom Action Handlers +% Update data periodically +t = timer('ExecutionMode','fixedRate','Period',0.1, ... + 'TimerFcn',@(~,~) liveUpdate()); +start(t); -```matlab -% Register actions with error handling -bridge.registerAction('process_data', @processDataHandler); - -function result = processDataHandler(params) - try - % Process the request - result = struct('success', true, 'data', processedData); - catch ME - result = struct('success', false, 'error', ME.message); - end +function liveUpdate() + newChunk = randn(10,1); + sensor.appendData(newChunk); + bridge.notifyDataChanged('live'); end ``` -## Protocol Details +### Multiple Connected Clients -### Message Format +WebBridge can serve an arbitrary number of clients simultaneously. Every client receives the same set of signals and actions, and any action request is executed once on the MATLAB side regardless of the number of clients. -All messages use NDJSON format (one JSON object per line): +### Dashboards with Multiple Sensors + +All sensors added to the `Dashboard` are automatically discovered and exposed via the bridge: ```matlab -% Example message types sent by WebBridge: -% {"type": "init", "signals": [...], "dashboard": {...}, "actions": [...]} -% {"type": "data_changed", "signalIds": ["sensor1", "sensor2"]} -% {"type": "config_changed", "dashboard": {...}} -% {"type": "actions_changed", "actionNames": ["action1", "action2"]} -% {"type": "action_result", "requestId": "123", "name": "action1", "ok": true, "data": {...}} +dashboard = Dashboard(); +dashboard.addSensor('vibration', dataVib, 'units', 'g'); +dashboard.addSensor('pressure', dataPress, 'units', 'psi'); +bridge = WebBridge(dashboard); +bridge.serve(); ``` -### Client Integration - -Web clients should handle these message types: - -- `init`: Initial data and configuration -- `data_changed`: Signal data updates -- `config_changed`: Dashboard configuration updates -- `actions_changed`: Available actions list updates -- `action_result`: Results from action execution +No additional registration is needed for sensors. ## Performance Considerations -### Efficient Data Updates - -```matlab -% Batch multiple signal updates -bridge.notifyDataChanged({'temp', 'pressure', 'humidity'}); +- **Batch data notifications** – use `notifyDataChanged` with a cell array of signal names instead of calling it multiple times in a loop. +- **Avoid excessive action calls** – Keep action handlers lightweight; heavy computation should be performed asynchronously inside MATLAB and only the result pushed to clients. -% Rather than individual notifications: -% bridge.notifyDataChanged('temp'); -% bridge.notifyDataChanged('pressure'); -% bridge.notifyDataChanged('humidity'); -``` +## Integration with Dashboard Engine -### Configuration Polling - -```matlab -% Adjust polling frequency based on needs -bridge.ConfigPollInterval = 2.0; % Less frequent for stable configurations -bridge.ConfigPollInterval = 0.1; % More frequent for dynamic dashboards -``` +WebBridge automatically picks up changes to the `Dashboard` object (e.g., layout, theme, sensor list) and broadcasts them to connected clients. For more information on dashboard configuration see [[Dashboard Engine Guide]]. ## Error Handling -### Connection Management - -```matlab -% WebBridge handles client connections automatically -% Multiple clients can connect simultaneously -% Disconnected clients are automatically cleaned up -``` - -### Action Error Reporting - -```matlab -% Action errors are automatically captured and sent to clients -bridge.registerAction('failing_action', @() error('Something went wrong')); -% Client receives: {"ok": false, "error": "Something went wrong"} -``` - -## Integration with Dashboard Engine - -WebBridge works seamlessly with the [[Dashboard Engine Guide]]: - -```matlab -% Dashboard engine changes are automatically synchronized -dashboard.Theme = 'dark'; -dashboard.Layout = 'grid'; -% WebBridge detects and broadcasts these changes -``` +- **Disconnected clients** are cleaned up automatically. +- **Action errors** are captured and returned to the requesting client with `ok = false`. +- **Stop gracefully** – always call `bridge.stop()` before clearing the dashboard or exiting MATLAB to ensure the TCP server and Python process are terminated. ## Best Practices -1. **Register all actions before starting the server** to ensure clients receive the complete actions list -2. **Use batch notifications** for multiple simultaneous data updates -3. **Handle action errors gracefully** with try-catch blocks -4. **Set appropriate polling intervals** based on configuration change frequency -5. **Clean up resources** by calling `stop()` when shutting down +1. **Register all actions before calling `serve()`** so that the `init` message includes the complete action list. +2. **Batch data change notifications** when updating multiple signals at once. +3. **Wrap complex action callbacks** in try–catch blocks when you need custom error messages. +4. **Do not block the MATLAB command window** from inside an action – use `timer` or `afterEach` for long‑running tasks. +5. **Keep the Python bridge** at the same version as the MATLAB WebBridge; they are tightly coupled. + +## See Also -For more information on dashboard configuration, see the [[Dashboard|API Reference: Dashboard]] page. +- [[Dashboard|API Reference: Dashboard]] +- [[Sensors|API Reference: Sensors]] +- [[Dashboard Engine Guide]] +- `WebBridgeProtocol` (internal message encoding/decoding)