# 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 |