@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,443 @@
1
+ import { defaultSensorAdapters } from './defaultAdapters'
2
+ import { BradOSCodec } from './BradOSCodec'
3
+ import type { ConversionOutput } from '@bradtech/sensor'
4
+ import type {
5
+ CompanyUri,
6
+ DeviceUri,
7
+ OkfMetricUri,
8
+ PlotUri,
9
+ } from '@bradtech/types'
10
+
11
+ /**
12
+ * Pure LoRaWAN Uplink message contract from ChirpStack or external Network Server.
13
+ * Contains purely radio, frame counter, port, and raw binary payload data.
14
+ * Note: Hardware probe payloads NEVER contain agronomic or soil calibration parameters.
15
+ */
16
+ export interface PipelineUplinkInput {
17
+ /** Device metadata from Network Server */
18
+ deviceInfo?: {
19
+ /** 64-bit IEEE Extended Unique Identifier in hex (e.g. "0018b20000001234") */
20
+ devEui?: string
21
+ /** Human device name (e.g. "b25s004" or "b26w001") */
22
+ deviceName?: string
23
+ /** ChirpStack Application UUID */
24
+ applicationId?: string
25
+ /** ChirpStack Application Name */
26
+ applicationName?: string
27
+ /** Optional generic Network Server metadata tags */
28
+ tags?: Record<string, string>
29
+ }
30
+ /** Alternative flat devEUI property */
31
+ devEUI?: string
32
+ /** LoRaWAN Application Port (FPort) indicating the physical channel */
33
+ fPort?: number
34
+ /** Frame uplink counter */
35
+ fCnt?: number
36
+ /** Base64-encoded binary payload string */
37
+ data?: string
38
+ /** Array of receiving gateway metadata records (RSSI, SNR, GPS) */
39
+ rxInfo?: Array<{
40
+ gatewayId?: string
41
+ rssi?: number
42
+ snr?: number
43
+ location?: { latitude?: number; longitude?: number; altitude?: number }
44
+ }>
45
+ /** Transmission RF metadata (frequency in Hz, DataRate) */
46
+ txInfo?: {
47
+ frequency?: number
48
+ dataRate?: number
49
+ }
50
+ /** ISO 8601 message publication timestamp */
51
+ publishedAt?: string
52
+ /** Alternative ISO 8601 message timestamp */
53
+ time?: string
54
+ }
55
+
56
+ /**
57
+ * Agronomic & Plot context resolved from the Backoffice / Master Data Management database.
58
+ * The Backoffice links a physical probe (devEUI) to an agricultural parcel (Plot),
59
+ * with its specific soil profile, texture classification, and optional lab calibration model.
60
+ */
61
+ export interface AgronomicPlotContext {
62
+ /** Canonical Plot identifier or URI (e.g. "plots/parcelle-nord-01" or "parcelle-nord-01") */
63
+ plot?: PlotUri | (string & {})
64
+ /** Canonical Company / Tenant identifier or URI (e.g. "companies/domaine-alpha" or "domaine-alpha") */
65
+ company?: CompanyUri | (string & {})
66
+ /** Soil texture classification assigned to the plot in Backoffice (e.g. "clay", "sand", "loam", "silt", "peat") */
67
+ soilTexture?: 'sand' | 'loam' | 'clay' | 'silt' | 'peat' | 'default' | (string & {})
68
+ /** Custom plot-specific laboratory calibration model (linear regression: y = slope * raw + intercept) */
69
+ soilLinearRegression?: {
70
+ slope: number
71
+ intercept: number
72
+ modelLabel?: string
73
+ }
74
+ /** Optional hardware vendor tag */
75
+ vendor?: string
76
+ /** Optional hardware model tag */
77
+ vendorModel?: string
78
+ }
79
+
80
+ /**
81
+ * Normalized output contract representing an immutable DataPoint ready for PostgreSQL storage.
82
+ */
83
+ export interface PipelineDataPointOutput {
84
+ /** Canonical device URI (e.g. 'probes/b25s004' or 'weather-stations/b26w001') */
85
+ device: DeviceUri | (string & {})
86
+ /** Canonical plot URI (e.g. 'plots/e5eadee3-...') */
87
+ plot?: PlotUri | (string & {})
88
+ /** Canonical company/tenant URI (e.g. 'companies/32c18f05-...') */
89
+ company?: CompanyUri | (string & {})
90
+ /** Canonical OKF metric identifier */
91
+ metric: OkfMetricUri | (string & {})
92
+ /** Calibrated numerical value */
93
+ value: number
94
+ /** Human-readable physical unit symbol */
95
+ unit: string
96
+
97
+ /** Kind indicator: 'measured' for direct sensor readings, 'computed' for agronomic algorithms */
98
+ kind: 'measured' | 'computed'
99
+ /** Quality and confidence score between 0.0 and 1.0 */
100
+ confidence: number
101
+ /** Observation timestamp */
102
+ timestamp: string
103
+ /** Comprehensive metadata contract */
104
+ metadata: Record<string, any>
105
+ }
106
+
107
+ /**
108
+ * End-to-End LoRaWAN Telemetry Ingestion & Transformation Pipeline.
109
+ *
110
+ * Coordinates:
111
+ * - IEEE 754 Float32 binary decoding via `BradOSCodec`.
112
+ * - Automatic hardware sensor attribution (SHT40, Brad soil sensor, Davis, SI1145, BMP280, MP34DT01).
113
+ * - Routing to decoupled domain converter packages (`@bradtech/sensor-*`).
114
+ * - Agronomic context enrichment from Backoffice (Plot association, Soil profile, Custom calibration curves).
115
+ */
116
+ export class LoRaWanPipeline {
117
+ private static _airConverter = defaultSensorAdapters.canopyAir
118
+ private static _soilMoistureConverter = defaultSensorAdapters.soilMoisture
119
+ private static _soilWpConverter = defaultSensorAdapters.soilWaterPotential
120
+ private static _soilTempConverter = defaultSensorAdapters.soilTemperature
121
+ private static _soilEcConverter = defaultSensorAdapters.soilEc
122
+ private static _solarConverter = defaultSensorAdapters.solarRadiation
123
+ private static _rainConverter = defaultSensorAdapters.rainGauge
124
+ private static _windConverter = defaultSensorAdapters.wind
125
+ private static _baroConverter = defaultSensorAdapters.barometricPressure
126
+ private static _batteryConverter = defaultSensorAdapters.battery
127
+ private static _acousticSplConverter = defaultSensorAdapters.acousticSpl
128
+ private static _acousticWeatherConverter = defaultSensorAdapters.acousticWeather
129
+
130
+ /**
131
+ * Ingests a raw LoRaWAN uplink message, decodes binary payload, evaluates domain converters
132
+ * with optional agronomic plot context provided by the Backoffice, and returns validated DataPoints.
133
+ *
134
+ * @param uplink - Incoming ChirpStack LoRaWAN message object (pure radio frame).
135
+ * @param agronomicContext - Optional agronomic context resolved from Backoffice (Plot ID, Soil Texture, Calibration).
136
+ * @returns Array of transformed DataPoints.
137
+ */
138
+ static process(
139
+ uplink: PipelineUplinkInput,
140
+ agronomicContext?: AgronomicPlotContext,
141
+ ): PipelineDataPointOutput[] {
142
+ const dataPoints: PipelineDataPointOutput[] = []
143
+
144
+ // 1. Resolve Canonical Device & Tenancy Identifiers
145
+ const rawDeviceName = uplink.deviceInfo?.deviceName || uplink.deviceInfo?.devEui || uplink.devEUI || 'unknown'
146
+ const deviceUri = this._buildDeviceUri(rawDeviceName)
147
+
148
+ // Plot & Company resolved from Backoffice agronomic context (with fallback to metadata tags)
149
+ const rawPlot = agronomicContext?.plot || uplink.deviceInfo?.tags?.plot
150
+ const plotUri = rawPlot ? (rawPlot.startsWith('plots/') ? rawPlot : `plots/${rawPlot}`) : undefined
151
+
152
+ const rawCompany = agronomicContext?.company || uplink.deviceInfo?.tags?.company
153
+ const companyUri = rawCompany ? (rawCompany.startsWith('companies/') ? rawCompany : `companies/${rawCompany}`) : undefined
154
+
155
+ const timestamp = uplink.publishedAt || uplink.time || new Date().toISOString()
156
+
157
+ // Soil texture and calibration model come from the Backoffice plot definition
158
+ const soilTexture = agronomicContext?.soilTexture || (uplink.deviceInfo?.tags?.soilTexture as any) || 'default'
159
+ const soilLinearRegression = agronomicContext?.soilLinearRegression || this._extractLinearRegression(uplink.deviceInfo?.tags)
160
+
161
+ // 2. Build LoRaWAN Radio Metadata Contract
162
+ const bestGateway = uplink.rxInfo?.[0]
163
+ const vendor = agronomicContext?.vendor || (uplink.deviceInfo?.tags?.vendor as any) || 'brad'
164
+ const radioMetadata = {
165
+ interface: '@bradtech/types:LoRaWanMetadataInterface',
166
+ vendor,
167
+ vendorModel: agronomicContext?.vendorModel || uplink.deviceInfo?.tags?.vendorModel || (rawDeviceName.startsWith('b26w') ? 'Brad Weather Station v1' : 'Brad Soil Probe v2.5'),
168
+ vendorDeviceId: rawDeviceName,
169
+ integrationType: 'lorawan' as const,
170
+ devEui: uplink.deviceInfo?.devEui || uplink.devEUI,
171
+ fPort: uplink.fPort,
172
+ fCnt: uplink.fCnt,
173
+ frequency: uplink.txInfo?.frequency,
174
+ dataRate: uplink.txInfo?.dataRate,
175
+ rssi: bestGateway?.rssi,
176
+ snr: bestGateway?.snr,
177
+ gatewayId: bestGateway?.gatewayId,
178
+ gatewayCount: uplink.rxInfo?.length || 0,
179
+ }
180
+
181
+ // 3. Emit Network Quality DataPoints
182
+ if (bestGateway?.rssi !== undefined) {
183
+ dataPoints.push({
184
+ device: deviceUri,
185
+ plot: plotUri,
186
+ company: companyUri,
187
+ metric: 'okf:radio/lorawan/rssi',
188
+ value: bestGateway.rssi,
189
+ unit: 'dBm',
190
+ kind: 'measured',
191
+ confidence: 1.0,
192
+ timestamp,
193
+ metadata: radioMetadata,
194
+ })
195
+ }
196
+
197
+ if (bestGateway?.snr !== undefined) {
198
+ dataPoints.push({
199
+ device: deviceUri,
200
+ plot: plotUri,
201
+ company: companyUri,
202
+ metric: 'okf:radio/lorawan/snr',
203
+ value: Number(bestGateway.snr.toFixed(1)),
204
+ unit: 'dB',
205
+ kind: 'measured',
206
+ confidence: 1.0,
207
+ timestamp,
208
+ metadata: radioMetadata,
209
+ })
210
+ }
211
+
212
+ // 4. Decode Payload via BradOSCodec
213
+ if (!uplink.fPort || !uplink.data) {
214
+ return dataPoints
215
+ }
216
+
217
+ // Special Case: FPort 1 (Boot Frame)
218
+ if (uplink.fPort === 1) {
219
+ const boot = BradOSCodec.decodeBootPayload(uplink.data)
220
+ if (boot) {
221
+ const battOutputs = LoRaWanPipeline._batteryConverter.convert({ voltageMv: boot.batteryMv })
222
+ for (const b of battOutputs) {
223
+ dataPoints.push({
224
+ device: deviceUri,
225
+ plot: plotUri,
226
+ company: companyUri,
227
+ metric: b.metric,
228
+ value: b.value,
229
+ unit: b.unit,
230
+ kind: 'measured',
231
+ confidence: b.confidence,
232
+ timestamp,
233
+ metadata: {
234
+ ...radioMetadata,
235
+ bootVersion: boot.version,
236
+ buildDoy: boot.buildDoy,
237
+ resetReason: boot.resetReason,
238
+ ...b.metadata,
239
+ },
240
+ })
241
+ }
242
+ }
243
+ return dataPoints
244
+ }
245
+
246
+ // Standard Telemetry: FPort 2 to 52
247
+ const channel = BradOSCodec.decodeFPortChannel(uplink.fPort, uplink.data)
248
+ if (!channel) {
249
+ return dataPoints
250
+ }
251
+
252
+ const conversionOutputs: ConversionOutput[] = []
253
+
254
+ switch (channel.channelType) {
255
+ case 'canopy_temp': {
256
+ const outputs = LoRaWanPipeline._runConverter(LoRaWanPipeline._airConverter, {
257
+ temperature: channel.rawValue,
258
+ humidity: 60.0,
259
+ })
260
+ const tempOut = outputs.find((o) => o.metric.includes('temperature'))
261
+ if (tempOut) conversionOutputs.push(tempOut)
262
+ break
263
+ }
264
+
265
+ case 'canopy_hum': {
266
+ const outputs = LoRaWanPipeline._runConverter(LoRaWanPipeline._airConverter, {
267
+ temperature: 20.0,
268
+ humidity: channel.rawValue,
269
+ })
270
+ const humOut = outputs.find((o) => o.metric.includes('humidity'))
271
+ if (humOut) conversionOutputs.push(humOut)
272
+ break
273
+ }
274
+
275
+ case 'soil_moisture': {
276
+ // Apply plot-specific soil texture & calibration model from agronomic context
277
+ const vwcOut = LoRaWanPipeline._runConverter(
278
+ LoRaWanPipeline._soilMoistureConverter,
279
+ { depthCm: channel.depthCm || 10, rawValue: channel.rawValue },
280
+ { soilTexture, soilLinearRegression },
281
+ )
282
+
283
+ conversionOutputs.push(...vwcOut)
284
+
285
+ // Automatically compute derived pF matric potential based on plot soil texture
286
+ if (vwcOut.length > 0) {
287
+ const wpOut = LoRaWanPipeline._runConverter(
288
+ LoRaWanPipeline._soilWpConverter,
289
+ { depthCm: channel.depthCm || 10, vwcPercent: vwcOut[0].value },
290
+ { soilTexture },
291
+ )
292
+ conversionOutputs.push(...wpOut)
293
+ }
294
+ break
295
+ }
296
+
297
+ case 'soil_temp': {
298
+ const tempOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._soilTempConverter, {
299
+ depthCm: channel.depthCm || 10,
300
+ temperature: channel.rawValue,
301
+ })
302
+ conversionOutputs.push(...tempOut)
303
+ break
304
+ }
305
+
306
+ case 'soil_ec': {
307
+ const ecOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._soilEcConverter, {
308
+ depthCm: channel.depthCm || 10,
309
+ bulkEcMsCm: channel.rawValue,
310
+ })
311
+ conversionOutputs.push(...ecOut)
312
+ break
313
+ }
314
+
315
+ case 'solar': {
316
+ const solarOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._solarConverter, { irradianceWm2: channel.rawValue })
317
+ conversionOutputs.push(...solarOut)
318
+ break
319
+ }
320
+
321
+ case 'rain': {
322
+ const rainOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._rainConverter, { tipCount: channel.rawValue })
323
+ conversionOutputs.push(...rainOut)
324
+ break
325
+ }
326
+
327
+ case 'wind_speed': {
328
+ const windOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._windConverter, { speedMs: channel.rawValue })
329
+ conversionOutputs.push(...windOut)
330
+ break
331
+ }
332
+
333
+ case 'wind_dir': {
334
+ const windDirOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._windConverter, { speedMs: 0, directionDegrees: channel.rawValue })
335
+ conversionOutputs.push(...windDirOut)
336
+ break
337
+ }
338
+
339
+ case 'pressure': {
340
+ const baroOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._baroConverter, { pressureHpa: channel.rawValue })
341
+ conversionOutputs.push(...baroOut)
342
+ break
343
+ }
344
+
345
+ case 'battery': {
346
+ const battOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._batteryConverter, { voltageV: channel.rawValue })
347
+ conversionOutputs.push(...battOut)
348
+ break
349
+ }
350
+
351
+ case 'acoustic_spl': {
352
+ const splOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._acousticSplConverter, { rmsDbfs: channel.rawValue })
353
+ conversionOutputs.push(...splOut)
354
+ break
355
+ }
356
+
357
+ case 'acoustic_rain':
358
+ case 'acoustic_wind': {
359
+ const acWeatherOut = LoRaWanPipeline._runConverter(LoRaWanPipeline._acousticWeatherConverter, {
360
+ highFrequencyEnergyRms: channel.channelType === 'acoustic_rain' ? channel.rawValue : -80,
361
+ lowFrequencyEnergyRms: channel.channelType === 'acoustic_wind' ? channel.rawValue : -80,
362
+ })
363
+ conversionOutputs.push(...acWeatherOut)
364
+ break
365
+ }
366
+ }
367
+
368
+ // Map conversion outputs to final DataPoints
369
+ for (const out of conversionOutputs) {
370
+ const isDerived = out.metric.includes('potential') || out.metric.includes('par_ppfd') || out.metric.includes('percentage')
371
+
372
+ dataPoints.push({
373
+ device: deviceUri,
374
+ plot: plotUri,
375
+ company: companyUri,
376
+ metric: out.metric,
377
+ value: out.value,
378
+ unit: out.unit,
379
+ kind: isDerived ? 'computed' : 'measured',
380
+ confidence: out.confidence,
381
+ timestamp,
382
+ metadata: {
383
+ ...radioMetadata,
384
+ sensorSource: channel.sensorSource,
385
+ sensorModel: channel.sensorModel,
386
+ ...out.metadata,
387
+ interface: isDerived ? '@bradtech/types:ComputedMetadataInterface' : '@bradtech/types:LoRaWanMetadataInterface',
388
+ },
389
+ })
390
+ }
391
+
392
+ return dataPoints
393
+ }
394
+
395
+ /**
396
+ * Executes a converter instance and injects class name, model code, and semver version into metadata.
397
+ */
398
+ private static _runConverter(
399
+ converter: any,
400
+ raw: any,
401
+ context?: any,
402
+ ): ConversionOutput[] {
403
+ const outputs: ConversionOutput[] = converter.convert(raw, context)
404
+ return outputs.map((out) => ({
405
+ ...out,
406
+ metadata: {
407
+ converterClass: converter.constructor.name,
408
+ modelCode: converter.modelCode,
409
+ modelVersion: converter.modelVersion,
410
+ ...out.metadata,
411
+ },
412
+ }))
413
+ }
414
+
415
+ /**
416
+ * Normalizes raw hardware device names into canonical OKF device URIs.
417
+ */
418
+ private static _buildDeviceUri(deviceName: string): string {
419
+ const clean = deviceName.trim().toLowerCase()
420
+ if (clean.startsWith('probes/') || clean.startsWith('weather-stations/')) {
421
+ return clean
422
+ }
423
+ if (clean.startsWith('b26w') || clean.startsWith('station')) {
424
+ return `weather-stations/${clean}`
425
+ }
426
+ return `probes/${clean}`
427
+ }
428
+
429
+ /**
430
+ * Helper extracting linear regression calibration parameters if provided in tags.
431
+ */
432
+ private static _extractLinearRegression(tags?: Record<string, string>) {
433
+ if (!tags?.soilSlope || !tags?.soilIntercept) return undefined
434
+ const slope = parseFloat(tags.soilSlope)
435
+ const intercept = parseFloat(tags.soilIntercept)
436
+ if (isNaN(slope) || isNaN(intercept)) return undefined
437
+ return {
438
+ slope,
439
+ intercept,
440
+ modelLabel: tags.soilModelLabel,
441
+ }
442
+ }
443
+ }
@@ -0,0 +1,82 @@
1
+ import { Sensor } from '@bradtech/sensor'
2
+ import {
3
+ CanopyMicroclimateConverter,
4
+ EvapotranspirationConverter,
5
+ GroundFrostRiskConverter,
6
+ } from '@bradtech/sensor-air'
7
+ import {
8
+ SoilMoistureConverter,
9
+ SoilWaterPotentialConverter,
10
+ SoilTemperatureConverter,
11
+ SoilElectricalConductivityConverter,
12
+ } from '@bradtech/sensor-soil'
13
+ import {
14
+ SolarRadiationConverter,
15
+ RainGaugeConverter,
16
+ WindConverter,
17
+ BarometricPressureConverter,
18
+ } from '@bradtech/sensor-weather'
19
+ import { BatterySoCConverter, SolarHarvestingConverter } from '@bradtech/sensor-power'
20
+ import { AcousticSplConverter, AcousticWeatherConverter } from '@bradtech/sensor-acoustic'
21
+
22
+ /**
23
+ * Pre-instantiated out-of-the-box domain sensor adapters dictionary.
24
+ * Avoids redundant allocations across high-frequency message processing pipelines.
25
+ */
26
+ export const defaultSensorAdapters = {
27
+ /** In-canopy temperature, humidity, dew point and foliar VPD adapter */
28
+ canopyAir: new CanopyMicroclimateConverter(),
29
+ /** Hargreaves-Samani FAO-56 daily reference evapotranspiration (ET0) adapter */
30
+ evapotranspiration: new EvapotranspirationConverter(),
31
+ /** Roland Stull wet-bulb temperature and ground frost risk evaluator */
32
+ frostRisk: new GroundFrostRiskConverter(),
33
+ /** Multi-depth soil volumetric water content (VWC %) calibrated adapter */
34
+ soilMoisture: new SoilMoistureConverter(),
35
+ /** Van Genuchten soil matric water potential (kPa) and pF scale adapter */
36
+ soilWaterPotential: new SoilWaterPotentialConverter(),
37
+ /** Multi-depth soil profile thermistor adapter */
38
+ soilTemperature: new SoilTemperatureConverter(),
39
+ /** 25°C temperature-normalized soil electrical conductivity (mS/cm) adapter */
40
+ soilEc: new SoilElectricalConductivityConverter(),
41
+ /** Broadband solar pyranometer (W/m²) and PAR PPFD photon flux adapter */
42
+ solarRadiation: new SolarRadiationConverter(),
43
+ /** Tipping bucket rainfall accumulation (mm) and rain rate (mm/h) adapter */
44
+ rainGauge: new RainGaugeConverter(),
45
+ /** Anemometer wind speed (km/h) and 16-cardinal vane direction adapter */
46
+ wind: new WindConverter(),
47
+ /** Absolute barometric pressure and sea-level QNH reduction adapter */
48
+ barometricPressure: new BarometricPressureConverter(),
49
+ /** Chemistry-aware non-linear battery SoC (%) and brownout adapter */
50
+ battery: new BatterySoCConverter(),
51
+ /** Solar panel photovoltaic harvesting voltage adapter */
52
+ solarHarvesting: new SolarHarvestingConverter(),
53
+ /** MEMS microphone Sound Pressure Level (dBA SPL) adapter */
54
+ acousticSpl: new AcousticSplConverter(),
55
+ /** Acoustic rain droplet impact and aerodynamic wind turbulence adapter */
56
+ acousticWeather: new AcousticWeatherConverter(),
57
+ }
58
+
59
+ /**
60
+ * Registers all built-in Brad domain sensor adapters into the global Quatrain Sensor facade.
61
+ *
62
+ * @returns The initialized Sensor singleton class for fluent chaining.
63
+ */
64
+ export function registerDefaultSensorAdapters(): typeof Sensor {
65
+ Sensor.addAdapter(defaultSensorAdapters.canopyAir, 'canopyAir')
66
+ Sensor.addAdapter(defaultSensorAdapters.evapotranspiration, 'evapotranspiration')
67
+ Sensor.addAdapter(defaultSensorAdapters.frostRisk, 'frostRisk')
68
+ Sensor.addAdapter(defaultSensorAdapters.soilMoisture, 'soilMoisture')
69
+ Sensor.addAdapter(defaultSensorAdapters.soilWaterPotential, 'soilWaterPotential')
70
+ Sensor.addAdapter(defaultSensorAdapters.soilTemperature, 'soilTemperature')
71
+ Sensor.addAdapter(defaultSensorAdapters.soilEc, 'soilEc')
72
+ Sensor.addAdapter(defaultSensorAdapters.solarRadiation, 'solarRadiation')
73
+ Sensor.addAdapter(defaultSensorAdapters.rainGauge, 'rainGauge')
74
+ Sensor.addAdapter(defaultSensorAdapters.wind, 'wind')
75
+ Sensor.addAdapter(defaultSensorAdapters.barometricPressure, 'barometricPressure')
76
+ Sensor.addAdapter(defaultSensorAdapters.battery, 'battery')
77
+ Sensor.addAdapter(defaultSensorAdapters.solarHarvesting, 'solarHarvesting')
78
+ Sensor.addAdapter(defaultSensorAdapters.acousticSpl, 'acousticSpl')
79
+ Sensor.addAdapter(defaultSensorAdapters.acousticWeather, 'acousticWeather')
80
+
81
+ return Sensor
82
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './BradOSCodec'
2
+ export * from './LoRaWanPipeline'
3
+ export * from './defaultAdapters'