2020-03-02 17:47:10 +00:00
syntax = "proto3" ;
// per https://developers.google.com/protocol-buffers/docs/proto3
// We are not placing any of these defs inside a package, because if you do the resulting nanopb version is super verbose
// package mesh;
option java_package = "com.geeksville.mesh" ;
option java_outer_classname = "MeshProtos" ;
/ * *
MESH RADIO PROTOCOL
Old TODO notes on the mesh radio protocol , merge into real docs below...
for each named group we have a pre - shared key known by all group members and wrapped around the device.
you can only be in one group at a time ( FIXME ? ! )
To join the group we read a qr code with the preshared key and ParamsCodeEnum. that gets sent via
bluetooth to the device. ParamsCodeEnum maps to a set of various radio params ( regulatory region ,
center freq , SF , bandwidth , bitrate , power etc... ) so all members of the mesh can have their radios set the same way.
once in that group , we can talk between 254 node numbers.
to get our node number ( and announce our presence in the channel ) we pick a random node number and
broadcast as that node with WANT - NODENUM ( my globally unique name ) . If anyone on the channel has
seen someone _else_ using that name within the last 24 hrs ( ? ) they reply with DENY - NODENUM.
Note : we might receive multiple denies. Note : this allows others to speak up for some other node
that might be saving battery right now.
Any time we hear from another node ( for any message type ) , we add that node number to the unpickable
list. To dramatically decrease the odds a node number we request is already used by someone.
If no one denies within TBD seconds , we assume that we have that node number. As long as we keep
talking to folks at least once every 24 hrs , others should remember we have it.
Once we have a node number we can broadcast POSITION - UPDATE ( my globally unique name , lat , lon , alt ,
amt battery remaining ) . All receivers will use this to a ) update the mapping of who is at what
node nums , b ) the time of last rx , c ) position. If we haven ' t heard from that node in a while we
reply to that node ( only ) with our current POSITION_UPDATE state - so that node ( presumably just
rejoined the network ) can build a map of all participants.
We will periodically broadcast POSITION - UPDATE as needed based on distance moved or a periodic minimum heartbeat.
If user wants to send a text they can SEND_TEXT ( dest user , short text message ) . Dest user is a
node number , or 0xff for broadcast.
* /
/ *
Protobuf build instructions :
protoc - I = . - - java_out / tmp mesh.proto
To generate Nanopb c code
/ home / kevinh / packages / nanopb - 0.4 .0 - linux - x86 / generator - bin / protoc - - nanopb_out = / tmp - I = app / src / main / proto mesh.proto
Nanopb binaries available here : https : //jpa.kapsi.fi/nanopb/download/ use nanopb 0.4.0
* /
// a gps position
message Position {
double latitude = 1 ;
double longitude = 2 ;
int32 altitude = 3 ;
int32 battery_level = 4 ; // 1-100 (0 means not provided)
/// This is usually not sent over the mesh (to save space), but it is sent from the phone so that the local device can set its RTC
/// If it is sent over the mesh (because there are devices on the mesh without GPS), it will only be sent by devices which has a
/// hardware GPS clock.
uint32 time = 6 ; // seconds since 1970
}
// a data message to forward to an external app (or possibly also be consumed internally in the case of CLEAR_TEXT and CLEAR_READACK
message Data {
enum Type {
/// A message sent from a device outside of the mesh, in a form the mesh does not understand
SIGNAL_OPAQUE = 0 ; // NOTE: This must be 0, because it is documented in IMeshService.aidl to be so
/// a simple UTF-8 text message, which even the little micros in the mesh can understand and show on their screen
/// eventually in some circumstances even signal might send messages in this form (see below)
CLEAR_TEXT = 1 ;
/// a message receive acknowledgement, sent in cleartext - allows radio to show user that a message has been read by the recipient, optional
CLEAR_READACK = 2 ;
/// Not yet used but eventually:
/// SIGNAL_CLEAR_DATA = 3; // Unencrypted at the signal level, relying on the radio crypt only (to keep size small), but contains Signal control data radios don't care about (ie non text)
}
Type typ = 1 ; // required
bytes payload = 2 ; // required
}
/ * Broadcast when a newly powered mesh node wants to find a node num it can use
// Sent from the phone over bluetooth to set the user id for the owner of this node.
// Also sent from nodes to each other when a new node signs on (so all clients can have this info)
The algorithm is as follows :
* when a node starts up , it broadcasts their user and the normal flow is for all other nodes to reply with their User as well ( so the new node can build its node db )
* If a node ever receives a User ( not just the first broadcast ) message where the sender node number equals our node number , that indicates a collision has occurred and the following steps should happen :
If the receiving node ( that was already in the mesh ) ' s macaddr is LOWER than the new User who just tried to sign in : it gets to keep its nodenum. We send a broadcast message
of OUR User ( we use a broadcast so that the other node can receive our message , considering we have the same id - it also serves to let observers correct their nodedb ) - this case is rare so it should be okay.
If any node receives a User where the macaddr is GTE than their local macaddr , they have been vetoed and should pick a new random nodenum ( filtering against whatever it knows about the nodedb ) and
rebroadcast their User.
A few nodenums are reserved and will never be requested :
0xff - broadcast
0 through 3 - for future use
* /
message User {
string id = 1 ; // a globally unique ID string for this user. In the case of Signal that would mean +16504442323, for the default macaddr derived id it would be !<6 hexidecimal bytes>
string long_name = 2 ; // A full name for this user, i.e. "Kevin Hester"
string short_name = 3 ; // A VERY short name, ideally two characters. Suitable for a tiny OLED screen
bytes macaddr = 4 ; // This is the addr of the radio. Not populated by the phone, but added by the esp32 when broadcasting
}
// The payload portion fo a packet, this is the actual bytes that are sent inside a radio packet (because from/to are broken out by the comms library)
message SubPacket {
oneof variant {
Position position = 1 ;
Data data = 3 ;
User user = 4 ;
}
/// Not normally used, but for testing a sender can request that recipient responds in kind (i.e. if it received a position, it should unicast back its position).
// Note: that if you set this on a broadcast you will receive many replies.
bool want_response = 5 ;
}
// Note: For simplicity reasons (and that we want to keep over the radio packets very small, we now assume that there is only _one_
// SubPacket in each MeshPacket). That might change in the far future, but for now that's the case.
// A packet sent over our mesh.
// NOTE: this raw payload does not include the from and to addresses, which are stripped off
// and passed into the mesh library code separately.
//message MeshPayload {
// repeated SubPacket subPackets = 3;
//}
// A full packet sent/received over the mesh
message MeshPacket {
int32 from = 1 ;
int32 to = 2 ;
// See note above:
// MeshPayload payloads = 4;
SubPacket payload = 3 ;
/// 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)
uint32 rx_time = 4 ;
}
/// Shared constants between device and phone
enum Constants {
Unused = 0 ; // First enum must be zero, and we are just using this enum to pass int constants between two very different environments
}
// Full settings (center freq, spread factor, pre-shared secret key etc...) needed to configure a radio for speaking on a particlar channel
// This information can be encoded as a QRcode/url so that other users can configure their radio to join the same channel.
message ChannelSettings {
int32 tx_power = 1 ;
2020-03-16 00:35:51 +00:00
// We now select channel number by hashing the name of the channel. We do this on the device and it is opaque
// to the phone. Therefore no need to show this in channel settings
// uint32 channel_num = 2;
2020-03-02 17:47:10 +00:00
enum ModemConfig {
// Note: these mappings must match ModemConfigChoice in the device code.
Bw125Cr45Sf128 = 0 ; ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
Bw500Cr45Sf128 = 1 ; ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
Bw31_25Cr48Sf512 = 2 ; ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
Bw125Cr48Sf4096 = 3 ; ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range
}
/// This value replaces bandwidth/spread_factor/coding_rate. If you'd like to experiment with other options
/// add them to MeshRadio.cpp in the device code.
ModemConfig modem_config = 3 ;
// uint32 bandwidth = 5;
// int32 spread_factor = 6;
// int32 coding_rate = 7;
/// A simple preshared key for now for crypto. At first I'm using 128 bit (16 byte) block for the Speck crypto
/// but for beta we'll want something more carefully thought through. I want to keep the QR code small
bytes psk = 4 ;
/// A SHORT name that will be packed into the URL. Less than 12 bytes. Something for end users to call the channel
string name = 5 ;
}
// The entire set of user settable/readable settings for our radio device. Includes both the current channel settings
// and any preferences the user has set for behavior of their node
message RadioConfig {
message UserPreferences {
// We should send our position this often (but only if it has changed significantly)
uint32 position_broadcast_secs = 1 ;
// Send our owner info at least this often (also we always send once at boot - to rejoin the mesh)
uint32 send_owner_interval = 2 ;
/// If we miss this many owner messages from a node, we declare the node offline (defaults to 3 - to allow for some lost packets)
uint32 num_missed_to_fail = 3 ;
/// see sw-design.md
uint32 wait_bluetooth_secs = 4 ;
uint32 screen_on_secs = 5 ;
uint32 phone_timeout_secs = 6 ;
uint32 phone_sds_timeout_sec = 7 ;
uint32 mesh_sds_timeout_secs = 8 ;
uint32 sds_secs = 9 ;
uint32 ls_secs = 10 ;
uint32 min_wake_secs = 11 ;
// If true, radio should not try to be smart about what packets to queue to the phone
bool keep_all_packets = 100 ;
// If true, we will try to capture all the packets sent on the mesh, not just the ones destined to our node.
bool promiscuous_mode = 101 ;
}
UserPreferences preferences = 1 ;
ChannelSettings channel_settings = 2 ;
}
/ * *
The bluetooth to device link :
Old BTLE protocol docs from TODO , merge in above and make real docs...
use protocol buffers , and NanoPB
messages from device to phone :
POSITION_UPDATE ( . . . , time )
TEXT_RECEIVED ( from , text , time )
OPAQUE_RECEIVED ( from , payload , time ) ( for signal messages or other applications )
messages from phone to device :
SET_MYID ( id , human readable long , human readable short ) ( send down the unique ID string used for this node , a human readable string shown for that id , and a very short human readable string suitable for oled screen )
SEND_OPAQUE ( dest , payload ) ( for signal messages or other applications )
SEND_TEXT ( dest , text )
Get all nodes ( ) ( returns list of nodes , with full info , last time seen , loc , battery level etc )
SET_CONFIG ( switches device to a new set of radio params and preshared key , drops all existing nodes , force our node to rejoin this new group )
* /
// Full information about a node on the mesh
message NodeInfo {
int32 num = 1 ; // the node number
User user = 2 ;
/// This position data will also contain a time last seen
Position position = 3 ;
/// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured
/// by the receiver.
/// return SNR of the last received message in dB
int32 snr = 5 ;
/// Returns the last measured frequency error.
/// The LoRa receiver estimates the frequency offset between the receiver centre frequency
/// and that of the received LoRa signal. This function returns the estimates offset (in Hz)
/// of the last received message. Caution: this measurement is not absolute, but is measured
/// relative to the local receiver's oscillator.
/// Apparent errors may be due to the transmitter, the receiver or both.
/// \return The estimated centre frequency offset in Hz of the last received message.
int32 frequency_error = 6 ;
}
/ * *
Unique local debugging info for this node
Note : we don ' t include position or the user info , because that will come in the
Sent to the phone in response to WantNodes.
* /
message MyNodeInfo {
2020-03-08 21:03:08 +00:00
/// Tells the phone what our node number is, default starting value is lowbyte of macaddr, but it will be fixed if that is already in use
2020-03-02 17:47:10 +00:00
int32 my_node_num = 1 ;
/// if false it would be great if the phone can help provide gps coordinates
bool has_gps = 2 ;
/// # of legal channels (set at build time in the device flash image)
int32 num_channels = 3 ;
2020-03-03 14:54:14 +00:00
/// The region code for my radio (US, CN, etc...)
string region = 4 ;
/// TBEAM, HELTEC, etc...
string hw_model = 5 ;
/// 0.0.5 etc...
string firmware_version = 6 ;
2020-03-02 17:47:10 +00:00
/// FIXME - add useful debugging state (queue depths etc)
}
// This message is never sent over the wire, but it is used for serializing DB state to flash in the device code
// FIXME, since we write this each time we enter deep sleep (and have infinite flash) it would be better to use some sort of append only data
// structure for the receive queue and use the preferences store for the other stuff
message DeviceState {
RadioConfig radio = 1 ;
2020-03-08 21:03:08 +00:00
/// Read only settings/info about this node
2020-03-02 17:47:10 +00:00
MyNodeInfo my_node = 2 ;
/// My owner info
User owner = 3 ;
repeated NodeInfo node_db = 4 ;
/// Received packets saved for delivery to the phone
repeated MeshPacket receive_queue = 5 ;
/// A version integer used to invalidate old save files when we make incompatible changes
2020-03-03 21:45:31 +00:00
/// This integer is set at build time and is private to NodeDB.cpp in the device code.
uint32 version = 8 ;
2020-03-02 17:47:10 +00:00
/// We keep the last received text message (only) stored in the device flash, so we can show it on the screen. Might be null
MeshPacket rx_text_message = 7 ;
}
// packets from the radio to the phone will appear on the fromRadio characteristic. It will support
// READ and NOTIFY. When a new packet arrives the device will notify? possibly identify instead?
// it will sit in that descriptor until consumed by the phone, at which point the next item in the FIFO
// will be populated. FIXME
message FromRadio {
// The packet num, used to allow the phone to request missing read packets from the FIFO, see our bluetooth docs
uint32 num = 1 ;
oneof variant {
MeshPacket packet = 2 ;
/// Tells the phone what our node number is, can be -1 if we've not yet joined a mesh.
// No longer sent in fromRadio, instead there is a mynodeinfo characteristic
// MyNodeInfo my_info = 3;
/// One packet is sent for each node in the on radio DB
// Note: this is no longer in FromRadio because, there is now a bluetooth nodeinfo characteristic
// after sending wantnodes that characteristic starts over with the first node in our DB
// NodeInfo node_info = 4;
}
}
// packets/commands to the radio will be written (reliably) to the toRadio characteristic. Once the
// write completes the phone can assume it is handled.
message ToRadio {
oneof variant {
MeshPacket packet = 1 ; // send this packet on the mesh
//
// Rare operations
//
// All of these have been moved to unique writable bluetooth characteristics
/// 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 FromRadio.my_node_num and a series of FromRadio.node_info
// WantNodes want_nodes = 100;
//RadioConfig set_radio = 101; // set the radio provisioning for this node
// User set_owner = 102; // Set the owner for this node
}
}