Files
znet-temp-sensor/docs/LORA_PROTOCOL.md
T
leehughesandClaude Opus 4.5 5554341b49 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>
2026-01-24 17:50:53 -06:00

410 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |