Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2010 The Android Open-Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License |
| 15 | */ |
| 16 | |
| 17 | /* |
| 18 | * Define the basic structure for messages from the host. |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 19 | * Messages are MESSAGE_SIZE bytes, with a 2-byte opcode, a 2-byte |
| 20 | * unique ID defined by the sender, and the remainder payload. |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 21 | * The remaining 2 bytes must be 0xFEEDFACE. This is used by |
| 22 | * the message handler as a tail sentinel to resync with the |
| 23 | * sender in case data is lost and the fixed-byte messages |
| 24 | * get out of sync. |
| 25 | */ |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 26 | #define MESSAGE_SIZE 128 |
| 27 | #define MESSAGE_DELIMITER 0xBEEF |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 28 | #define MESSAGE_ESCAPE 0x2a |
| 29 | struct message { |
| 30 | uint16_t opcode; |
| 31 | uint16_t id; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 32 | uint8_t data[MESSAGE_SIZE - 6]; |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 33 | uint16_t tail; |
| 34 | }; |
| 35 | struct message CURRENT_MESSAGE; |
| 36 | |
| 37 | #define OPCODE_RESET 0x00 |
| 38 | struct reset { |
| 39 | uint16_t opcode; |
| 40 | uint16_t id; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 41 | uint8_t unused[MESSAGE_SIZE - 4]; |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 42 | }; |
| 43 | |
| 44 | #define OPCODE_INIT_TIME (OPCODE_RESET + 1) |
| 45 | struct init_time { |
| 46 | uint16_t opcode; |
| 47 | uint16_t id; |
| 48 | uint32_t cur_raw_time; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 49 | uint8_t unused[MESSAGE_SIZE - 6]; |
| 50 | }; |
| 51 | |
| 52 | #define OPCODE_CURRENT_TIME (OPCODE_RESET + 2) |
| 53 | struct current_time { // we never actually use this, but here for consistency |
| 54 | uint16_t opcode; |
| 55 | uint16_t id; |
| 56 | uint8_t unused[MESSAGE_SIZE - 4]; |
| 57 | }; |
| 58 | |
| 59 | #define OPCODE_SETMODE_PONG (OPCODE_RESET + 3) |
| 60 | struct setmode_pong { |
| 61 | uint16_t opcode; |
| 62 | uint16_t id; |
| 63 | uint16_t playfield_width; |
| 64 | uint16_t playfield_height; |
| 65 | uint16_t paddle_width; |
| 66 | uint16_t paddle_offset; |
| 67 | uint16_t max_paddle_motion; |
| 68 | uint8_t unused[MESSAGE_SIZE - 14]; |
| 69 | }; |
| 70 | |
| 71 | #define OPCODE_PONG_BALL_STATE (OPCODE_RESET + 4) |
| 72 | struct pong_ball_state { |
| 73 | uint16_t opcode; |
| 74 | uint16_t id; |
| 75 | uint16_t ball_x; |
| 76 | uint16_t ball_y; |
| 77 | uint8_t unused[MESSAGE_SIZE - 8]; |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 78 | }; |
| 79 | |
| 80 | struct wall_time_struct { |
| 81 | uint32_t raw; // long == 4-byte on AVR |
| 82 | uint8_t initialized; |
| 83 | uint8_t hours; |
| 84 | uint8_t minutes; |
| 85 | uint8_t seconds; |
| 86 | }; |
| 87 | struct wall_time_struct WALL_TIME; |
| 88 | |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 89 | struct pong_state_struct { |
| 90 | uint16_t playfield_width; |
| 91 | uint16_t playfield_height; |
| 92 | uint16_t paddle_width; |
| 93 | uint16_t paddle_offset; |
| 94 | uint16_t max_paddle_motion; |
| 95 | uint16_t paddle_x; |
| 96 | uint16_t last_ball_x; |
| 97 | uint16_t last_ball_y; |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 98 | }; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 99 | struct pong_state_struct PONG_STATE; |
| 100 | |
| 101 | |
| 102 | void print_current_time() { |
| 103 | if (WALL_TIME.initialized) { |
| 104 | Serial.print("current_time="); |
| 105 | Serial.print(WALL_TIME.hours, DEC); |
| 106 | Serial.print(":"); |
| 107 | if (WALL_TIME.minutes < 10) |
| 108 | Serial.print("0"); |
| 109 | Serial.print(WALL_TIME.minutes, DEC); |
| 110 | Serial.print(":"); |
| 111 | if (WALL_TIME.seconds < 10) |
| 112 | Serial.print("0"); |
| 113 | Serial.println(WALL_TIME.seconds, DEC); |
| 114 | } else { |
| 115 | Serial.println("current_time=00:00:00"); |
| 116 | } |
| 117 | } |
| 118 | |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 119 | |
| 120 | void handle_current_message() { |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 121 | static uint16_t last_id; |
| 122 | static struct setmode_pong* setmode_pong_msg; |
| 123 | static struct pong_ball_state* pong_ball_state_msg; |
| 124 | static uint16_t paddle_half_width; |
| 125 | static uint16_t paddle_max; |
| 126 | static uint16_t danger; |
| 127 | static uint8_t invert; |
| 128 | static uint16_t delta; |
| 129 | |
| 130 | if (CURRENT_MESSAGE.id == 0 || CURRENT_MESSAGE.id == last_id) { |
| 131 | return; |
| 132 | } |
| 133 | last_id = CURRENT_MESSAGE.id; |
| 134 | |
| 135 | switch (CURRENT_MESSAGE.opcode) { |
| 136 | |
| 137 | case OPCODE_SETMODE_PONG: |
| 138 | memset(&PONG_STATE, 0, sizeof(PONG_STATE)); |
| 139 | setmode_pong_msg = (struct setmode_pong*)(&CURRENT_MESSAGE); |
| 140 | PONG_STATE.playfield_width = setmode_pong_msg->playfield_width; |
| 141 | PONG_STATE.playfield_height = setmode_pong_msg->playfield_height; |
| 142 | PONG_STATE.paddle_width = setmode_pong_msg->paddle_width; |
| 143 | PONG_STATE.paddle_offset = setmode_pong_msg->paddle_offset; |
| 144 | PONG_STATE.max_paddle_motion = setmode_pong_msg->max_paddle_motion; |
| 145 | |
| 146 | paddle_half_width = PONG_STATE.paddle_width / 2; |
| 147 | paddle_max = PONG_STATE.playfield_width - paddle_half_width; |
| 148 | |
| 149 | Serial.println("message_type=setmode_pong_ack"); |
| 150 | Serial.print("id="); |
| 151 | Serial.println(CURRENT_MESSAGE.id); |
| 152 | print_current_time(); |
| 153 | Serial.println(""); |
| 154 | break; |
| 155 | |
| 156 | case OPCODE_PONG_BALL_STATE: |
| 157 | pong_ball_state_msg = (struct pong_ball_state*)(&CURRENT_MESSAGE); |
| 158 | danger = pong_ball_state_msg->ball_x - PONG_STATE.paddle_x; |
| 159 | invert = (danger < 0); |
| 160 | danger *= invert ? -1 : 1; |
| 161 | if (danger < paddle_half_width) { |
| 162 | delta = 0; |
| 163 | } else if (danger < PONG_STATE.playfield_width / 3) { |
| 164 | delta = PONG_STATE.max_paddle_motion / 3; |
| 165 | } else if (danger < PONG_STATE.playfield_width * 2 / 3) { |
| 166 | delta = PONG_STATE.max_paddle_motion * 2 / 3; |
| 167 | } else { |
| 168 | delta = PONG_STATE.max_paddle_motion; |
| 169 | } |
| 170 | delta *= invert ? 1 : -1; |
| 171 | PONG_STATE.paddle_x += delta; |
| 172 | if (PONG_STATE.paddle_x < paddle_half_width) { |
| 173 | PONG_STATE.paddle_x = paddle_half_width; |
| 174 | } else if (PONG_STATE.paddle_x > paddle_max) { |
| 175 | PONG_STATE.paddle_x = paddle_max; |
| 176 | } |
| 177 | |
| 178 | Serial.println("message_type=pong_paddle_state"); |
| 179 | Serial.print("id="); |
| 180 | Serial.println(CURRENT_MESSAGE.id); |
| 181 | print_current_time(); |
| 182 | Serial.print("paddle_x="); |
| 183 | Serial.println(PONG_STATE.paddle_x); |
| 184 | Serial.println(""); |
| 185 | break; |
| 186 | |
| 187 | default: |
| 188 | break; |
| 189 | } |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 190 | } |
| 191 | |
| 192 | /* This is a temporary buffer used by the message handler */ |
| 193 | struct message_buffer { |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 194 | uint16_t count; // number of bytes read into the buffer |
| 195 | uint8_t buffer[MESSAGE_SIZE]; // contents of a 'struct message' |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 196 | }; |
| 197 | struct message_buffer MESSAGE_BUFFER; |
| 198 | |
| 199 | /* |
| 200 | * Clears all stateful values, including the wall clock time, current message |
| 201 | * data, and user/app state. Also clears the message handler's buffer. By |
| 202 | * "clear" we mean "memset to 0". |
| 203 | */ |
| 204 | void reset() { |
| 205 | memset(&WALL_TIME, 0, sizeof(WALL_TIME)); |
| 206 | memset(&CURRENT_MESSAGE, 0, sizeof(CURRENT_MESSAGE)); |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 207 | memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER)); |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 208 | memset(&PONG_STATE, 0, sizeof(PONG_STATE)); |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | |
| 212 | /* |
| 213 | * Pumps the message processor. That is, this function is intended to be |
| 214 | * called once per loop to read all pending Serial/TTL data, and decode a |
| 215 | * message from the peer if one is complete. In cases where data is corrupted |
| 216 | * (such as by dropping bytes), this function also attempts to re-sync with |
| 217 | * the host by discarding messages until it finds a MESSAGE_DELIMITER, after |
| 218 | * which is resyncs its buffer on the first subsequent byte. |
| 219 | * |
| 220 | * This functional also handles two low-level 'system' messages: a reset |
| 221 | * instruction which invokes reset(), and an init_time instruction which |
| 222 | * provides the soft clock with the current time so that it can start keeping |
| 223 | * time. |
| 224 | */ |
| 225 | void pump_message_processor() { |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 226 | static uint8_t cur_byte; |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 227 | static uint16_t* cur_word; |
| 228 | static int8_t delimiter_index; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 229 | static char buf[4]; |
| 230 | while (Serial.available() > 0) { // keep going as long as we might have messages |
| 231 | cur_byte = (uint8_t)(Serial.read() & 0x000000ff); |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 232 | MESSAGE_BUFFER.buffer[(MESSAGE_BUFFER.count)++] = cur_byte; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 233 | Serial.print("booga "); |
| 234 | Serial.print(itoa(MESSAGE_BUFFER.count, buf, 10)); |
| 235 | Serial.print(" "); |
| 236 | Serial.print(itoa(Serial.available(), buf, 10)); |
| 237 | Serial.print(" "); |
| 238 | Serial.println(itoa(cur_byte, buf, 10)); |
| 239 | if (MESSAGE_BUFFER.count >= MESSAGE_SIZE) { |
| 240 | if ((*(uint16_t*)(MESSAGE_BUFFER.buffer + MESSAGE_SIZE - 2)) != MESSAGE_DELIMITER) { |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 241 | // whoops, we got out of sync with the transmitter. Scan current |
| 242 | // buffer for the delimiter, discard previous message, and shift |
| 243 | // partial next message to front of buffer. This loses a message but |
| 244 | // gets us back in sync |
| 245 | delimiter_index = -2; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 246 | for (int i = MESSAGE_SIZE - 2; i >= 0; --i) { |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 247 | if (*((uint16_t*)(MESSAGE_BUFFER.buffer + i)) == MESSAGE_DELIMITER) { |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 248 | if (((i - 1) >= 0) && (MESSAGE_BUFFER.buffer[i - 1] != MESSAGE_ESCAPE)) { |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 249 | delimiter_index = i; |
| 250 | break; |
| 251 | } |
| 252 | } |
| 253 | } |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 254 | Serial.print("klaxon "); |
| 255 | Serial.println(itoa(delimiter_index, buf, 10)); |
| 256 | Serial.print("klaxon "); |
| 257 | Serial.println(itoa(*((uint16_t*)(MESSAGE_BUFFER.buffer + MESSAGE_SIZE - 2)), buf, 10)); |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 258 | MESSAGE_BUFFER.count = 0; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 259 | if (delimiter_index >= 0) { |
| 260 | for (int i = delimiter_index + 2; i < MESSAGE_SIZE; ++i, ++(MESSAGE_BUFFER.count)) { |
| 261 | MESSAGE_BUFFER.buffer[MESSAGE_BUFFER.count] = MESSAGE_BUFFER.buffer[i]; |
| 262 | } |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 263 | } |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 264 | memset(MESSAGE_BUFFER.buffer + MESSAGE_BUFFER.count, 0, MESSAGE_SIZE - MESSAGE_BUFFER.count); |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 265 | } else { |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 266 | memcpy(&CURRENT_MESSAGE, MESSAGE_BUFFER.buffer, MESSAGE_SIZE); |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 267 | memset(&MESSAGE_BUFFER, 0, sizeof(MESSAGE_BUFFER)); |
| 268 | switch (CURRENT_MESSAGE.opcode) { |
| 269 | case OPCODE_RESET: |
| 270 | reset(); |
| 271 | return; |
| 272 | |
| 273 | case OPCODE_INIT_TIME: |
| 274 | // cast CURRENT_MESSAGE to our time struct to conveniently fetch |
| 275 | // out the current time |
| 276 | WALL_TIME.raw = ((struct init_time*)(&CURRENT_MESSAGE))->cur_raw_time; |
| 277 | WALL_TIME.initialized = 1; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 278 | |
| 279 | Serial.println("message_type=init_time_ack"); |
| 280 | Serial.print("id="); |
| 281 | Serial.println(CURRENT_MESSAGE.id); |
| 282 | print_current_time(); |
| 283 | Serial.println(""); |
| 284 | |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 285 | CURRENT_MESSAGE.id = 0; |
| 286 | break; |
| 287 | |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 288 | case OPCODE_CURRENT_TIME: |
| 289 | Serial.println("message_type=current_time_ack"); |
| 290 | Serial.print("id="); |
| 291 | Serial.println(CURRENT_MESSAGE.id); |
| 292 | print_current_time(); |
| 293 | Serial.println(""); |
| 294 | |
| 295 | CURRENT_MESSAGE.id = 0; |
| 296 | |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 297 | default: |
| 298 | // no-op -- actually means main loop will handle it |
| 299 | break; |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | } |
| 304 | } |
| 305 | |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 306 | /* |
| 307 | * Pumps the system wall clock. This checks the device's monotonic clock to |
| 308 | * determine elapsed time since last invocation, and updates wall clock time |
| 309 | * by dead reckoning. Since the device has no battery backup, a power-off will |
| 310 | * lose the current time, so timekeeping cannot begin until an INIT_TIME |
| 311 | * message is received. (The pump_message_processor() function handles that.) |
| 312 | * |
| 313 | * Once timekeeping is underway, current time is exposed to user/app code via |
| 314 | * the WALL_TIME object, which has 24-hour HH/MM/SS fields. |
| 315 | */ |
| 316 | void pump_clock() { |
| 317 | static uint32_t prev_millis = 0; |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 318 | static uint32_t tmp_prev_millis = 0; |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 319 | uint32_t tmp = 0; |
| 320 | |
Dan Morrill | 7b12399 | 2010-10-20 12:22:44 -0700 | [diff] [blame^] | 321 | if (millis() / 1000 != tmp_prev_millis) { |
| 322 | tmp_prev_millis = millis() / 1000; |
| 323 | print_current_time(); |
| 324 | } |
| 325 | |
Dan Morrill | 8f1028f | 2010-09-12 12:02:59 -0700 | [diff] [blame] | 326 | if (WALL_TIME.initialized) { |
| 327 | tmp = millis() / 1000; |
| 328 | if (tmp != prev_millis) { |
| 329 | prev_millis = tmp; |
| 330 | WALL_TIME.raw++; |
| 331 | } |
| 332 | WALL_TIME.seconds = WALL_TIME.raw % 60; |
| 333 | WALL_TIME.minutes = (WALL_TIME.raw / 60) % 60; |
| 334 | WALL_TIME.hours = (WALL_TIME.raw / (60 * 60)) % 24; |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | |
| 339 | /* |
| 340 | * Standard Arduino setup hook. |
| 341 | */ |
| 342 | void setup() { |
| 343 | Serial.begin(115200); |
| 344 | } |
| 345 | |
| 346 | |
| 347 | /* |
| 348 | * Standard Arduino loop-pump hook. |
| 349 | */ |
| 350 | void loop() { |
| 351 | static uint16_t last_id = 0; |
| 352 | |
| 353 | // pump the clock and message processor |
| 354 | pump_clock(); |
| 355 | pump_message_processor(); |
| 356 | |
| 357 | // ignore any "system" messages (those with ID == 0) but dispatch app messages |
| 358 | if ((last_id != CURRENT_MESSAGE.id) && (CURRENT_MESSAGE.id != 0)) { |
| 359 | handle_current_message(); |
| 360 | } |
| 361 | last_id = CURRENT_MESSAGE.id; |
| 362 | } |