@bradtech/sensor-lorawan 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Standard decoded telemetry channel extracted from a binary LoRaWAN payload frame.
3
+ */
4
+ export interface DecodedRawChannel {
5
+ /** Semantic channel type corresponding to the decoded physical quantity */
6
+ channelType: 'canopy_temp' | 'canopy_hum' | 'soil_moisture' | 'soil_temp' | 'soil_ec' | 'solar' | 'rain' | 'wind_speed' | 'wind_dir' | 'pressure' | 'battery' | 'acoustic_spl' | 'acoustic_rain' | 'acoustic_wind' | 'generic';
7
+ /** Numerical value extracted from the 32-bit floating point payload */
8
+ rawValue: number;
9
+ /** Physical hardware transducer manufacturer identifier (e.g. 'SHT40', 'Brad soil sensor', 'SI1145', 'BMP280') */
10
+ sensorSource?: string;
11
+ /** Full commercial model name of the sensor component (e.g. 'Sensirion SHT40', 'Silicon Labs SI1145') */
12
+ sensorModel?: string;
13
+ /** Depth placement in centimeters for multi-layer soil probes (10, 20, 30 cm) */
14
+ depthCm?: number;
15
+ /** Optional unit hint for human diagnostic display */
16
+ unitHint?: string;
17
+ }
18
+ /**
19
+ * Decoded fields extracted from the 9-byte BradOS boot frame (FPort 1).
20
+ */
21
+ export interface DecodedBootPayload {
22
+ /** Firmware semantic version string (e.g. "2.1.0") */
23
+ version: string;
24
+ /** Build day of year code (e.g. 236 for DOY 236) */
25
+ buildDoy: number;
26
+ /** Battery voltage at boot in millivolts */
27
+ batteryMv: number;
28
+ /** Microcontroller hardware reset reason bitmask/code */
29
+ resetReason: number;
30
+ }
31
+ /**
32
+ * BradOS Binary LoRaWAN Payload Codec.
33
+ *
34
+ * Decodes compact IEEE 754 Float32 Little-Endian telemetry payloads and maps
35
+ * LoRaWAN FPort identifiers to hardware sensor sources and physical channels.
36
+ */
37
+ export declare class BradOSCodec {
38
+ /**
39
+ * Decodes a Base64-encoded string or raw binary Buffer into an IEEE 754 32-bit Little-Endian float.
40
+ *
41
+ * @param data - Base64 string from ChirpStack or Node.js Buffer.
42
+ * @returns Decoded number, or null if buffer length is invalid (< 4 bytes).
43
+ */
44
+ static decodeFloat32(data: string | Buffer): number | null;
45
+ /**
46
+ * Decodes the 9-byte BradOS boot payload frame transmitted on FPort 1.
47
+ *
48
+ * Byte layout:
49
+ * - Byte 0..2: Major, Minor, Patch version (uint8, uint8, uint8)
50
+ * - Byte 3..4: Build Day-of-Year (uint16 LE)
51
+ * - Byte 5..6: Battery voltage in mV (uint16 LE)
52
+ * - Byte 7: Hardware reset reason (uint8)
53
+ *
54
+ * @param data - Base64 string or binary buffer.
55
+ * @returns Decoded boot parameters, or null if corrupted.
56
+ */
57
+ static decodeBootPayload(data: string | Buffer): DecodedBootPayload | null;
58
+ /**
59
+ * Maps an incoming LoRaWAN FPort number and binary payload to a standard channel descriptor
60
+ * with automatic hardware sensor attribution (SHT40, Brad soil sensor, Davis, SI1145, BMP280, MP34DT01).
61
+ *
62
+ * @param fPort - LoRaWAN Application Port number.
63
+ * @param data - Base64 string or raw binary buffer containing the 4-byte Float32 payload.
64
+ * @returns Decoded raw channel descriptor, or null if payload is malformed.
65
+ */
66
+ static decodeFPortChannel(fPort: number, data: string | Buffer): DecodedRawChannel | null;
67
+ }
68
+ //# sourceMappingURL=BradOSCodec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BradOSCodec.d.ts","sourceRoot":"","sources":["../src/BradOSCodec.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC/B,2EAA2E;IAC3E,WAAW,EACN,aAAa,GACb,YAAY,GACZ,eAAe,GACf,WAAW,GACX,SAAS,GACT,OAAO,GACP,MAAM,GACN,YAAY,GACZ,UAAU,GACV,UAAU,GACV,SAAS,GACT,cAAc,GACd,eAAe,GACf,eAAe,GACf,SAAS,CAAA;IACd,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAA;IAChB,kHAAkH;IAClH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yGAAyG;IACzG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAChC,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAA;IAChB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;CACrB;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACrB;;;;;OAKG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI;IAU1D;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAuB1E;;;;;;;OAOG;IACH,MAAM,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,iBAAiB,GAAG,IAAI;CAmD3F"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BradOSCodec = void 0;
4
+ /**
5
+ * BradOS Binary LoRaWAN Payload Codec.
6
+ *
7
+ * Decodes compact IEEE 754 Float32 Little-Endian telemetry payloads and maps
8
+ * LoRaWAN FPort identifiers to hardware sensor sources and physical channels.
9
+ */
10
+ class BradOSCodec {
11
+ /**
12
+ * Decodes a Base64-encoded string or raw binary Buffer into an IEEE 754 32-bit Little-Endian float.
13
+ *
14
+ * @param data - Base64 string from ChirpStack or Node.js Buffer.
15
+ * @returns Decoded number, or null if buffer length is invalid (< 4 bytes).
16
+ */
17
+ static decodeFloat32(data) {
18
+ try {
19
+ const buffer = typeof data === 'string' ? Buffer.from(data, 'base64') : data;
20
+ if (buffer.length < 4)
21
+ return null;
22
+ return buffer.readFloatLE(0);
23
+ }
24
+ catch (_a) {
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Decodes the 9-byte BradOS boot payload frame transmitted on FPort 1.
30
+ *
31
+ * Byte layout:
32
+ * - Byte 0..2: Major, Minor, Patch version (uint8, uint8, uint8)
33
+ * - Byte 3..4: Build Day-of-Year (uint16 LE)
34
+ * - Byte 5..6: Battery voltage in mV (uint16 LE)
35
+ * - Byte 7: Hardware reset reason (uint8)
36
+ *
37
+ * @param data - Base64 string or binary buffer.
38
+ * @returns Decoded boot parameters, or null if corrupted.
39
+ */
40
+ static decodeBootPayload(data) {
41
+ try {
42
+ const buffer = typeof data === 'string' ? Buffer.from(data, 'base64') : data;
43
+ if (buffer.length < 8)
44
+ return null;
45
+ const major = buffer.readUInt8(0);
46
+ const minor = buffer.readUInt8(1);
47
+ const patch = buffer.readUInt8(2);
48
+ const buildDoy = buffer.readUInt16LE(3);
49
+ const batteryMv = buffer.readUInt16LE(5);
50
+ const resetReason = buffer.readUInt8(7);
51
+ return {
52
+ version: `${major}.${minor}.${patch}`,
53
+ buildDoy,
54
+ batteryMv,
55
+ resetReason,
56
+ };
57
+ }
58
+ catch (_a) {
59
+ return null;
60
+ }
61
+ }
62
+ /**
63
+ * Maps an incoming LoRaWAN FPort number and binary payload to a standard channel descriptor
64
+ * with automatic hardware sensor attribution (SHT40, Brad soil sensor, Davis, SI1145, BMP280, MP34DT01).
65
+ *
66
+ * @param fPort - LoRaWAN Application Port number.
67
+ * @param data - Base64 string or raw binary buffer containing the 4-byte Float32 payload.
68
+ * @returns Decoded raw channel descriptor, or null if payload is malformed.
69
+ */
70
+ static decodeFPortChannel(fPort, data) {
71
+ const floatVal = this.decodeFloat32(data);
72
+ if (floatVal === null)
73
+ return null;
74
+ switch (fPort) {
75
+ case 2:
76
+ return { channelType: 'battery', rawValue: floatVal, sensorSource: 'ASR6502 ADC', sensorModel: 'Internal ADC', unitHint: 'V' };
77
+ case 9:
78
+ return { channelType: 'solar', rawValue: floatVal, sensorSource: 'SI1145', sensorModel: 'Silicon Labs SI1145', unitHint: 'index' };
79
+ case 11:
80
+ return { channelType: 'canopy_hum', rawValue: floatVal, sensorSource: 'SHT40', sensorModel: 'Sensirion SHT40', unitHint: '%' };
81
+ case 12:
82
+ return { channelType: 'soil_moisture', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 10cm', depthCm: 10, unitHint: '%' };
83
+ case 13:
84
+ return { channelType: 'soil_moisture', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 20cm', depthCm: 20, unitHint: '%' };
85
+ case 14:
86
+ return { channelType: 'soil_moisture', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 30cm', depthCm: 30, unitHint: '%' };
87
+ case 15:
88
+ return { channelType: 'canopy_temp', rawValue: floatVal, sensorSource: 'SHT40', sensorModel: 'Sensirion SHT40', unitHint: '°C' };
89
+ case 16:
90
+ return { channelType: 'soil_temp', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 10cm', depthCm: 10, unitHint: '°C' };
91
+ case 17:
92
+ return { channelType: 'soil_temp', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 20cm', depthCm: 20, unitHint: '°C' };
93
+ case 18:
94
+ return { channelType: 'soil_temp', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 30cm', depthCm: 30, unitHint: '°C' };
95
+ case 19:
96
+ return { channelType: 'solar', rawValue: floatVal, sensorSource: 'SI1145', sensorModel: 'Silicon Labs SI1145', unitHint: 'W/m²' };
97
+ case 20:
98
+ return { channelType: 'rain', rawValue: floatVal, sensorSource: 'Davis 7852', sensorModel: 'Davis Rain Collector', unitHint: 'mm' };
99
+ case 21:
100
+ return { channelType: 'wind_speed', rawValue: floatVal, sensorSource: 'Davis 6410', sensorModel: 'Davis Anemometer', unitHint: 'km/h' };
101
+ case 22:
102
+ return { channelType: 'wind_dir', rawValue: floatVal, sensorSource: 'Davis 6410', sensorModel: 'Davis Vane', unitHint: '°' };
103
+ case 23:
104
+ return { channelType: 'pressure', rawValue: floatVal, sensorSource: 'BMP280', sensorModel: 'Bosch BMP280', unitHint: 'hPa' };
105
+ case 24:
106
+ return { channelType: 'acoustic_spl', rawValue: floatVal, sensorSource: 'MP34DT01', sensorModel: 'ST MEMS Microphone', unitHint: 'dBA' };
107
+ case 25:
108
+ return { channelType: 'acoustic_rain', rawValue: floatVal, sensorSource: 'MP34DT01', sensorModel: 'ST MEMS Microphone', unitHint: 'index' };
109
+ case 26:
110
+ return { channelType: 'acoustic_wind', rawValue: floatVal, sensorSource: 'MP34DT01', sensorModel: 'ST MEMS Microphone', unitHint: 'index' };
111
+ case 31:
112
+ return { channelType: 'soil_ec', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 10cm', depthCm: 10, unitHint: 'mS/cm' };
113
+ case 32:
114
+ return { channelType: 'soil_ec', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 20cm', depthCm: 20, unitHint: 'mS/cm' };
115
+ case 33:
116
+ return { channelType: 'soil_ec', rawValue: floatVal, sensorSource: 'Brad soil sensor', sensorModel: 'Brad Soil Sensor 30cm', depthCm: 30, unitHint: 'mS/cm' };
117
+ default:
118
+ return { channelType: 'generic', rawValue: floatVal };
119
+ }
120
+ }
121
+ }
122
+ exports.BradOSCodec = BradOSCodec;
123
+ //# sourceMappingURL=BradOSCodec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BradOSCodec.js","sourceRoot":"","sources":["../src/BradOSCodec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ import type { CompanyUri, DeviceUri, OkfMetricUri, PlotUri } from '@bradtech/types';
2
+ /**
3
+ * Pure LoRaWAN Uplink message contract from ChirpStack or external Network Server.
4
+ * Contains purely radio, frame counter, port, and raw binary payload data.
5
+ * Note: Hardware probe payloads NEVER contain agronomic or soil calibration parameters.
6
+ */
7
+ export interface PipelineUplinkInput {
8
+ /** Device metadata from Network Server */
9
+ deviceInfo?: {
10
+ /** 64-bit IEEE Extended Unique Identifier in hex (e.g. "0018b20000001234") */
11
+ devEui?: string;
12
+ /** Human device name (e.g. "b25s004" or "b26w001") */
13
+ deviceName?: string;
14
+ /** ChirpStack Application UUID */
15
+ applicationId?: string;
16
+ /** ChirpStack Application Name */
17
+ applicationName?: string;
18
+ /** Optional generic Network Server metadata tags */
19
+ tags?: Record<string, string>;
20
+ };
21
+ /** Alternative flat devEUI property */
22
+ devEUI?: string;
23
+ /** LoRaWAN Application Port (FPort) indicating the physical channel */
24
+ fPort?: number;
25
+ /** Frame uplink counter */
26
+ fCnt?: number;
27
+ /** Base64-encoded binary payload string */
28
+ data?: string;
29
+ /** Array of receiving gateway metadata records (RSSI, SNR, GPS) */
30
+ rxInfo?: Array<{
31
+ gatewayId?: string;
32
+ rssi?: number;
33
+ snr?: number;
34
+ location?: {
35
+ latitude?: number;
36
+ longitude?: number;
37
+ altitude?: number;
38
+ };
39
+ }>;
40
+ /** Transmission RF metadata (frequency in Hz, DataRate) */
41
+ txInfo?: {
42
+ frequency?: number;
43
+ dataRate?: number;
44
+ };
45
+ /** ISO 8601 message publication timestamp */
46
+ publishedAt?: string;
47
+ /** Alternative ISO 8601 message timestamp */
48
+ time?: string;
49
+ }
50
+ /**
51
+ * Agronomic & Plot context resolved from the Backoffice / Master Data Management database.
52
+ * The Backoffice links a physical probe (devEUI) to an agricultural parcel (Plot),
53
+ * with its specific soil profile, texture classification, and optional lab calibration model.
54
+ */
55
+ export interface AgronomicPlotContext {
56
+ /** Canonical Plot identifier or URI (e.g. "plots/parcelle-nord-01" or "parcelle-nord-01") */
57
+ plot?: PlotUri | (string & {});
58
+ /** Canonical Company / Tenant identifier or URI (e.g. "companies/domaine-alpha" or "domaine-alpha") */
59
+ company?: CompanyUri | (string & {});
60
+ /** Soil texture classification assigned to the plot in Backoffice (e.g. "clay", "sand", "loam", "silt", "peat") */
61
+ soilTexture?: 'sand' | 'loam' | 'clay' | 'silt' | 'peat' | 'default' | (string & {});
62
+ /** Custom plot-specific laboratory calibration model (linear regression: y = slope * raw + intercept) */
63
+ soilLinearRegression?: {
64
+ slope: number;
65
+ intercept: number;
66
+ modelLabel?: string;
67
+ };
68
+ /** Optional hardware vendor tag */
69
+ vendor?: string;
70
+ /** Optional hardware model tag */
71
+ vendorModel?: string;
72
+ }
73
+ /**
74
+ * Normalized output contract representing an immutable DataPoint ready for PostgreSQL storage.
75
+ */
76
+ export interface PipelineDataPointOutput {
77
+ /** Canonical device URI (e.g. 'probes/b25s004' or 'weather-stations/b26w001') */
78
+ device: DeviceUri | (string & {});
79
+ /** Canonical plot URI (e.g. 'plots/e5eadee3-...') */
80
+ plot?: PlotUri | (string & {});
81
+ /** Canonical company/tenant URI (e.g. 'companies/32c18f05-...') */
82
+ company?: CompanyUri | (string & {});
83
+ /** Canonical OKF metric identifier */
84
+ metric: OkfMetricUri | (string & {});
85
+ /** Calibrated numerical value */
86
+ value: number;
87
+ /** Human-readable physical unit symbol */
88
+ unit: string;
89
+ /** Kind indicator: 'measured' for direct sensor readings, 'computed' for agronomic algorithms */
90
+ kind: 'measured' | 'computed';
91
+ /** Quality and confidence score between 0.0 and 1.0 */
92
+ confidence: number;
93
+ /** Observation timestamp */
94
+ timestamp: string;
95
+ /** Comprehensive metadata contract */
96
+ metadata: Record<string, any>;
97
+ }
98
+ /**
99
+ * End-to-End LoRaWAN Telemetry Ingestion & Transformation Pipeline.
100
+ *
101
+ * Coordinates:
102
+ * - IEEE 754 Float32 binary decoding via `BradOSCodec`.
103
+ * - Automatic hardware sensor attribution (SHT40, Brad soil sensor, Davis, SI1145, BMP280, MP34DT01).
104
+ * - Routing to decoupled domain converter packages (`@bradtech/sensor-*`).
105
+ * - Agronomic context enrichment from Backoffice (Plot association, Soil profile, Custom calibration curves).
106
+ */
107
+ export declare class LoRaWanPipeline {
108
+ private static _airConverter;
109
+ private static _soilMoistureConverter;
110
+ private static _soilWpConverter;
111
+ private static _soilTempConverter;
112
+ private static _soilEcConverter;
113
+ private static _solarConverter;
114
+ private static _rainConverter;
115
+ private static _windConverter;
116
+ private static _baroConverter;
117
+ private static _batteryConverter;
118
+ private static _acousticSplConverter;
119
+ private static _acousticWeatherConverter;
120
+ /**
121
+ * Ingests a raw LoRaWAN uplink message, decodes binary payload, evaluates domain converters
122
+ * with optional agronomic plot context provided by the Backoffice, and returns validated DataPoints.
123
+ *
124
+ * @param uplink - Incoming ChirpStack LoRaWAN message object (pure radio frame).
125
+ * @param agronomicContext - Optional agronomic context resolved from Backoffice (Plot ID, Soil Texture, Calibration).
126
+ * @returns Array of transformed DataPoints.
127
+ */
128
+ static process(uplink: PipelineUplinkInput, agronomicContext?: AgronomicPlotContext): PipelineDataPointOutput[];
129
+ /**
130
+ * Executes a converter instance and injects class name, model code, and semver version into metadata.
131
+ */
132
+ private static _runConverter;
133
+ /**
134
+ * Normalizes raw hardware device names into canonical OKF device URIs.
135
+ */
136
+ private static _buildDeviceUri;
137
+ /**
138
+ * Helper extracting linear regression calibration parameters if provided in tags.
139
+ */
140
+ private static _extractLinearRegression;
141
+ }
142
+ //# sourceMappingURL=LoRaWanPipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoRaWanPipeline.d.ts","sourceRoot":"","sources":["../src/LoRaWanPipeline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACZ,OAAO,EACT,MAAM,iBAAiB,CAAA;AAExB;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACjC,0CAA0C;IAC1C,UAAU,CAAC,EAAE;QACV,8EAA8E;QAC9E,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,sDAAsD;QACtD,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,kCAAkC;QAClC,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,kCAAkC;QAClC,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,oDAAoD;QACpD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAC/B,CAAA;IACD,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,MAAM,CAAC,EAAE,KAAK,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,QAAQ,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KACzE,CAAC,CAAA;IACF,2DAA2D;IAC3D,MAAM,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;IACD,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IAClC,6FAA6F;IAC7F,IAAI,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC9B,uGAAuG;IACvG,OAAO,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACpC,mHAAmH;IACnH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACpF,yGAAyG;IACzG,oBAAoB,CAAC,EAAE;QACpB,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACrC,iFAAiF;IACjF,MAAM,EAAE,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACjC,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACpC,sCAAsC;IACtC,MAAM,EAAE,YAAY,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;IACpC,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;IAEZ,iGAAiG;IACjG,IAAI,EAAE,UAAU,GAAG,UAAU,CAAA;IAC7B,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAA;IAClB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC/B;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IACzB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAkC;IAC9D,OAAO,CAAC,MAAM,CAAC,sBAAsB,CAAqC;IAC1E,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAA2C;IAC1E,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwC;IACzE,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAA+B;IAC9D,OAAO,CAAC,MAAM,CAAC,eAAe,CAAuC;IACrE,OAAO,CAAC,MAAM,CAAC,cAAc,CAAkC;IAC/D,OAAO,CAAC,MAAM,CAAC,cAAc,CAA6B;IAC1D,OAAO,CAAC,MAAM,CAAC,cAAc,CAA2C;IACxE,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAgC;IAChE,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAoC;IACxE,OAAO,CAAC,MAAM,CAAC,yBAAyB,CAAwC;IAEhF;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,CACX,MAAM,EAAE,mBAAmB,EAC3B,gBAAgB,CAAC,EAAE,oBAAoB,GACvC,uBAAuB,EAAE;IA8P5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAiB5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAW9B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;CAWzC"}
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoRaWanPipeline = void 0;
4
+ const defaultAdapters_1 = require("./defaultAdapters");
5
+ const BradOSCodec_1 = require("./BradOSCodec");
6
+ /**
7
+ * End-to-End LoRaWAN Telemetry Ingestion & Transformation Pipeline.
8
+ *
9
+ * Coordinates:
10
+ * - IEEE 754 Float32 binary decoding via `BradOSCodec`.
11
+ * - Automatic hardware sensor attribution (SHT40, Brad soil sensor, Davis, SI1145, BMP280, MP34DT01).
12
+ * - Routing to decoupled domain converter packages (`@bradtech/sensor-*`).
13
+ * - Agronomic context enrichment from Backoffice (Plot association, Soil profile, Custom calibration curves).
14
+ */
15
+ class LoRaWanPipeline {
16
+ /**
17
+ * Ingests a raw LoRaWAN uplink message, decodes binary payload, evaluates domain converters
18
+ * with optional agronomic plot context provided by the Backoffice, and returns validated DataPoints.
19
+ *
20
+ * @param uplink - Incoming ChirpStack LoRaWAN message object (pure radio frame).
21
+ * @param agronomicContext - Optional agronomic context resolved from Backoffice (Plot ID, Soil Texture, Calibration).
22
+ * @returns Array of transformed DataPoints.
23
+ */
24
+ static process(uplink, agronomicContext) {
25
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
26
+ const dataPoints = [];
27
+ // 1. Resolve Canonical Device & Tenancy Identifiers
28
+ const rawDeviceName = ((_a = uplink.deviceInfo) === null || _a === void 0 ? void 0 : _a.deviceName) || ((_b = uplink.deviceInfo) === null || _b === void 0 ? void 0 : _b.devEui) || uplink.devEUI || 'unknown';
29
+ const deviceUri = this._buildDeviceUri(rawDeviceName);
30
+ // Plot & Company resolved from Backoffice agronomic context (with fallback to metadata tags)
31
+ const rawPlot = (agronomicContext === null || agronomicContext === void 0 ? void 0 : agronomicContext.plot) || ((_d = (_c = uplink.deviceInfo) === null || _c === void 0 ? void 0 : _c.tags) === null || _d === void 0 ? void 0 : _d.plot);
32
+ const plotUri = rawPlot ? (rawPlot.startsWith('plots/') ? rawPlot : `plots/${rawPlot}`) : undefined;
33
+ const rawCompany = (agronomicContext === null || agronomicContext === void 0 ? void 0 : agronomicContext.company) || ((_f = (_e = uplink.deviceInfo) === null || _e === void 0 ? void 0 : _e.tags) === null || _f === void 0 ? void 0 : _f.company);
34
+ const companyUri = rawCompany ? (rawCompany.startsWith('companies/') ? rawCompany : `companies/${rawCompany}`) : undefined;
35
+ const timestamp = uplink.publishedAt || uplink.time || new Date().toISOString();
36
+ // Soil texture and calibration model come from the Backoffice plot definition
37
+ const soilTexture = (agronomicContext === null || agronomicContext === void 0 ? void 0 : agronomicContext.soilTexture) || ((_h = (_g = uplink.deviceInfo) === null || _g === void 0 ? void 0 : _g.tags) === null || _h === void 0 ? void 0 : _h.soilTexture) || 'default';
38
+ const soilLinearRegression = (agronomicContext === null || agronomicContext === void 0 ? void 0 : agronomicContext.soilLinearRegression) || this._extractLinearRegression((_j = uplink.deviceInfo) === null || _j === void 0 ? void 0 : _j.tags);
39
+ // 2. Build LoRaWAN Radio Metadata Contract
40
+ const bestGateway = (_k = uplink.rxInfo) === null || _k === void 0 ? void 0 : _k[0];
41
+ const vendor = (agronomicContext === null || agronomicContext === void 0 ? void 0 : agronomicContext.vendor) || ((_m = (_l = uplink.deviceInfo) === null || _l === void 0 ? void 0 : _l.tags) === null || _m === void 0 ? void 0 : _m.vendor) || 'brad';
42
+ const radioMetadata = {
43
+ interface: '@bradtech/types:LoRaWanMetadataInterface',
44
+ vendor,
45
+ vendorModel: (agronomicContext === null || agronomicContext === void 0 ? void 0 : agronomicContext.vendorModel) || ((_p = (_o = uplink.deviceInfo) === null || _o === void 0 ? void 0 : _o.tags) === null || _p === void 0 ? void 0 : _p.vendorModel) || (rawDeviceName.startsWith('b26w') ? 'Brad Weather Station v1' : 'Brad Soil Probe v2.5'),
46
+ vendorDeviceId: rawDeviceName,
47
+ integrationType: 'lorawan',
48
+ devEui: ((_q = uplink.deviceInfo) === null || _q === void 0 ? void 0 : _q.devEui) || uplink.devEUI,
49
+ fPort: uplink.fPort,
50
+ fCnt: uplink.fCnt,
51
+ frequency: (_r = uplink.txInfo) === null || _r === void 0 ? void 0 : _r.frequency,
52
+ dataRate: (_s = uplink.txInfo) === null || _s === void 0 ? void 0 : _s.dataRate,
53
+ rssi: bestGateway === null || bestGateway === void 0 ? void 0 : bestGateway.rssi,
54
+ snr: bestGateway === null || bestGateway === void 0 ? void 0 : bestGateway.snr,
55
+ gatewayId: bestGateway === null || bestGateway === void 0 ? void 0 : bestGateway.gatewayId,
56
+ gatewayCount: ((_t = uplink.rxInfo) === null || _t === void 0 ? void 0 : _t.length) || 0,
57
+ };
58
+ // 3. Emit Network Quality DataPoints
59
+ if ((bestGateway === null || bestGateway === void 0 ? void 0 : bestGateway.rssi) !== undefined) {
60
+ dataPoints.push({
61
+ device: deviceUri,
62
+ plot: plotUri,
63
+ company: companyUri,
64
+ metric: 'okf:radio/lorawan/rssi',
65
+ value: bestGateway.rssi,
66
+ unit: 'dBm',
67
+ kind: 'measured',
68
+ confidence: 1.0,
69
+ timestamp,
70
+ metadata: radioMetadata,
71
+ });
72
+ }
73
+ if ((bestGateway === null || bestGateway === void 0 ? void 0 : bestGateway.snr) !== undefined) {
74
+ dataPoints.push({
75
+ device: deviceUri,
76
+ plot: plotUri,
77
+ company: companyUri,
78
+ metric: 'okf:radio/lorawan/snr',
79
+ value: Number(bestGateway.snr.toFixed(1)),
80
+ unit: 'dB',
81
+ kind: 'measured',
82
+ confidence: 1.0,
83
+ timestamp,
84
+ metadata: radioMetadata,
85
+ });
86
+ }
87
+ // 4. Decode Payload via BradOSCodec
88
+ if (!uplink.fPort || !uplink.data) {
89
+ return dataPoints;
90
+ }
91
+ // Special Case: FPort 1 (Boot Frame)
92
+ if (uplink.fPort === 1) {
93
+ const boot = BradOSCodec_1.BradOSCodec.decodeBootPayload(uplink.data);
94
+ if (boot) {
95
+ const battOutputs = LoRaWanPipeline._batteryConverter.convert({ voltageMv: boot.batteryMv });
96
+ for (const b of battOutputs) {
97
+ dataPoints.push({
98
+ device: deviceUri,
99
+ plot: plotUri,
100
+ company: companyUri,
101
+ metric: b.metric,
102
+ value: b.value,
103
+ unit: b.unit,
104
+ kind: 'measured',
105
+ confidence: b.confidence,
106
+ timestamp,
107
+ metadata: Object.assign(Object.assign(Object.assign({}, radioMetadata), { bootVersion: boot.version, buildDoy: boot.buildDoy, resetReason: boot.resetReason }), b.metadata),
108
+ });
109
+ }
110
+ }
111
+ return dataPoints;
112
+ }
113
+ // Standard Telemetry: FPort 2 to 52
114
+ const channel = BradOSCodec_1.BradOSCodec.decodeFPortChannel(uplink.fPort, uplink.data);
115
+ if (!channel) {
116
+ return dataPoints;
117
+ }
118
+ const conversionOutputs = [];
119
+ switch (channel.channelType) {
120
+ case 'canopy_temp': {
121
+ const outputs = LoRaWanPipeline._runConverter(LoRaWanPipeline._airConverter, {
122
+ temperature: channel.rawValue,
123
+ humidity: 60.0,
124
+ });
125
+ const tempOut = outputs.find((o) => o.metric.includes('temperature'));
126
+ if (tempOut)
127
+ conversionOutputs.push(tempOut);
128
+ break;
129
+ }
130
+ case 'canopy_hum': {
131
+ const outputs = LoRaWanPipeline._runConverter(LoRaWanPipeline._airConverter, {
132
+ temperature: 20.0,
133
+ humidity: channel.rawValue,
134
+ });
135
+ const humOut = outputs.find((o) => o.metric.includes('humidity'));
136
+ if (humOut)
137
+ conversionOutputs.push(humOut);
138
+ break;
139
+ }
140
+ case 'soil_moisture': {
141
+ // Apply plot-specific soil texture & calibration model from agronomic context
142
+ const vwcOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._soilMoistureConverter, { depthCm: channel.depthCm || 10, rawValue: channel.rawValue }, { soilTexture, soilLinearRegression });
143
+ conversionOutputs.push(...vwcOut);
144
+ // Automatically compute derived pF matric potential based on plot soil texture
145
+ if (vwcOut.length > 0) {
146
+ const wpOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._soilWpConverter, { depthCm: channel.depthCm || 10, vwcPercent: vwcOut[0].value }, { soilTexture });
147
+ conversionOutputs.push(...wpOut);
148
+ }
149
+ break;
150
+ }
151
+ case 'soil_temp': {
152
+ const tempOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._soilTempConverter, {
153
+ depthCm: channel.depthCm || 10,
154
+ temperature: channel.rawValue,
155
+ });
156
+ conversionOutputs.push(...tempOut);
157
+ break;
158
+ }
159
+ case 'soil_ec': {
160
+ const ecOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._soilEcConverter, {
161
+ depthCm: channel.depthCm || 10,
162
+ bulkEcMsCm: channel.rawValue,
163
+ });
164
+ conversionOutputs.push(...ecOut);
165
+ break;
166
+ }
167
+ case 'solar': {
168
+ const solarOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._solarConverter, { irradianceWm2: channel.rawValue });
169
+ conversionOutputs.push(...solarOut);
170
+ break;
171
+ }
172
+ case 'rain': {
173
+ const rainOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._rainConverter, { tipCount: channel.rawValue });
174
+ conversionOutputs.push(...rainOut);
175
+ break;
176
+ }
177
+ case 'wind_speed': {
178
+ const windOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._windConverter, { speedMs: channel.rawValue });
179
+ conversionOutputs.push(...windOut);
180
+ break;
181
+ }
182
+ case 'wind_dir': {
183
+ const windDirOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._windConverter, { speedMs: 0, directionDegrees: channel.rawValue });
184
+ conversionOutputs.push(...windDirOut);
185
+ break;
186
+ }
187
+ case 'pressure': {
188
+ const baroOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._baroConverter, { pressureHpa: channel.rawValue });
189
+ conversionOutputs.push(...baroOut);
190
+ break;
191
+ }
192
+ case 'battery': {
193
+ const battOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._batteryConverter, { voltageV: channel.rawValue });
194
+ conversionOutputs.push(...battOut);
195
+ break;
196
+ }
197
+ case 'acoustic_spl': {
198
+ const splOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._acousticSplConverter, { rmsDbfs: channel.rawValue });
199
+ conversionOutputs.push(...splOut);
200
+ break;
201
+ }
202
+ case 'acoustic_rain':
203
+ case 'acoustic_wind': {
204
+ const acWeatherOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._acousticWeatherConverter, {
205
+ highFrequencyEnergyRms: channel.channelType === 'acoustic_rain' ? channel.rawValue : -80,
206
+ lowFrequencyEnergyRms: channel.channelType === 'acoustic_wind' ? channel.rawValue : -80,
207
+ });
208
+ conversionOutputs.push(...acWeatherOut);
209
+ break;
210
+ }
211
+ }
212
+ // Map conversion outputs to final DataPoints
213
+ for (const out of conversionOutputs) {
214
+ const isDerived = out.metric.includes('potential') || out.metric.includes('par_ppfd') || out.metric.includes('percentage');
215
+ dataPoints.push({
216
+ device: deviceUri,
217
+ plot: plotUri,
218
+ company: companyUri,
219
+ metric: out.metric,
220
+ value: out.value,
221
+ unit: out.unit,
222
+ kind: isDerived ? 'computed' : 'measured',
223
+ confidence: out.confidence,
224
+ timestamp,
225
+ metadata: Object.assign(Object.assign(Object.assign(Object.assign({}, radioMetadata), { sensorSource: channel.sensorSource, sensorModel: channel.sensorModel }), out.metadata), { interface: isDerived ? '@bradtech/types:ComputedMetadataInterface' : '@bradtech/types:LoRaWanMetadataInterface' }),
226
+ });
227
+ }
228
+ return dataPoints;
229
+ }
230
+ /**
231
+ * Executes a converter instance and injects class name, model code, and semver version into metadata.
232
+ */
233
+ static _runConverter(converter, raw, context) {
234
+ const outputs = converter.convert(raw, context);
235
+ return outputs.map((out) => (Object.assign(Object.assign({}, out), { metadata: Object.assign({ converterClass: converter.constructor.name, modelCode: converter.modelCode, modelVersion: converter.modelVersion }, out.metadata) })));
236
+ }
237
+ /**
238
+ * Normalizes raw hardware device names into canonical OKF device URIs.
239
+ */
240
+ static _buildDeviceUri(deviceName) {
241
+ const clean = deviceName.trim().toLowerCase();
242
+ if (clean.startsWith('probes/') || clean.startsWith('weather-stations/')) {
243
+ return clean;
244
+ }
245
+ if (clean.startsWith('b26w') || clean.startsWith('station')) {
246
+ return `weather-stations/${clean}`;
247
+ }
248
+ return `probes/${clean}`;
249
+ }
250
+ /**
251
+ * Helper extracting linear regression calibration parameters if provided in tags.
252
+ */
253
+ static _extractLinearRegression(tags) {
254
+ if (!(tags === null || tags === void 0 ? void 0 : tags.soilSlope) || !(tags === null || tags === void 0 ? void 0 : tags.soilIntercept))
255
+ return undefined;
256
+ const slope = parseFloat(tags.soilSlope);
257
+ const intercept = parseFloat(tags.soilIntercept);
258
+ if (isNaN(slope) || isNaN(intercept))
259
+ return undefined;
260
+ return {
261
+ slope,
262
+ intercept,
263
+ modelLabel: tags.soilModelLabel,
264
+ };
265
+ }
266
+ }
267
+ exports.LoRaWanPipeline = LoRaWanPipeline;
268
+ LoRaWanPipeline._airConverter = defaultAdapters_1.defaultSensorAdapters.canopyAir;
269
+ LoRaWanPipeline._soilMoistureConverter = defaultAdapters_1.defaultSensorAdapters.soilMoisture;
270
+ LoRaWanPipeline._soilWpConverter = defaultAdapters_1.defaultSensorAdapters.soilWaterPotential;
271
+ LoRaWanPipeline._soilTempConverter = defaultAdapters_1.defaultSensorAdapters.soilTemperature;
272
+ LoRaWanPipeline._soilEcConverter = defaultAdapters_1.defaultSensorAdapters.soilEc;
273
+ LoRaWanPipeline._solarConverter = defaultAdapters_1.defaultSensorAdapters.solarRadiation;
274
+ LoRaWanPipeline._rainConverter = defaultAdapters_1.defaultSensorAdapters.rainGauge;
275
+ LoRaWanPipeline._windConverter = defaultAdapters_1.defaultSensorAdapters.wind;
276
+ LoRaWanPipeline._baroConverter = defaultAdapters_1.defaultSensorAdapters.barometricPressure;
277
+ LoRaWanPipeline._batteryConverter = defaultAdapters_1.defaultSensorAdapters.battery;
278
+ LoRaWanPipeline._acousticSplConverter = defaultAdapters_1.defaultSensorAdapters.acousticSpl;
279
+ LoRaWanPipeline._acousticWeatherConverter = defaultAdapters_1.defaultSensorAdapters.acousticWeather;
280
+ //# sourceMappingURL=LoRaWanPipeline.js.map