1717
1818#include " FirmataParser.h"
1919
20+ #include " FirmataConstants.h"
21+
2022// ******************************************************************************
2123// * Constructors
2224// ******************************************************************************
2325
2426/* *
25- * The Firmata class.
26- * An instance named "Firmata" is created automatically for the user.
27+ * The FirmataParser class.
28+ * @param dataBuffer A pointer to an external buffer used to store parsed data
29+ * @param dataBufferSize The size of the external buffer
2730 */
28- FirmataParser::FirmataParser ()
31+ FirmataParser::FirmataParser (uint8_t * const dataBuffer, size_t dataBufferSize )
2932:
30- executeMultiByteCommand(0 ),
31- multiByteChannel(0 ),
32- waitForData(0 ),
33- parsingSysex(false ),
34- sysexBytesRead(0 ),
35- currentAnalogCallback((callbackFunction)NULL),
36- currentDigitalCallback((callbackFunction)NULL),
37- currentReportAnalogCallback((callbackFunction)NULL),
38- currentReportDigitalCallback((callbackFunction)NULL),
39- currentPinModeCallback((callbackFunction)NULL),
40- currentPinValueCallback((callbackFunction)NULL),
41- currentReportFirmwareCallback((systemCallbackFunction)NULL),
42- currentReportVersionCallback((systemCallbackFunction)NULL),
43- currentSystemResetCallback((systemCallbackFunction)NULL),
44- currentStringCallback((stringCallbackFunction)NULL),
45- currentSysexCallback((sysexCallbackFunction)NULL)
33+ dataBuffer(dataBuffer),
34+ dataBufferSize(dataBufferSize),
35+ executeMultiByteCommand(0 ),
36+ multiByteChannel(0 ),
37+ waitForData(0 ),
38+ parsingSysex(false ),
39+ sysexBytesRead(0 ),
40+ currentDataBufferOverflowCallbackContext((void *)NULL),
41+ currentAnalogCallback((callbackFunction)NULL),
42+ currentDigitalCallback((callbackFunction)NULL),
43+ currentReportAnalogCallback((callbackFunction)NULL),
44+ currentReportDigitalCallback((callbackFunction)NULL),
45+ currentPinModeCallback((callbackFunction)NULL),
46+ currentPinValueCallback((callbackFunction)NULL),
47+ currentReportFirmwareCallback((systemCallbackFunction)NULL),
48+ currentReportVersionCallback((systemCallbackFunction)NULL),
49+ currentSystemResetCallback((systemCallbackFunction)NULL),
50+ currentStringCallback((stringCallbackFunction)NULL),
51+ currentSysexCallback((sysexCallbackFunction)NULL),
52+ currentDataBufferOverflowCallback((dataBufferOverflowCallbackFunction)NULL)
4653{
54+ allowBufferUpdate = ((uint8_t *)NULL == dataBuffer);
4755}
4856
4957// ******************************************************************************
@@ -53,47 +61,6 @@ FirmataParser::FirmataParser()
5361// ------------------------------------------------------------------------------
5462// Serial Receive Handling
5563
56- /* *
57- * Process incoming sysex messages. Handles REPORT_FIRMWARE and STRING_DATA internally.
58- * Calls callback function for STRING_DATA and all other sysex messages.
59- * @private
60- */
61- void FirmataParser::processSysexMessage (void )
62- {
63- switch (storedInputData[0 ]) { // first byte in buffer is command
64- case REPORT_FIRMWARE:
65- if (currentReportFirmwareCallback)
66- (*currentReportFirmwareCallback)();
67- break ;
68- case STRING_DATA:
69- if (currentStringCallback) {
70- size_t bufferLength = (sysexBytesRead - 1 ) / 2 ;
71- size_t i = 1 ;
72- size_t j = 0 ;
73- while (j < bufferLength) {
74- // The string length will only be at most half the size of the
75- // stored input buffer so we can decode the string within the buffer.
76- storedInputData[j] = storedInputData[i];
77- i++;
78- storedInputData[j] += (storedInputData[i] << 7 );
79- i++;
80- j++;
81- }
82- // Make sure string is null terminated. This may be the case for data
83- // coming from client libraries in languages that don't null terminate
84- // strings.
85- if (storedInputData[j - 1 ] != ' \0 ' ) {
86- storedInputData[j] = ' \0 ' ;
87- }
88- (*currentStringCallback)((char *)&storedInputData[0 ]);
89- }
90- break ;
91- default :
92- if (currentSysexCallback)
93- (*currentSysexCallback)(storedInputData[0 ], sysexBytesRead - 1 , storedInputData + 1 );
94- }
95- }
96-
9764/* *
9865 * Parse data from the input stream.
9966 * @param inputData A single byte to be added to the parser.
@@ -110,43 +77,43 @@ void FirmataParser::parse(uint8_t inputData)
11077 processSysexMessage ();
11178 } else {
11279 // normal data byte - add to buffer
113- storedInputData[sysexBytesRead] = inputData;
114- sysexBytesRead++ ;
80+ bufferDataAtPosition ( inputData, sysexBytesRead) ;
81+ ++sysexBytesRead ;
11582 }
11683 } else if ( (waitForData > 0 ) && (inputData < 128 ) ) {
117- waitForData-- ;
118- storedInputData[waitForData] = inputData;
84+ --waitForData ;
85+ bufferDataAtPosition ( inputData, waitForData) ;
11986 if ( (waitForData == 0 ) && executeMultiByteCommand ) { // got the whole message
12087 switch (executeMultiByteCommand) {
12188 case ANALOG_MESSAGE:
12289 if (currentAnalogCallback) {
12390 (*currentAnalogCallback)(multiByteChannel,
124- (storedInputData [0 ] << 7 )
125- + storedInputData [1 ]);
91+ (dataBuffer [0 ] << 7 )
92+ + dataBuffer [1 ]);
12693 }
12794 break ;
12895 case DIGITAL_MESSAGE:
12996 if (currentDigitalCallback) {
13097 (*currentDigitalCallback)(multiByteChannel,
131- (storedInputData [0 ] << 7 )
132- + storedInputData [1 ]);
98+ (dataBuffer [0 ] << 7 )
99+ + dataBuffer [1 ]);
133100 }
134101 break ;
135102 case SET_PIN_MODE:
136103 if (currentPinModeCallback)
137- (*currentPinModeCallback)(storedInputData [1 ], storedInputData [0 ]);
104+ (*currentPinModeCallback)(dataBuffer [1 ], dataBuffer [0 ]);
138105 break ;
139106 case SET_DIGITAL_PIN_VALUE:
140107 if (currentPinValueCallback)
141- (*currentPinValueCallback)(storedInputData [1 ], storedInputData [0 ]);
108+ (*currentPinValueCallback)(dataBuffer [1 ], dataBuffer [0 ]);
142109 break ;
143110 case REPORT_ANALOG:
144111 if (currentReportAnalogCallback)
145- (*currentReportAnalogCallback)(multiByteChannel, storedInputData [0 ]);
112+ (*currentReportAnalogCallback)(multiByteChannel, dataBuffer [0 ]);
146113 break ;
147114 case REPORT_DIGITAL:
148115 if (currentReportDigitalCallback)
149- (*currentReportDigitalCallback)(multiByteChannel, storedInputData [0 ]);
116+ (*currentReportDigitalCallback)(multiByteChannel, dataBuffer [0 ]);
150117 break ;
151118 }
152119 executeMultiByteCommand = 0 ;
@@ -197,6 +164,31 @@ const
197164 return (waitForData > 0 || parsingSysex);
198165}
199166
167+ /* *
168+ * Provides a mechanism to either set or update the working buffer of the parser.
169+ * The method will be enabled when no buffer has been provided, or an overflow
170+ * condition exists.
171+ * @param dataBuffer A pointer to an external buffer used to store parsed data
172+ * @param dataBufferSize The size of the external buffer
173+ */
174+ int FirmataParser::setDataBufferOfSize (uint8_t * dataBuffer, size_t dataBufferSize)
175+ {
176+ int result;
177+
178+ if ( !allowBufferUpdate ) {
179+ result = __LINE__;
180+ } else if ((uint8_t *)NULL == dataBuffer) {
181+ result = __LINE__;
182+ } else {
183+ this ->dataBuffer = dataBuffer;
184+ this ->dataBufferSize = dataBufferSize;
185+ allowBufferUpdate = false ;
186+ result = 0 ;
187+ }
188+
189+ return result;
190+ }
191+
200192/* *
201193 * Attach a generic sysex callback function to a command (options are: ANALOG_MESSAGE,
202194 * DIGITAL_MESSAGE, REPORT_ANALOG, REPORT DIGITAL, SET_PIN_MODE and SET_DIGITAL_PIN_VALUE).
@@ -249,9 +241,21 @@ void FirmataParser::attach(uint8_t command, stringCallbackFunction newFunction)
249241 */
250242void FirmataParser::attach (uint8_t command, sysexCallbackFunction newFunction)
251243{
244+ (void )command;
252245 currentSysexCallback = newFunction;
253246}
254247
248+ /* *
249+ * Attach a buffer overflow callback
250+ * @param newFunction A reference to the buffer overflow callback function to attach.
251+ * @param context The context supplied by the end-user, and provided during the execution of the callback
252+ */
253+ void FirmataParser::attach (dataBufferOverflowCallbackFunction newFunction, void * context)
254+ {
255+ currentDataBufferOverflowCallback = newFunction;
256+ currentDataBufferOverflowCallbackContext = context;
257+ }
258+
255259/* *
256260 * Detach a callback function for a specified command (such as SYSTEM_RESET, STRING_DATA,
257261 * ANALOG_MESSAGE, DIGITAL_MESSAGE, etc).
@@ -272,10 +276,91 @@ void FirmataParser::detach(uint8_t command)
272276 }
273277}
274278
279+ /* *
280+ * Detach the buffer overflow callback
281+ * @param <unused> Any pointer of type dataBufferOverflowCallbackFunction.
282+ */
283+ void FirmataParser::detach (dataBufferOverflowCallbackFunction)
284+ {
285+ currentDataBufferOverflowCallback = (dataBufferOverflowCallbackFunction)NULL ;
286+ currentDataBufferOverflowCallbackContext = (void *)NULL ;
287+ }
288+
275289// ******************************************************************************
276290// * Private Methods
277291// ******************************************************************************
278292
293+ /* *
294+ * Buffer abstraction to prevent memory corruption
295+ * @param data The byte to put into the buffer
296+ * @param pos The position to insert the byte into the buffer
297+ * @return writeError A boolean to indicate if an error occured
298+ * @private
299+ */
300+ bool FirmataParser::bufferDataAtPosition (const uint8_t data, const size_t pos)
301+ {
302+ bool bufferOverflow = (pos >= dataBufferSize);
303+
304+ // Notify of overflow condition
305+ if ( bufferOverflow
306+ && ((dataBufferOverflowCallbackFunction)NULL != currentDataBufferOverflowCallback) )
307+ {
308+ allowBufferUpdate = true ;
309+ currentDataBufferOverflowCallback (currentDataBufferOverflowCallbackContext);
310+ // Check if overflow was resolved during callback
311+ bufferOverflow = (pos >= dataBufferSize);
312+ }
313+
314+ // Write data to buffer if no overflow condition persist
315+ if ( !bufferOverflow )
316+ {
317+ dataBuffer[pos] = data;
318+ }
319+
320+ return bufferOverflow;
321+ }
322+
323+ /* *
324+ * Process incoming sysex messages. Handles REPORT_FIRMWARE and STRING_DATA internally.
325+ * Calls callback function for STRING_DATA and all other sysex messages.
326+ * @private
327+ */
328+ void FirmataParser::processSysexMessage (void )
329+ {
330+ switch (dataBuffer[0 ]) { // first byte in buffer is command
331+ case REPORT_FIRMWARE:
332+ if (currentReportFirmwareCallback)
333+ (*currentReportFirmwareCallback)();
334+ break ;
335+ case STRING_DATA:
336+ if (currentStringCallback) {
337+ size_t bufferLength = (sysexBytesRead - 1 ) / 2 ;
338+ size_t i = 1 ;
339+ size_t j = 0 ;
340+ while (j < bufferLength) {
341+ // The string length will only be at most half the size of the
342+ // stored input buffer so we can decode the string within the buffer.
343+ bufferDataAtPosition (dataBuffer[i], j);
344+ ++i;
345+ bufferDataAtPosition ((dataBuffer[j] + (dataBuffer[i] << 7 )), j);
346+ ++i;
347+ ++j;
348+ }
349+ // Make sure string is null terminated. This may be the case for data
350+ // coming from client libraries in languages that don't null terminate
351+ // strings.
352+ if (dataBuffer[j - 1 ] != ' \0 ' ) {
353+ bufferDataAtPosition (' \0 ' , j);
354+ }
355+ (*currentStringCallback)((char *)&dataBuffer[0 ]);
356+ }
357+ break ;
358+ default :
359+ if (currentSysexCallback)
360+ (*currentSysexCallback)(dataBuffer[0 ], sysexBytesRead - 1 , dataBuffer + 1 );
361+ }
362+ }
363+
279364/* *
280365 * Resets the system state upon a SYSTEM_RESET message from the host software.
281366 * @private
@@ -288,8 +373,8 @@ void FirmataParser::systemReset(void)
288373 executeMultiByteCommand = 0 ; // execute this after getting multi-byte data
289374 multiByteChannel = 0 ; // channel data for multiByteCommands
290375
291- for (i = 0 ; i < MAX_DATA_BYTES; i++ ) {
292- storedInputData [i] = 0 ;
376+ for (i = 0 ; i < dataBufferSize; ++i ) {
377+ dataBuffer [i] = 0 ;
293378 }
294379
295380 parsingSysex = false ;
@@ -298,4 +383,3 @@ void FirmataParser::systemReset(void)
298383 if (currentSystemResetCallback)
299384 (*currentSystemResetCallback)();
300385}
301-
0 commit comments