fix(mesh): rename Meshtastic radio to the node's server name
Meshtastic device rename was a no-op — set_advert_name only updated an
in-memory field and never told the radio, so the device kept its firmware
default ('Meshtastic xxxx') and wasn't findable from external Meshtastic
apps. MeshCore already renamed correctly (CMD_SET_ADVERT_NAME); this brings
Meshtastic to parity.
Send an AdminMessage{set_owner=User{long_name,short_name}} to the locally
connected node (admin packet to our own node_num on the ADMIN_APP port).
Local serial admin needs no session passkey, matching the official client.
long_name = server name (<=39 chars); short_name = first 4 alphanumerics,
upper-cased. Verified on real hardware: .120 -> 'Archy-X250-EXP', .5 ->
'Archy-X250-Beta' (name read back from the radio after reconnect).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b00c5247f5
commit
d00d1b20d7
@ -22,6 +22,13 @@ const START2: u8 = 0xc3;
|
||||
const TO_RADIO_MAX: usize = 512;
|
||||
const BROADCAST_NUM: u32 = 0xffff_ffff;
|
||||
const TEXT_MESSAGE_APP: u32 = 1;
|
||||
/// Meshtastic PortNum for admin (config) packets.
|
||||
const ADMIN_APP: u32 = 6;
|
||||
/// AdminMessage.set_owner oneof field number (carries a `User`).
|
||||
const ADMIN_SET_OWNER_FIELD: u64 = 32;
|
||||
/// Meshtastic firmware caps long_name at ~40 bytes and short_name at 4 bytes.
|
||||
const MESHTASTIC_LONG_NAME_MAX: usize = 39;
|
||||
const MESHTASTIC_SHORT_NAME_MAX: usize = 4;
|
||||
|
||||
const TO_RADIO_PACKET: u64 = 1;
|
||||
const TO_RADIO_WANT_CONFIG_ID: u64 = 3;
|
||||
@ -143,8 +150,56 @@ impl MeshtasticDevice {
|
||||
})
|
||||
}
|
||||
|
||||
/// Rename the connected Meshtastic radio to match the node's server name so
|
||||
/// it's findable from external Meshtastic apps (phone/desktop) on the same
|
||||
/// mesh. Previously this only updated the in-memory field and never told the
|
||||
/// device — so the radio kept its firmware-default name ("Meshtastic xxxx").
|
||||
///
|
||||
/// We push an `AdminMessage { set_owner: User { long_name, short_name } }` to
|
||||
/// the locally-connected node (an admin packet addressed to our own
|
||||
/// `node_num`, on the ADMIN_APP port). Local admin over the serial link needs
|
||||
/// no session passkey, so this is the same path the official phone/CLI client
|
||||
/// uses for "set owner".
|
||||
pub async fn set_advert_name(&mut self, name: &str) -> Result<()> {
|
||||
self.long_name = Some(name.to_string());
|
||||
let long_name: String = name.chars().take(MESHTASTIC_LONG_NAME_MAX).collect();
|
||||
let short_name = derive_short_name(name).unwrap_or_else(|| {
|
||||
self.short_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| "NODE".to_string())
|
||||
});
|
||||
|
||||
let Some(node_num) = self.node_num else {
|
||||
// No local node number yet (initialize() not completed) — can't
|
||||
// address a local admin packet. Record the intent so advert_name()
|
||||
// still reflects it, but skip the device write.
|
||||
warn!("Meshtastic set_advert_name: node_num unknown, skipping device write");
|
||||
self.long_name = Some(long_name);
|
||||
self.short_name = Some(short_name);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// User { id?(1), long_name(2), short_name(3) }. Echo back the existing id
|
||||
// when known so the firmware keeps the node's stable `!xxxxxxxx` id.
|
||||
let mut user = Vec::new();
|
||||
if let Some(id) = &self.user_id {
|
||||
encode_len_field(1, id.as_bytes(), &mut user);
|
||||
}
|
||||
encode_len_field(2, long_name.as_bytes(), &mut user);
|
||||
encode_len_field(3, short_name.as_bytes(), &mut user);
|
||||
|
||||
// AdminMessage { set_owner(32): User }
|
||||
let mut admin = Vec::new();
|
||||
encode_len_field(ADMIN_SET_OWNER_FIELD, &user, &mut admin);
|
||||
|
||||
// Admin packet to ourselves on the ADMIN_APP port.
|
||||
let packet = encode_mesh_packet(node_num, ADMIN_APP, &admin);
|
||||
self.send_to_radio(&encode_to_radio_variant(TO_RADIO_PACKET, &packet))
|
||||
.await
|
||||
.context("Failed to send Meshtastic set_owner admin packet")?;
|
||||
|
||||
info!(node_num, long_name = %long_name, short_name = %short_name, "Set Meshtastic device owner");
|
||||
self.long_name = Some(long_name);
|
||||
self.short_name = Some(short_name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -428,6 +483,23 @@ fn encode_want_config() -> Vec<u8> {
|
||||
encode_varint_field(TO_RADIO_WANT_CONFIG_ID, 1)
|
||||
}
|
||||
|
||||
/// Derive a Meshtastic short_name (≤4 chars, the label shown on node icons) from
|
||||
/// the human node name: the first few alphanumeric characters, upper-cased.
|
||||
/// Returns `None` when the name has no usable alphanumeric characters.
|
||||
fn derive_short_name(name: &str) -> Option<String> {
|
||||
let short: String = name
|
||||
.chars()
|
||||
.filter(|c| c.is_alphanumeric())
|
||||
.take(MESHTASTIC_SHORT_NAME_MAX)
|
||||
.collect::<String>()
|
||||
.to_uppercase();
|
||||
if short.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(short)
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_heartbeat() -> Vec<u8> {
|
||||
encode_to_radio_variant(TO_RADIO_HEARTBEAT, &[])
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user