observer self-advert on mqtt when lat/lon and custom name is set

This commit is contained in:
liquidraver committed 2026-04-05 17:21:37 +02:00
1 parent f22af597db
commit 7998b5eae3
6 files changed
+213 -1

No files matched your search

@@ -50,6 +50,7 @@ static char s_status_topic[PUB_TOPIC_MAX];
static char s_packets_topic[PUB_TOPIC_MAX];
static volatile bool s_connected;
static void (*s_connect_cb)(void);
/* Event bit to trigger reconnect from external callers */
#define PUB_RECONNECT_BIT BIT(0)
@@ -73,6 +74,9 @@ static void mqtt_evt_handler(struct mqtt_client *client,
if (evt->result == 0) {
LOG_INF("MQTT connected");
s_connected = true;
if (s_connect_cb) {
s_connect_cb();
}
} else {
/* Broker return codes: 1=bad proto, 2=id rejected, 3=server unavail,
* 4=bad user/pass, 5=not authorized */
@@ -467,3 +471,8 @@ void mqtt_publisher_reconnect(void)
{
k_event_post(&s_pub_events, PUB_RECONNECT_BIT);
}
void mqtt_publisher_set_connect_cb(void (*cb)(void))
{
s_connect_cb = cb;
}
@@ -48,6 +48,13 @@ bool mqtt_publisher_is_connected(void);
*/
void mqtt_publisher_reconnect(void);
/*
* Register a callback invoked once each time MQTT connects (CONNACK received).
* Called from the MQTT publisher thread — keep it short.
* Pass NULL to clear.
*/
void mqtt_publisher_set_connect_cb(void (*cb)(void));
#ifdef __cplusplus
}
#endif
+181
View File
@@ -285,6 +285,20 @@ bool ObserverMesh::handleCLI(const char *command, char *reply, int reply_size)
snprintf(reply, reply_size, "%u",
(_creds) ? (unsigned)_creds->mqtt_tls : 1u);
} else if (strcmp(key, "lat") == 0) {
if (_creds && _creds->lat_e6 != 0) {
snprintf(reply, reply_size, "%.6f", (double)_creds->lat_e6 / 1e6);
} else {
snprintf(reply, reply_size, "(not set)");
}
} else if (strcmp(key, "lon") == 0) {
if (_creds && _creds->lon_e6 != 0) {
snprintf(reply, reply_size, "%.6f", (double)_creds->lon_e6 / 1e6);
} else {
snprintf(reply, reply_size, "(not set)");
}
} else {
snprintf(reply, reply_size, "ERR unknown key: %s", key);
}
@@ -434,6 +448,26 @@ bool ObserverMesh::handleCLI(const char *command, char *reply, int reply_size)
snprintf(reply, reply_size, "mqtt.iata=%s (topics updated, reconnecting)", _creds->mqtt_iata);
mqtt_publisher_reconnect();
} else if ((val = find_val(rest, "lat")) != nullptr) {
double lat_d = atof(val);
if (lat_d < -90.0 || lat_d > 90.0) {
snprintf(reply, reply_size, "ERR lat must be -90..90");
} else {
_creds->lat_e6 = (int32_t)(lat_d * 1e6);
observer_creds_save(_creds, _store->getBasePath());
snprintf(reply, reply_size, "lat=%.6f", lat_d);
}
} else if ((val = find_val(rest, "lon")) != nullptr) {
double lon_d = atof(val);
if (lon_d < -180.0 || lon_d > 180.0) {
snprintf(reply, reply_size, "ERR lon must be -180..180");
} else {
_creds->lon_e6 = (int32_t)(lon_d * 1e6);
observer_creds_save(_creds, _store->getBasePath());
snprintf(reply, reply_size, "lon=%.6f", lon_d);
}
} else {
snprintf(reply, reply_size, "ERR unknown key");
}
@@ -444,4 +478,151 @@ bool ObserverMesh::handleCLI(const char *command, char *reply, int reply_size)
return false;
}
/* ========== Self-advert ========== */
void ObserverMesh::publishSelfAdvert()
{
if (!_creds || (_creds->lat_e6 == 0 && _creds->lon_e6 == 0)) {
LOG_DBG("publishSelfAdvert: lat/lon not configured, skipping");
return;
}
/* Skip if the node name is still the auto-generated default ("Observer-XXXXXXXX").
* The user must have set a meaningful name before advertising. */
if (strncmp(_prefs.node_name, "Observer-", 9) == 0) {
LOG_DBG("publishSelfAdvert: name is default, skipping");
return;
}
/* ---- Build raw wire frame (zero-hop advert) ----
*
* Byte 0: header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT)
* Byte 1: path_len_byte = 0 (zero hops)
* Bytes 2..33: pubkey (32)
* Bytes 34..37: timestamp (uint32 LE)
* Bytes 38..101: signature (64) — signed over pubkey + ts + appdata
* Byte 102: flags = Type:Chat(1) | HasLocation(0x10) | HasName(0x80)
* Bytes 103..106: lat_e6 (int32 LE)
* Bytes 107..110: lon_e6 (int32 LE)
* Bytes 111..: node name (null-terminated)
*/
uint8_t raw[192];
int pos = 0;
/* Header and zero-hop path */
raw[pos++] = (uint8_t)((PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT) | ROUTE_TYPE_DIRECT);
raw[pos++] = 0x00; /* path_len_byte = 0 → zero hop */
/* Payload: pubkey */
int pubkey_off = pos;
memcpy(&raw[pos], _self_id.pub_key, PUB_KEY_SIZE); pos += PUB_KEY_SIZE;
/* Payload: timestamp */
int ts_off = pos;
uint32_t now_ts = _rtc ? (uint32_t)_rtc->getCurrentTime() : 0;
raw[pos++] = (uint8_t)(now_ts);
raw[pos++] = (uint8_t)(now_ts >> 8);
raw[pos++] = (uint8_t)(now_ts >> 16);
raw[pos++] = (uint8_t)(now_ts >> 24);
/* Payload: signature placeholder (64 bytes, filled below) */
int sig_off = pos;
memset(&raw[pos], 0, SIGNATURE_SIZE); pos += SIGNATURE_SIZE;
/* AppData: flags | lat | lon | name */
int appdata_off = pos;
raw[pos++] = 0x01 | 0x10 | 0x80; /* Type=Chat, HasLocation, HasName */
int32_t lat = _creds->lat_e6;
raw[pos++] = (uint8_t)(lat);
raw[pos++] = (uint8_t)(lat >> 8);
raw[pos++] = (uint8_t)(lat >> 16);
raw[pos++] = (uint8_t)(lat >> 24);
int32_t lon = _creds->lon_e6;
raw[pos++] = (uint8_t)(lon);
raw[pos++] = (uint8_t)(lon >> 8);
raw[pos++] = (uint8_t)(lon >> 16);
raw[pos++] = (uint8_t)(lon >> 24);
const char *name = _prefs.node_name;
size_t name_len = strlen(name);
if (name_len >= sizeof(raw) - pos - 1) {
name_len = sizeof(raw) - pos - 2;
}
memcpy(&raw[pos], name, name_len);
pos += name_len;
raw[pos++] = '\0';
int raw_len = pos;
/* Sign: pubkey + timestamp + appdata */
int sign_input_len = PUB_KEY_SIZE + 4 + (appdata_off - ts_off - 4) + (raw_len - appdata_off);
sign_input_len = PUB_KEY_SIZE + 4 + (raw_len - appdata_off);
uint8_t sign_input[PUB_KEY_SIZE + 4 + 128]; /* generous upper bound */
memcpy(sign_input, &raw[pubkey_off], PUB_KEY_SIZE);
memcpy(sign_input + PUB_KEY_SIZE, &raw[ts_off], 4);
memcpy(sign_input + PUB_KEY_SIZE + 4, &raw[appdata_off], raw_len - appdata_off);
_self_id.sign(&raw[sig_off], sign_input, (size_t)(PUB_KEY_SIZE + 4 + raw_len - appdata_off));
/* ---- Build JSON ---- */
uint32_t now_epoch = now_ts;
struct tm tm_now;
time_t t = (time_t)now_epoch;
gmtime_r(&t, &tm_now);
char ts_buf[48];
snprintf(ts_buf, sizeof(ts_buf), "%04d-%02d-%02dT%02d:%02d:%02d.000000",
tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday,
tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
char time_buf[12], date_buf[32];
snprintf(time_buf, sizeof(time_buf), "%02d:%02d:%02d",
tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
snprintf(date_buf, sizeof(date_buf), "%d/%d/%04d",
tm_now.tm_mday, tm_now.tm_mon + 1, tm_now.tm_year + 1900);
char raw_hex[sizeof(raw) * 2 + 1];
Utils::toHex(raw_hex, raw, raw_len);
raw_hex[raw_len * 2] = '\0';
static char json_buf[1024];
int json_len = snprintf(json_buf, sizeof(json_buf),
"{"
"\"type\":\"PACKET\","
"\"origin\":\"%s\","
"\"origin_id\":\"%s\","
"\"timestamp\":\"%s\","
"\"direction\":\"rx\","
"\"time\":\"%s\","
"\"date\":\"%s\","
"\"len\":\"%d\","
"\"packet_type\":\"%u\","
"\"route\":\"D\","
"\"payload_len\":\"%d\","
"\"raw\":\"%s\","
"\"SNR\":\"0\","
"\"RSSI\":\"0\","
"\"score\":\"0\","
"\"hash\":\"0000000000000000\""
"}",
name,
_pubkey_hex,
ts_buf,
time_buf,
date_buf,
raw_len,
(unsigned)PAYLOAD_TYPE_ADVERT,
raw_len - 2, /* payload_len = raw_len minus header and path_len_byte */
raw_hex);
if (json_len < 0 || json_len >= (int)sizeof(json_buf)) {
LOG_WRN("publishSelfAdvert: JSON truncated");
return;
}
mqtt_publisher_enqueue(_packets_topic, json_buf, json_len);
LOG_INF("Self-advert published (lat=%.6f lon=%.6f)",
(double)_creds->lat_e6 / 1e6, (double)_creds->lon_e6 / 1e6);
}
} /* namespace mesh */
+5
View File
@@ -74,6 +74,11 @@ public:
* the full banner (too long for the reply buffer). */
bool handleCLI(const char *command, char *reply, int reply_size);
/* Publish a synthetic zero-hop advert for this observer to the packets
* topic so that CoreScope can place it on the map. No-op if lat/lon
* are not configured in the creds struct. */
void publishSelfAdvert();
/* Accessors used by main_observer.cpp */
NodePrefs *getNodePrefs() { return &_prefs; }
const LocalIdentity &getSelfId() const { return _self_id; }
+8
View File
@@ -379,6 +379,14 @@ int main(void)
observer_mesh.getStatusTopic(),
observer_mesh.getPacketsTopic());
/* Publish self-advert once on each MQTT connect so CoreScope can pin
* this observer on the map (requires lat/lon to be configured). */
mqtt_publisher_set_connect_cb([]() {
if (s_mesh_ptr) {
s_mesh_ptr->publishSelfAdvert();
}
});
LOG_INF("Observer event loop running");
/* ========== Event loop ========== */
+3 -1
View File
@@ -24,7 +24,9 @@ struct ObserverCreds {
char mqtt_user[64]; /* MQTT username */
char mqtt_password[64]; /* MQTT password */
char mqtt_iata[8]; /* IATA location code, e.g. "BUD", "BTS", "VIE" */
uint8_t _reserved[5]; /* alignment / future use */
uint8_t _reserved[1]; /* alignment / future use */
int32_t lat_e6; /* latitude × 1 000 000 (e.g. 47497900 = 47.497900°N) */
int32_t lon_e6; /* longitude × 1 000 000 (e.g. 19040200 = 19.040200°E) */
};
/* Load creds from /lfs/repeater/obs_creds.