MySensors protocol format
-
So... how about something like this packed?
typedef struct { uint8_t last; // 8 bit - Id of last node this message passed uint8_t sender; // 8 bit - Id of sender node (origin) uint8_t destination; // 8 bit - Id of destination node uint8_t ack; // 1 bit - Request an ack - Indicator that receiver should send an ack back. // 1 bit - Is ack messsage - Indicator that this is the actual ack message. // 6 bit - Reserved for future use. Perhaps encryption? char header[MAX_MESSAGE_LENGTH-4]; } transportation; typedef struct { uint8_t command; // 8 bit - Command type char commandPayload[MAX_MESSAGE_LENGTH - sizeof(transportation) - 1]; } header; typedef struct { uint8_t sensorId; // 8 bit - child sensor id uint8_t sensorType; // 8 bit - sensor type uint8_t valueType_dataType; // 5 bit - value type // 3 bit - data type union { uint8_t bValue; unsigned long ulValue; long lValue; unsigned int uiValue; int iValue; struct { float fValue; uint8_t fPrecision; // Number of decimals when serializing }; char data[MAX_MESSAGE_LENGTH - sizeof(header) - 4]; }; } commandSetReq;But this would only allow 32 different valueTypes per sensorType. But it should be enough. The worst case "Node" would require 22 unique values including the 10 V_CONFIG/V_VAR.
@hek said:
version_command
Almost there ;-)
- I think we could drop the protocol version number and use a full byte for command type.
When, in the future, a command gets extended we can just reserve a new command-number for it. All existing applications will only recognize the old command-number and ignore the new one. As @zeph already pointed out this allows for more efficient usage of the 8 bits and a new version will not immediately disqualify an onld node completely as it will stil listen & respond to old commands. - It might be better to not include the remaining part of the message in a structure (e.g. header[MAX_MESSAGE_LENGTH-4], commandPayload[MAX_MESSAGE_LENGTH - sizeof(transportation) - 1] as this will make it very inefficient to store single instances of it (e.g. declaring a local instance of transportation or header will always clame a full message size)
- As a second though, it might be best to declare everything as union's, so the structures will overlap eachother (will become one badass strusture then....)
- I think I'll dive a bit deeper into the RadioHead library to understand what is already possible with it and to be able to exchange some ideas with @kolaf
Edit @hek you were right about the RadioHead library:
RHRouter (and therefore RHMesh) use reliable hop-to-hop delivery of messages using hop-to-hop acknowledgements, but not end-to-end acknowledgements.
- I think we could drop the protocol version number and use a full byte for command type.
-
We should also consider interoperability with the the MQTT delivery concepts of "at most once", "at least once" and "exactly once" so the interface is smooth.
There are 4 dataflows where this interoperability could might be at least considered, I think.
controller to MQTT MQTT to node node to MQTT MQTT to controller -
I think it's important that if we use structs and unions here, we understand that this is just a convenience for discussion, not the specification. For discussion we can assume that the structs will have 1 byte alignment for themselves and for all members, and will use little endianness.
What really matters to the spec is that the bytes in the order they go OTA be exactly the same for all senders and all receivers.
Those bytes might be interpreted in place as a struct (via static cast of a pointer into a byte buffer for example) IN SOME CASES, depending on CPU and on compiler and its options. In other cases it might be necessary to pack and unpack dynamically to avoid alignment and endianness issues.
Just a caveat to watch out for.
-
@hek said:
version_command
Almost there ;-)
- I think we could drop the protocol version number and use a full byte for command type.
When, in the future, a command gets extended we can just reserve a new command-number for it. All existing applications will only recognize the old command-number and ignore the new one. As @zeph already pointed out this allows for more efficient usage of the 8 bits and a new version will not immediately disqualify an onld node completely as it will stil listen & respond to old commands. - It might be better to not include the remaining part of the message in a structure (e.g. header[MAX_MESSAGE_LENGTH-4], commandPayload[MAX_MESSAGE_LENGTH - sizeof(transportation) - 1] as this will make it very inefficient to store single instances of it (e.g. declaring a local instance of transportation or header will always clame a full message size)
- As a second though, it might be best to declare everything as union's, so the structures will overlap eachother (will become one badass strusture then....)
- I think I'll dive a bit deeper into the RadioHead library to understand what is already possible with it and to be able to exchange some ideas with @kolaf
Edit @hek you were right about the RadioHead library:
RHRouter (and therefore RHMesh) use reliable hop-to-hop delivery of messages using hop-to-hop acknowledgements, but not end-to-end acknowledgements.
@Yveaux said:
- I think we could drop the protocol version number and use a full byte for command type.
When, in the future, a command gets extended we can just reserve a new command-number for it. All existing applications will only recognize the old command-number and ignore the new one. As @zeph already pointed out this allows for more efficient usage of the 8 bits and a new version will not immediately disqualify an onld node completely as it will stil listen & respond to old commands.
The value of having defined groups of command associated with a given protocol version is that you can get a pretty good idea of what a node will understand (or send). The first approach was to have some fixed number of bits for a protocol id, and some number for command-within-protocol (like 8+8 or 5+3 or whatever). I'm glad you see the advantages of adding command codes sequentially instead, especially if we are using one byte.
I was suggesting that a given protocol version define a contiguous range of commands (plus some universal ones at the beginning of the codes). One caveat was that the "current protocol version" would be allowed to add new commands at the end of the sequence - the "last command allowed in this version" doesn't get frozen until a version is superceded by another one. Any given node need only understand the universal commands plus the range corresponding to the protocol version it was written to. So each version has the common universal commands plus a non-overlapping range of command specific to this version.
The disadvantage is that you may have to redefine new command codes for commonly needed commands like "set value" every time you move to the next version. So in the first version, command 4 might be set (part of range 3..9). Then in the next version with commands (10..17) we have to define a new "set value" within that range, maybe code 11. Nodes implementing the second version only understand commands 0..2 (universal) and 10..17, they have no need to understand version one commands 3..9. A gateway would not expect a version 2 node to understand command 6. The hub could ask the node for the start and end of the range of commands it can accept.
===
One variant, which you are suggesting I think, is to ignore versoin based commadn code sub-ranges and just keep all commands allowable to all nodes. This has the advantage that "Set value" command 4 (in the above example) can continue to be used "forever", and not need to be reallocated code 11 to fit within the new version's range.
But it may over time cause fragmentation or bloat - either we carry around the code to handle all commands (eg: 1.3 and 1.4 both), or the set of commands allowed once you switch to a new protocol version is non-contiguous (it contains some but not all commands from the older protocol version). I actually considered this, and the possibility of having the node respond to "what commands can you handle" with a bitmap instead of a start and end value, but decided that was getting too complicated. That was just my judgement call tho.
(If you can correctly anticpate this kind of command in the "universal commands" that are part of all versions, the issue goes away. In the first appraoch, maybe "set value" is command 2 and globally available, not needing to be allocated a code in each versions's code range) But sometimes your concept changes more than you anticipated and "set value" might need to become a new command type in newer protocols).
Deciding among these options depends somewhat on one's philosophy of where configuration information should be stored - in the nodes or in the hub. There are many answers to this question at different granularities. If you accept the concept of storing much config on the hub, then another approach emerges:
- each node can use a basic common protocol to report on first connecting what version of the full protocol it implements
- that per-node protocol version is stored in the hub
- the hub has bitmaps or tables of what command codes work for each protocol version it understands
- if the hub understands more than one version, it can communicate with nodes using any of those versions
In this case, you don't make every packet include an explicit (fixed bits allocated) or implicit (code range) protocol version. But the hub could still know which commands work and don't work for any given node.
- I think we could drop the protocol version number and use a full byte for command type.