-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcommunication.h
220 lines (183 loc) · 6.61 KB
/
communication.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
//Communicate via Serial connection
#pragma once
#include <EEPROM.h>
#include <Arduino.h>
#include <ArduinoSTL.h>
//#include "storage.h"
#include <pb_encode.h>
#include <pb_decode.h>
namespace pb{
#include "iris.pb.h"
}
namespace freilite{
namespace iris{
namespace communication{
// Maximum size of nanopb's internal buffer
const size_t MAX_SIZE_PB_BUFFER = 300;
// Maximum ms to wait for the next message
// 2000 seems to be a safe number, 1000 was too low
const size_t RECEIVE_TIMEOUT = 2000;
using namespace pb;
// Print just like std::printf but from a string stored in program
// memory and with leading EOT, see iris.proto for details
// Should always be used instead of std::printf
int printf(const __FlashStringHelper* format, ... ){
// Convert back from pseudo-class to pointer to program memory
const char* flash_string_pgm_ptr = reinterpret_cast<const char*>(format);
size_t string_length = strlen_P(flash_string_pgm_ptr);
// Initialise buffer
std::string buffer = std::string(string_length + 1, '\0');
// Write leading EOT to buffer
buffer[0] = '\x04';
// Load string from progmem to buffer
for(int i = 0; i < string_length; ++i){
buffer[i+1] = pgm_read_byte(flash_string_pgm_ptr + i);
}
// Execute stl implementation of printf
va_list arglist;
va_start(arglist, format);
int num_written = std::vprintf(buffer.c_str(), arglist);
va_end(arglist);
return num_written;
}
namespace {
// Read protobuf data into buffer from serial connection
static bool read_callback(pb_istream_t* stream,
uint8_t* buf,
size_t count){
int bytes_read = 0;
for(;bytes_read < count; ++bytes_read){
if(!SerialUSB.available()){
// Reached end of data
buf[bytes_read] = 0;
stream->bytes_left = 0;
} else {
// Read data into buffer
buf[bytes_read] = SerialUSB.read();
}
}
return count == bytes_read;
}
// Decoder for protobuf messages
pb_istream_t pb_serial_in = {&read_callback,
nullptr,
MAX_SIZE_PB_BUFFER};
// Write protobuf data onto serial connection
static bool write_callback(pb_ostream_t* stream,
const uint8_t* buf,
size_t count){
return SerialUSB.write(buf, count) == count;
}
// Encoder for protobuf messages
pb_ostream_t pb_serial_out = {&write_callback,
nullptr,
MAX_SIZE_PB_BUFFER};
}
MessageData receive_message(){
MessageData message = MessageData_init_default;
pb_decode(&pb_serial_in,
MessageData_fields,
&message);
return message;
}
void send_message(const MessageData& message){
pb_encode(&pb_serial_out,
MessageData_fields,
&message);
}
void send_message(MessageData_Signal signal){
MessageData message_data = MessageData_init_default;
message_data.which_content = MessageData_signal_tag;
message_data.content.signal = signal;
send_message(message_data);
}
void send_message(const pb::Cue& cue){
MessageData message_data = MessageData_init_default;
message_data.which_content = MessageData_cue_tag;
message_data.content.cue = cue;
send_message(message_data);
}
void send_message(const pb::Schedule& schedule){
MessageData message_data = MessageData_init_default;
message_data.which_content = MessageData_schedule_tag;
message_data.content.schedule = schedule;
send_message(message_data);
}
// Check if message is incoming
// Blocks for no more than RECEIVE_TIMEOUT milliseconds
bool message_incoming(){
const size_t TIMESTEP = 3;
unsigned long milliseconds = 0;
while(!SerialUSB.available()){
delay(TIMESTEP);
milliseconds += TIMESTEP;
if(milliseconds > RECEIVE_TIMEOUT){
printf(F("Timeout reached: %ims"), milliseconds);
return false;
}
}
return true;
}
// Wait for the next signal and return true if it's
bool next_requested(){
if(!message_incoming()){
return false;
}
return receive_message().content.signal ==
MessageData_Signal_RequestNext;
}
void handle_info(){
printf(F("Communication works!"));
}
void handle_download_configuration(){
// Send cues
size_t num_cues = Cues::count();
for(int i = 0; i < num_cues; ++i){
send_message(Cues::get(i).as_pb_cue());
if(!next_requested()){
printf(F("Did not receive RequestNext."));
return;
}
}
// Send schedules
size_t num_schedules = Schedules::count();
for(int i = 0; i < num_schedules; ++i){
send_message(Schedule(i).as_pb_schedule());
if(!next_requested()){
printf(F("Did not receive RequestNext."));
return;
}
}
// Send Confirm signal to indicate end of transmission
send_message(MessageData_Signal_Confirm);
}
// Handle I/O
void handle_serial_io(){
// Don't do anything if there are no incoming requests
if(!SerialUSB.available()){
return;
}
MessageData request = receive_message();
if(request.which_content != MessageData_signal_tag){
send_message(MessageData_Signal_Error);
return;
}
switch(request.content.signal){
case MessageData_Signal_RequestInfo:
handle_info();
return;
case MessageData_Signal_DownloadConfiguration:
handle_download_configuration();
return;
// Confirmations are always okay
case MessageData_Signal_Confirm:
send_message(MessageData_Signal_Confirm);
return;
default:
printf(F("Signal unexpected, unknown, or not implemented."));
return;
}
}
}
}
}