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,346 @@
|
||||
/**
|
||||
* ZNET LoRa Protocol Definition
|
||||
*
|
||||
* Packet format and constants for communication between remote
|
||||
* sensor nodes and the ZNET gateway.
|
||||
*
|
||||
* See docs/LORA_PROTOCOL.md for full specification.
|
||||
*/
|
||||
|
||||
#ifndef LORA_PROTOCOL_H
|
||||
#define LORA_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// ============================================================================
|
||||
// PROTOCOL CONSTANTS
|
||||
// ============================================================================
|
||||
|
||||
// Sync bytes (magic number)
|
||||
#define LORA_SYNC_BYTE_1 0x5A // 'Z'
|
||||
#define LORA_SYNC_BYTE_2 0x4E // 'N'
|
||||
|
||||
// Maximum packet size
|
||||
#define LORA_MAX_PACKET_SIZE 72 // 8 header + 64 payload max
|
||||
#define LORA_MAX_PAYLOAD_SIZE 64
|
||||
#define LORA_MIN_PACKET_SIZE 8 // Sync + Header + NodeID + Seq + CRC
|
||||
|
||||
// Header byte masks
|
||||
#define LORA_HDR_TYPE_MASK 0xF0 // Upper nibble = packet type
|
||||
#define LORA_HDR_TYPE_SHIFT 4
|
||||
#define LORA_HDR_ACK_REQ 0x08 // Bit 3: Request acknowledgment
|
||||
#define LORA_HDR_BAT_LOW 0x04 // Bit 2: Battery low warning
|
||||
#define LORA_HDR_FIRST_BOOT 0x02 // Bit 1: First boot since power
|
||||
#define LORA_HDR_RESERVED 0x01 // Bit 0: Reserved
|
||||
|
||||
// ============================================================================
|
||||
// PACKET TYPES
|
||||
// ============================================================================
|
||||
|
||||
typedef enum {
|
||||
LORA_PKT_SENSOR_DATA = 0x0, // Node → Gateway: Sensor readings
|
||||
LORA_PKT_ACK = 0x1, // Gateway → Node: Acknowledgment
|
||||
LORA_PKT_NAK = 0x2, // Gateway → Node: Request resend
|
||||
LORA_PKT_CONFIG_REQ = 0x3, // Node → Gateway: Request config
|
||||
LORA_PKT_CONFIG_RESP = 0x4, // Gateway → Node: Configuration
|
||||
LORA_PKT_PING = 0x5, // Gateway → Node: Alive check
|
||||
LORA_PKT_PONG = 0x6, // Node → Gateway: Alive response
|
||||
LORA_PKT_ALERT = 0x7, // Node → Gateway: Critical alert
|
||||
LORA_PKT_TIME_SYNC = 0x8, // Gateway → Node: Time sync
|
||||
LORA_PKT_FIRMWARE_INFO = 0x9, // Node → Gateway: FW version
|
||||
LORA_PKT_REGISTER = 0xA, // Node → Gateway: Registration
|
||||
LORA_PKT_REGISTER_ACK = 0xB, // Gateway → Node: Reg accepted
|
||||
} lora_packet_type_t;
|
||||
|
||||
// ============================================================================
|
||||
// SENSOR TYPES
|
||||
// ============================================================================
|
||||
|
||||
typedef enum {
|
||||
SENSOR_TYPE_DHT22 = 0x01, // Temperature + Humidity
|
||||
SENSOR_TYPE_BME680 = 0x02, // Temp + Humidity + Pressure + Gas
|
||||
SENSOR_TYPE_DS18B20 = 0x03, // Temperature only (high precision)
|
||||
SENSOR_TYPE_MULTI = 0x10, // Multiple sensors
|
||||
} lora_sensor_type_t;
|
||||
|
||||
// ============================================================================
|
||||
// ALERT TYPES
|
||||
// ============================================================================
|
||||
|
||||
typedef enum {
|
||||
ALERT_TEMP_HIGH = 0x01,
|
||||
ALERT_TEMP_LOW = 0x02,
|
||||
ALERT_HUMIDITY_HIGH = 0x03,
|
||||
ALERT_HUMIDITY_LOW = 0x04,
|
||||
ALERT_BATTERY_CRITICAL = 0x05,
|
||||
ALERT_SENSOR_FAILURE = 0x06,
|
||||
ALERT_VOC_HIGH = 0x07,
|
||||
} lora_alert_type_t;
|
||||
|
||||
// ============================================================================
|
||||
// NODE ID RANGES
|
||||
// ============================================================================
|
||||
|
||||
#define NODE_ID_BROADCAST 0x0000
|
||||
#define NODE_ID_TANK_START 0x0001
|
||||
#define NODE_ID_TANK_END 0x00FF
|
||||
#define NODE_ID_AMBIENT_START 0x0100
|
||||
#define NODE_ID_AMBIENT_END 0x01FF
|
||||
#define NODE_ID_ENVIRO_START 0x0200
|
||||
#define NODE_ID_ENVIRO_END 0x02FF
|
||||
#define NODE_ID_AUTO_START 0x1000
|
||||
#define NODE_ID_AUTO_END 0xFFFE
|
||||
#define NODE_ID_AUTO_REQUEST 0xFFFF
|
||||
|
||||
// ============================================================================
|
||||
// PACKET STRUCTURES
|
||||
// ============================================================================
|
||||
|
||||
// Packet header (common to all packets)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t sync[2]; // 0x5A 0x4E
|
||||
uint8_t header; // Type (4 bits) + Flags (4 bits)
|
||||
uint16_t node_id; // Big-endian
|
||||
uint8_t sequence; // Sequence number
|
||||
} lora_packet_header_t;
|
||||
|
||||
// DHT22 sensor payload (7 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t sensor_type; // SENSOR_TYPE_DHT22
|
||||
int16_t temp_c_x10; // Temperature °C × 10 (big-endian)
|
||||
uint16_t humidity_x10; // Humidity % × 10 (big-endian)
|
||||
uint8_t battery_mv20; // Battery mV ÷ 20
|
||||
int8_t rssi_dbm; // RSSI in dBm
|
||||
} lora_payload_dht22_t;
|
||||
|
||||
// BME680 sensor payload (12 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t sensor_type; // SENSOR_TYPE_BME680
|
||||
int16_t temp_c_x10; // Temperature °C × 10 (big-endian)
|
||||
uint16_t humidity_x10; // Humidity % × 10 (big-endian)
|
||||
uint16_t pressure_offset;// Pressure - 900 hPa (big-endian)
|
||||
uint16_t gas_kohm; // Gas resistance kΩ (big-endian)
|
||||
uint8_t iaq_index; // Air quality index 0-255
|
||||
uint8_t battery_mv20; // Battery mV ÷ 20
|
||||
int8_t rssi_dbm; // RSSI in dBm
|
||||
} lora_payload_bme680_t;
|
||||
|
||||
// DS18B20 sensor payload (5 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t sensor_type; // SENSOR_TYPE_DS18B20
|
||||
int16_t temp_c_x100; // Temperature °C × 100 (big-endian)
|
||||
uint8_t battery_mv20; // Battery mV ÷ 20
|
||||
int8_t rssi_dbm; // RSSI in dBm
|
||||
} lora_payload_ds18b20_t;
|
||||
|
||||
// Alert payload (6 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t alert_type; // lora_alert_type_t
|
||||
int16_t alert_value; // Type-specific value (big-endian)
|
||||
int16_t threshold; // Threshold that was exceeded (big-endian)
|
||||
uint8_t duration_sec; // How long condition persisted
|
||||
} lora_payload_alert_t;
|
||||
|
||||
// Registration request payload (13 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t proposed_id; // Proposed ID or 0xFFFF for auto
|
||||
uint8_t sensor_type; // Primary sensor type
|
||||
uint8_t fw_major; // Firmware major version
|
||||
uint8_t fw_minor; // Firmware minor version
|
||||
char node_name[8]; // Node name (null-padded)
|
||||
} lora_payload_register_t;
|
||||
|
||||
// Registration ACK payload (5 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t assigned_id; // Assigned node ID
|
||||
uint8_t status; // 0=OK, 1=ID conflict, 2=rejected
|
||||
uint16_t interval_sec; // Recommended report interval
|
||||
} lora_payload_register_ack_t;
|
||||
|
||||
// Configuration request payload (12 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t current_interval; // Current interval in seconds
|
||||
uint8_t fw_major;
|
||||
uint8_t fw_minor;
|
||||
char node_name[8];
|
||||
} lora_payload_config_req_t;
|
||||
|
||||
// Configuration response payload (11 bytes)
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t interval_sec; // 0 = use default
|
||||
int16_t warn_high_c_x10; // Warning high threshold
|
||||
int16_t alert_high_c_x10; // Alert high threshold
|
||||
int16_t warn_low_c_x10; // 0x8000 = disabled
|
||||
int16_t alert_low_c_x10; // 0x8000 = disabled
|
||||
uint8_t flags; // Bit 0: alerts enabled, Bit 1: ACK required
|
||||
} lora_payload_config_resp_t;
|
||||
|
||||
// ============================================================================
|
||||
// CRC16-CCITT CALCULATION
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Calculate CRC16-CCITT checksum
|
||||
*
|
||||
* @param data Pointer to data buffer
|
||||
* @param len Length of data
|
||||
* @return 16-bit CRC
|
||||
*/
|
||||
static inline uint16_t lora_crc16(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;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Build packet header byte
|
||||
*/
|
||||
static inline uint8_t lora_make_header(lora_packet_type_t type, bool ack_req,
|
||||
bool bat_low, bool first_boot) {
|
||||
uint8_t hdr = (type << LORA_HDR_TYPE_SHIFT) & LORA_HDR_TYPE_MASK;
|
||||
if (ack_req) hdr |= LORA_HDR_ACK_REQ;
|
||||
if (bat_low) hdr |= LORA_HDR_BAT_LOW;
|
||||
if (first_boot) hdr |= LORA_HDR_FIRST_BOOT;
|
||||
return hdr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract packet type from header
|
||||
*/
|
||||
static inline lora_packet_type_t lora_get_type(uint8_t header) {
|
||||
return (lora_packet_type_t)((header & LORA_HDR_TYPE_MASK) >> LORA_HDR_TYPE_SHIFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ACK requested
|
||||
*/
|
||||
static inline bool lora_ack_requested(uint8_t header) {
|
||||
return (header & LORA_HDR_ACK_REQ) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if battery low
|
||||
*/
|
||||
static inline bool lora_battery_low(uint8_t header) {
|
||||
return (header & LORA_HDR_BAT_LOW) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert battery voltage to encoded value (mV ÷ 20)
|
||||
*/
|
||||
static inline uint8_t lora_encode_battery(uint16_t mv) {
|
||||
if (mv > 5100) mv = 5100;
|
||||
return (uint8_t)(mv / 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode battery value to mV
|
||||
*/
|
||||
static inline uint16_t lora_decode_battery(uint8_t encoded) {
|
||||
return (uint16_t)encoded * 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert temperature to encoded value (°C × 10)
|
||||
*/
|
||||
static inline int16_t lora_encode_temp_c10(float temp_c) {
|
||||
return (int16_t)(temp_c * 10.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode temperature from encoded value
|
||||
*/
|
||||
static inline float lora_decode_temp_c10(int16_t encoded) {
|
||||
return (float)encoded / 10.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert temperature to high-precision encoded value (°C × 100)
|
||||
*/
|
||||
static inline int16_t lora_encode_temp_c100(float temp_c) {
|
||||
return (int16_t)(temp_c * 100.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode high-precision temperature
|
||||
*/
|
||||
static inline float lora_decode_temp_c100(int16_t encoded) {
|
||||
return (float)encoded / 100.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert humidity to encoded value (% × 10)
|
||||
*/
|
||||
static inline uint16_t lora_encode_humidity(float humidity_pct) {
|
||||
return (uint16_t)(humidity_pct * 10.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode humidity
|
||||
*/
|
||||
static inline float lora_decode_humidity(uint16_t encoded) {
|
||||
return (float)encoded / 10.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Celsius to Fahrenheit
|
||||
*/
|
||||
static inline float lora_c_to_f(float celsius) {
|
||||
return celsius * 9.0f / 5.0f + 32.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Fahrenheit to Celsius
|
||||
*/
|
||||
static inline float lora_f_to_c(float fahrenheit) {
|
||||
return (fahrenheit - 32.0f) * 5.0f / 9.0f;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BYTE ORDER HELPERS (for big-endian protocol)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Write uint16_t as big-endian
|
||||
*/
|
||||
static inline void lora_write_be16(uint8_t* buf, uint16_t val) {
|
||||
buf[0] = (val >> 8) & 0xFF;
|
||||
buf[1] = val & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read uint16_t from big-endian
|
||||
*/
|
||||
static inline uint16_t lora_read_be16(const uint8_t* buf) {
|
||||
return ((uint16_t)buf[0] << 8) | buf[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Write int16_t as big-endian
|
||||
*/
|
||||
static inline void lora_write_be16s(uint8_t* buf, int16_t val) {
|
||||
lora_write_be16(buf, (uint16_t)val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read int16_t from big-endian
|
||||
*/
|
||||
static inline int16_t lora_read_be16s(const uint8_t* buf) {
|
||||
return (int16_t)lora_read_be16(buf);
|
||||
}
|
||||
|
||||
#endif // LORA_PROTOCOL_H
|
||||
Reference in New Issue
Block a user