/** * ZNET LoRa Packet Builder and Parser * * High-level functions for building and parsing LoRa packets. * Use these in both gateway and remote node firmware. */ #ifndef LORA_PACKET_H #define LORA_PACKET_H #include "lora_protocol.h" #include // ============================================================================ // PACKET BUFFER // ============================================================================ typedef struct { uint8_t data[LORA_MAX_PACKET_SIZE]; uint8_t length; uint16_t node_id; uint8_t sequence; lora_packet_type_t type; bool ack_requested; bool battery_low; bool first_boot; bool valid; } lora_packet_t; // ============================================================================ // PACKET BUILDING // ============================================================================ /** * Initialize packet buffer with header * * @param pkt Packet buffer to initialize * @param type Packet type * @param node_id Source/destination node ID * @param seq Sequence number * @param ack_req Request acknowledgment * @param bat_low Battery low flag * @param first First boot flag */ static inline void lora_packet_init(lora_packet_t* pkt, lora_packet_type_t type, uint16_t node_id, uint8_t seq, bool ack_req, bool bat_low, bool first) { pkt->data[0] = LORA_SYNC_BYTE_1; pkt->data[1] = LORA_SYNC_BYTE_2; pkt->data[2] = lora_make_header(type, ack_req, bat_low, first); lora_write_be16(&pkt->data[3], node_id); pkt->data[5] = seq; pkt->length = 6; // Header without payload or CRC pkt->node_id = node_id; pkt->sequence = seq; pkt->type = type; pkt->ack_requested = ack_req; pkt->battery_low = bat_low; pkt->first_boot = first; pkt->valid = true; } /** * Add DHT22 payload to packet */ static inline void lora_packet_add_dht22(lora_packet_t* pkt, float temp_c, float humidity_pct, uint16_t battery_mv, int8_t rssi_dbm) { uint8_t* payload = &pkt->data[pkt->length]; payload[0] = SENSOR_TYPE_DHT22; lora_write_be16s(&payload[1], lora_encode_temp_c10(temp_c)); lora_write_be16(&payload[3], lora_encode_humidity(humidity_pct)); payload[5] = lora_encode_battery(battery_mv); payload[6] = (uint8_t)rssi_dbm; pkt->length += 7; } /** * Add BME680 payload to packet */ static inline void lora_packet_add_bme680(lora_packet_t* pkt, float temp_c, float humidity_pct, float pressure_hpa, uint16_t gas_kohm, uint8_t iaq_index, uint16_t battery_mv, int8_t rssi_dbm) { uint8_t* payload = &pkt->data[pkt->length]; payload[0] = SENSOR_TYPE_BME680; lora_write_be16s(&payload[1], lora_encode_temp_c10(temp_c)); lora_write_be16(&payload[3], lora_encode_humidity(humidity_pct)); // Pressure offset from 900 hPa uint16_t press_offset = (uint16_t)(pressure_hpa - 900.0f); if (pressure_hpa < 900.0f) press_offset = 0; lora_write_be16(&payload[5], press_offset); lora_write_be16(&payload[7], gas_kohm); payload[9] = iaq_index; payload[10] = lora_encode_battery(battery_mv); payload[11] = (uint8_t)rssi_dbm; pkt->length += 12; } /** * Add DS18B20 payload to packet (high precision) */ static inline void lora_packet_add_ds18b20(lora_packet_t* pkt, float temp_c, uint16_t battery_mv, int8_t rssi_dbm) { uint8_t* payload = &pkt->data[pkt->length]; payload[0] = SENSOR_TYPE_DS18B20; lora_write_be16s(&payload[1], lora_encode_temp_c100(temp_c)); payload[3] = lora_encode_battery(battery_mv); payload[4] = (uint8_t)rssi_dbm; pkt->length += 5; } /** * Add alert payload to packet */ static inline void lora_packet_add_alert(lora_packet_t* pkt, lora_alert_type_t type, int16_t value, int16_t threshold, uint8_t duration_sec) { uint8_t* payload = &pkt->data[pkt->length]; payload[0] = type; lora_write_be16s(&payload[1], value); lora_write_be16s(&payload[3], threshold); payload[5] = duration_sec; pkt->length += 6; } /** * Add registration payload to packet */ static inline void lora_packet_add_register(lora_packet_t* pkt, uint16_t proposed_id, lora_sensor_type_t sensor_type, uint8_t fw_major, uint8_t fw_minor, const char* node_name) { uint8_t* payload = &pkt->data[pkt->length]; lora_write_be16(&payload[0], proposed_id); payload[2] = sensor_type; payload[3] = fw_major; payload[4] = fw_minor; // Copy node name, null-padded memset(&payload[5], 0, 8); if (node_name) { size_t len = strlen(node_name); if (len > 8) len = 8; memcpy(&payload[5], node_name, len); } pkt->length += 13; } /** * Add registration ACK payload to packet */ static inline void lora_packet_add_register_ack(lora_packet_t* pkt, uint16_t assigned_id, uint8_t status, uint16_t interval_sec) { uint8_t* payload = &pkt->data[pkt->length]; lora_write_be16(&payload[0], assigned_id); payload[2] = status; lora_write_be16(&payload[3], interval_sec); pkt->length += 5; } /** * Finalize packet by adding CRC * * Call this after adding all payloads and before transmitting. */ static inline void lora_packet_finalize(lora_packet_t* pkt) { uint16_t crc = lora_crc16(pkt->data, pkt->length); lora_write_be16(&pkt->data[pkt->length], crc); pkt->length += 2; } // ============================================================================ // PACKET PARSING // ============================================================================ /** * Parse received packet * * @param pkt Output packet structure * @param data Raw received data * @param len Length of received data * @return true if packet is valid */ static inline bool lora_packet_parse(lora_packet_t* pkt, const uint8_t* data, uint8_t len) { pkt->valid = false; // Minimum size check if (len < LORA_MIN_PACKET_SIZE) { return false; } // Sync bytes check if (data[0] != LORA_SYNC_BYTE_1 || data[1] != LORA_SYNC_BYTE_2) { return false; } // CRC check uint16_t received_crc = lora_read_be16(&data[len - 2]); uint16_t computed_crc = lora_crc16(data, len - 2); if (received_crc != computed_crc) { return false; } // Copy data memcpy(pkt->data, data, len); pkt->length = len; // Parse header pkt->type = lora_get_type(data[2]); pkt->ack_requested = lora_ack_requested(data[2]); pkt->battery_low = lora_battery_low(data[2]); pkt->first_boot = (data[2] & LORA_HDR_FIRST_BOOT) != 0; pkt->node_id = lora_read_be16(&data[3]); pkt->sequence = data[5]; pkt->valid = true; return true; } /** * Get pointer to payload data */ static inline const uint8_t* lora_packet_payload(const lora_packet_t* pkt) { return &pkt->data[6]; // After header } /** * Get payload length (excluding header and CRC) */ static inline uint8_t lora_packet_payload_len(const lora_packet_t* pkt) { if (pkt->length < LORA_MIN_PACKET_SIZE) return 0; return pkt->length - 8; // Total - header(6) - CRC(2) } // ============================================================================ // PAYLOAD PARSING // ============================================================================ /** * Parse DHT22 payload */ typedef struct { float temp_c; float humidity_pct; uint16_t battery_mv; int8_t rssi_dbm; bool valid; } lora_dht22_data_t; static inline bool lora_parse_dht22(const uint8_t* payload, uint8_t len, lora_dht22_data_t* out) { if (len < 7 || payload[0] != SENSOR_TYPE_DHT22) { out->valid = false; return false; } out->temp_c = lora_decode_temp_c10(lora_read_be16s(&payload[1])); out->humidity_pct = lora_decode_humidity(lora_read_be16(&payload[3])); out->battery_mv = lora_decode_battery(payload[5]); out->rssi_dbm = (int8_t)payload[6]; out->valid = true; return true; } /** * Parse BME680 payload */ typedef struct { float temp_c; float humidity_pct; float pressure_hpa; uint16_t gas_kohm; uint8_t iaq_index; uint16_t battery_mv; int8_t rssi_dbm; bool valid; } lora_bme680_data_t; static inline bool lora_parse_bme680(const uint8_t* payload, uint8_t len, lora_bme680_data_t* out) { if (len < 12 || payload[0] != SENSOR_TYPE_BME680) { out->valid = false; return false; } out->temp_c = lora_decode_temp_c10(lora_read_be16s(&payload[1])); out->humidity_pct = lora_decode_humidity(lora_read_be16(&payload[3])); out->pressure_hpa = 900.0f + (float)lora_read_be16(&payload[5]); out->gas_kohm = lora_read_be16(&payload[7]); out->iaq_index = payload[9]; out->battery_mv = lora_decode_battery(payload[10]); out->rssi_dbm = (int8_t)payload[11]; out->valid = true; return true; } /** * Parse DS18B20 payload */ typedef struct { float temp_c; uint16_t battery_mv; int8_t rssi_dbm; bool valid; } lora_ds18b20_data_t; static inline bool lora_parse_ds18b20(const uint8_t* payload, uint8_t len, lora_ds18b20_data_t* out) { if (len < 5 || payload[0] != SENSOR_TYPE_DS18B20) { out->valid = false; return false; } out->temp_c = lora_decode_temp_c100(lora_read_be16s(&payload[1])); out->battery_mv = lora_decode_battery(payload[3]); out->rssi_dbm = (int8_t)payload[4]; out->valid = true; return true; } /** * Parse alert payload */ typedef struct { lora_alert_type_t type; int16_t value; int16_t threshold; uint8_t duration_sec; bool valid; } lora_alert_data_t; static inline bool lora_parse_alert(const uint8_t* payload, uint8_t len, lora_alert_data_t* out) { if (len < 6) { out->valid = false; return false; } out->type = (lora_alert_type_t)payload[0]; out->value = lora_read_be16s(&payload[1]); out->threshold = lora_read_be16s(&payload[3]); out->duration_sec = payload[5]; out->valid = true; return true; } /** * Parse registration payload */ typedef struct { uint16_t proposed_id; lora_sensor_type_t sensor_type; uint8_t fw_major; uint8_t fw_minor; char node_name[9]; // 8 chars + null bool valid; } lora_register_data_t; static inline bool lora_parse_register(const uint8_t* payload, uint8_t len, lora_register_data_t* out) { if (len < 13) { out->valid = false; return false; } out->proposed_id = lora_read_be16(&payload[0]); out->sensor_type = (lora_sensor_type_t)payload[2]; out->fw_major = payload[3]; out->fw_minor = payload[4]; memcpy(out->node_name, &payload[5], 8); out->node_name[8] = '\0'; out->valid = true; return true; } /** * Parse registration ACK payload */ typedef struct { uint16_t assigned_id; uint8_t status; uint16_t interval_sec; bool valid; } lora_register_ack_data_t; static inline bool lora_parse_register_ack(const uint8_t* payload, uint8_t len, lora_register_ack_data_t* out) { if (len < 5) { out->valid = false; return false; } out->assigned_id = lora_read_be16(&payload[0]); out->status = payload[2]; out->interval_sec = lora_read_be16(&payload[3]); out->valid = true; return true; } // ============================================================================ // ACK/NAK HELPERS // ============================================================================ /** * Build ACK packet in response to received packet */ static inline void lora_build_ack(lora_packet_t* ack, const lora_packet_t* received) { lora_packet_init(ack, LORA_PKT_ACK, received->node_id, received->sequence, false, false, false); lora_packet_finalize(ack); } /** * Build NAK packet requesting retransmission */ static inline void lora_build_nak(lora_packet_t* nak, uint16_t node_id, uint8_t expected_seq) { lora_packet_init(nak, LORA_PKT_NAK, node_id, expected_seq, false, false, false); lora_packet_finalize(nak); } #endif // LORA_PACKET_H