Created
September 18, 2019 23:11
-
-
Save uniphil/34e76c9d8c435fa07b96bde82c7079f2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "packetizer.h" | |
Packetizer::Packetizer(Stream *s) : stream(s) {} | |
int print_packet(Stream &s, String m, uint8_t mode=PACKET_NORMAL); | |
int send_packet(Stream &s, byte * message, size_t message_length, uint8_t mode=PACKET_NORMAL); | |
boolean Packetizer::might_have_something() { | |
return stream->available() > 0; | |
} | |
uint8_t get_sequence_length(byte * stuff, uint8_t bytes_left) { | |
uint8_t i = 0; | |
while (i < bytes_left) { | |
uint8_t b = ((i + 1) == bytes_left) ? 0x00 : stuff[i]; | |
if (b == 0x00) { | |
i += 1; | |
break; | |
} | |
i += 1; | |
} | |
return i; | |
} | |
int Packetizer::send(byte * message, size_t message_length, uint8_t mode=PACKET_NORMAL) { | |
if (message_length == 0 || message_length > 62) { | |
send("invalid packet length", PACKET_TX_ERR); | |
send_mode(String(message_length, DEC), PACKET_TX_ERR); | |
return -1; | |
} | |
uint8_t len = message_length + 1; | |
stream->write((byte)0x00); | |
stream->write(len | mode); | |
uint8_t sequence_start = 0; | |
while (true) { | |
uint8_t bytes_left = len - sequence_start; | |
uint8_t sequence_length = get_sequence_length(message + sequence_start, bytes_left); | |
stream->write(sequence_length); | |
stream->write(message + sequence_start, sequence_length - 1); | |
sequence_start += sequence_length; | |
if (sequence_start >= len) { | |
break; | |
} | |
} | |
} | |
int Packetizer::send(String message) { | |
return send(message.c_str(), message.length(), PACKET_NORMAL); | |
} | |
int Packetizer::send_mode(String message, uint8_t mode=PACKET_NORMAL) { | |
return send(message.c_str(), message.length(), mode); | |
} | |
int Packetizer::log(byte * message, size_t message_length) { | |
return send(message, message_length, PACKET_USER_LOG); | |
} | |
int Packetizer::log(String message) { | |
return send(message.c_str(), message.length(), PACKET_USER_LOG); | |
} | |
int Packetizer::receive(byte * out, uint8_t * out_length) { | |
bool packet_started = false; | |
byte next_byte; | |
*out_length = 0; | |
uint8_t out_index = 0; | |
uint8_t packet_mode; | |
while (*out_length == 0 || out_index < *out_length) { | |
stream->readBytes(&next_byte, 1); | |
if (next_byte == 0x00) { | |
if (packet_started) { | |
send_mode("Incomplete packet", PACKET_RX_ERR); | |
send(out, *out_length, PACKET_RX_ERR); | |
} | |
*out_length = 0; | |
out_index = 0; | |
packet_started = true; | |
continue; | |
} | |
if (!packet_started) { | |
send_mode("Unexpected byte", PACKET_RX_ERR); | |
send(&next_byte, 1, PACKET_RX_ERR); | |
continue; | |
} | |
if (*out_length == 0) { | |
packet_mode = next_byte & (0xFF << 6); | |
*out_length = next_byte & (0xFF >> 2); | |
continue; | |
} | |
out[out_index] = next_byte; | |
out_index += 1; | |
} | |
*out_length -= 1; | |
unstuff(out, *out_length); | |
} | |
int Packetizer::unstuff(byte * bytes, uint8_t out_length) { | |
uint8_t sequence_length, | |
i = 0; | |
while (i < out_length) { | |
sequence_length = bytes[i]; | |
while (sequence_length > 1) { | |
bytes[i] = bytes[i+1]; | |
i += 1; | |
sequence_length -= 1; | |
} | |
bytes[i] = 0x00; | |
i += 1; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Arduino.h> | |
#define PACKET_NORMAL 0b00 << 6 | |
#define PACKET_RX_ERR 0b01 << 6 | |
#define PACKET_TX_ERR 0b10 << 6 | |
#define PACKET_USER_LOG 0b11 << 6 | |
class Packetizer { | |
public: | |
Packetizer(Stream *s); | |
boolean | |
might_have_something(); | |
int | |
receive(byte * out, uint8_t * out_length), | |
send(byte * message, size_t message_length, uint8_t mode=PACKET_NORMAL), | |
send(String message), | |
log(byte * message, size_t message_length), | |
log(String message); | |
template < typename T > T &put(T &t) { | |
send((uint8_t*)&t, sizeof(T)); | |
return t; | |
} | |
private: | |
int | |
unstuff(byte * bytes, uint8_t out_length), | |
send_mode(String message, uint8_t mode=PACKET_NORMAL); | |
Stream | |
*stream; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <EEPROM.h> | |
#include "packetizer.h" | |
#define EXPECTED_TOKEN_TIME 26 // ms | |
#define ACCEPTABLE_ERROR 6 // ms | |
#define TEAM_SELECT_TIMEOUT 5000 // ms | |
#define FAKE_GND 9 | |
#define FAKE_VCC 11 | |
#define COIN_DETECT 2 | |
#define TEAMS 3 | |
#define TEAM_0_PIN 4 | |
#define TEAM_1_PIN 5 | |
#define TEAM_2_PIN 6 | |
class Team { | |
public: | |
Team(uint8_t pin): select_pin(pin), score_value(0), | |
last_score_time(0), manual_overrides(0) {} | |
void init() { | |
pinMode(select_pin, INPUT_PULLUP); | |
} | |
uint8_t select_pin; | |
unsigned long score_value; | |
unsigned long last_score_time; | |
unsigned long manual_overrides; | |
unsigned long score(unsigned long now, int inc = 1, bool override = false) { | |
score_value += inc; | |
if (override || inc != 1) { | |
manual_overrides += 1; | |
} | |
last_score_time = now; | |
return score_value; | |
}; | |
unsigned long dt(unsigned long now) { | |
return now - last_score_time; | |
}; | |
boolean pressed() { | |
return digitalRead(select_pin) == LOW; | |
}; | |
}; | |
uint8_t count = 0; | |
volatile boolean token_uncounted = false; | |
volatile uint8_t counts_lost = 0; | |
volatile unsigned long _token_entry; | |
volatile unsigned long _token_time; | |
uint8_t score_up_for_grabs = 0; | |
uint8_t last_team_selected = 0; | |
unsigned long last_team_select_time = 0; | |
Packetizer pk = Packetizer(&Serial); | |
Team teams[3] = { | |
Team(TEAM_0_PIN), | |
Team(TEAM_1_PIN), | |
Team(TEAM_2_PIN), | |
}; | |
void token_isr() { | |
unsigned long now = millis(); | |
boolean entering = digitalRead(COIN_DETECT) == 0; | |
if (entering) { | |
cli(); | |
_token_entry = now; | |
sei(); | |
if (token_uncounted) { | |
counts_lost++; | |
} | |
} else { | |
cli(); | |
_token_time = now - _token_entry; | |
sei(); | |
} | |
token_uncounted = !entering; | |
} | |
void get_packet(unsigned long now) { | |
byte rec[62]; | |
uint8_t len; | |
pk.receive(rec, &len); | |
if (len == 0) { | |
pk.log("received empty packet"); | |
return; | |
} | |
byte cmd = rec[0]; | |
switch (cmd) { | |
case 0x00: | |
pk.log("got init!"); | |
break; | |
case 0x01: | |
pk.log("got dump!"); | |
pk.log(&rec[1]); | |
break; | |
default: | |
pk.log("unhandled command"); | |
pk.log(String(cmd, HEX)); | |
pk.log(rec, len); | |
} | |
} | |
int get_last_team(unsigned long now) { | |
unsigned long dt_selected = now - last_team_select_time; | |
unsigned long dt_scored = teams[last_team_selected].dt(now); | |
if (min(dt_selected, dt_scored) > TEAM_SELECT_TIMEOUT) { | |
return -1; | |
} | |
return last_team_selected; | |
} | |
void check_new_token(unsigned long now) { | |
if (counts_lost > 0) { | |
counts_lost = 0; | |
pk.log("lost some counts"); | |
pk.log(String(counts_lost, DEC)); | |
} | |
if (!token_uncounted) { | |
return; | |
} | |
token_uncounted = false; | |
cli(); | |
unsigned long token_time = _token_time; | |
sei(); | |
long token_time_variance = token_time < EXPECTED_TOKEN_TIME | |
? EXPECTED_TOKEN_TIME - token_time | |
: token_time - EXPECTED_TOKEN_TIME; | |
if (token_time_variance > ACCEPTABLE_ERROR) { | |
pk.log("bad token?"); | |
pk.log(String(token_time_variance, DEC)); | |
return; | |
} | |
int last_team = get_last_team(now); | |
if (last_team == -1) { | |
score_up_for_grabs += 1; | |
} else { | |
teams[last_team].score(now); | |
} | |
} | |
void check_team_select(unsigned long now) { | |
for (size_t i = 0; i < TEAMS; i++) { | |
if (teams[i].pressed()) { | |
last_team_selected = i; | |
last_team_select_time = now; | |
} | |
} | |
} | |
void setup() { | |
Serial.begin(9600); | |
pinMode(FAKE_GND, OUTPUT); | |
digitalWrite(FAKE_GND, LOW); | |
pinMode(FAKE_VCC, OUTPUT); | |
digitalWrite(FAKE_VCC, HIGH); | |
pinMode(COIN_DETECT, INPUT_PULLUP); | |
for (size_t i = 0; i < TEAMS; i++) { | |
teams[i].init(); | |
} | |
attachInterrupt(digitalPinToInterrupt(COIN_DETECT), token_isr, CHANGE); | |
} | |
void loop() { | |
unsigned long now = millis(); | |
if (pk.might_have_something()) get_packet(now); | |
check_new_token(now); | |
check_team_select(now); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment