/** * 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 #include // ============================================================================ // 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