Message Protocol
API: Message Structure
All important communication between subsystems is done over the UART daisy chain. UART messages all follow the same message structure which uses up to 64 bytes. This structure is started by two start bytes, followed by the sender ID byte and the recipient ID byte. The message information is held in the following bytes and can be up to 58 bytes in length. The message is terminated with two stop bytes.
If any of the 4 prefix bytes are corrupted, the message is rejected. Messages are terminated after 64 bytes to stop an open loop upon failure to receive either of the two stop bytes.
Structure Overview
| 0 | 1 | 2 | 3 | 4 - 61 | 62 | 63 |
|---|---|---|---|---|---|---|
0x41 |
0x5A |
Send ID | Receive ID | Message | 0x59 |
0x42 |
| Team Member | Subsystem ID |
|---|---|
| Broadcast | 0x58 'X' |
| Aarshon George | 0x61 'a' |
| Alex Comeaux | 0x63 'c' |
| Ian Anderson | 0x69 'i' |
| Kushagra Dashora | 0x6B 'k' |
The following defines the various messages and their structures to be sent within the UART message protocol. The first message byte is used to identify the type of message, and the following 57 bytes contain the data.
| Message type byte[5] ( char) |
Description | Ian Role: Sensor 'i' |
Alex Role: Actuator 'c' |
KD Role: MQTT Server 'k' |
Aarshon Role: HMI 'a' |
|---|---|---|---|---|---|
| 1 | print sensor X data Y | Send | ~ | Receive | Receive |
| 2 | move motor X param Y | ~ | Receive | Send | Send |
| 3 | subsystem status code X | Send | Send | Receive | Send/Receive |
| 4 | subsystem Z error msg | Send | Send | Receive | Send/Receive |
Message Type 1: Sensor Data Transmission
Message type for sending measured wind speed, temperature, humidity, and air pressure to all other subsystems.
| byte 1 | byte 2 | byte 3-4 big endian |
|---|---|---|
0x31 |
X(uint8_t) |
Y(uint16_t) |
| ~ | sensor number | data value |
| Number Code | Sensor |
|---|---|
'1' 0x01 |
wind speed |
'2' 0x02 |
temperature |
'3' 0x03 |
humidity |
'4' 0x04 |
atm pressure |
| Sender | Destination |
|---|---|
Ian 'i' |
broadcast 'X' |
Message Type 2: Shift Motor
Message type for sending a command to rotate base stepper "Y" degrees.
| byte 1 | byte 2 | byte 3 |
|---|---|---|
0x32 |
X(uint8_t) |
Y(uint8_t) |
| ~ | direction 0x01 = clockwise 0x02 = counterclockwise |
degree shift |
| Senders | Destination |
|---|---|
Aarshon 'a'Kushagra 'k' |
Alex 'c' |
Message Type 3: Subsystem Status Code
Message type for sending status code of a subsystem to be displayed. Sender ID is used to determine affected subsystem.
| byte 1 | byte 2 |
|---|---|
0x33 |
X(uint8_t) |
| ~ | error code |
| code number | meaning |
|---|---|
'1' 0x01 |
full funtionality |
'2' 0x02 |
partial funtionality |
'3' 0x03 |
no funtionality |
| Senders | Destination |
|---|---|
Alex 'c'Ian 'i'Kushagra 'k' |
Aarshon 'a' |
Message Type 4: Subsystem Error Message
Message type for sending string about subsystem error. Sender ID is used to determine affected subsystem.
| byte 1 | byte 2-58 |
|---|---|
0x34 |
Error Message char(uint8_t) |
| Senders | Destination |
|---|---|
Alex 'c'Ian 'i'Kushagra 'k' |
Aarshon 'a' |
Start Bytes: Two specific start bytes (0x41 “A” and 0x5A “Z”) mark the beginning of a message frame.These were chosen as an easy-to-remember signature ("AZ") that also serves as a simple error check – if a node does not see "AZ" at the start where expected, it knows a message is misaligned or corrupted and will reset its parser (preventing false triggers).
Sender and Receiver IDs: Each subsystem has a unique ID embedded in the message so that nodes can identify messages. We used both character IDs and matching hex codes for clarity. For example, 'i' (0x69) represents the Sensor (Ian), 'c' (0x63) the Actuator (Alex), 'a' (0x61) the HMI (Aarshon), and 'k' (0x6B) the Wi-Fi/Cloud module (Kushagra). A special ID 'X' (0x58) is reserved for broadcast messages intended for all nodes. In each message, the third byte is the sender’s ID and the fourth byte is the intended recipient’s ID (or 'X' if broadcast). This addressing scheme was a design decision to make routing simple – every board can quickly check the Receiver ID byte against itself (or 'X') to know if it should process or forward the message.
Data Payload: Depending on the message type, the data section can range from 0 bytes (for a simple ping or acknowledgment) up to 58 bytes. Most of our messages are short (sensor readings fit in 2 bytes, status codes in 1 byte, small commands in a few bytes). We set an upper limit of 64 bytes total for a message frame, which is a reasonable size for our needs and prevents any runaway transmissions from blocking the bus. If a message’s end bytes are not received (due to interference or a reset), the protocol dictates that once 64 bytes total are read, the message is terminated anyway. This failsafe ensures that a missing terminator doesn’t lock the system waiting indefinitely.
End Bytes: We use two specific end-of-message bytes (0x59 'Y' and 0x42 'B') to mark the conclusion of a message frame. Thus every message effectively is wrapped by “AZ” at the start and “YB” at the end. These end bytes were also chosen to be distinct from common data values to reduce the chance of accidental occurrence. When a node sees “YB”, it knows the message is complete and can be processed (if addressed to it) or passed on. If a node doesn’t see “YB” where expected by the length, as mentioned, it will drop the message after 64 bytes to avoid lock-up.