277 lines
8.8 KiB
C++
277 lines
8.8 KiB
C++
#pragma once
|
|
|
|
#ifdef WITH_MQTT_BRIDGE
|
|
|
|
#include "MeshCore.h"
|
|
#include <ArduinoJson.h>
|
|
#include "MQTTPayloadBuilder.h"
|
|
#include "MQTTWireScratch.h"
|
|
#include <Mesh.h>
|
|
#include <Timezone.h>
|
|
|
|
/**
|
|
* @brief Utility class for building MQTT JSON messages
|
|
*
|
|
* This class handles the formatting of mesh packets and device status
|
|
* into JSON messages for MQTT publishing according to the MeshCore
|
|
* packet capture specification.
|
|
*
|
|
* Timestamps in the JSON `timestamp` field are emitted in UTC with an explicit
|
|
* "+00:00" offset (equivalent to Python datetime.now(timezone.utc).isoformat()) for
|
|
* status, packet, and raw topics. Packet payloads also include separate `time` and
|
|
* `date` strings in UTC (gmtime) so they stay aligned with meshcoretomqtt serial
|
|
* regex fields.
|
|
*/
|
|
class MQTTMessageBuilder {
|
|
public:
|
|
// Wire-format scratch sizing and validation live in the pure, host-tested
|
|
// MQTTWireScratch; these are the firmware-facing aliases.
|
|
static const size_t WIRE_SCRATCH_SIZE = MQTTWireScratch::kWireBytes;
|
|
static const size_t WIRE_HEX_SCRATCH_SIZE = MQTTWireScratch::kWireHexChars;
|
|
|
|
static bool canSerializePacket(const mesh::Packet* packet, size_t dest_size) {
|
|
return packet != nullptr && MQTTWireScratch::canSerialize(*packet, dest_size);
|
|
}
|
|
|
|
/**
|
|
* Format the MQTT JSON `timestamp` field (same rule for status, packet, raw).
|
|
* Always UTC with an explicit "+00:00" offset, ISO-8601
|
|
* "%Y-%m-%dT%H:%M:%S.uuuuuu+00:00" (matches Python
|
|
* datetime.now(timezone.utc).isoformat()). The `timezone` parameter is retained
|
|
* for API compatibility but ignored - the system clock is UTC.
|
|
*
|
|
* `usec` is the sub-second component in microseconds (0..999999), normally taken
|
|
* from the same gettimeofday() read as `now` so the two don't tear at a second
|
|
* boundary. It is a real sub-second (SNTP-maintained wall clock), not a literal;
|
|
* pass 0 if no sub-second source is available.
|
|
*/
|
|
static void formatIsoTimestampForMqtt(time_t now, long usec, Timezone* timezone, char* buffer, size_t buffer_size);
|
|
|
|
/**
|
|
* Build status message JSON
|
|
*
|
|
* @param origin Device name
|
|
* @param origin_id Device public key (hex string)
|
|
* @param model Device model
|
|
* @param firmware_version Firmware version
|
|
* @param radio Radio information
|
|
* @param client_version Client version
|
|
* @param status Connection status ("online" or "offline")
|
|
* @param timestamp ISO-like timestamp (see formatIsoTimestampForMqtt)
|
|
* @param buffer Output buffer for JSON string
|
|
* @param buffer_size Size of output buffer
|
|
* @param battery_mv Battery voltage in millivolts (optional, -1 to omit)
|
|
* @param uptime_secs Uptime in seconds (optional, -1 to omit)
|
|
* @param errors Error flags (optional, -1 to omit)
|
|
* @param queue_len Queue length (optional, -1 to omit)
|
|
* @param noise_floor Noise floor in dBm (optional, -999 to omit)
|
|
* @param tx_air_secs TX air time in seconds (optional, -1 to omit)
|
|
* @param rx_air_secs RX air time in seconds (optional, -1 to omit)
|
|
* @param recv_errors Radio receive/CRC errors (optional, -1 to omit)
|
|
* @param internal_heap Internal heap free bytes (optional, -1 to omit)
|
|
* @param repeat Repeat/forwarding status ("on" or "off"); nullptr omits the field
|
|
* @param conn_health Slot connection health; defaults omit every field
|
|
* @return Length of JSON string, or 0 on error
|
|
*/
|
|
static int buildStatusMessage(
|
|
JsonDocument& doc,
|
|
const char* origin,
|
|
const char* origin_id,
|
|
const char* model,
|
|
const char* firmware_version,
|
|
const char* radio,
|
|
const char* client_version,
|
|
const char* status,
|
|
const char* timestamp,
|
|
char* buffer,
|
|
size_t buffer_size,
|
|
int battery_mv = -1,
|
|
int uptime_secs = -1,
|
|
int errors = -1,
|
|
int queue_len = -1,
|
|
int noise_floor = -999,
|
|
int tx_air_secs = -1,
|
|
int rx_air_secs = -1,
|
|
int recv_errors = -1,
|
|
int internal_heap = -1,
|
|
int packets_sent = -1,
|
|
int packets_received = -1,
|
|
const char* repeat = nullptr,
|
|
const MQTTConnHealth& conn_health = MQTTConnHealth()
|
|
);
|
|
|
|
/**
|
|
* Build packet message JSON
|
|
*
|
|
* @param origin Device name
|
|
* @param origin_id Device public key (hex string)
|
|
* @param timestamp ISO-like timestamp (see formatIsoTimestampForMqtt)
|
|
* @param direction Packet direction ("rx" or "tx")
|
|
* @param time Time in HH:MM:SS (UTC, gmtime; meshcoretomqtt serial parity)
|
|
* @param date Date in DD/MM/YYYY (UTC, gmtime)
|
|
* @param len Total packet length
|
|
* @param packet_type Packet type code
|
|
* @param route Routing type
|
|
* @param payload_len Payload length
|
|
* @param raw Raw packet data (hex string)
|
|
* @param snr Signal-to-noise ratio
|
|
* @param rssi Received signal strength
|
|
* @param hash Packet hash
|
|
* @param path_bytes Raw routing-path bytes (direct packets only; nullptr to omit)
|
|
* @param path_hop_count Number of path hops (0 to omit the path field)
|
|
* @param path_hash_size Bytes per hop hash (1-4)
|
|
* @param buffer Output buffer for JSON string
|
|
* @param buffer_size Size of output buffer
|
|
* @return Length of JSON string, or 0 on error
|
|
*/
|
|
static int buildPacketMessage(
|
|
JsonDocument& doc,
|
|
const char* origin,
|
|
const char* origin_id,
|
|
const char* timestamp,
|
|
const char* direction,
|
|
const char* time,
|
|
const char* date,
|
|
int len,
|
|
int packet_type,
|
|
const char* route,
|
|
int payload_len,
|
|
const char* raw,
|
|
float snr,
|
|
int rssi,
|
|
float score,
|
|
const char* hash,
|
|
const uint8_t* path_bytes,
|
|
int path_hop_count,
|
|
int path_hash_size,
|
|
char* buffer,
|
|
size_t buffer_size
|
|
);
|
|
|
|
/**
|
|
* Build raw message JSON
|
|
*
|
|
* @param origin Device name
|
|
* @param origin_id Device public key (hex string)
|
|
* @param timestamp ISO-like timestamp (see formatIsoTimestampForMqtt)
|
|
* @param raw Raw packet data (hex string)
|
|
* @param buffer Output buffer for JSON string
|
|
* @param buffer_size Size of output buffer
|
|
* @return Length of JSON string, or 0 on error
|
|
*/
|
|
static int buildRawMessage(
|
|
JsonDocument& doc,
|
|
const char* origin,
|
|
const char* origin_id,
|
|
const char* timestamp,
|
|
const char* raw,
|
|
char* buffer,
|
|
size_t buffer_size
|
|
);
|
|
|
|
// Neighbors table entry + JSON builder. The layout logic lives in the pure,
|
|
// host-tested MQTTPayloadBuilder; this is the firmware-facing alias/delegate,
|
|
// matching the status/packet/raw builders.
|
|
using NeighborsMessageEntry = MQTTPayloadBuilder::NeighborsMessageEntry;
|
|
static int buildNeighborsMessage(
|
|
JsonDocument& doc,
|
|
const char* origin,
|
|
const char* origin_id,
|
|
const char* timestamp,
|
|
const char* self_scopes,
|
|
const char* self_default_scope,
|
|
const NeighborsMessageEntry* neighbors,
|
|
int neighbor_count,
|
|
char* buffer,
|
|
size_t buffer_size,
|
|
int total_neighbors = -1,
|
|
int queried_neighbors = -1,
|
|
bool truncated = false
|
|
);
|
|
static size_t measureNeighborsMessageBase(
|
|
const char* origin,
|
|
const char* origin_id,
|
|
const char* timestamp,
|
|
const char* self_scopes,
|
|
const char* self_default_scope,
|
|
int total_neighbors
|
|
);
|
|
static size_t measureNeighborsMessageEntry(const NeighborsMessageEntry& neighbor);
|
|
|
|
/**
|
|
* Convert packet to JSON message
|
|
*
|
|
* @param packet Mesh packet
|
|
* @param is_tx Whether packet was transmitted (true) or received (false)
|
|
* @param origin Device name
|
|
* @param origin_id Device public key (hex string)
|
|
* @param buffer Output buffer for JSON string
|
|
* @param buffer_size Size of output buffer
|
|
* @return Length of JSON string, or 0 on error
|
|
*/
|
|
static int buildPacketJSON(
|
|
JsonDocument& doc,
|
|
mesh::Packet* packet,
|
|
bool is_tx,
|
|
const char* origin,
|
|
const char* origin_id,
|
|
Timezone* timezone,
|
|
char* buffer,
|
|
size_t buffer_size
|
|
);
|
|
|
|
static int buildPacketJSONFromRaw(
|
|
JsonDocument& doc,
|
|
const uint8_t* raw_data,
|
|
int raw_len,
|
|
mesh::Packet* packet,
|
|
bool is_tx,
|
|
const char* origin,
|
|
const char* origin_id,
|
|
float snr,
|
|
float rssi,
|
|
float score,
|
|
Timezone* timezone,
|
|
char* buffer,
|
|
size_t buffer_size
|
|
);
|
|
|
|
/**
|
|
* Convert packet to raw JSON message
|
|
*
|
|
* @param packet Mesh packet
|
|
* @param origin Device name
|
|
* @param origin_id Device public key (hex string)
|
|
* @param buffer Output buffer for JSON string
|
|
* @param buffer_size Size of output buffer
|
|
* @return Length of JSON string, or 0 on error
|
|
*/
|
|
static int buildRawJSON(
|
|
JsonDocument& doc,
|
|
mesh::Packet* packet,
|
|
const char* origin,
|
|
const char* origin_id,
|
|
Timezone* timezone,
|
|
char* buffer,
|
|
size_t buffer_size
|
|
);
|
|
|
|
private:
|
|
/**
|
|
* Convert route type to string
|
|
*/
|
|
static const char* getRouteTypeString(int route_type);
|
|
|
|
/**
|
|
* Convert bytes to hex string (uppercase)
|
|
*/
|
|
static void bytesToHex(const uint8_t* data, size_t len, char* hex, size_t hex_size);
|
|
|
|
/**
|
|
* Convert packet to hex string
|
|
*/
|
|
static void packetToHex(mesh::Packet* packet, char* hex, size_t hex_size);
|
|
};
|
|
|
|
#endif
|