commit
247c4800bb
10 changed files with 748 additions and 49 deletions
20
README.md
20
README.md
|
@ -1,7 +1,15 @@
|
|||
# Ontmijnen van een Bom vereist Uitstekende Samenwerking
|
||||
|
||||
OBUS is a real-life version of the multiplayer game "Keep Talking And Nobody Explodes"
|
||||
OBUS is a real-life version of the multiplayer game "Keep Talking And Nobody Explodes".
|
||||
|
||||
# Get started writing a module
|
||||
|
||||
1. [Install](https://www.arduino.cc/en/Guide/#install-the-arduino-desktop-ide) the Arduino IDE.
|
||||
2. Clone this repository with Git in a permanent location on your drive.
|
||||
3. Symlink the library: `ln -s /ABSOLUTE/PATH/TO/REPO/lib /PATH/TO/Arduino/libraries/obus`
|
||||
4. TODO
|
||||
|
||||
# Background
|
||||
## Game
|
||||
|
||||
The game is played by at least two players. The goal is to defuse a bomb,
|
||||
|
@ -10,10 +18,10 @@ timer runs out.
|
|||
|
||||
There are two roles:
|
||||
|
||||
- Experts: this person can read the manual about how to defuse the bomb, but cannot see nor interact with the bomb
|
||||
- Expert: this person can read the manual about how to defuse the bomb, but cannot see nor interact with the bomb
|
||||
- Defuser: this person can see and interact with the bomb, but cannot read the manual
|
||||
|
||||
These two roles can communicate with each other. To succesfully defuse the bomb, they must
|
||||
These two roles can communicate with each other. To successfully defuse the bomb, they must
|
||||
communicate efficiently and clearly. If a mistake is made, the team gets a strike.
|
||||
If they get too many strikes, the bomb explodes, even if the timer hasn't run out yet.
|
||||
|
||||
|
@ -24,7 +32,7 @@ it should be easy to 1) make new modules and 2) attach them to a bomb. To do thi
|
|||
a protocol, both in hardware and in software.
|
||||
|
||||
The idea is to have one bomb controller that keeps track of the timer, the amount of strikes and of whether
|
||||
the bomb has been succesfully disarmed, and to have multiple modules that have one or more challenges on them
|
||||
the bomb has been successfully disarmed, and to have multiple modules that have one or more challenges on them
|
||||
that need to be solved.
|
||||
|
||||
### Hardware
|
||||
|
@ -48,9 +56,9 @@ The payload of a CAN packet is 8 bytes long, this should be enough.
|
|||
|
||||
## Software
|
||||
|
||||
We need to decide on a protocol to communicate between the bomb controller and the modules (and possibly also between modules?).
|
||||
We needed to decide on a protocol to communicate between the bomb controller and the modules (and possibly also between modules?).
|
||||
|
||||
Some things we'll need to consider:
|
||||
Some things we had to consider:
|
||||
|
||||
- payload is 8 bytes per packet
|
||||
- packets can be delayed or not received on every node, so detection of this and retransmission might be needed: if the bomb interactor solves a module and the packet that communicates this with the bomb does not get delivered to the controller, the bomb will still go off, even if all modules have been solved
|
||||
|
|
|
@ -21,8 +21,8 @@ b bb bbbbbbbb
|
|||
priority bit (so that each type can send priority messages if need be, 0 = high priority, 1 = low priority)
|
||||
|
||||
type:
|
||||
- 0 controller; module ID is ignored
|
||||
- 1 puzzle
|
||||
- 0 module-ID 0: controller, >0: info
|
||||
- 1 puzzel
|
||||
- 2 needy
|
||||
- 3 reserved for future use
|
||||
|
||||
|
@ -68,6 +68,15 @@ Types for controller:
|
|||
|
||||
- - - - - - - - - - - - - - - - - - -
|
||||
|
||||
Types for info:
|
||||
|
||||
These are managed by MOANA: the Modules of OBUS Authority for Numbers Assignment.
|
||||
You can reach MOANA in the ~obus Mattermost channel.
|
||||
|
||||
(none yet)
|
||||
|
||||
- - - - - - - - - - - - - - - - - - -
|
||||
|
||||
Types for modules:
|
||||
|
||||
- 0 register
|
||||
|
|
29
lib/module.cpp
Normal file
29
lib/module.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "obus_can.h"
|
||||
#include "module.h"
|
||||
|
||||
|
||||
struct module this_module;
|
||||
uint8_t strike_count;
|
||||
|
||||
|
||||
void obusmodule_setup(uint8_t type, uint8_t id) {
|
||||
this_module.type = type;
|
||||
this_module.id = id;
|
||||
|
||||
obuscan_init();
|
||||
|
||||
strike_count = 0;
|
||||
}
|
||||
|
||||
void obusmodule_loop() {
|
||||
|
||||
}
|
||||
|
||||
void obusmodule_strike() {
|
||||
strike_count++;
|
||||
obuscan_send_m_strike(this_module, strike_count);
|
||||
}
|
||||
|
||||
void obusmodule_solve() {
|
||||
obuscan_send_m_solved(this_module);
|
||||
}
|
7
lib/module.hpp
Normal file
7
lib/module.hpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
void obusmodule_setup(uint8_t type, uint8_t id);
|
||||
|
||||
void obusmodule_loop();
|
||||
|
||||
void obusmodule_strike();
|
||||
|
||||
void obusmodule_solve();
|
180
lib/obus_can.cpp
Normal file
180
lib/obus_can.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
#include <mcp2515.h>
|
||||
|
||||
#include "obus_can.h"
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
uint8_t obuscan_payload_type(uint8_t module_type, uint8_t msg_type) {
|
||||
if (module_type == OBUS_TYPE_CONTROLLER) {
|
||||
switch (msg_type) {
|
||||
case OBUS_MSGTYPE_C_ACK:
|
||||
case OBUS_MSGTYPE_C_HELLO:
|
||||
return OBUS_PAYLDTYPE_EMPTY;
|
||||
|
||||
case OBUS_MSGTYPE_C_GAMESTART:
|
||||
case OBUS_MSGTYPE_C_STATE:
|
||||
case OBUS_MSGTYPE_C_SOLVED:
|
||||
case OBUS_MSGTYPE_C_TIMEOUT:
|
||||
case OBUS_MSGTYPE_C_STRIKEOUT:
|
||||
return OBUS_PAYLDTYPE_GAMESTATUS;
|
||||
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Module messages
|
||||
} else {
|
||||
switch (msg_type) {
|
||||
case OBUS_MSGTYPE_M_STRIKE:
|
||||
return OBUS_PAYLDTYPE_COUNT;
|
||||
|
||||
case OBUS_MSGTYPE_M_HELLO:
|
||||
case OBUS_MSGTYPE_M_SOLVED:
|
||||
return OBUS_PAYLDTYPE_EMPTY;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void obuscan_init() {
|
||||
obuscan_is_init = true;
|
||||
mcp2515.reset();
|
||||
mcp2515.setBitrate(CAN_50KBPS);
|
||||
mcp2515.setNormalMode();
|
||||
}
|
||||
|
||||
|
||||
bool obuscan_receive(struct obus_message *msg) {
|
||||
if (!obuscan_is_init) {
|
||||
Serial.println(F("E 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];
|
||||
|
||||
struct module from;
|
||||
bool priority;
|
||||
_decode_can_id(receive_frame.can_id, &from, &priority);
|
||||
|
||||
// Controller messages
|
||||
switch (obuscan_payload_type(from.type, msg_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_COUNT:
|
||||
msg->count = receive_frame.data[1];
|
||||
break;
|
||||
|
||||
default:
|
||||
Serial.println(F("W Couldn't determine payload type"));
|
||||
return false;
|
||||
}
|
||||
|
||||
msg->from = from;
|
||||
msg->priority = priority;
|
||||
msg->msg_type = msg_type;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void obuscan_send(struct obus_message *msg) {
|
||||
if (!obuscan_is_init) {
|
||||
Serial.println(F("E 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 (obuscan_payload_type(msg->from.type, msg->msg_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;
|
||||
break;
|
||||
|
||||
case OBUS_PAYLDTYPE_COUNT:
|
||||
send_frame.data[1] = msg->count;
|
||||
length = 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
Serial.println(F("Unknown payload type"));
|
||||
return;
|
||||
}
|
||||
|
||||
send_frame.can_id = _encode_can_id(msg->from, msg->priority);
|
||||
send_frame.can_dlc = length;
|
||||
|
||||
mcp2515.sendMessage(&send_frame);
|
||||
}
|
226
lib/obus_can.h
Normal file
226
lib/obus_can.h
Normal file
|
@ -0,0 +1,226 @@
|
|||
#ifndef OBUS_CAN_H
|
||||
#define OBUS_CAN_H
|
||||
|
||||
#include <assert.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_COUNT 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 obus_message {
|
||||
struct module from;
|
||||
bool priority;
|
||||
uint8_t msg_type;
|
||||
union {
|
||||
struct payld_empty empty;
|
||||
struct payld_gamestatus gamestatus;
|
||||
uint8_t count;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine payload type for message
|
||||
*
|
||||
* @param module_type One of OBUS_TYPE_*
|
||||
* @param module_type One of OBUS_MSGTYPE_*
|
||||
*
|
||||
* @return One of OBUS_PAYLDTYPE_*
|
||||
*/
|
||||
uint8_t obuscan_payload_type(uint8_t module_type, uint8_t message_type);
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the CAN controller for OBUS messaging
|
||||
*/
|
||||
void obuscan_init();
|
||||
/**
|
||||
* Receive a message
|
||||
*
|
||||
* @param msg Pointer to memory where the received message will be wriitten
|
||||
* @return true if a message was received, false otherwise
|
||||
*/
|
||||
bool obuscan_receive(struct obus_message *msg);
|
||||
|
||||
/**
|
||||
* Lowlevel interface to send a message, you may want to use one of the helpers, like
|
||||
* obuscan_send_m_strike
|
||||
*
|
||||
* @param msg Pointer to a message to send
|
||||
*/
|
||||
void obuscan_send(struct obus_message *msg);
|
||||
|
||||
|
||||
/**
|
||||
* For internal use only
|
||||
*
|
||||
* Send an OBUS message
|
||||
*/
|
||||
inline struct obus_message _obuscan_msg(struct module from, bool priority, uint8_t msg_type) {
|
||||
|
||||
struct obus_message msg;
|
||||
msg.from = from;
|
||||
msg.priority = priority;
|
||||
msg.msg_type = msg_type;
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For internal use only
|
||||
*
|
||||
* Send a controller OBUS message with a gamestatus payload
|
||||
*/
|
||||
inline void _obuscan_send_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 msg = _obuscan_msg(from, priority, msg_type);
|
||||
msg.gamestatus.time_left = time_left;
|
||||
msg.gamestatus.strikes = strikes;
|
||||
msg.gamestatus.max_strikes = max_strikes;
|
||||
obuscan_send(&msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send a controller "ACK" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_c_ack(struct module from) {
|
||||
assert(from.type == OBUS_TYPE_CONTROLLER);
|
||||
struct obus_message msg = _obuscan_msg(from, false, OBUS_MSGTYPE_C_ACK);
|
||||
obuscan_send(&msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a controller "hello" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_c_hello(struct module from) {
|
||||
assert(from.type == OBUS_TYPE_CONTROLLER);
|
||||
struct obus_message msg = _obuscan_msg(from, false, OBUS_MSGTYPE_C_HELLO);
|
||||
obuscan_send(&msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a controller "game start" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_c_gamestart(
|
||||
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
|
||||
|
||||
assert(from.type == OBUS_TYPE_CONTROLLER);
|
||||
_obuscan_send_payld_gamestatus(
|
||||
from, false, OBUS_MSGTYPE_C_GAMESTART, time_left, strikes, max_strikes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a controller "state" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_c_state(
|
||||
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
|
||||
|
||||
assert(from.type == OBUS_TYPE_CONTROLLER);
|
||||
_obuscan_send_payld_gamestatus(
|
||||
from, false, OBUS_MSGTYPE_C_STATE, time_left, strikes, max_strikes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a controller "solved" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_c_solved(
|
||||
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
|
||||
|
||||
assert(from.type == OBUS_TYPE_CONTROLLER);
|
||||
_obuscan_send_payld_gamestatus(
|
||||
from, false, OBUS_MSGTYPE_C_SOLVED, time_left, strikes, max_strikes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a controller "timeout" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_c_timeout(
|
||||
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
|
||||
|
||||
assert(from.type == OBUS_TYPE_CONTROLLER);
|
||||
_obuscan_send_payld_gamestatus(
|
||||
from, false, OBUS_MSGTYPE_C_TIMEOUT, time_left, strikes, max_strikes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a controller "strikeout" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_c_strikeout(
|
||||
struct module from, uint32_t time_left, uint8_t strikes, uint8_t max_strikes) {
|
||||
|
||||
assert(from.type == OBUS_TYPE_CONTROLLER);
|
||||
_obuscan_send_payld_gamestatus(
|
||||
from, false, OBUS_MSGTYPE_C_STRIKEOUT, time_left, strikes, max_strikes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a module "hello" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_m_hello(struct module from) {
|
||||
assert(from.type != OBUS_TYPE_CONTROLLER);
|
||||
struct obus_message msg = _obuscan_msg(from, false, OBUS_MSGTYPE_M_HELLO);
|
||||
obuscan_send(&msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a module "strike" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_m_strike(struct module from, uint8_t count) {
|
||||
assert(from.type != OBUS_TYPE_CONTROLLER);
|
||||
struct obus_message msg = _obuscan_msg(from, false, OBUS_MSGTYPE_M_STRIKE);
|
||||
msg.count = count;
|
||||
obuscan_send(&msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a module "solved" OBUS message
|
||||
*/
|
||||
inline void obuscan_send_m_solved(struct module from) {
|
||||
assert(from.type != OBUS_TYPE_CONTROLLER);
|
||||
struct obus_message msg = _obuscan_msg(from, false, OBUS_MSGTYPE_M_STRIKE);
|
||||
obuscan_send(&msg);
|
||||
}
|
||||
|
||||
#endif /* end of include guard: OBUS_CAN_H */
|
|
@ -1,68 +1,213 @@
|
|||
#include <mcp2515.h>
|
||||
#include <obus_can.h>
|
||||
|
||||
|
||||
#define STATE_INACTIVE 0
|
||||
#define STATE_HELLO 1
|
||||
#define STATE_GAME 2
|
||||
|
||||
#define OBUS_CONTROLLER_ID 0x000
|
||||
#define OBUS_MAX_STRIKES 3 // Number of strikes allowed until game over
|
||||
#define OBUS_GAME_DURATION 10 // Duration of the game in seconds
|
||||
|
||||
#define OBUS_TYPE_CONTROLLER 0
|
||||
#define OBUS_TYPE_PUZZLE 1
|
||||
#define OBUS_TYPE_NEEDY 2
|
||||
#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_MSG_LENGTH 8 // Max 8 to fit in a CAN message
|
||||
#define OBUS_GAME_DURATION_MS ((uint32_t) OBUS_GAME_DURATION*1000)
|
||||
#define OBUS_DISC_DURATION_MS ((uint32_t) OBUS_DISC_DURATION*1000)
|
||||
|
||||
#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 DIVIDE_CEIL(dividend, divisor) ((dividend + (divisor - 1)) / divisor)
|
||||
#define MAX_AMOUNT_PUZZLES 256 // The ID of a puzzle is uint8
|
||||
|
||||
#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
|
||||
|
||||
MCP2515 mcp2515(10);
|
||||
|
||||
uint8_t state = STATE_INACTIVE;
|
||||
struct module connected_modules_ids[OBUS_MAX_MODULES];
|
||||
uint8_t nr_connected_modules;
|
||||
uint8_t strikes;
|
||||
|
||||
// Bitvector for checking if game is solved or not
|
||||
// 32 bits per uint32 bitvector field
|
||||
#define N_UNSOLVED_PUZZLES DIVIDE_CEIL(MAX_AMOUNT_PUZZLES, 32)
|
||||
uint32_t unsolved_puzzles[N_UNSOLVED_PUZZLES];
|
||||
|
||||
// Timers
|
||||
uint32_t hello_round_start;
|
||||
uint32_t game_start;
|
||||
uint32_t last_update;
|
||||
|
||||
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();
|
||||
Serial.println("begin");
|
||||
obuscan_init();
|
||||
|
||||
state = STATE_INACTIVE;
|
||||
}
|
||||
|
||||
uint16_t make_id(uint8_t id, bool priority, uint8_t type) {
|
||||
assert(type <= 0x11);
|
||||
|
||||
/* b bb bbbbbbbb
|
||||
* ↓ type module ID
|
||||
* priority bit
|
||||
*/
|
||||
return \
|
||||
((uint16_t) (priority ? CAN_DOMINANT : CAN_RECESSIVE) << 10) | \
|
||||
((uint16_t) type << 8) | \
|
||||
(uint16_t) id;
|
||||
bool check_solved() {
|
||||
for (uint8_t i = 0; i < N_UNSOLVED_PUZZLES; i++) {
|
||||
if (unsolved_puzzles[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void add_module_to_bit_vector(uint8_t module_id) {
|
||||
uint8_t byte_index = module_id >> 3;
|
||||
uint8_t bit_index = module_id & 0x07;
|
||||
unsolved_puzzles[byte_index] |= 0x1 << bit_index;
|
||||
}
|
||||
|
||||
|
||||
void solve_module_in_bit_vector(uint8_t module_id) {
|
||||
uint8_t byte_index = module_id >> 3;
|
||||
uint8_t bit_index = module_id & 0x07;
|
||||
unsolved_puzzles[byte_index] &= ~(0x1 << bit_index);
|
||||
}
|
||||
|
||||
|
||||
void start_hello() {
|
||||
state = STATE_HELLO;
|
||||
hello_round_start = millis();
|
||||
nr_connected_modules = 0;
|
||||
|
||||
char message[OBUS_MSG_LENGTH + 1];
|
||||
message[OBUS_MSG_LENGTH] = '\0';
|
||||
send_frame.can_id = make_id(OBUS_CONTROLLER_ID, false, OBUS_TYPE_CONTROLLER);
|
||||
send_frame.can_dlc = OBUS_MSG_LENGTH;
|
||||
mcp2515.sendMessage(&send_frame);
|
||||
Serial.println("sent");
|
||||
// Zero bit vectors
|
||||
for (uint8_t i = 0; i < N_UNSOLVED_PUZZLES; i++) {
|
||||
unsolved_puzzles[i] = 0;
|
||||
}
|
||||
|
||||
obuscan_send_c_hello(this_module);
|
||||
|
||||
Serial.println(F(" Start of discovery round"));
|
||||
}
|
||||
|
||||
|
||||
uint16_t full_module_id(struct module mod) {
|
||||
return \
|
||||
((uint16_t) mod.type << 8) | \
|
||||
(uint16_t) mod.id;
|
||||
}
|
||||
|
||||
|
||||
void receive_hello() {
|
||||
struct obus_message msg;
|
||||
uint32_t current_time = millis();
|
||||
|
||||
if (obuscan_receive(&msg)) {
|
||||
if (msg.msg_type == OBUS_MSGTYPE_M_HELLO) {
|
||||
Serial.print(" Registered module ");
|
||||
Serial.println(full_module_id(msg.from));
|
||||
|
||||
if (nr_connected_modules < OBUS_MAX_MODULES) {
|
||||
connected_modules_ids[nr_connected_modules] = msg.from;
|
||||
nr_connected_modules++;
|
||||
|
||||
if (msg.from.type == OBUS_TYPE_PUZZLE) {
|
||||
add_module_to_bit_vector(full_module_id(msg.from));
|
||||
}
|
||||
}
|
||||
|
||||
obuscan_send_c_ack(this_module);
|
||||
Serial.println(" ACK");
|
||||
}
|
||||
} else if (current_time - hello_round_start > OBUS_DISC_DURATION_MS) {
|
||||
Serial.println(" End of discovery round");
|
||||
initialize_game();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void initialize_game() {
|
||||
strikes = 0;
|
||||
game_start = millis();
|
||||
|
||||
last_update = game_start;
|
||||
state = STATE_GAME;
|
||||
|
||||
Serial.println(" Game started");
|
||||
|
||||
obuscan_send_c_gamestart(this_module, OBUS_GAME_DURATION_MS, strikes, OBUS_MAX_STRIKES);
|
||||
}
|
||||
|
||||
|
||||
void receive_module_update() {
|
||||
struct obus_message msg;
|
||||
|
||||
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 game_loop() {
|
||||
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");
|
||||
obuscan_send_c_solved(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
state = STATE_INACTIVE;
|
||||
return;
|
||||
}
|
||||
if (time_left == 0) {
|
||||
Serial.println(" Time's up");
|
||||
obuscan_send_c_timeout(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
state = STATE_INACTIVE;
|
||||
return;
|
||||
}
|
||||
if (strikes >= OBUS_MAX_STRIKES) {
|
||||
Serial.println(" Strikeout");
|
||||
obuscan_send_c_strikeout(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
state = STATE_INACTIVE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_update + OBUS_UPDATE_INTERVAL <= current_time) {
|
||||
obuscan_send_c_state(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
last_update = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
switch (state) {
|
||||
case STATE_INACTIVE:
|
||||
start_hello();
|
||||
break;
|
||||
|
||||
case STATE_HELLO:
|
||||
receive_hello();
|
||||
break;
|
||||
|
||||
case STATE_GAME:
|
||||
game_loop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
57
src/new_module.sh
Executable file
57
src/new_module.sh
Executable file
|
@ -0,0 +1,57 @@
|
|||
#/bin/bash
|
||||
|
||||
# "Bash strict mode", see http://redsymbol.net/articles/unofficial-bash-strict-mode/
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
# Go to the current working directory so things work if people are in a different one and e.g. use ../src/new_module.sh
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Make sure the template dir exists so we don't let people enter details unnecessarily
|
||||
if [ ! -d ./template_module ]; then
|
||||
echo "template_module doesn't exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ask for module name
|
||||
read -p "Name of module (e.g. Oil gauge): " module_name
|
||||
if [[ $module_name == *%* ]]; then
|
||||
echo "Module name must not contain %" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine a "clean" module name: lowercase, no spaces
|
||||
module="${module_name,,}"
|
||||
module="${module// /_}"
|
||||
module="${module//\'/}"
|
||||
|
||||
# Make sure `modules` directory exists and target directory doesn't
|
||||
mkdir -p modules
|
||||
module_dir="modules/$module"
|
||||
if [[ -e "$module_dir" ]]; then
|
||||
echo "$module_dir already exists" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ask for author name
|
||||
read -p "How would you like to be credited? Your name: " author
|
||||
if [[ $author == *%* ]]; then
|
||||
echo "Author name must not contain %" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy the template directory
|
||||
cp -r -T template_module "$module_dir"
|
||||
cd "$module_dir"
|
||||
|
||||
# Fill in the blanks in the template
|
||||
sed -i "
|
||||
s/{YEAR}/$(date +%Y)/
|
||||
s%{AUTHOR}%$author%
|
||||
s%{MODULE_NAME}%$module_name%
|
||||
s%{MODULE}%$module%
|
||||
" $(find -type f)
|
||||
# Arduino IDE requires .ino sketches to have the same name as their directory
|
||||
mv main.ino "$module.ino"
|
||||
|
||||
echo "The basic structure for your module is now ready in $module_dir"
|
19
src/template_module/doc/index.md
Normal file
19
src/template_module/doc/index.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
## {MODULE_NAME}
|
||||
|
||||
Write the defusing guide for your module here. For inspiration, look at the documentation of
|
||||
existing modules.
|
||||
|
||||
Use drawings! Put them in the same directory as this file and include them like this:
|
||||
|
||||
![](filename.pdf)
|
||||
|
||||
Use tables! Write them like this:
|
||||
|
||||
| Symbol | Action to take |
|
||||
|------------------|----------------|
|
||||
| ![](symbol1.pdf) | Do nothing |
|
||||
| ![](symbol2.pdf) | Press the red button |
|
||||
| ![](symbol3.pdf) | Press the yellow button |
|
||||
|
||||
### Credits
|
||||
Module developed by {AUTHOR}.
|
19
src/template_module/main.ino
Normal file
19
src/template_module/main.ino
Normal file
|
@ -0,0 +1,19 @@
|
|||
// (c) {YEAR}, {AUTHOR}
|
||||
// See the LICENSE file for conditions for copying
|
||||
|
||||
#include <obus_module.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
|
||||
// Choose one
|
||||
// Puzzle: a module that must be solved
|
||||
obusmodule_init(OBUS_TYPE_PUZZLE, /* Retrieve ID from MOANA */);
|
||||
// Needy: a module that periodically requires an action not to get strikes
|
||||
// obusmodule_init(OBUS_TYPE_NEEDY, /* Retrieve ID from MOANA */);
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
obusmodule_loop();
|
||||
}
|
Loading…
Reference in a new issue