TinyRobotics
Loading...
Searching...
No Matches
MessageParser.h
1#pragma once
2#include "Message.h"
3#include "TinyRobotics/utils/MemoryStream.h"
4
5
6namespace tinyrobotics {
7
8/**
9 * @enum MessageValueType
10 * @ingroup communications
11 * @brief Enum to indicate the type of parsed TinyRobotics message.
12 *
13 * Used by MessageParser to specify which variant of the union in ParsedMessage
14 * is valid.
15 */
16enum class MessageValueType {
17 Float, ///< Message<float>
18 Coordinate, ///< Message<Coordinate<float>>
19 GPSCoordinate, ///< Message<GPSCoordinate>
20 Unknown ///< Unknown or parse error
21};
22
23/**
24 * @brief Holds a parsed TinyRobotics message of the correct type.
25 *
26 * The type field indicates which member of the union is valid after parsing.
27 * Used as an output parameter for MessageParser::parse().
28 * @ingroup communication
29 */
30struct ParsedMessage {
31 MessageValueType type; ///< Indicates which union member is valid. @see MessageValueType
32 union {
33 Message<float> floatMsg;
34 Message<Coordinate<float>> coordMsg;
35 Message<GPSCoordinate> gpsMsg;
36 };
37 ParsedMessage() : type(MessageValueType::Unknown) {}
38 ~ParsedMessage() {} // Add proper destructors if needed
39};
40
41/**
42 * @brief Parses binary TinyRobotics messages from a Stream and dispatches them
43 * by type.
44 *
45 * MessageParser provides two main interfaces:
46 *
47 * 1. parse(Stream&, ParsedMessage&):
48 * - Parses a message from the stream and fills a ParsedMessage union with
49 * the correct type.
50 * - The caller can inspect parsed.type and access the appropriate union
51 * member.
52 *
53 * 2. parse(Stream&, MessageHandler&):
54 * - Parses a message from the stream and automatically dispatches it to the
55 * correct onMessage() overload of the provided MessageHandler instance.
56 * - This is convenient for direct message handling without manual type
57 * inspection.
58 *
59 * The parser reads the message header (prefix, size, origin_id), then inspects
60 * the content field to determine the value type (float, Coordinate<float>,
61 * GPSCoordinate). It then reads the value and remaining fields accordingly.
62 *
63 * Usage (manual type inspection):
64 * @code
65 * MessageParser parser;
66 * ParsedMessage parsed;
67 * if (parser.parse(stream, parsed)) {
68 * switch (parsed.type) { ... }
69 * }
70 * @endcode
71 *
72 * Usage (automatic dispatch):
73 * @code
74 * MessageParser parser;
75 * parser.parse(stream, handler); // handler is a MessageHandler subclass
76 * @endcode
77 * @ingroup communication
78 */
79class MessageParser {
80 public:
81 // Parses a message from the stream and fills out the correct type in result
82 bool parse(Stream& io, ParsedMessage& result) {
83 // 1. Read prefix, size, and origin_id
84 char prefix[4] = {0};
85 if (io.readBytes(prefix, 3) != 3) return false;
86 if (strncmp(prefix, "MSG", 3) != 0) return false;
87
88 uint8_t size = io.read();
89 uint8_t origin_id = io.read();
90
91 // 2. Read content and unit fields (assuming they are next)
92 MessageContent content;
93 if (io.readBytes((char*)&content, sizeof(content)) != sizeof(content))
94 return false;
95 Unit unit;
96 if (io.readBytes((char*)&unit, sizeof(unit)) != sizeof(unit)) return false;
97
98 // 3. Decide type and read value accordingly
99 if (content == MessageContent::Position) {
100 result.type = MessageValueType::Coordinate;
101 // Read Coordinate<float> value
102 if (io.readBytes((char*)&result.coordMsg.value,
103 sizeof(Coordinate<float>)) != sizeof(Coordinate<float>))
104 return false;
105 result.coordMsg.prefix = "MSG";
106 result.coordMsg.size = size;
107 result.coordMsg.origin_id = origin_id;
108 result.coordMsg.content = content;
109 result.coordMsg.unit = unit;
110 // Read source
111 io.readBytes((char*)&result.coordMsg.origin, sizeof(MessageOrigin));
112 } else if (content == MessageContent::PositionGPS) {
113 result.type = MessageValueType::GPSCoordinate;
114 if (io.readBytes((char*)&result.gpsMsg.value, sizeof(GPSCoordinate)) !=
115 sizeof(GPSCoordinate))
116 return false;
117 result.gpsMsg.prefix = "MSG";
118 result.gpsMsg.size = size;
119 result.gpsMsg.origin_id = origin_id;
120 result.gpsMsg.content = content;
121 result.gpsMsg.unit = unit;
122 io.readBytes((char*)&result.gpsMsg.origin, sizeof(MessageOrigin));
123 } else {
124 result.type = MessageValueType::Float;
125 if (io.readBytes((char*)&result.floatMsg.value, sizeof(float)) !=
126 sizeof(float))
127 return false;
128 result.floatMsg.prefix = "MSG";
129 result.floatMsg.size = size;
130 result.floatMsg.origin_id = origin_id;
131 result.floatMsg.content = content;
132 result.floatMsg.unit = unit;
133 io.readBytes((char*)&result.floatMsg.origin, sizeof(MessageOrigin));
134 }
135 return true;
136 }
137
138 /**
139 * @brief Parses a message from the stream and dispatches it to the given
140 * handler.
141 *
142 * This method parses the message type and calls the appropriate onMessage
143 * overload on the provided MessageHandler instance.
144 *
145 * @param io The input stream to parse from.
146 * @param handler The MessageHandler to dispatch the parsed message to.
147 * @return true if a message was successfully parsed and dispatched, false
148 * otherwise.
149 */
150 bool parse(Stream& io, MessageHandler& handler) {
151 ParsedMessage parsed;
152 if (!parse(io, parsed)) return false;
153 switch (parsed.type) {
154 case MessageValueType::Float:
155 handler.onMessage(parsed.floatMsg);
156 break;
157 case MessageValueType::Coordinate:
158 handler.onMessage(parsed.coordMsg);
159 break;
160 case MessageValueType::GPSCoordinate:
161 handler.onMessage(parsed.gpsMsg);
162 break;
163 default:
164 return false;
165 }
166 return true;
167 }
168
169 /**
170 * @brief Parses a message from a raw buffer and dispatches it to the given handler.
171 *
172 * This overload wraps the buffer in a MemoryStream and calls the Stream-based parse.
173 *
174 * @param data Pointer to the message buffer.
175 * @param len Length of the buffer.
176 * @param handler The MessageHandler to dispatch the parsed message to.
177 * @return true if a message was successfully parsed and dispatched, false otherwise.
178 */
179 bool parse(uint8_t* data, size_t len, MessageHandler& handler) {
180 MemoryStream stream(data, len);
181 return parse(stream, handler);
182 }
183};
184
185} // namespace tinyrobotics
Read-only memory stream for wrapping a buffer as an Arduino Stream.
Definition: MemoryStream.h:16
Parses binary TinyRobotics messages from a Stream and dispatches them by type.
Definition: MessageParser.h:79
bool parse(uint8_t *data, size_t len, MessageHandler &handler)
Parses a message from a raw buffer and dispatches it to the given handler.
Definition: MessageParser.h:179
bool parse(Stream &io, MessageHandler &handler)
Parses a message from the stream and dispatches it to the given handler.
Definition: MessageParser.h:150
Unit
Units for message values.
Definition: Common.h:45
Generic message structure for communication, parameterized by value type.
Definition: Message.h:72
Holds a parsed TinyRobotics message of the correct type.
Definition: MessageParser.h:30
MessageValueType type
Indicates which union member is valid.
Definition: MessageParser.h:31