Files
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

347 lines
11 KiB
C
Raw Permalink 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 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