IoT Data Ingestion
This page documents how sensor data flows from a physical IoT device into the system, triggering threshold comparisons and live alerts.
Endpoint
POST /api/v1/devices/sensor-dataAuth: deviceAuthCheck — static token in Authorization header (hardware devices only, not user JWT).
Request Format
POST /api/v1/devices/sensor-data
Authorization: Au@jsjKAKL9IJK@@Kks
Content-Type: application/json
{
"id": "DEVICE-CODE-001",
"temperature": 5.2,
"humidity": 62.1,
"volt": 220.5,
"seq_no": 1234
}The id field is the unique device code stored in the devices collection.
Processing Pipeline
POST body arrives
│
▼
deviceAuthCheck → static token match
│
▼
saveSensorData() in deviceSensorData.controller.js
│
├─ 1. Find Device by code
│ DeviceModel.findOne({ code: body.id })
│
├─ 2. Find associated Unit
│ UnitModel.findOne({ device_id: device._id })
│
├─ 3. Parse sensor records
│ prepareSensorRecords(body)
│ → sensorRecords[] (temperature, humidity, volt, seq_no, date)
│ → networkRecords[] (operator, mnc, sinr, mcc, rssi, band, tech ...)
│
├─ 4. Compute live alerts
│ generateErrorAlerts(recentSensorData, unit)
│
├─ 5. Persist data
│ SensorDataModel.insertMany(sensorRecords)
│ NetworkDataModel.insertMany(networkRecords)
│ unit.set({ recent_sensor_data, live_alerts, last_communicated_at })
│ unit.save()
│ device.set({ recent_sensor_data, last_communicated_at })
│ device.save()
│
└─ 6. Return success responseAlert Generation (generateErrorAlerts)
This function computes the current alert state by comparing the latest sensor reading against the unit's configured thresholds.
generateErrorAlerts(recentSensorData, unit)Threshold Checks
| Sensor | Below minimum | Above maximum |
|---|---|---|
| temperature | error_code: 'LOW_TEMP' | error_code: 'HIGH_TEMP' |
| humidity | error_code: 'LOW_HUM' | error_code: 'HIGH_HUM' |
| voltage | error_code: 'LOW_VOLT' | error_code: 'HIGH_VOLT' |
Alert Object Shape
{
"error_code": "HIGH_TEMP",
"start_date": "2024-01-15T08:30:00.000Z",
"value": 12.4,
"count": 7
}| Field | Description |
|---|---|
error_code | Identifies the type of threshold breach |
start_date | When the breach started — preserved from previous alerts |
value | Current sensor value at the time of this reading |
count | How many consecutive readings have been in breach |
Start Date Preservation
A key feature: if an alert with the same error_code already existed in unit.live_alerts, the new alert inherits its start_date. This prevents the start time from resetting on every reading.
// Pseudocode
const existingAlert = previousAlerts.find(a => a.error_code === errorCode)
const startDate = existingAlert?.start_date ?? new Date()Alert Clearing
When the sensor value returns to within the acceptable range, the corresponding alert is simply not included in the new live_alerts array. Setting unit.live_alerts to the new array atomically clears resolved alerts.
Cached Readings (recent_sensor_data)
Both Device and Unit documents store recent_sensor_data — a copy of the latest reading. This enables:
- Fast retrieval of "current conditions" without querying the time-series
sensor_datacollection - Populating the live dashboard without a database aggregation
{
"temperature": 5.2,
"humidity": 62.1,
"volt": 220.5,
"date": "2024-01-15T08:30:00.000Z"
}Historical Storage
Every incoming payload also creates permanent time-series records:
| Collection | Model | Stored fields |
|---|---|---|
sensor_data | DeviceSensorDataModel | temperature, humidity, volt, seq_no, date |
network_data | DeviceNetworkDataModel | operator, mnc, sinr, mcc, rssi, channel, tech, band, ... |
raw_sensor_data | RawDeviceSensorDataModel | device_id, date (raw payload reference) |
This historical data is used for report generation, admin dashboards, and diagnostic analysis.
Reports from Historical Data
The ReportBusiness class queries sensor_data for a date range and computes aggregate statistics:
// MongoDB aggregation
{
avg_temperature: { $avg: '$temperature' },
min_temperature: { $min: '$temperature' },
max_temperature: { $max: '$temperature' },
avg_humidity: { $avg: '$humidity' },
// ... etc.
}Reports can be downloaded as:
- CSV — streamed directly from the Express response
- PDF — generated with
pdfkitand streamed; usesmoment-timezonefor formatted dates
