About Meshtastic Communication Protocol With BLE To Mobile. Source Doc
以下有經過Claude AI協助解釋,可能會有不正確的內容,還請見諒。
6ba1b218-15a8-461f-9fa8-5dcae273eafd
f75c76d2-129e-4dad-a1dd-7866124401e7
2c55e69e-4993-11ed-b878-0242ac120002
ed9da18c-a800-4f66-a670-aa7547e34453
0x2a19
0x2A19
0x2904
0x27AD
(Percentage)NimbleBluetoothToRadioCallback::onWrite()
:負責處理藍牙通訊的 ToRadio Services。當該特性值發生變化時,會觸發此Callback。 (ID: f75c76d2-129e-4dad-a1dd-7866124401e7
)
LOG_INFO("To Radio onwrite\n");
auto val = pCharacteristic->getValue();
bluetoothPhoneAPI->handleToRadio(val.data(), val.length());
NimbleBluetoothFromRadioCallback::onRead()
:負責處理藍牙通訊的 FromRadio Services。當手機讀取此特性值時,會觸發此Callback。 (ID: 2c55e69e-4993-11ed-b878-0242ac120002
)
LOG_INFO("From Radio onread\n");
uint8_t fromRadioBytes[meshtastic_FromRadio_size];
size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes);
std::string fromRadioByteString(fromRadioBytes, fromRadioBytes + numBytes);
pCharacteristic->setValue(fromRadioByteString);
PhoneAPI::handleToRadio()
:處理從手機向無線電發送的 ToRadio 協定訊息。
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep
pb_decode_from_bytes(buf, bufLength, &meshtastic_ToRadio_msg, &toRadioScratch);
meshtastic_ToRadio_packet_tag
: 呼叫 handleToRadioPacket() 函數處理封包。case meshtastic_ToRadio_packet_tag:
return handleToRadioPacket(toRadioScratch.packet);
meshtastic_ToRadio_want_config_id_tag
: 取得設定檔 ID (config_nonce),並呼叫 handleStartConfig() 函數。case meshtastic_ToRadio_want_config_id_tag:
config_nonce = toRadioScratch.want_config_id;
LOG_INFO("Client wants config, nonce=%u\n", config_nonce);
handleStartConfig();
break;
meshtastic_ToRadio_disconnect_tag
: 關閉與手機的連接。case meshtastic_ToRadio_disconnect_tag:
LOG_INFO("Disconnecting from phone\n");
close();
break;
meshtastic_ToRadio_xmodemPacket_tag
: 呼叫 xModem.handlePacket() 處理 xmodem 封包。2024.4.13 blackcat
)case meshtastic_ToRadio_xmodemPacket_tag:
LOG_INFO("Got xmodem packet\n");
xModem.handlePacket(toRadioScratch.xmodemPacket);
break;
meshtastic_ToRadio_mqttClientProxyMessage_tag
: 呼叫 mqtt->onClientProxyReceive() 處理 MQTT 代理訊息。#if !MESHTASTIC_EXCLUDE_MQTT
case meshtastic_ToRadio_mqttClientProxyMessage_tag:
LOG_INFO("Got MqttClientProxy message\n");
if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled &&
(channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) {
mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage);
} else {
LOG_WARN("MqttClientProxy received but proxy is not enabled, no channels have up/downlink, or map reporting "
"not enabled\n");
}
break;
#endif
meshtastic_ToRadio_heartbeat_tag
: 記錄已收到手機的心跳訊號。case meshtastic_ToRadio_heartbeat_tag:
LOG_DEBUG("Got client heartbeat\n");
break;
LOG_ERROR("Error: ignoring malformed toradio\n");
PhoneAPI::getFromRadio()
:處理從無線電向手機發送的 FromRadio 協定訊息。
STATE_SEND_MY_INFO
:發送個人資訊。STATE_SEND_METADATA
:發送設備 (這裡指無線電) 的原始資料。STATE_SEND_NODEINFO
:發送節點資訊。STATE_SEND_CHANNELS
:發送頻道配置。STATE_SEND_CONFIG
:發送設備 (這裡指無線電) 設定資訊。
meshtastic_Config_device_tag
:裝置資訊meshtastic_Config_position_tag
:GPS定位meshtastic_Config_power_tag
:省電meshtastic_Config_network_tag
:WiFi網路meshtastic_Config_display_tag
:螢幕 (如SSD1306 / SSD1308…等)meshtastic_Config_lora_tag
:LoRa模組 (如SX1262 / 1276 / 1278…等)meshtastic_Config_bluetooth_tag
:藍芽STATE_SEND_MODULECONFIG
:發送功能模組的設定資訊。
meshtastic_ModuleConfig_mqtt_tag
:MQTT網路meshtastic_ModuleConfig_serial_tag
:序列埠 (如TTL / RS232…等)meshtastic_ModuleConfig_external_notification_tag
:外部裝置輸出 (如LED / 蜂鳴器…等)meshtastic_ModuleConfig_store_forward_tag
:告訴路由節點暫時存儲 / 轉發訊息給其他裝置
2024.4.13 blackcat
)meshtastic_ModuleConfig_range_test_tag
:兩節點之間的距離測試meshtastic_ModuleConfig_telemetry_tag
:遙測模組 (如BME280、SHT31…等 Sensor)meshtastic_ModuleConfig_canned_message_tag
:事先設定好預發送的訊息meshtastic_ModuleConfig_audio_tag
:音頻資料meshtastic_ModuleConfig_remote_hardware_tag
:遙控其他節點的GPIO開/關meshtastic_ModuleConfig_neighbor_info_tag
:鄰近節點的資訊meshtastic_ModuleConfig_detection_sensor_tag
:開關 / 壓力感測器 / 紅外線感測器 (輸出訊號為Low或者High)meshtastic_ModuleConfig_ambient_lighting_tag
:漸變燈 / 呼吸燈meshtastic_ModuleConfig_paxcounter_tag
:透過WiFi或者藍芽 MAC位置計算該周圍的人數STATE_SEND_COMPLETE_ID
:發送設定完成的標識。STATE_SEND_PACKETS
:發送從Mesh網路接收的封包。// Do we have a message from the mesh?
if (fromRadioScratch.which_payload_variant != 0) {
// Encapsulate as a FromRadio packet
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes);
return numbytes;
}
<Top Folder>\protoc\bin
protoc.exe --python_out=out -I=../protobufs ../protobufs/meshtastic/*.proto
protoc.exe --csharp_out=out --proto_path=../protobufs ../protobufs/meshtastic/*.proto
meshtastic專案資料夾/bin/regen-protos.bat
protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:out\" -I=..\protobufs ..\protobufs\meshtastic\*.proto
message MeshPacket {
/*
* The priority of this message for sending.
* Higher priorities are sent first (when managing the transmit queue).
* This field is never sent over the air, it is only used internally inside of a local device node.
* API clients (either on the local node or connected directly to the node)
* can set this parameter if necessary.
* (values must be <= 127 to keep protobuf field to one byte in size.
* Detailed background on this field:
* I noticed a funny side effect of lora being so slow: Usually when making
* a protocol there isn’t much need to use message priority to change the order
* of transmission (because interfaces are fairly fast).
* But for lora where packets can take a few seconds each, it is very important
* to make sure that critical packets are sent ASAP.
* In the case of meshtastic that means we want to send protocol acks as soon as possible
* (to prevent unneeded retransmissions), we want routing messages to be sent next,
* then messages marked as reliable and finally 'background' packets like periodic position updates.
* So I bit the bullet and implemented a new (internal - not sent over the air)
* field in MeshPacket called 'priority'.
* And the transmission queue in the router object is now a priority queue.
*/
enum Priority {
/*
* Treated as Priority.DEFAULT
*/
UNSET = 0;
/*
* TODO: REPLACE
*/
MIN = 1;
/*
* Background position updates are sent with very low priority -
* if the link is super congested they might not go out at all
*/
BACKGROUND = 10;
/*
* This priority is used for most messages that don't have a priority set
*/
DEFAULT = 64;
/*
* If priority is unset but the message is marked as want_ack,
* assume it is important and use a slightly higher priority
*/
RELIABLE = 70;
/*
* Ack/naks are sent with very high priority to ensure that retransmission
* stops as soon as possible
*/
ACK = 120;
/*
* TODO: REPLACE
*/
MAX = 127;
}
/*
* Identify if this is a delayed packet
*/
enum Delayed {
/*
* If unset, the message is being sent in real time.
*/
NO_DELAY = 0;
/*
* The message is delayed and was originally a broadcast
*/
DELAYED_BROADCAST = 1;
/*
* The message is delayed and was originally a direct message
*/
DELAYED_DIRECT = 2;
}
/*
* The sending node number.
* Note: Our crypto implementation uses this field as well.
* See [crypto](/docs/overview/encryption) for details.
*/
fixed32 from = 1;
/*
* The (immediate) destination for this packet
*/
fixed32 to = 2;
/*
* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on.
* If unset, packet was on the primary channel.
* A particular node might know only a subset of channels in use on the mesh.
* Therefore channel_index is inherently a local concept and meaningless to send between nodes.
* Very briefly, while sending and receiving deep inside the device Router code, this field instead
* contains the 'channel hash' instead of the index.
* This 'trick' is only used while the payload_variant is an 'encrypted'.
*/
uint32 channel = 3;
/*
* Internally to the mesh radios we will route SubPackets encrypted per [this](docs/developers/firmware/encryption).
* However, when a particular node has the correct
* key to decode a particular packet, it will decode the payload into a SubPacket protobuf structure.
* Software outside of the device nodes will never encounter a packet where
* "decoded" is not populated (i.e. any encryption/decryption happens before reaching the applications)
* The numeric IDs for these fields were selected to keep backwards compatibility with old applications.
*/
oneof payload_variant {
/*
* TODO: REPLACE
*/
Data decoded = 4;
/*
* TODO: REPLACE
*/
bytes encrypted = 5;
}
/*
* A unique ID for this packet.
* Always 0 for no-ack packets or non broadcast packets (and therefore take zero bytes of space).
* Otherwise a unique ID for this packet, useful for flooding algorithms.
* ID only needs to be unique on a _per sender_ basis, and it only
* needs to be unique for a few minutes (long enough to last for the length of
* any ACK or the completion of a mesh broadcast flood).
* Note: Our crypto implementation uses this id as well.
* See [crypto](/docs/overview/encryption) for details.
*/
fixed32 id = 6;
/*
* The time this message was received by the esp32 (secs since 1970).
* Note: this field is _never_ sent on the radio link itself (to save space) Times
* are typically not sent over the mesh, but they will be added to any Packet
* (chain of SubPacket) sent to the phone (so the phone can know exact time of reception)
*/
fixed32 rx_time = 7;
/*
* *Never* sent over the radio links.
* Set during reception to indicate the SNR of this packet.
* Used to collect statistics on current link quality.
*/
float rx_snr = 8;
/*
* If unset treated as zero (no forwarding, send to adjacent nodes only)
* if 1, allow hopping through one node, etc...
* For our usecase real world topologies probably have a max of about 3.
* This field is normally placed into a few of bits in the header.
*/
uint32 hop_limit = 9;
/*
* This packet is being sent as a reliable message, we would prefer it to arrive at the destination.
* We would like to receive a ack packet in response.
* Broadcasts messages treat this flag specially: Since acks for broadcasts would
* rapidly flood the channel, the normal ack behavior is suppressed.
* Instead, the original sender listens to see if at least one node is rebroadcasting this packet (because naive flooding algorithm).
* If it hears that the odds (given typical LoRa topologies) the odds are very high that every node should eventually receive the message.
* So FloodingRouter.cpp generates an implicit ack which is delivered to the original sender.
* If after some time we don't hear anyone rebroadcast our packet, we will timeout and retransmit, using the regular resend logic.
* Note: This flag is normally sent in a flag bit in the header when sent over the wire
*/
bool want_ack = 10;
/*
* The priority of this message for sending.
* See MeshPacket.Priority description for more details.
*/
Priority priority = 11;
/*
* rssi of received packet. Only sent to phone for dispay purposes.
*/
int32 rx_rssi = 12;
/*
* Describe if this message is delayed
*/
Delayed delayed = 13 [deprecated = true];
/*
* Describes whether this packet passed via MQTT somewhere along the path it currently took.
*/
bool via_mqtt = 14;
/*
* Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
* When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled.
*/
uint32 hop_start = 15;
}
message FromRadio {
/*
* The packet id, used to allow the phone to request missing read packets from the FIFO,
* see our bluetooth docs
*/
uint32 id = 1;
/*
* Log levels, chosen to match python logging conventions.
*/
oneof payload_variant {
/*
* Log levels, chosen to match python logging conventions.
*/
MeshPacket packet = 2;
/*
* Tells the phone what our node number is, can be -1 if we've not yet joined a mesh.
* NOTE: This ID must not change - to keep (minimal) compatibility with <1.2 version of android apps.
*/
MyNodeInfo my_info = 3;
/*
* One packet is sent for each node in the on radio DB
* starts over with the first node in our DB
*/
NodeInfo node_info = 4;
/*
* Include a part of the config (was: RadioConfig radio)
*/
Config config = 5;
/*
* Set to send debug console output over our protobuf stream
*/
LogRecord log_record = 6;
/*
* Sent as true once the device has finished sending all of the responses to want_config
* recipient should check if this ID matches our original request nonce, if
* not, it means your config responses haven't started yet.
* NOTE: This ID must not change - to keep (minimal) compatibility with <1.2 version of android apps.
*/
uint32 config_complete_id = 7;
/*
* Sent to tell clients the radio has just rebooted.
* Set to true if present.
* Not used on all transports, currently just used for the serial console.
* NOTE: This ID must not change - to keep (minimal) compatibility with <1.2 version of android apps.
*/
bool rebooted = 8;
/*
* Include module config
*/
ModuleConfig moduleConfig = 9;
/*
* One packet is sent for each channel
*/
Channel channel = 10;
/*
* Queue status info
*/
QueueStatus queueStatus = 11;
/*
* File Transfer Chunk
*/
XModem xmodemPacket = 12;
/*
* Device metadata message
*/
DeviceMetadata metadata = 13;
/*
* MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT)
*/
MqttClientProxyMessage mqttClientProxyMessage = 14;
}
}
message ToRadio {
/*
* Log levels, chosen to match python logging conventions.
*/
oneof payload_variant {
/*
* Send this packet on the mesh
*/
MeshPacket packet = 1;
/*
* Phone wants radio to send full node db to the phone, This is
* typically the first packet sent to the radio when the phone gets a
* bluetooth connection. The radio will respond by sending back a
* MyNodeInfo, a owner, a radio config and a series of
* FromRadio.node_infos, and config_complete
* the integer you write into this field will be reported back in the
* config_complete_id response this allows clients to never be confused by
* a stale old partially sent config.
*/
uint32 want_config_id = 3;
/*
* Tell API server we are disconnecting now.
* This is useful for serial links where there is no hardware/protocol based notification that the client has dropped the link.
* (Sending this message is optional for clients)
*/
bool disconnect = 4;
/*
* File Transfer Chunk
*/
XModem xmodemPacket = 5;
/*
* MQTT Client Proxy Message (for client / phone subscribed to MQTT sending to device)
*/
MqttClientProxyMessage mqttClientProxyMessage = 6;
/*
* Heartbeat message (used to keep the device connection awake on serial)
*/
Heartbeat heartbeat = 7;
}
}
NimbleBluetooth.cpp
,將封包透過Hex印出:
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
{
virtual void onWrite(NimBLECharacteristic *pCharacteristic)
{
LOG_INFO("To Radio onwrite\n");
auto val = pCharacteristic->getValue();
for(int i = 0; i < val.length(); i++)
LOG_DEBUG("%2x ", val[i]);
LOG_DEBUG("\n");
bluetoothPhoneAPI->handleToRadio(val.data(), val.length());
}
};
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
{
virtual void onRead(NimBLECharacteristic *pCharacteristic)
{
LOG_INFO("From Radio onread\n");
uint8_t fromRadioBytes[meshtastic_FromRadio_size];
size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes);
std::string fromRadioByteString(fromRadioBytes, fromRadioBytes + numBytes);
for(int i = 0; i < fromRadioByteString.length(); i++)
LOG_DEBUG("%2x ", fromRadioByteString[i]);
LOG_DEBUG("\n");
pCharacteristic->setValue(fromRadioByteString);
}
};
0a 17 15 ff ff ff ff 22 09 08 01 12 05 48 65 6c 6c 6f 35 5b 06 ec 94 50 01
0a 17
: 表示這是一個 ToRadio 訊息,長度為 0x17(23)個字節。15 ff ff ff ff
: 這是一個 Packet 類型的訊息,具有 4 個字節的目的地 ID。22 09
: 是協議版本號。08 01
: 表示這是一個 DATA_MESSAGE 類型的封包。12 05
: 表示接下來有 5 個字節的荷載資料。48 65 6c 6c 6f
: 這就是荷載資料,解碼出來是 "Hello" 字串。35 5b 06 ec 94 50 01
: 這是一些其他的封包資訊,如來源 ID、crc 校驗碼等。