Add LoRa sensor network support
- Design LoRa packet protocol with CRC16 validation - Add protocol header files (lora_protocol.h, lora_packet.h) - Create battery-powered remote node firmware template - Support for DHT22, BME680, DS18B20 sensors - Deep sleep for battery conservation - Automatic gateway registration - Add LoRa receive to gateway firmware - RadioLib SX1262 integration - Node registry for tracking up to 16 nodes - HTTP forwarding of received readings - ACK responses to remote nodes - Create comprehensive setup guide with wiring diagrams Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
265a53768e
commit
5554341b49
@@ -0,0 +1,409 @@
|
||||
# ZNET LoRa Sensor Network Protocol
|
||||
|
||||
## Overview
|
||||
|
||||
This document specifies the LoRa packet protocol for communication between remote sensor nodes and the ZNET gateway. The protocol is designed for:
|
||||
|
||||
- **Efficiency**: Minimal packet size for LoRa's limited bandwidth
|
||||
- **Reliability**: CRC16 checksums and sequence numbers for error detection
|
||||
- **Flexibility**: Support for multiple sensor types
|
||||
- **Battery life**: Low power design with configurable sleep intervals
|
||||
|
||||
## Network Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ LoRa 915MHz ┌─────────────────┐
|
||||
│ Remote Node 1 │◄───────────────────►│ │
|
||||
│ (DHT22) │ │ Gateway │
|
||||
├─────────────────┤ LoRa 915MHz │ ESP32 LoRa V3 │ WiFi ┌──────────┐
|
||||
│ Remote Node 2 │◄───────────────────►│ │◄─────────────►│ ZNET Web │
|
||||
│ (BME680) │ │ tank_gateway_1│ │ Backend │
|
||||
├─────────────────┤ LoRa 915MHz │ │ └──────────┘
|
||||
│ Remote Node 3 │◄───────────────────►│ │
|
||||
│ (DHT22) │ └─────────────────┘
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Packet Format
|
||||
|
||||
### General Structure
|
||||
|
||||
All packets follow this structure:
|
||||
|
||||
```
|
||||
┌───────┬────────┬──────────┬─────────┬────────────┬──────┐
|
||||
│ SYNC │ HEADER │ NODE ID │ SEQ │ PAYLOAD │ CRC │
|
||||
│ 2B │ 1B │ 2B │ 1B │ 0-64B │ 2B │
|
||||
└───────┴────────┴──────────┴─────────┴────────────┴──────┘
|
||||
```
|
||||
|
||||
| Field | Size | Description |
|
||||
|-------|------|-------------|
|
||||
| SYNC | 2 bytes | Magic bytes `0x5A 0x4E` ("ZN" for ZNET) |
|
||||
| HEADER | 1 byte | Packet type and flags |
|
||||
| NODE ID | 2 bytes | Unique node identifier (0x0001-0xFFFE) |
|
||||
| SEQ | 1 byte | Sequence number (0-255, wraps) |
|
||||
| PAYLOAD | 0-64 bytes | Type-specific data |
|
||||
| CRC | 2 bytes | CRC16-CCITT of all preceding bytes |
|
||||
|
||||
### Header Byte
|
||||
|
||||
```
|
||||
Bit 7-4: Packet Type (0-15)
|
||||
Bit 3: ACK Request (1 = wants acknowledgment)
|
||||
Bit 2: Battery Low (1 = battery < 20%)
|
||||
Bit 1: First Boot (1 = node just powered on)
|
||||
Bit 0: Reserved (0)
|
||||
```
|
||||
|
||||
### Packet Types
|
||||
|
||||
| Type | Value | Direction | Description |
|
||||
|------|-------|-----------|-------------|
|
||||
| SENSOR_DATA | 0x0 | Node → Gateway | Sensor readings |
|
||||
| ACK | 0x1 | Gateway → Node | Acknowledgment |
|
||||
| NAK | 0x2 | Gateway → Node | Negative ack (resend) |
|
||||
| CONFIG_REQ | 0x3 | Node → Gateway | Request configuration |
|
||||
| CONFIG_RESP | 0x4 | Gateway → Node | Configuration response |
|
||||
| PING | 0x5 | Gateway → Node | Check if node alive |
|
||||
| PONG | 0x6 | Node → Gateway | Response to ping |
|
||||
| ALERT | 0x7 | Node → Gateway | Critical alert (immediate) |
|
||||
| TIME_SYNC | 0x8 | Gateway → Node | Time synchronization |
|
||||
| FIRMWARE_INFO | 0x9 | Node → Gateway | Firmware version info |
|
||||
| REGISTER | 0xA | Node → Gateway | Node registration |
|
||||
| REGISTER_ACK | 0xB | Gateway → Node | Registration accepted |
|
||||
| Reserved | 0xC-0xF | - | Future use |
|
||||
|
||||
## Sensor Data Payload
|
||||
|
||||
### DHT22 Sensor (Type 0x01)
|
||||
|
||||
Temperature and humidity sensor for ambient conditions.
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 1 Sensor Type = 0x01
|
||||
1 2 Temperature (°C × 10, signed int16, big-endian)
|
||||
3 2 Humidity (% × 10, uint16, big-endian)
|
||||
5 1 Battery Voltage (mV ÷ 20, 0-255 = 0-5100mV)
|
||||
6 1 RSSI (signed int8, dBm)
|
||||
─────────────
|
||||
Total: 7 bytes
|
||||
```
|
||||
|
||||
**Example**: 25.5°C, 65.0% humidity, 3.7V battery, -45 dBm RSSI
|
||||
```
|
||||
01 00 FF 02 8A B9 D3
|
||||
│ └──┴── └──┴── │ └─ RSSI: -45 dBm
|
||||
│ │ │ └─── Battery: 185 × 20 = 3700mV
|
||||
│ │ └──────── Humidity: 650 / 10 = 65.0%
|
||||
│ └────────────── Temperature: 255 / 10 = 25.5°C
|
||||
└──────────────────── Sensor type: DHT22
|
||||
```
|
||||
|
||||
### BME680 Sensor (Type 0x02)
|
||||
|
||||
Environmental sensor with gas resistance (VOC proxy).
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 1 Sensor Type = 0x02
|
||||
1 2 Temperature (°C × 10, signed int16, big-endian)
|
||||
3 2 Humidity (% × 10, uint16, big-endian)
|
||||
5 2 Pressure (hPa - 900, uint16, big-endian)
|
||||
7 2 Gas Resistance (kΩ, uint16, big-endian)
|
||||
9 1 IAQ Index (0-500 air quality index, uint8)
|
||||
10 1 Battery Voltage (mV ÷ 20)
|
||||
11 1 RSSI (signed int8, dBm)
|
||||
─────────────
|
||||
Total: 12 bytes
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- Pressure is stored as offset from 900 hPa (range 900-965 hPa typical)
|
||||
- IAQ Index: 0-50 = Good, 51-100 = Moderate, 101-150 = Poor, 151+ = Unhealthy
|
||||
- Gas resistance correlates inversely with VOC presence
|
||||
|
||||
### DS18B20 Sensor (Type 0x03)
|
||||
|
||||
Waterproof temperature probe (if remote nodes need them).
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 1 Sensor Type = 0x03
|
||||
1 2 Temperature (°C × 100, signed int16, big-endian)
|
||||
3 1 Battery Voltage (mV ÷ 20)
|
||||
4 1 RSSI (signed int8, dBm)
|
||||
─────────────
|
||||
Total: 5 bytes
|
||||
```
|
||||
|
||||
**Note**: DS18B20 has 0.0625°C resolution, so we use × 100 for precision.
|
||||
|
||||
### Multi-Sensor Payload (Type 0x10)
|
||||
|
||||
For nodes with multiple sensors attached.
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 1 Sensor Type = 0x10
|
||||
1 1 Sensor Count (1-4)
|
||||
2 1 Sensor 1 Type
|
||||
3 N Sensor 1 Data (type-specific, without type byte)
|
||||
... (repeat for each sensor)
|
||||
Last 1 Battery Voltage (mV ÷ 20)
|
||||
Last+1 1 RSSI
|
||||
```
|
||||
|
||||
## Configuration Payload
|
||||
|
||||
### CONFIG_REQ (Node → Gateway)
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 2 Current interval (seconds)
|
||||
2 1 Firmware version major
|
||||
3 1 Firmware version minor
|
||||
4 8 Node name (null-padded ASCII)
|
||||
```
|
||||
|
||||
### CONFIG_RESP (Gateway → Node)
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 2 Report interval (seconds, 0 = use default)
|
||||
2 2 Warning threshold high (°C × 10)
|
||||
4 2 Alert threshold high (°C × 10)
|
||||
6 2 Warning threshold low (°C × 10, 0x8000 = disabled)
|
||||
8 2 Alert threshold low (°C × 10, 0x8000 = disabled)
|
||||
10 1 Flags:
|
||||
Bit 0: Alerts enabled
|
||||
Bit 1: ACK required
|
||||
Bit 2-7: Reserved
|
||||
```
|
||||
|
||||
## Registration Process
|
||||
|
||||
When a node powers on, it should register with the gateway:
|
||||
|
||||
```
|
||||
1. Node sends REGISTER packet:
|
||||
┌────────────────────────────────────────┐
|
||||
│ Payload: │
|
||||
│ 0-1: Proposed Node ID (or 0xFFFF) │
|
||||
│ 2: Sensor Type │
|
||||
│ 3: Firmware Version Major │
|
||||
│ 4: Firmware Version Minor │
|
||||
│ 5-12: Node Name (8 chars, null-pad) │
|
||||
└────────────────────────────────────────┘
|
||||
|
||||
2. Gateway responds with REGISTER_ACK:
|
||||
┌────────────────────────────────────────┐
|
||||
│ Payload: │
|
||||
│ 0-1: Assigned Node ID │
|
||||
│ 2: Status (0=OK, 1=ID conflict) │
|
||||
│ 3-4: Report interval (seconds) │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Alert Packet
|
||||
|
||||
For critical conditions requiring immediate attention:
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 1 Alert Type:
|
||||
0x01 = Temperature high
|
||||
0x02 = Temperature low
|
||||
0x03 = Humidity high
|
||||
0x04 = Humidity low
|
||||
0x05 = Battery critical
|
||||
0x06 = Sensor failure
|
||||
0x07 = VOC alert
|
||||
1 2 Alert Value (type-specific)
|
||||
3 2 Threshold Value
|
||||
5 1 Duration (seconds this condition persisted)
|
||||
```
|
||||
|
||||
## CRC16-CCITT Calculation
|
||||
|
||||
**Polynomial**: 0x1021
|
||||
**Initial value**: 0xFFFF
|
||||
**No final XOR**
|
||||
|
||||
```c
|
||||
uint16_t crc16_ccitt(const uint8_t* data, size_t len) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
crc ^= (uint16_t)data[i] << 8;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 0x8000) {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
```
|
||||
|
||||
## Timing and Duty Cycle
|
||||
|
||||
### LoRa Parameters (US 915 MHz)
|
||||
|
||||
| Parameter | Value | Notes |
|
||||
|-----------|-------|-------|
|
||||
| Frequency | 915.0 MHz | US ISM band |
|
||||
| Bandwidth | 125 kHz | Standard LoRa |
|
||||
| Spreading Factor | 9 | Balance of range/speed |
|
||||
| Coding Rate | 4/7 | Forward error correction |
|
||||
| Preamble | 8 symbols | Standard |
|
||||
| Sync Word | 0x12 | Private network |
|
||||
| TX Power | 14 dBm | Legal limit |
|
||||
|
||||
### Transmission Timing
|
||||
|
||||
| Packet Size | Air Time (SF9) | Duty Cycle @ 1% |
|
||||
|-------------|----------------|-----------------|
|
||||
| 15 bytes | ~51 ms | 1 packet / 5.1 sec |
|
||||
| 20 bytes | ~67 ms | 1 packet / 6.7 sec |
|
||||
| 30 bytes | ~97 ms | 1 packet / 9.7 sec |
|
||||
|
||||
**Recommended intervals**:
|
||||
- Normal operation: 30-60 seconds
|
||||
- Battery saving: 5-10 minutes
|
||||
- Alert condition: 10 seconds (temporary)
|
||||
|
||||
### Node Sleep Schedule
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Wake Read TX RX Window Sleep │
|
||||
│ (5ms) (100ms) (100ms) (500ms) (59s) │
|
||||
│ ├──────────┼──────────┼──────────┼────────────┤ │
|
||||
│ │ │ │ │ │ │
|
||||
└───┴──────────┴──────────┴──────────┴────────────┴──────┘
|
||||
```
|
||||
|
||||
## Node ID Assignment
|
||||
|
||||
| Range | Purpose |
|
||||
|-------|---------|
|
||||
| 0x0000 | Reserved (broadcast) |
|
||||
| 0x0001-0x00FF | Tank sensors (DS18B20) |
|
||||
| 0x0100-0x01FF | Ambient sensors (DHT22) |
|
||||
| 0x0200-0x02FF | Environmental (BME680) |
|
||||
| 0x0300-0x0FFF | Reserved for expansion |
|
||||
| 0x1000-0xFFFE | Auto-assigned |
|
||||
| 0xFFFF | Reserved (request auto-assign) |
|
||||
|
||||
## Example Packets
|
||||
|
||||
### DHT22 Sensor Data
|
||||
|
||||
Node 0x0101 reports 23.5°C, 55.0% humidity, 3.8V battery:
|
||||
|
||||
```
|
||||
Hex: 5A 4E 08 01 01 42 01 00 EB 02 26 BE D3 XX XX
|
||||
└──┴── │ └──┴── │ └─────────────────┴── CRC
|
||||
│ │ └─ Seq: 0x42 (66)
|
||||
│ └───── Node ID: 0x0101
|
||||
└───────── Header: Type=0 (SENSOR_DATA), ACK_REQ=1
|
||||
|
||||
Payload breakdown:
|
||||
01 - DHT22 type
|
||||
00 EB - Temperature: 235 / 10 = 23.5°C
|
||||
02 26 - Humidity: 550 / 10 = 55.0%
|
||||
BE - Battery: 190 × 20 = 3800mV
|
||||
D3 - RSSI: -45 dBm
|
||||
```
|
||||
|
||||
### Gateway ACK
|
||||
|
||||
Gateway acknowledges sequence 0x42 from node 0x0101:
|
||||
|
||||
```
|
||||
Hex: 5A 4E 10 01 01 42 XX XX
|
||||
└──┴── │ └──┴── │ └── CRC
|
||||
│ │ └─ Seq: 0x42 (echoed)
|
||||
│ └───── Node ID: 0x0101
|
||||
└───────── Header: Type=1 (ACK)
|
||||
```
|
||||
|
||||
### BME680 Alert (High VOC)
|
||||
|
||||
Node 0x0201 sends VOC alert (IAQ = 175):
|
||||
|
||||
```
|
||||
Hex: 5A 4E 78 02 01 15 07 00 AF 00 96 05 XX XX
|
||||
└──┴── │ └──┴── │ │ └──┴── └──┴── └── CRC
|
||||
│ │ │ │ │ └─ Threshold: 150
|
||||
│ │ │ │ └──────── Alert value: 175
|
||||
│ │ │ └────────────── Alert type: 0x07 (VOC)
|
||||
│ │ └───────────────── Seq: 0x15 (21)
|
||||
│ └───────────────────── Node ID: 0x0201
|
||||
└─────────────────────────── Header: Type=7 (ALERT), ACK_REQ=1, BAT_LOW=1
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Gateway Responsibilities
|
||||
|
||||
1. **Receive and decode** all incoming LoRa packets
|
||||
2. **Validate CRC** and discard corrupt packets
|
||||
3. **Track sequence numbers** per node for duplicate detection
|
||||
4. **Send ACKs** for packets with ACK_REQ flag
|
||||
5. **Forward data** to ZNET Web via HTTP POST
|
||||
6. **Store node registry** with last-seen timestamps
|
||||
7. **Send alerts** for nodes not reporting (timeout)
|
||||
|
||||
### Remote Node Responsibilities
|
||||
|
||||
1. **Sleep** between readings to conserve battery
|
||||
2. **Read sensors** and validate readings
|
||||
3. **Build and transmit** LoRa packet
|
||||
4. **Listen for ACK** in RX window (if ACK_REQ set)
|
||||
5. **Retry** up to 3 times if no ACK received
|
||||
6. **Track battery** voltage and set BAT_LOW flag
|
||||
7. **Register** with gateway on first boot
|
||||
|
||||
## Backend API Integration
|
||||
|
||||
The gateway should POST remote sensor data to the same endpoint as local sensors:
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "tank_gateway_1",
|
||||
"device_name": "E-Coat Tank Gateway",
|
||||
"readings": [
|
||||
{
|
||||
"sensor_id": "lora_0x0101",
|
||||
"sensor_type": "dht22",
|
||||
"temperature_f": 74.3,
|
||||
"humidity_pct": 55.0,
|
||||
"is_valid": true,
|
||||
"rssi_dbm": -45,
|
||||
"battery_mv": 3800
|
||||
},
|
||||
{
|
||||
"sensor_id": "lora_0x0201",
|
||||
"sensor_type": "bme680",
|
||||
"temperature_f": 76.1,
|
||||
"humidity_pct": 48.5,
|
||||
"pressure_hpa": 1013.2,
|
||||
"gas_resistance_kohm": 150.5,
|
||||
"iaq_index": 85,
|
||||
"is_valid": true,
|
||||
"rssi_dbm": -52,
|
||||
"battery_mv": 3650
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0 | 2026-01-24 | Initial protocol specification |
|
||||
Reference in New Issue
Block a user