Problem / motivation
BatchTagPipeline recently gained a 'ReadFn' option (commit f94d8f64) — an injectable raw-parser seam that lets a caller ingest file formats the generic delimited reader cannot (proprietary, binary, fixed-width, etc.) by supplying their own @(absPath) -> parsed function, as long as it returns the same parsed shape selectTimeAndValue_ expects.
LiveTagPipeline shares the exact same ingestion plumbing — its own header says it "Shares readRawDelimited_ / selectTimeAndValue_ / writeTagMat_ with BatchTagPipeline" (LiveTagPipeline.m:42), and its dispatchParse_ is documented as "Same internal parser dispatch as BatchTagPipeline (D-02)" (LiveTagPipeline.m:770). But it hardcodes dispatchDelimitedParse_(abspath) and exposes no ReadFn option — its constructor accepts only OutputDir / Interval / ErrorFcn / Verbose / SharedRoot / LockTimeout (LiveTagPipeline.m:188-202).
The result is a feature asymmetry: a live / long-recording ingestion workflow over a custom file format is impossible today, while the identical batch workflow already supports it. Long-recording live ingestion is a core sensor-analysis domain for FastSense, so the live path is exactly where this is most wanted.
Proposed feature
Extend the same ReadFn custom-reader seam to LiveTagPipeline, mirroring the batch implementation byte-for-byte.
% Same shape as BatchTagPipeline today:
ltp = LiveTagPipeline('OutputDir', outDir, ...
'Interval', 2.0, ...
'ReadFn', @parseMyBinaryFormat); % @(absPath) -> parsed
ltp.start();
ReadFn must be a function_handle; anything else throws TagPipeline:invalidReadFn (the same error id batch already uses).
Rough sketch
Single file: libs/SensorThreshold/LiveTagPipeline.m. Copy the pattern from BatchTagPipeline:
- Add a private property
readFn_ = @dispatchDelimitedParse_ (the DI seam, default = current behavior) — BatchTagPipeline.m:48.
- In the constructor option loop (
LiveTagPipeline.m:188-202), add case 'ReadFn' and after parsing, validate + assign exactly as batch does (BatchTagPipeline.m:126-131):
if ~isempty(opts.ReadFn)
if ~isa(opts.ReadFn, 'function_handle')
error('TagPipeline:invalidReadFn', 'ReadFn must be a function handle @(absPath)->parsed.');
end
obj.readFn_ = opts.ReadFn;
end
- In
dispatchParse_ (LiveTagPipeline.m:770-783), replace the hardcoded parsed = dispatchDelimitedParse_(abspath); with parsed = obj.readFn_(abspath); — identical to BatchTagPipeline.m:310.
No public-API removal, no signature change.
Value
High. Unlocks live ingestion of non-delimited / proprietary / binary streaming formats — which the batch path already supports — closing a sharp, recently-introduced asymmetry. Directly serves the long-recording live workflow that is central to FastSense's audience.
Constraints check
- Toolbox-free: ✅ — pure function-handle dispatch, no toolbox calls.
- Backward-compatible: ✅ — default
readFn_ = @dispatchDelimitedParse_ preserves current behavior exactly; no change to serialized state or existing scripts.
- Pure MATLAB/Octave: ✅.
- Contracts: ✅ — touches only
LiveTagPipeline; no change to the Tag or DashboardWidget interfaces. Parsed-shape contract is the same one batch and selectTimeAndValue_ already rely on.
Effort estimate
S — single file, mirrors a pattern that already exists in the sibling class. Add a small parity test (a ReadFn that returns a synthetic parsed struct ingests correctly; a non-handle ReadFn throws TagPipeline:invalidReadFn).
AI-proposed via /feature-scout — needs a human product decision before implementation.
Problem / motivation
BatchTagPipelinerecently gained a'ReadFn'option (commitf94d8f64) — an injectable raw-parser seam that lets a caller ingest file formats the generic delimited reader cannot (proprietary, binary, fixed-width, etc.) by supplying their own@(absPath) -> parsedfunction, as long as it returns the same parsed shapeselectTimeAndValue_expects.LiveTagPipelineshares the exact same ingestion plumbing — its own header says it "SharesreadRawDelimited_/selectTimeAndValue_/writeTagMat_withBatchTagPipeline" (LiveTagPipeline.m:42), and itsdispatchParse_is documented as "Same internal parser dispatch as BatchTagPipeline (D-02)" (LiveTagPipeline.m:770). But it hardcodesdispatchDelimitedParse_(abspath)and exposes noReadFnoption — its constructor accepts onlyOutputDir / Interval / ErrorFcn / Verbose / SharedRoot / LockTimeout(LiveTagPipeline.m:188-202).The result is a feature asymmetry: a live / long-recording ingestion workflow over a custom file format is impossible today, while the identical batch workflow already supports it. Long-recording live ingestion is a core sensor-analysis domain for FastSense, so the live path is exactly where this is most wanted.
Proposed feature
Extend the same
ReadFncustom-reader seam toLiveTagPipeline, mirroring the batch implementation byte-for-byte.ReadFnmust be afunction_handle; anything else throwsTagPipeline:invalidReadFn(the same error id batch already uses).Rough sketch
Single file:
libs/SensorThreshold/LiveTagPipeline.m. Copy the pattern fromBatchTagPipeline:readFn_ = @dispatchDelimitedParse_(the DI seam, default = current behavior) —BatchTagPipeline.m:48.LiveTagPipeline.m:188-202), addcase 'ReadFn'and after parsing, validate + assign exactly as batch does (BatchTagPipeline.m:126-131):dispatchParse_(LiveTagPipeline.m:770-783), replace the hardcodedparsed = dispatchDelimitedParse_(abspath);withparsed = obj.readFn_(abspath);— identical toBatchTagPipeline.m:310.No public-API removal, no signature change.
Value
High. Unlocks live ingestion of non-delimited / proprietary / binary streaming formats — which the batch path already supports — closing a sharp, recently-introduced asymmetry. Directly serves the long-recording live workflow that is central to FastSense's audience.
Constraints check
readFn_ = @dispatchDelimitedParse_preserves current behavior exactly; no change to serialized state or existing scripts.LiveTagPipeline; no change to theTagorDashboardWidgetinterfaces. Parsed-shape contract is the same one batch andselectTimeAndValue_already rely on.Effort estimate
S — single file, mirrors a pattern that already exists in the sibling class. Add a small parity test (a
ReadFnthat returns a syntheticparsedstruct ingests correctly; a non-handleReadFnthrowsTagPipeline:invalidReadFn).AI-proposed via /feature-scout — needs a human product decision before implementation.