-
Notifications
You must be signed in to change notification settings - Fork 14
/
RaceChronoDiyBleDevice.ino
363 lines (296 loc) · 9.62 KB
/
RaceChronoDiyBleDevice.ino
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#include <CAN.h>
#include <RaceChrono.h>
#include "config.h"
// Connections:
// MCP | BOARD
// INT | Not used, can connect to Pin 9
// SCK | SCK
// SI | MO
// SO | MI
// CS | Pin 7
// GND | GND
// VCC | 3.3V
const int CS_PIN = 7;
const int IRQ_PIN = 9;
const long QUARTZ_CLOCK_FREQUENCY = 16 * 1E6; // 16 MHz.
const uint32_t SPI_FREQUENCY = 10 * 1E6; // 10 MHz.
bool isCanBusReaderActive = false;
long lastCanMessageReceivedMs;
struct PidExtra {
// Only send one out of |updateRateDivider| packets per PID.
uint8_t updateRateDivider = DEFAULT_UPDATE_RATE_DIVIDER;
// Varies between 0 and |updateRateDivider - 1|.
uint8_t skippedUpdates = 0;
};
RaceChronoPidMap<PidExtra> pidMap;
uint32_t loop_iteration = 0;
uint32_t last_time_num_can_bus_timeouts_sent_ms = 0;
uint16_t num_can_bus_timeouts = 0;
// Forward declarations to help put code in a natural reading order.
void waitForConnection();
void bufferNewPacket(uint32_t pid, uint8_t *data, uint8_t data_length);
void handleOneBufferedPacket();
void flushBufferedPackets();
void sendNumCanBusTimeouts();
void resetSkippedUpdatesCounters();
void dumpMapToSerial() {
Serial.println("Current state of the PID map:");
uint16_t updateIntervalForAllEntries;
bool areAllPidsAllowed =
pidMap.areAllPidsAllowed(&updateIntervalForAllEntries);
if (areAllPidsAllowed) {
Serial.println(" All PIDs are allowed.");
}
if (pidMap.isEmpty()) {
if (areAllPidsAllowed) {
Serial.println(" Map is empty.");
Serial.println("");
} else {
Serial.println(" No PIDs are allowed.");
Serial.println("");
}
return;
}
struct {
void operator() (void *entry) {
uint32_t pid = pidMap.getPid(entry);
const PidExtra *extra = pidMap.getExtra(entry);
Serial.print(" ");
Serial.print(pid);
Serial.print(" (0x");
Serial.print(pid, HEX);
Serial.print("), sending 1 out of ");
Serial.print(extra->updateRateDivider);
Serial.println(" messages.");
}
} dumpEntry;
pidMap.forEach(dumpEntry);
Serial.println("");
}
class UpdateMapOnRaceChronoCommands : public RaceChronoBleCanHandler {
public:
void allowAllPids(uint16_t updateIntervalMs) {
Serial.print("Command: ALLOW ALL PIDS, update interval: ");
Serial.print(updateIntervalMs);
Serial.println(" ms.");
pidMap.allowAllPids(updateIntervalMs);
dumpMapToSerial();
}
void denyAllPids() {
Serial.println("Command: DENY ALL PIDS.");
pidMap.reset();
dumpMapToSerial();
}
void allowPid(uint32_t pid, uint16_t updateIntervalMs) {
Serial.print("Command: ALLOW PID ");
Serial.print(pid);
Serial.print(" (0x");
Serial.print(pid, HEX);
Serial.print("), requested update interval: ");
Serial.print(updateIntervalMs);
Serial.println(" ms.");
if (!pidMap.allowOnePid(pid, updateIntervalMs)) {
Serial.println("WARNING: unable to handle this request!");
}
void *entry = pidMap.getEntryId(pid);
if (entry != nullptr) {
PidExtra *pidExtra = pidMap.getExtra(entry);
pidExtra->skippedUpdates = 0;
pidExtra->updateRateDivider = getUpdateRateDivider(pid);
}
dumpMapToSerial();
}
void handleDisconnect() {
Serial.println("Resetting the map.");
pidMap.reset();
dumpMapToSerial();
}
} raceChronoHandler;
void setup() {
uint32_t startTimeMs = millis();
Serial.begin(115200);
while (!Serial && millis() - startTimeMs < 5000) {
}
Serial.println("Setting up BLE...");
RaceChronoBle.setUp(DEVICE_NAME, &raceChronoHandler);
RaceChronoBle.startAdvertising();
Serial.println("BLE is set up, waiting for an incoming connection.");
waitForConnection();
}
void waitForConnection() {
uint32_t iteration = 0;
bool lastPrintHadNewline = false;
while (!RaceChronoBle.waitForConnection(1000)) {
Serial.print(".");
if ((++iteration) % 10 == 0) {
lastPrintHadNewline = true;
Serial.println();
} else {
lastPrintHadNewline = false;
}
}
if (!lastPrintHadNewline) {
Serial.println();
}
Serial.println("Connected.");
}
bool startCanBusReader() {
Serial.println("Connecting to the CAN bus...");
CAN.setClockFrequency(QUARTZ_CLOCK_FREQUENCY);
CAN.setSPIFrequency(SPI_FREQUENCY);
CAN.setPins(CS_PIN, IRQ_PIN);
boolean result = CAN.begin(BAUD_RATE);
if (!result) {
Serial.println("ERROR: Unable to start the CAN bus listener.");
return false;
}
Serial.println("Success!");
isCanBusReaderActive = true;
return true;
}
void stopCanBusReader() {
CAN.end();
isCanBusReaderActive = false;
}
void loop() {
loop_iteration++;
// First, verify that we have both Bluetooth and CAN up and running.
// Not clear how heavy is the isConnected() call. Only check the connectivity
// every 100 iterations to avoid stalling the CAN bus loop.
if ((loop_iteration % 100) == 0 && !RaceChronoBle.isConnected()) {
Serial.println("RaceChrono disconnected!");
raceChronoHandler.handleDisconnect();
stopCanBusReader();
Serial.println("Waiting for a new connection.");
waitForConnection();
sendNumCanBusTimeouts();
}
while (!isCanBusReaderActive) {
if (startCanBusReader()) {
flushBufferedPackets();
resetSkippedUpdatesCounters();
lastCanMessageReceivedMs = millis();
break;
}
delay(1000);
}
uint32_t timeNowMs = millis();
// I've observed that MCP2515 has a tendency to just stop responding for some
// weird reason. Just kill it if we don't have any data for 100 ms. The
// timeout might need to be tweaked for some cars.
if (timeNowMs - lastCanMessageReceivedMs > 100) {
Serial.println("ERROR: CAN bus timeout, aborting.");
num_can_bus_timeouts++;
sendNumCanBusTimeouts();
stopCanBusReader();
return;
}
// Sending data over Bluetooth takes time, and MCP2515 only has two buffers
// for received messages. Once a buffer is full, further messages are dropped.
// Instead of relying on the MCP2515 buffering, we aggressively read messages
// from the MCP2515 and put them into our memory.
//
// TODO: It might be more efficient to use interrupts to read data from
// MCP2515 as soon as it's available instead of polling all the time. The
// interrupts don't work out of the box with nRF52 Adafruit boards though, and
// need to be handled carefully wrt synchronization between the interrupt
// handling and processing of received data.
int packetSize;
while ((packetSize = CAN.parsePacket()) > 0) {
if (CAN.packetRtr()) {
// Ignore RTRs as they don't contain any data.
continue;
}
uint32_t pid = CAN.packetId();
uint8_t data[8];
int data_length = 0;
while (data_length < packetSize && data_length < sizeof(data)) {
int byte_read = CAN.read();
if (byte_read == -1) {
break;
}
data[data_length++] = byte_read;
}
if (data_length == 0) {
// Nothing to send here. Can this even happen?
continue;
}
bufferNewPacket(pid, data, data_length);
lastCanMessageReceivedMs = millis();
}
handleOneBufferedPacket();
if (millis() - last_time_num_can_bus_timeouts_sent_ms > 2000) {
sendNumCanBusTimeouts();
}
}
struct BufferedMessage {
uint32_t pid;
uint8_t data[8];
uint8_t length;
};
// Circular buffer to put received messages, used to buffer messages in memory
// (which is relatively abundant) instead of relying on the very limited
// buffering ability of the MCP2515.
const uint8_t NUM_BUFFERS = 16; // Must be a power of 2, but less than 256.
BufferedMessage buffers[NUM_BUFFERS];
uint8_t bufferToWriteTo = 0;
uint8_t bufferToReadFrom = 0;
void bufferNewPacket(uint32_t pid, uint8_t *data, uint8_t data_length) {
if (bufferToWriteTo - bufferToReadFrom == NUM_BUFFERS) {
Serial.println("WARNING: Receive buffer overflow, dropping one message.");
// In case of a buffer overflow, drop the oldest message in the buffer, as
// it's likely less useful than the newest one.
bufferToReadFrom++;
}
BufferedMessage *message = &buffers[bufferToWriteTo % NUM_BUFFERS];
message->pid = pid;
memcpy(message->data, data, data_length);
message->length = data_length;
bufferToWriteTo++;
}
void handleOneBufferedPacket() {
if (bufferToReadFrom == bufferToWriteTo) {
// No buffered messages.
return;
}
BufferedMessage *message = &buffers[bufferToReadFrom % NUM_BUFFERS];
uint32_t pid = message->pid;
void *entry = pidMap.getEntryId(pid);
if (entry != nullptr) {
// TODO: we could do something smart here. For example, if there are more
// messages pending with the same PID, we could count them towards
// |skippedUpdates| in a way that we only send the latest one, but maintain
// roughly the desired rate.
PidExtra *extra = pidMap.getExtra(entry);
if (extra->skippedUpdates == 0) {
RaceChronoBle.sendCanData(pid, message->data, message->length);
}
extra->skippedUpdates++;
if (extra->skippedUpdates >= extra->updateRateDivider) {
// The next message with this PID will be sent.
extra->skippedUpdates = 0;
}
}
bufferToReadFrom++;
}
void flushBufferedPackets() {
bufferToWriteTo = 0;
bufferToReadFrom = 0;
}
void sendNumCanBusTimeouts() {
// Send the count of timeouts to RaceChrono in a "fake" PID=0x777 message.
uint8_t data[2];
data[0] = num_can_bus_timeouts & 0xff;
data[1] = num_can_bus_timeouts >> 8;
RaceChronoBle.sendCanData(0x777, data, 2);
last_time_num_can_bus_timeouts_sent_ms = millis();
}
void resetSkippedUpdatesCounters() {
struct {
void operator() (void *entry) {
PidExtra *extra = pidMap.getExtra(entry);
extra->skippedUpdates = 0;
}
} resetSkippedUpdatesCounter;
pidMap.forEach(resetSkippedUpdatesCounter);
}