Finish an obuscan abstraction

This commit is contained in:
Midgard 2020-08-20 17:47:32 +02:00
parent 71df9f71b9
commit 341ff1bb1e
Signed by: midgard
GPG key ID: 511C112F1331BBB4
5 changed files with 432 additions and 167 deletions

View file

@ -1,42 +1,40 @@
#include <mcp2515.h>
#include <assert.h>
#include "shared.hpp"
#include "obus_can.hpp"
#include "../shared/shared.hpp"
#define STATE_INACTIVE 0
#define STATE_HELLO 1
#define STATE_GAME 2
#define OBUS_GAME_DURATION 60 // Duration of the game in seconds
#define OBUS_MAX_STRIKEOUTS 3 // Number of strikeouts allowed until game over
#define OBUS_MAX_STRIKES 3 // Number of strikes allowed until game over
#define OBUS_GAME_DURATION 10 // Duration of the game in seconds
MCP2515 mcp2515(10);
#define OBUS_GAME_DURATION_MS ((uint32_t) OBUS_GAME_DURATION*1000)
uint8_t state = STATE_INACTIVE;
struct module connected_modules_ids[OBUS_MAX_MODULES];
uint8_t nr_connected_modules;
uint8_t strikeouts;
uint8_t strikes;
// Bit vectors for checking if game is solved or not
uint8_t unsolved_puzzles[32]; // 256 bits
// TIMERS
uint16_t hello_round_start;
uint16_t game_start;
uint16_t last_update;
uint32_t hello_round_start;
uint32_t game_start;
uint32_t last_update;
struct module this_module = (struct module) {
.type = OBUS_TYPE_CONTROLLER;
.id = OBUS_CONTROLLER_ID;
struct module this_module = {
.type = OBUS_TYPE_CONTROLLER,
.id = OBUS_CONTROLLER_ID
};
void setup() {
Serial.begin(9600);
mcp2515.reset();
mcp2515.setBitrate(CAN_50KBPS);
mcp2515.setNormalMode();
obuscan_init();
state = STATE_INACTIVE;
}
@ -44,7 +42,7 @@ void setup() {
uint8_t check_solved() {
uint8_t solved = 1;
for (int i; i<32; i++) {
for (uint8_t i = 0; i < 32; i++) {
if (unsolved_puzzles != 0) {
solved = 0;
break;
@ -68,68 +66,48 @@ void solve_module_in_bit_vector(uint8_t module_id) {
}
void send_message(uint8_t* message, uint8_t length) {
send_message(message, length, false);
}
void send_message(uint8_t* message, uint8_t length, bool priority) {
struct can_frame send_frame;
send_frame.can_id = encode_can_id(this_module, priority);
send_frame.can_dlc = length;
memcpy(send_frame.data, message, OBUS_MSG_LENGTH);
mcp2515.sendMessage(&send_frame);
}
void start_hello() {
state = STATE_HELLO;
hello_round_start = millis();
nr_connected_modules = 0;
// Zero bit vectors
for (int i; i<32; i++) {
for (uint8_t i = 0; i < 32; i++) {
unsolved_puzzles[i] = 0;
}
uint8_t message[OBUS_MSG_LENGTH];
message[0] = OBUS_MSGTYPE_C_HELLO;
struct obus_message msg = obuscan_msg_c_hello(this_module);
obuscan_send(&msg);
send_message(message, 1);
Serial.println("Start of discovery round");
Serial.println(F("Start of discovery round"));
}
void send_ack() {
uint8_t message[OBUS_MSG_LENGTH];
message[0] = OBUS_MSGTYPE_C_ACK;
send_message(message, 1);
struct obus_message msg = obuscan_msg_c_ack(this_module);
obuscan_send(&msg);
}
void receive_hello() {
struct can_frame receive_frame;
uint16_t current_time = millis();
struct obus_message msg;
uint32_t current_time = millis();
if (mcp2515.readMessage(&receive_frame) == MCP2515::ERROR_OK) {
if (receive_frame.data[0] == OBUS_MSGTYPE_M_HELLO) {
struct module new_module = decode_can_id(receive_frame.can_id);
if (obuscan_receive(&msg)) {
if (msg.msg_type == OBUS_MSGTYPE_M_HELLO) {
Serial.print("Registered module ");
Serial.println(full_module_id(new_module));
connected_modules_ids[nr_connected_modules] = new_module;
Serial.println(full_module_id(msg.from));
connected_modules_ids[nr_connected_modules] = msg.from;
nr_connected_modules++;
if (new_module.type == OBUS_TYPE_PUZZLE) {
add_module_to_bit_vector(full_module_id(new_module));
if (msg.from.type == OBUS_TYPE_PUZZLE) {
add_module_to_bit_vector(full_module_id(msg.from));
}
send_ack();
Serial.println("ACK");
}
} else if (current_time - hello_round_start > OBUS_DISC_DURATION * 1000) {
} else if (current_time - hello_round_start > OBUS_DISC_DURATION_MS) {
Serial.println("End of discovery round");
initialize_game();
}
@ -137,86 +115,87 @@ void receive_hello() {
void initialize_game() {
strikeouts = 0;
uint16_t game_duration_millis = (uint16_t) OBUS_GAME_DURATION * 1000;
uint8_t message[OBUS_MSG_LENGTH];
message[0] = OBUS_MSGTYPE_C_GAMESTART;
message[1] = (uint8_t) ((game_duration_millis & 0xFF000000) >> 0x18);
message[2] = (uint8_t) ((game_duration_millis & 0x00FF0000) >> 0x10);
message[3] = (uint8_t) ((game_duration_millis & 0x0000FF00) >> 0x08);
message[4] = (uint8_t) (game_duration_millis & 0x000000FF);
message[5] = strikeouts;
message[6] = OBUS_MAX_STRIKEOUTS;
send_message(message, 7);
strikes = 0;
game_start = millis();
last_update = game_start;
last_update = game_start;
state = STATE_GAME;
Serial.println("Game started");
send_game_update(OBUS_MSGTYPE_C_GAMESTART, OBUS_GAME_DURATION_MS);
}
void receive_module_update() {
struct can_frame receive_frame;
struct obus_message msg;
if (mcp2515.readMessage(&receive_frame) == MCP2515::ERROR_OK) {
if (receive_frame.data[0] == OBUS_MSGTYPE_M_STRIKE) {
strikeouts++;
} else if (receive_frame.data[0] == OBUS_MSGTYPE_M_SOLVED) {
uint16_t module_id = full_module_id(decode_can_id(receive_frame.can_id));
solve_module_in_bit_vector(module_id);
if (obuscan_receive(&msg)) {
switch (msg.msg_type) {
case OBUS_MSGTYPE_M_STRIKE:
// TODO check idempotency ID
strikes++;
break;
case OBUS_MSGTYPE_M_SOLVED:
solve_module_in_bit_vector(full_module_id(msg.from));
break;
default:
Serial.print(F("W Ignoring msg "));
Serial.println(msg.msg_type);
break;
}
}
}
void send_game_update(uint8_t status, uint16_t timestamp) {
uint8_t message[OBUS_MSG_LENGTH];
message[0] = status;
message[1] = (uint8_t) ((timestamp & 0xFF000000) >> 0x18);
message[2] = (uint8_t) ((timestamp & 0x00FF0000) >> 0x10);
message[3] = (uint8_t) ((timestamp & 0x0000FF00) >> 0x08);
message[4] = (uint8_t) (timestamp & 0x000000FF);
message[5] = strikeouts;
message[6] = OBUS_MAX_STRIKEOUTS;
void send_game_update(uint8_t msg_type, uint32_t time_left) {
Serial.print(F("Send "));
Serial.print(msg_type);
Serial.print(F(": "));
Serial.print(time_left);
Serial.print(F(", "));
Serial.print(strikes);
Serial.print(F("/"));
Serial.println(OBUS_MAX_STRIKES);
send_message(message, 7);
struct obus_message msg = obuscan_msg_c_payld_gamestatus(
this_module, false, msg_type, time_left, strikes, OBUS_MAX_STRIKES);
obuscan_send(&msg);
}
void game_loop() {
uint16_t current_time = millis();
uint16_t game_duration = current_time - game_start;
uint32_t current_time = millis();
uint32_t time_elapsed = current_time - game_start;
uint32_t time_left =
OBUS_GAME_DURATION_MS < time_elapsed ? 0 : OBUS_GAME_DURATION_MS - time_elapsed;
// We cannot check for '<= 0' in an uint type so we check the terms prior to subtraction
receive_module_update();
if (check_solved()) {
Serial.println("Game solved");
send_game_update(OBUS_MSGTYPE_C_SOLVED, game_duration);
send_game_update(OBUS_MSGTYPE_C_SOLVED, time_left);
state = STATE_INACTIVE;
return;
} else if (game_duration >= (uint16_t) OBUS_GAME_DURATION * 1000) {
} else if (time_left == 0) {
Serial.println("Time's up");
send_game_update(OBUS_MSGTYPE_C_TIMEOUT, game_duration);
send_game_update(OBUS_MSGTYPE_C_TIMEOUT, time_left);
state = STATE_INACTIVE;
return;
} else if (strikeouts >= OBUS_MAX_STRIKEOUTS) {
} else if (strikes >= OBUS_MAX_STRIKES) {
Serial.println("Strikeout");
send_game_update(OBUS_MSGTYPE_C_STRIKEOUT, game_duration);
send_game_update(OBUS_MSGTYPE_C_STRIKEOUT, time_left);
state = STATE_INACTIVE;
return;
}
uint16_t elapsed_time = current_time - last_update;
if (elapsed_time > OBUS_UPDATE_INTERVAL) {
Serial.print("Sending game update: ");
Serial.println(game_duration);
send_game_update(OBUS_MSGTYPE_C_STATE, (uint16_t) OBUS_GAME_DURATION * 1000 - game_duration);
if (last_update + OBUS_UPDATE_INTERVAL <= current_time) {
send_game_update(OBUS_MSGTYPE_C_STATE, time_left);
last_update = current_time;
}
}

259
src/controller/obus_can.cpp Normal file
View file

@ -0,0 +1,259 @@
#include <mcp2515.h>
#include <assert.h>
#include "obus_can.hpp"
MCP2515 mcp2515(10);
bool obuscan_is_init = false;
uint16_t _encode_can_id(struct module mod, bool priority) {
assert(mod.type <= 0b11);
/* b bb bbbbbbbb
* type module ID
* priority bit
*/
return \
((uint16_t) (priority ? CAN_DOMINANT : CAN_RECESSIVE) << 10) | \
((uint16_t) mod.type << 8) | \
(uint16_t) mod.id;
}
void _decode_can_id(uint16_t can_id, struct module *mod, bool *priority) {
*priority = ((can_id >> 10) & 1) == CAN_DOMINANT;
mod->type = (can_id >> 8) & 0b11;
mod->id = can_id & 0b11111111;
assert(mod->type <= 0b11);
}
void obuscan_init() {
obuscan_is_init = true;
mcp2515.reset();
mcp2515.setBitrate(CAN_50KBPS);
mcp2515.setNormalMode();
}
void obuscan_send(struct obus_message *msg) {
if (!obuscan_is_init) {
Serial.println(F("Call obuscan_init first"));
return;
}
struct can_frame send_frame;
memset(&send_frame.data, 0, CAN_MAX_DLEN);
uint8_t length = 1;
send_frame.data[0] = msg->msg_type;
switch (msg->payload_type) {
case OBUS_PAYLDTYPE_EMPTY: break;
case OBUS_PAYLDTYPE_GAMESTATUS:
send_frame.data[1] = (uint8_t) ((msg->gamestatus.time_left & 0xFF000000) >> 0x18);
send_frame.data[2] = (uint8_t) ((msg->gamestatus.time_left & 0x00FF0000) >> 0x10);
send_frame.data[3] = (uint8_t) ((msg->gamestatus.time_left & 0x0000FF00) >> 0x08);
send_frame.data[4] = (uint8_t) (msg->gamestatus.time_left & 0x000000FF);
send_frame.data[5] = msg->gamestatus.strikes;
send_frame.data[6] = msg->gamestatus.max_strikes;
length = 7;
}
send_frame.can_id = _encode_can_id(msg->from, msg->priority);
send_frame.can_dlc = length;
mcp2515.sendMessage(&send_frame);
}
bool obuscan_receive(struct obus_message *msg) {
if (!obuscan_is_init) {
Serial.println(F("Call obuscan_init first"));
return false;
}
struct can_frame receive_frame;
memset(&receive_frame.data, 0, CAN_MAX_DLEN);
MCP2515::ERROR status = mcp2515.readMessage(&receive_frame);
if (status != MCP2515::ERROR_OK) {
return false;
}
// Always at least OBUS message type required
if (receive_frame.can_dlc < 1) {
Serial.println(F("W Received illegal msg: payload <1"));
return false;
}
uint8_t msg_type = receive_frame.data[0];
uint8_t payload_type = -1;
_decode_can_id(receive_frame.can_id, &msg->from, &msg->priority);
// Controller messages
// TODO ifdef, ignore not for us and assume for us
if (msg->from.type == OBUS_TYPE_CONTROLLER) {
switch (msg_type) {
case OBUS_MSGTYPE_C_ACK: // fall-through
case OBUS_MSGTYPE_C_HELLO:
payload_type = OBUS_PAYLDTYPE_EMPTY;
break;
case OBUS_MSGTYPE_C_GAMESTART: // fall-through
case OBUS_MSGTYPE_C_STATE:
case OBUS_MSGTYPE_C_SOLVED:
case OBUS_MSGTYPE_C_TIMEOUT:
case OBUS_MSGTYPE_C_STRIKEOUT:
payload_type = OBUS_PAYLDTYPE_GAMESTATUS;
break;
default:
return false;
break;
}
// Module messages
} else {
switch (msg_type) {
case OBUS_MSGTYPE_M_STRIKE:
payload_type = OBUS_PAYLDTYPE_IDEMPOTENCY_ID;
break;
case OBUS_MSGTYPE_M_HELLO:
case OBUS_MSGTYPE_M_SOLVED:
payload_type = OBUS_PAYLDTYPE_EMPTY;
break;
default:
return false;
break;
}
}
switch (payload_type) {
case OBUS_PAYLDTYPE_EMPTY:
break;
case OBUS_PAYLDTYPE_GAMESTATUS:
if (receive_frame.can_dlc < 7) {
Serial.println(F("W Received illegal gamestatus msg: payload <7"));
return false;
}
msg->gamestatus.time_left =
((uint32_t) receive_frame.data[1] << 0x18) |
((uint32_t) receive_frame.data[2] << 0x10) |
((uint32_t) receive_frame.data[3] << 0x08) |
((uint32_t) receive_frame.data[4]);
msg->gamestatus.strikes = receive_frame.data[5];
msg->gamestatus.max_strikes = receive_frame.data[6];
break;
case OBUS_PAYLDTYPE_IDEMPOTENCY_ID:
msg->idempotency.id = receive_frame.data[1];
break;
default:
assert(false);
break;
}
msg->msg_type = msg_type;
msg->payload_type = payload_type;
return true;
}
inline struct obus_message _obuscan_msg(
struct module from, bool priority, uint8_t msg_type, uint8_t payload_type) {
struct obus_message msg;
msg.from = from;
msg.priority = priority;
msg.msg_type = msg_type;
msg.payload_type = payload_type;
return msg;
}
struct obus_message obuscan_msg_c_payld_gamestatus(
struct module from, bool priority, uint8_t msg_type,
uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
assert(from.type == OBUS_TYPE_CONTROLLER);
struct obus_message msg = _obuscan_msg(
from, priority, msg_type, OBUS_PAYLDTYPE_GAMESTATUS);
msg.gamestatus.time_left = time_left;
msg.gamestatus.strikes = strikes;
msg.gamestatus.max_strikes = max_strikes;
return msg;
}
struct obus_message obuscan_msg_c_ack(struct module from) {
assert(from.type == OBUS_TYPE_CONTROLLER);
return _obuscan_msg(from, false, OBUS_MSGTYPE_C_ACK, OBUS_PAYLDTYPE_EMPTY);
}
struct obus_message obuscan_msg_c_hello(struct module from) {
assert(from.type == OBUS_TYPE_CONTROLLER);
return _obuscan_msg(from, false, OBUS_MSGTYPE_C_HELLO, OBUS_PAYLDTYPE_EMPTY);
}
struct obus_message obuscan_msg_c_gamestart(
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
return obuscan_msg_c_payld_gamestatus(
from, false, OBUS_MSGTYPE_C_GAMESTART, time_left, strikes, max_strikes);
}
struct obus_message obuscan_msg_c_state(
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
return obuscan_msg_c_payld_gamestatus(
from, false, OBUS_MSGTYPE_C_STATE, time_left, strikes, max_strikes);
}
struct obus_message obuscan_msg_c_solved(
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
return obuscan_msg_c_payld_gamestatus(
from, false, OBUS_MSGTYPE_C_SOLVED, time_left, strikes, max_strikes);
}
struct obus_message obuscan_msg_c_timeout(
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
return obuscan_msg_c_payld_gamestatus(
from, false, OBUS_MSGTYPE_C_TIMEOUT, time_left, strikes, max_strikes);
}
struct obus_message obuscan_msg_c_strikeout(
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
return obuscan_msg_c_payld_gamestatus(
from, false, OBUS_MSGTYPE_C_STRIKEOUT, time_left, strikes, max_strikes);
}
struct obus_message obuscan_msg_m_hello(struct module from) {
assert(from.type != OBUS_TYPE_CONTROLLER);
return _obuscan_msg(from, false, OBUS_MSGTYPE_M_HELLO, OBUS_PAYLDTYPE_EMPTY);
}
struct obus_message obuscan_msg_m_strike(struct module from) {
assert(from.type != OBUS_TYPE_CONTROLLER);
return _obuscan_msg(from, false, OBUS_MSGTYPE_M_STRIKE, OBUS_PAYLDTYPE_EMPTY);
}
struct obus_message obuscan_msg_m_solved(struct module from) {
assert(from.type != OBUS_TYPE_CONTROLLER);
return _obuscan_msg(from, false, OBUS_MSGTYPE_M_STRIKE, OBUS_PAYLDTYPE_EMPTY);
}

View file

@ -0,0 +1,79 @@
#ifndef OBUS_CAN_H
#define OBUS_CAN_H
#define CAN_DOMINANT 0
#define CAN_RECESSIVE 1
#define OBUS_CONTROLLER_ID 0x000
#define OBUS_MSG_LENGTH 8 // Max 8 to fit in a CAN message
#define OBUS_TYPE_CONTROLLER 0
#define OBUS_TYPE_PUZZLE 1
#define OBUS_TYPE_NEEDY 2
#define OBUS_MSGTYPE_C_ACK 0
#define OBUS_MSGTYPE_C_HELLO 1
#define OBUS_MSGTYPE_C_GAMESTART 2
#define OBUS_MSGTYPE_C_STATE 3
#define OBUS_MSGTYPE_C_SOLVED 4
#define OBUS_MSGTYPE_C_TIMEOUT 5
#define OBUS_MSGTYPE_C_STRIKEOUT 6
#define OBUS_MSGTYPE_M_HELLO 0
#define OBUS_MSGTYPE_M_STRIKE 1
#define OBUS_MSGTYPE_M_SOLVED 2
#define OBUS_PAYLDTYPE_EMPTY 0
#define OBUS_PAYLDTYPE_GAMESTATUS 1
#define OBUS_PAYLDTYPE_IDEMPOTENCY_ID 2
struct module {
uint8_t type;
uint8_t id;
};
struct payld_empty {};
struct payld_gamestatus {
uint32_t time_left;
uint8_t strikes;
uint8_t max_strikes;
};
struct payld_idempotency {
uint8_t id;
};
struct obus_message {
struct module from;
bool priority;
uint8_t msg_type;
uint8_t payload_type;
union {
struct payld_empty empty;
struct payld_gamestatus gamestatus;
struct payld_idempotency idempotency;
};
};
void obuscan_init();
void obuscan_send(struct obus_message *msg);
bool obuscan_receive(struct obus_message *msg);
struct obus_message obuscan_msg_c_payld_gamestatus(
struct module from, bool priority, uint8_t msg_type,
uint32_t time_left, uint8_t strikes, uint8_t max_strikes);
struct obus_message obuscan_msg_c_ack(struct module from);
struct obus_message obuscan_msg_c_hello(struct module from);
struct obus_message obuscan_msg_c_gamestart(struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes);
struct obus_message obuscan_msg_c_state(struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes);
struct obus_message obuscan_msg_c_solved(struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes);
struct obus_message obuscan_msg_c_timeout(struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes);
struct obus_message obuscan_msg_c_strikeout(struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes);
struct obus_message obuscan_msg_m_hello(struct module from);
struct obus_message obuscan_msg_m_strike(struct module from);
struct obus_message obuscan_msg_m_solved(struct module from);
#endif /* end of include guard: OBUS_CAN_H */

18
src/controller/shared.hpp Normal file
View file

@ -0,0 +1,18 @@
#ifndef OBUS_SHARED_H
#define OBUS_SHARED_H
#include "obus_can.hpp"
#define OBUS_MAX_MODULES 16
#define OBUS_DISC_DURATION 5 // Duration of discovery round in seconds
#define OBUS_UPDATE_INTERVAL 500 // Number of milliseconds between game updates
#define OBUS_DISC_DURATION_MS ((uint32_t) OBUS_DISC_DURATION*1000)
uint16_t full_module_id(struct module mod) {
return \
((uint16_t) mod.type << 8) | \
(uint16_t) mod.id;
}
#endif /* end of include guard: OBUS_DEFS_H */

View file

@ -1,70 +0,0 @@
#ifndef OBUS_SHARED_H
#define OBUS_SHARED_H
#define OBUS_CONTROLLER_ID 0x000
#define OBUS_TYPE_CONTROLLER 0
#define OBUS_TYPE_PUZZLE 1
#define OBUS_TYPE_NEEDY 2
#define OBUS_MSG_LENGTH 8 // Max 8 to fit in a CAN message
#define OBUS_MAX_MODULES 16
#define OBUS_DISC_DURATION 5 // Duration of discovery round in seconds
#define OBUS_UPDATE_INTERVAL 500 // Number of milliseconds between game updates
#define OBUS_MSGTYPE_C_ACK 0
#define OBUS_MSGTYPE_C_HELLO 1
#define OBUS_MSGTYPE_C_GAMESTART 2
#define OBUS_MSGTYPE_C_STATE 3
#define OBUS_MSGTYPE_C_SOLVED 4
#define OBUS_MSGTYPE_C_TIMEOUT 5
#define OBUS_MSGTYPE_C_STRIKEOUT 6
#define OBUS_MSGTYPE_M_HELLO 0
#define OBUS_MSGTYPE_M_STRIKE 1
#define OBUS_MSGTYPE_M_SOLVED 2
#define CAN_DOMINANT 0
#define CAN_RECESSIVE 1
#define OBUS_MASK_PRIORITY = 0b10000000000
#define OBUS_MASK_TYPE = 0b01100000000
#define OBUS_MASK_ID = 0b00011111111
struct module {
uint8_t type;
uint8_t id;
};
uint16_t encode_can_id(struct module mod, bool priority) {
assert(mod.type <= 0b11);
/* b bb bbbbbbbb
* type module ID
* priority bit
*/
return \
((uint16_t) (priority ? CAN_DOMINANT : CAN_RECESSIVE) << 10) | \
((uint16_t) mod.type << 8) | \
(uint16_t) mod.id;
}
uint16_t full_module_id(struct module mod) {
return \
((uint16_t) mod.type << 8) | \
(uint16_t) mod.id;
}
struct module decode_can_id(uint16_t can_id) {
struct module mod;
mod.type = (can_id & OBUS_MASK_TYPE) >> 8;
mod.id = can_id & OBUS_MASK_ID;
assert(mod.type <= 0x11);
return mod;
}
#endif /* end of include guard: OBUS_DEFS_H */