Skip to content

Instantly share code, notes, and snippets.

@uniphil
Created September 18, 2019 23:11
Show Gist options
  • Save uniphil/34e76c9d8c435fa07b96bde82c7079f2 to your computer and use it in GitHub Desktop.
Save uniphil/34e76c9d8c435fa07b96bde82c7079f2 to your computer and use it in GitHub Desktop.
#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;
}
}
#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;
};
#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