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

14 KiB
Raw Blame History

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

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:

{
  "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