-
Notifications
You must be signed in to change notification settings - Fork 0
/
SerialRecord.h
221 lines (204 loc) · 6.31 KB
/
SerialRecord.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/* SerialRecord — an Arduino library for sending and receiving data over a
* serial connection. Created by Oliver Steele, 2020-2022. Available under the
* GNU LGPL license.
*/
#ifndef SerialRecord_h
#define SerialRecord_h
class SerialRecord {
public:
const int size;
int *values;
SerialRecord(int count = 1)
: size(count),
values(new int[count]),
m_buffer(new int[count]),
m_fieldNames(new String[size]) {}
/** Returns the value at the given index. */
int &get(int index = 0) {
static int sentinel;
if (0 <= index && index < size) {
return values[index];
} else {
Serial.println("Error: SerialValueReader index out of bounds (" +
String(index) +
(index < 0 ? " < 0)" : +" >= " + String(size) + ")"));
sentinel = -1;
return sentinel;
}
}
/** Sets the value at the given index. */
void set(int index, int value) {
if (0 <= index && index < size) {
values[index] = value;
} else {
Serial.println("Error: SerialValueReader.set index out of bounds");
}
}
/** Sets the first value. */
void set(int value) {
set(0, value);
}
/** References the value at the given index. */
int &operator[](int index) {
return get(index);
}
void setFieldName(int index, String name) {
if (0 <= index && index < size) {
m_fieldNames[index] = name;
} else {
Serial.println(
"Error: SerialValueReader.setFieldName index out of bounds");
}
}
/** Receive serial data. Returns true if a new record was received. */
bool read() {
bool recordIsComplete = false;
while (Serial.available() && !recordIsComplete) {
char c = Serial.read();
if (c == '\n') {
firstLine = false;
switch (m_readState) {
case IN_FIELD:
m_buffer[m_ix++] = m_accum;
// fall through
case FIELD_START:
if (m_ix < size) {
Serial.println(
"Error: SerialValueReader received too few values: " +
String(m_ix) + " < " + String(size));
}
// Go ahead and copy the buffered values to the values array, even
// if there is the wrong number of them. This is more convenient for
// incremental development.
memcpy(values, m_buffer, size * sizeof values[0]);
recordIsComplete = true;
break;
case COMMAND:
case LINE_START:
case SKIP_LINE:
break;
}
m_readState = LINE_START;
} else {
switch (m_readState) {
case LINE_START:
// This clause handles characters that are meaningful only at the
// beginning of a line. It then falls through to FIELD_START (the
// beginning of a line is also the beginning of a field), to handle
// characters that don't have a special line-initial meaning.
if (c == ' ' || c == '\t')
break; // ignore whitespace at the beginning of a line
if (c == '!') { // Command prefix character. The next character is
// a command.
m_readState = COMMAND;
break;
}
m_readState = FIELD_START;
m_ix = 0;
// fall through
case FIELD_START:
if (m_ix >= size) {
Serial.println(
"Error: SerialValueReader received too many values");
m_readState = SKIP_LINE;
break;
}
m_readState = IN_FIELD;
m_accum = 0; // initialize the field value accumulator
// fall through
case IN_FIELD:
switch (c) {
case '0' ... '9':
m_accum *= 10;
m_accum += c - '0';
// TODO: warn on overflow
break;
case ' ': // space, tab, semicolon, and comma separate (and
// therefore terminate) fields
case '\t':
case ';':
case ',':
m_buffer[m_ix++] = m_accum;
m_readState = FIELD_START;
break;
default:
reportInvalidCharacter(
"Error: SerialValueReader received invalid character: ", c);
m_readState = SKIP_LINE;
break;
}
break;
case COMMAND:
// This clause handles characters that follow a '!' command prefix.
// (There is currently only one such command, "!e".)
switch (c) {
case 'e':
// Echo. Transmit back the last received values received on this
// SerialRecord, for debugging.
send();
m_readState = SKIP_LINE;
break;
case ' ':
case '\t':
break;
default:
reportInvalidCharacter(
"Error: SerialValueReader received unknown command: ", c);
m_readState = SKIP_LINE;
}
break;
case SKIP_LINE:
break;
}
}
}
return recordIsComplete;
}
/** Send data to the serial port. */
void send() {
for (int i = 0; i < size; i++) {
if (i > 0) {
Serial.print(',');
}
if (m_fieldNames[i].length() > 0) {
Serial.print(m_fieldNames[i]);
Serial.print(':');
}
Serial.print(values[i]);
}
Serial.println();
}
/** Send data to the serial port. This method is a synonym for send.*/
void send(int value) {
set(value);
send();
}
/** Send data to the serial port. This method is a synonym for send.*/
void write() {
send();
}
private:
int m_ix = 0;
int m_accum = 0;
bool firstLine = true;
int *m_buffer;
String *m_fieldNames;
enum ReadState {
LINE_START,
FIELD_START,
IN_FIELD,
COMMAND,
SKIP_LINE,
};
ReadState m_readState = LINE_START;
void reportInvalidCharacter(const char *message, int c) {
if (firstLine) return;
Serial.print(message);
Serial.print('\'');
Serial.print(c);
Serial.print("' (");
Serial.print(static_cast<unsigned char>(c));
Serial.println(")");
}
};
#endif // SerialRecord_h