So like many, I’ve been caught up in the hype that is Meshtastic. I have a use case where it may be very handy BUT I need to actually talk to the radio using packets.
This is a WIP and a bit of a brain dump. And yes I *could* ask the Devs, but I can already see from the docs its going to involve a chase round the houses.
When I first started looking at this it looked easy, the documentation seems to be good and complete however once you start digging it all goes a bit wrong. The first thing you’ll hit is Google’s Protobuf. ALl the documentation basically shunts you off to this with no good explaination. There’s a few examples and when you poke through them, erm, it starts to go a bit wrong.
Everything points you at Protobuf of the Meshtastic .proto files. This is all wonderful but there’s two issues here. Although I dont use most of the languages they have examples for, I do use C++ and Arduino on Microcontrollers, so n theory I shold be fine…
The example for arduino doesnt work, it has a grab bag of odd setups and dependancies and when they are finally sorted, you have to compile other parts to make it work and THAT doesnt compile. and NOTHING is commented or easy to follow, well, by easy I mean possible. So the Arduino client I’m left with nothing workable, and a missing file. And the missing file is missing from everything because it has to be compiled (and wont compile) AND appears to be non existant. So I’m not going to be able to use Protobufs with Delhpi for this. Fine, I’ve written my own parsers before not an issue. Let’s at least get some packets…
Oh, the way you go from debug to packet mode isnt documented AT ALL!
Seriously?! Its hidden away in those poory documented source files. This is insane. As I work a lot with RS232 I fire up a prtocol analyser between the web client and a rdio and I find this:
0x94 0x3C 0x00 0x06 0x18 0xA7 0xF8 0xCE 0xE3 0x02
I have some idea what this is. Its there in the code examples for arduino are useful for picking apart the packet structure
// Magic number at the start of all MT packets
define MT_MAGIC_0 0x94
define MT_MAGIC_1 0xc3
So thats our first two bytes. Awesome. It also tells us that we are going to get 4 bytes, the MSB and LSB of the packet length…
// The header is the magic number plus a 16-bit payload-length field
define MT_HEADER_SIZE 4
and a bit further down we can see the packet being built….
pb_buf[0] = MT_MAGIC_0;
pb_buf[1] = MT_MAGIC_1;
pb_ostream_t stream = pb_ostream_from_buffer(pb_buf + 4, PB_BUFSIZE);
bool status = pb_encode(&stream, meshtastic_ToRadio_fields, &toRadio);
if (!status) {
d(“Couldn’t encode toRadio”);
return false;
}
// Store the payload length in the header
pb_buf[2] = stream.bytes_written / 256;
pb_buf[3] = stream.bytes_written % 256;
This is in mt_protocol.cpp for _mt_send_to_radio. And this is where it goes off the rails. pb_encode() is in pb_encode.h which is (I beleive) part of nanoPb. It’s not part of the arduino library for Meshtastic and it’s not actually mentioned except on the GitHub Repo and an exmple is given here: https://www.dfrobot.com/blog-1161.html
This *should* get us those missing includes, and it does. So let’s see how PB_encode helps us.
bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);
Ah-Ha so in the Arduino code our feilds MUST be in
meshtastic_ToRadio_fields, so where does that come from. Bear in mind we are now into a third party library and there is no clear program flow here becasue nothing is commented. It’s been assumed we just know all about ProtoBufs and NanoPB and *most* using the Arduino libraries arent going to be seasoned coders. So where does meshtastic_ToRadio_fields come from?
We find it in mesh.pb.h which seems to be our Rosetta Stone. Its now pointed at meshtastic_ToRadio_msg, where on earth does that go? A dig through thaty doesnt help BUT we finally find _meshtastic_ToRadio which looks like what we actually want AND there’s comments
/* Send this packet on the mesh */ meshtastic_MeshPacket packet; /* 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. */
So what we need is to ask for a node db, how do we do that? It apears the radio assumes if we ask for that we will get everything. This seems silly that we can’t just say hello, maybe we can, but on a constrained system we might not want the whole node list, we would just have to bin it!.
That 0x18 seems to be what we are doing here but theres another 5 bytes of info, what are they? So looking at what happens when we connect we are definately sending something more than just a “gimme everything” The arduino code does an init once the connection is there, chasing this through a dozen files and casts we come to meshtastic_ToRadio_init_default and on to meshtastic_MeshPacket_init_default. This should NOT be this hard!
At this point i’m just going to blast that packet back. I don’t like that I dont know what I’m doing. Lets try… Boom, a screenfull of garbage with interspersed text. So we are in packet mode and we got a node list:
Now we in theory have our key to this which we found above. So lets parse this out into packets andthen we can try and do something with it…
I’m going to use a state machine to look for the magic numbers and then pull out the packet length and grab the data. Ironically TRNet doesnt work much different to this, we just have an addess in the header. To make things easier I’ll try and get it printing in Intel hex format too…
Now the 5th byte tells us what a packet is. protobuffs can be nested and I’d urge you to at least go and read up about how they work. Its really quite clever and quite analogous to structs/records and indeed that’s probobly how I’ll deal with this.
Having read what we found above whe have a good ideas what we have here. The 0x22’s are going to be node information so we now need to know what that 5th byte represents. It may represent data, another struct or whatever.
Converting to ASCII we can see we clearly get two packets of unknown function then the start of the node list.
14:11:19 : 1A 0C 08 C0 CF 8E D3 0D 40 5E 58 F8 EB 01 : . . . À Ï Ó . @ ^ X ø ë .
14:11:19 : 6A 1C 0A 0D 32 2E 33 2E 34 2E 65 61 36 31 38 30 38 10 16 18 01 20 01 28 01 40 AB 06 48 2B : j . . . 2 . 3 . 4 . e a 6 1 8 0 8 . . . . . ( . @ « . H +14:11:19 : 22 53 08 C0 CF 8E D3 0D 12 2C 0A 09 21 64 61 36 33 61 37 63 30 12 0F 54 44 30 35 20 47 69 6C 6C 6B 69 63 6B 65 72 1A 04 54 44 30 35 22 06 34 B7 DA 63 A7 C0 28 2B 1A 05 25 74 E1 17 66 2D 74 E1 17 66 32 11 08 50 15 14 AE 7F 40 1D 4E 1B 34 41 25 15 67 4D 40 : ” S . À Ï Ó . . , . . ! d a 6 3 a 7 c 0 . . T D 0 5 G i l l k i c k e r . . T D 0 5 ” . 4 · Ú c § À ( + . . % t á . f – t á . f 2 . . P . . ® @ . N . 4 A % . g M @
From this we now know that 0x22 is a node info packet. We need to find out what 0x1A and 0x6A are. This info *should* be in the source code for the Protobuffs and the registry here. The issue is we seem to be missing the root entries. I could be misreading something but there are references to constants for these all over the place but no actual definition of these constants.