Merge branch 'master' of github.com:ZeusWPI/OBUS
This commit is contained in:
commit
d4485350f6
11 changed files with 284 additions and 181 deletions
|
@ -68,4 +68,6 @@ Some things we had to consider:
|
|||
|
||||
## Development setup
|
||||
|
||||
In the Arduino IDE, select the correct board (Arduino Nano) and processor (ATmega328P (Old Bootloader)).
|
||||
|
||||
We use [this](https://github.com/autowp/arduino-mcp2515/) library for CAN communications. See [this](https://github.com/autowp/arduino-mcp2515/#software-usage) header for 3 simple steps on how to use it in the arduino IDE
|
||||
|
|
|
@ -41,30 +41,32 @@ class Message:
|
|||
def parse_message(self):
|
||||
sender_type = self.sender_type()
|
||||
message_type = self.payload[0]
|
||||
if sender_type == 0b00: # controller
|
||||
if message_type == 0:
|
||||
return "ACK"
|
||||
elif message_type == 1:
|
||||
return "HELLO"
|
||||
elif message_type == 2:
|
||||
return "START " + self._parse_state_update()
|
||||
elif message_type == 3:
|
||||
return "STATE " + self._parse_state_update()
|
||||
elif message_type == 4:
|
||||
return "SOLVED " + self._parse_state_update()
|
||||
elif message_type == 5:
|
||||
return "TIMEOUT " + self._parse_state_update()
|
||||
elif message_type == 6:
|
||||
return "STRIKEOUT " + self._parse_state_update()
|
||||
elif sender_type == 0b01: # puzzle
|
||||
if message_type == 0:
|
||||
return "REGISTER"
|
||||
elif message_type == 1:
|
||||
return f"STRIKE {self.payload[1]}"
|
||||
elif message_type == 2:
|
||||
return f"SOLVED"
|
||||
else:
|
||||
return f"PARSE ERROR {self.received_from:011b} {self.payload.hex(' ')}"
|
||||
try:
|
||||
if sender_type == 0b00: # controller
|
||||
if message_type == 0:
|
||||
return "ACK"
|
||||
elif message_type == 1:
|
||||
return "HELLO"
|
||||
elif message_type == 2:
|
||||
return "START " + self._parse_state_update()
|
||||
elif message_type == 3:
|
||||
return "STATE " + self._parse_state_update()
|
||||
elif message_type == 4:
|
||||
return "SOLVED " + self._parse_state_update()
|
||||
elif message_type == 5:
|
||||
return "TIMEOUT " + self._parse_state_update()
|
||||
elif message_type == 6:
|
||||
return "STRIKEOUT " + self._parse_state_update()
|
||||
elif sender_type == 0b01: # puzzle
|
||||
if message_type == 0:
|
||||
return "REGISTER"
|
||||
elif message_type == 1:
|
||||
return f"STRIKE {self.payload[1]}"
|
||||
elif message_type == 2:
|
||||
return f"SOLVED"
|
||||
except:
|
||||
print("Unexpected error: ", sys.exc_info()[0])
|
||||
return "PARSE ERROR"
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
|
@ -105,4 +107,4 @@ def api():
|
|||
if __name__ == '__main__':
|
||||
thread = Thread(target=serial_reader, args=(shared_message_log, ))
|
||||
thread.start()
|
||||
app.run(debug=True, host='0.0.0.0')
|
||||
app.run(debug=False, host='0.0.0.0')
|
||||
|
|
|
@ -5,70 +5,103 @@
|
|||
<meta charset="utf-8">
|
||||
<title>CAN debugger</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.parsed {
|
||||
background: lightgreen;
|
||||
}
|
||||
|
||||
td.raw {
|
||||
font-family: monospace, monospace;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border-left: 1px solid black;
|
||||
border-right: 1px solid black;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #ff7f00;;
|
||||
border-bottom: 2px solid black;
|
||||
border-top: 2px solid black;
|
||||
height: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.message {
|
||||
display: flex;
|
||||
animation: fadein 1s;
|
||||
}
|
||||
.message > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 0 1.5ch 0 1.5ch;
|
||||
}
|
||||
.hide_details .message .pretty_raw_sender_id {
|
||||
display: none;
|
||||
|
||||
.fade {
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
|
||||
.hide_details .message .raw_message {
|
||||
display: none;
|
||||
table.hide_raw .raw {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.human_readable_type {
|
||||
order: -5;
|
||||
}
|
||||
.time {
|
||||
order: 5;
|
||||
}
|
||||
.sender_id {
|
||||
order: 1;
|
||||
tr:hover {
|
||||
background-color: #aaa;
|
||||
}
|
||||
|
||||
.parsed {
|
||||
order: 2;
|
||||
flex: 1;
|
||||
background: lightgreen;
|
||||
td.error, td.error > div {
|
||||
background-color: rgb(255, 71, 71);
|
||||
}
|
||||
|
||||
.pretty_raw_sender_id, .raw_message {
|
||||
font-family: monospace, monospace;
|
||||
td.controller > div {
|
||||
background-color: lightseagreen;
|
||||
}
|
||||
|
||||
.pretty_raw_sender_id {
|
||||
order: 9998;
|
||||
td.puzzle > div {
|
||||
background-color: gold;
|
||||
}
|
||||
|
||||
.raw_message {
|
||||
order: 9999;
|
||||
td.needy > div {
|
||||
background-color: rgb(128, 10, 128);
|
||||
}
|
||||
|
||||
.time, .raw_id, .sender_id {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.colorblock {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin: 3.4px;
|
||||
margin-right: 8.4px;
|
||||
display: inline-block;
|
||||
background-color:lime;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<input type="checkbox" id="show_raw" name="show_raw" checked onchange="updateShow()">
|
||||
<label for="show_raw">Show raw address and payload</label>
|
||||
<input type="checkbox" id="pause" name="pause">
|
||||
<label for="pause">Pause</label>
|
||||
</div>
|
||||
<div id="messages">
|
||||
<button onclick="toggle_logging()" id="toggle_button">Start</button>
|
||||
|
||||
</div>
|
||||
<input type="checkbox" id="show_raw" name="show_raw" checked autocomplete="off" onchange="updateShow()">
|
||||
<label for="show_raw">Show raw address and payload</label>
|
||||
|
||||
<table id="message_table">
|
||||
<tr>
|
||||
<th>Human-readable type</th>
|
||||
<th>Sender ID</th>
|
||||
<th>Parsed payload</th>
|
||||
<th>Time</th>
|
||||
<th class="raw">Raw Message</th>
|
||||
<th class="raw">Raw ID</th>
|
||||
</tr>
|
||||
</table>
|
||||
<script src="static/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
maxseen = 0;
|
||||
let maxseen = 0;
|
||||
let paused = true;
|
||||
let updaterID = null;
|
||||
|
||||
let color_classes = {
|
||||
"RESERVED TYPE": "error",
|
||||
"controller": "controller",
|
||||
"puzzle": "puzzle",
|
||||
"needy": "needy",
|
||||
}
|
||||
|
||||
function updateShow() {
|
||||
if (document.getElementById('show_raw').checked) {
|
||||
document.getElementById('messages').classList = '';
|
||||
} else {
|
||||
document.getElementById('messages').classList = 'hide_details';
|
||||
}
|
||||
document.getElementById("message_table").classList.toggle("hide_raw", !document.getElementById('show_raw').checked);
|
||||
}
|
||||
|
||||
function updateMessages() {
|
||||
|
@ -17,59 +22,65 @@ function updateMessages() {
|
|||
return;
|
||||
}
|
||||
response.json().then(function(data) {
|
||||
console.log(data);
|
||||
if (data.length > maxseen) {
|
||||
var messageContainer = document.getElementById('messages');
|
||||
for (let i = maxseen; i < data.length; i++) {
|
||||
var current = data[i];
|
||||
var time = document.createElement("p");
|
||||
time.innerHTML = current['time'];
|
||||
time.className = 'time';
|
||||
let messageTable = document.getElementById('message_table');
|
||||
|
||||
var parsed = document.createElement("p");
|
||||
parsed.innerHTML = current['parsed'];
|
||||
parsed.className = 'parsed';
|
||||
for (let i = maxseen; i < data.length; i++) {
|
||||
let row = messageTable.insertRow(1);
|
||||
row.classList.add("fade");
|
||||
let current = data[i];
|
||||
|
||||
var sender_id = document.createElement("p");
|
||||
sender_id.innerHTML = current['sender_id'];
|
||||
sender_id.className = 'sender_id';
|
||||
let human_readable_type = row.insertCell(0)
|
||||
let colorblock = document.createElement("div");
|
||||
colorblock.classList.add("colorblock");
|
||||
human_readable_type.append(colorblock);
|
||||
|
||||
var pretty_raw_sender_id = document.createElement("p");
|
||||
pretty_raw_sender_id.innerHTML = current['pretty_raw_sender_id'];
|
||||
pretty_raw_sender_id.className = 'pretty_raw_sender_id';
|
||||
human_readable_type.innerHTML += current['human_readable_type'];
|
||||
|
||||
var raw_message = document.createElement("p");
|
||||
raw_message.innerHTML = current['raw_message'];
|
||||
raw_message.className = 'raw_message';
|
||||
human_readable_type.classList.add(color_classes[current['human_readable_type']]);
|
||||
human_readable_type.classList.add('human_readable_type');
|
||||
|
||||
var human_readable_type = document.createElement("p");
|
||||
human_readable_type.innerHTML = current['human_readable_type'];
|
||||
human_readable_type.className = 'human_readable_type';
|
||||
|
||||
var newNode = document.createElement("div");
|
||||
newNode.className = "message";
|
||||
newNode.append(time, parsed, sender_id, pretty_raw_sender_id, raw_message, human_readable_type);
|
||||
messageContainer.prepend(newNode)
|
||||
let sender_id = row.insertCell(-1)
|
||||
sender_id.innerHTML = current['sender_id'];
|
||||
sender_id.classList.add('sender_id');
|
||||
let parsed = row.insertCell(-1)
|
||||
if (current['parsed'].startsWith("PARSE ERROR")) {
|
||||
parsed.classList.add("error");
|
||||
}
|
||||
maxseen = data.length;
|
||||
parsed.innerHTML = current['parsed'];
|
||||
parsed.classList.add('parsed');
|
||||
|
||||
let time = row.insertCell(-1)
|
||||
time.innerHTML = current['time'];
|
||||
time.classList.add('time');
|
||||
|
||||
let raw_message = row.insertCell(-1);
|
||||
raw_message.innerHTML = current['raw_message'];
|
||||
raw_message.classList.add("raw");
|
||||
raw_message.classList.add("raw_message");
|
||||
|
||||
let raw_id = row.insertCell(-1);
|
||||
raw_id.innerHTML = current['pretty_raw_sender_id'];
|
||||
raw_id.classList.add("raw");
|
||||
raw_id.classList.add("raw_id");
|
||||
}
|
||||
maxseen = data.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function toggle_logging() {
|
||||
if (paused) {
|
||||
paused = false;
|
||||
document.getElementById("toggle_button").innerHTML = "Pause";
|
||||
updaterID = setInterval(updateMessages, 1000);
|
||||
} else {
|
||||
paused = true;
|
||||
document.getElementById("toggle_button").innerHTML = "Start";
|
||||
clearInterval(updaterID);
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
updateShow()
|
||||
console.log("loaded");
|
||||
updateMessages();
|
||||
|
||||
|
||||
setInterval(function() {
|
||||
if (document.getElementById('pause').checked) {
|
||||
return;
|
||||
}
|
||||
updateMessages()
|
||||
|
||||
}, 1000);
|
||||
};
|
||||
window.onload = toggle_logging;
|
||||
|
|
|
@ -64,7 +64,12 @@ Types for controller:
|
|||
end time ↓ ↓ reserved
|
||||
#strikes #max strikes
|
||||
|
||||
- 7-255 reserved
|
||||
- 7 info start
|
||||
[ X B B B B B B B ]
|
||||
--------------
|
||||
reserved
|
||||
|
||||
- 8-255 reserved
|
||||
|
||||
- - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
|
|
@ -124,6 +124,10 @@ bool receive(struct message *msg) {
|
|||
break;
|
||||
|
||||
case OBUS_PAYLDTYPE_COUNT:
|
||||
if (receive_frame.can_dlc < 2) {
|
||||
Serial.println(F("W Received illegal count msg: payload <2"));
|
||||
return false;
|
||||
}
|
||||
msg->count = receive_frame.data[1];
|
||||
break;
|
||||
|
||||
|
@ -156,7 +160,8 @@ void send(struct message *msg) {
|
|||
uint8_t length = 1;
|
||||
send_frame.data[0] = msg->msg_type;
|
||||
|
||||
switch (payload_type(msg->from.type, msg->msg_type)) {
|
||||
uint8_t pyld_type = payload_type(msg->from.type, msg->msg_type);
|
||||
switch (pyld_type) {
|
||||
case OBUS_PAYLDTYPE_EMPTY:
|
||||
break;
|
||||
|
||||
|
@ -176,7 +181,8 @@ void send(struct message *msg) {
|
|||
break;
|
||||
|
||||
default:
|
||||
Serial.println(F("Unknown payload type"));
|
||||
Serial.print(F("E Unknown payload type "));
|
||||
Serial.println(pyld_type);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ inline void send_m_strike(struct module from, uint8_t count) {
|
|||
*/
|
||||
inline void send_m_solved(struct module from) {
|
||||
assert(from.type != OBUS_TYPE_CONTROLLER);
|
||||
struct message msg = _msg(from, false, OBUS_MSGTYPE_M_STRIKE);
|
||||
struct message msg = _msg(from, false, OBUS_MSGTYPE_M_SOLVED);
|
||||
send(&msg);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
#define STATE_INACTIVE 0
|
||||
#define STATE_HELLO 1
|
||||
#define STATE_GAME 2
|
||||
#define STATE_GAMEOVER 3
|
||||
|
||||
#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_GAME_DURATION 60 // Duration of the game in seconds
|
||||
|
||||
#define OBUS_MAX_MODULES 16
|
||||
#define OBUS_DISC_DURATION 5 // Duration of discovery round in seconds
|
||||
|
@ -27,13 +28,14 @@ uint8_t nr_connected_puzzles;
|
|||
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];
|
||||
// 8 bits per uint8 bitvector field
|
||||
#define N_UNSOLVED_PUZZLES DIVIDE_CEIL(MAX_AMOUNT_PUZZLES, 8)
|
||||
uint8_t unsolved_puzzles[N_UNSOLVED_PUZZLES];
|
||||
|
||||
// Timers
|
||||
uint32_t hello_round_start;
|
||||
uint32_t game_start;
|
||||
uint32_t last_draw;
|
||||
uint32_t last_update;
|
||||
|
||||
struct obus_can::module this_module = {
|
||||
|
@ -51,7 +53,7 @@ TM1638plus tm(STROBE_TM, CLOCK_TM , DIO_TM, HI_FREQ);
|
|||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
Serial.begin(19200);
|
||||
obus_can::init();
|
||||
|
||||
state = STATE_INACTIVE;
|
||||
|
@ -70,14 +72,14 @@ bool check_solved() {
|
|||
}
|
||||
|
||||
|
||||
void add_module_to_bit_vector(uint8_t module_id) {
|
||||
void add_puzzle_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) {
|
||||
void solve_puzzle_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);
|
||||
|
@ -104,8 +106,8 @@ void start_hello() {
|
|||
|
||||
uint16_t full_module_id(struct obus_can::module mod) {
|
||||
return \
|
||||
((uint16_t) mod.type << 8) | \
|
||||
(uint16_t) mod.id;
|
||||
(((uint16_t) mod.type) << 8) | \
|
||||
((uint16_t) mod.id);
|
||||
}
|
||||
|
||||
|
||||
|
@ -115,17 +117,23 @@ void receive_hello() {
|
|||
|
||||
if (obus_can::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) {
|
||||
Serial.print(F(" Registered module "));
|
||||
Serial.println(full_module_id(msg.from));
|
||||
|
||||
connected_modules_ids[nr_connected_modules] = msg.from;
|
||||
nr_connected_modules++;
|
||||
|
||||
if (msg.from.type == OBUS_TYPE_PUZZLE) {
|
||||
nr_connected_puzzles++;
|
||||
add_module_to_bit_vector(full_module_id(msg.from));
|
||||
add_puzzle_to_bit_vector(msg.from.id);
|
||||
}
|
||||
|
||||
char buffer[10];
|
||||
snprintf(buffer, 10, "%02d oF %02d", nr_connected_modules, OBUS_MAX_MODULES);
|
||||
tm.displayText(buffer);
|
||||
} else {
|
||||
Serial.println(F("W Max # modules reached"));
|
||||
}
|
||||
|
||||
obus_can::send_c_ack(this_module);
|
||||
|
@ -135,10 +143,12 @@ void receive_hello() {
|
|||
} else if (current_time - hello_round_start > OBUS_DISC_DURATION_MS) {
|
||||
if (nr_connected_puzzles == 0) {
|
||||
hello_round_start = current_time;
|
||||
Serial.println(" No puzzle modules found, restarting discovery round");
|
||||
obus_can::send_c_hello(this_module);
|
||||
Serial.println(F(" No puzzle modules, resend hello"));
|
||||
} else {
|
||||
Serial.println(F(" End of discovery round"));
|
||||
initialize_game();
|
||||
}
|
||||
Serial.println(" End of discovery round");
|
||||
initialize_game();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,11 +157,13 @@ void initialize_game() {
|
|||
strikes = 0;
|
||||
game_start = millis();
|
||||
|
||||
last_draw = 0;
|
||||
last_update = game_start;
|
||||
state = STATE_GAME;
|
||||
|
||||
Serial.println(" Game started");
|
||||
|
||||
draw_display(millis(), OBUS_GAME_DURATION_MS);
|
||||
obus_can::send_c_gamestart(this_module, OBUS_GAME_DURATION_MS, strikes, OBUS_MAX_STRIKES);
|
||||
}
|
||||
|
||||
|
@ -168,7 +180,7 @@ void receive_module_update() {
|
|||
break;
|
||||
|
||||
case OBUS_MSGTYPE_M_SOLVED:
|
||||
solve_module_in_bit_vector(full_module_id(msg.from));
|
||||
solve_puzzle_in_bit_vector(full_module_id(msg.from));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -181,6 +193,22 @@ void receive_module_update() {
|
|||
}
|
||||
|
||||
|
||||
void draw_display(uint32_t current_time, uint32_t time_left) {
|
||||
if (last_draw + 100 <= current_time) {
|
||||
// +25 to avoid rounding down when the loop runs early
|
||||
int totaldecisec = (time_left + 25) / 100;
|
||||
int decisec = totaldecisec % 10;
|
||||
int seconds = (totaldecisec / 10) % 60;
|
||||
int minutes = (totaldecisec / 10 / 60) % 60;
|
||||
int hours = totaldecisec / 10 / 60 / 60;
|
||||
char buffer[10];
|
||||
snprintf(buffer, 10, "%01dh%02d %02d.%01d", hours, minutes, seconds, decisec);
|
||||
tm.displayText(buffer);
|
||||
last_draw = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void game_loop() {
|
||||
uint32_t current_time = millis();
|
||||
uint32_t time_elapsed = current_time - game_start;
|
||||
|
@ -193,34 +221,36 @@ void game_loop() {
|
|||
if (check_solved()) {
|
||||
Serial.println(" Game solved");
|
||||
obus_can::send_c_solved(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
state = STATE_INACTIVE;
|
||||
state = STATE_GAMEOVER;
|
||||
tm.displayText("dISArmEd");
|
||||
return;
|
||||
}
|
||||
if (time_left == 0) {
|
||||
Serial.println(" Time's up");
|
||||
obus_can::send_c_timeout(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
state = STATE_INACTIVE;
|
||||
tm.displayText("boom");
|
||||
state = STATE_GAMEOVER;
|
||||
tm.displayText(" boo t");
|
||||
// m
|
||||
tm.display7Seg(4, 0b01010100);
|
||||
tm.display7Seg(5, 0b01000100);
|
||||
return;
|
||||
}
|
||||
if (strikes >= OBUS_MAX_STRIKES) {
|
||||
Serial.println(" Strikeout");
|
||||
obus_can::send_c_strikeout(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
state = STATE_INACTIVE;
|
||||
tm.displayText("boom");
|
||||
state = STATE_GAMEOVER;
|
||||
tm.displayText(" boo S");
|
||||
// m
|
||||
tm.display7Seg(4, 0b01010100);
|
||||
tm.display7Seg(5, 0b01000100);
|
||||
return;
|
||||
}
|
||||
|
||||
draw_display(current_time, time_left);
|
||||
|
||||
if (last_update + OBUS_UPDATE_INTERVAL <= current_time) {
|
||||
obus_can::send_c_state(this_module, time_left, strikes, OBUS_MAX_STRIKES);
|
||||
last_update = current_time;
|
||||
|
||||
int totalsec = (current_time + 100) / 1000;
|
||||
int minutes = totalsec / 60;
|
||||
char buffer[10];
|
||||
snprintf(buffer, 10, "%06d.%02d", minutes, totalsec % 60);
|
||||
tm.displayText(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,5 +268,8 @@ void loop() {
|
|||
case STATE_GAME:
|
||||
game_loop();
|
||||
break;
|
||||
|
||||
case STATE_GAMEOVER:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,7 @@ ezButton green_button(6);
|
|||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
// WARNING: do not use 255 for your module
|
||||
obus_module::setup(OBUS_TYPE_PUZZLE, 255);
|
||||
obus_module::setup(OBUS_TYPE_PUZZLE, OBUS_PUZZLE_ID_DEVELOPMENT);
|
||||
red_button.setDebounceTime(100);
|
||||
green_button.setDebounceTime(100);
|
||||
}
|
||||
|
|
|
@ -10,8 +10,7 @@ ezButton green_button(6);
|
|||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
// WARNING: do not use 255 for your module
|
||||
obus_module::setup(OBUS_TYPE_NEEDY, 255);
|
||||
obus_module::setup(OBUS_TYPE_NEEDY, OBUS_NEEDY_ID_DEVELOPMENT);
|
||||
green_button.setDebounceTime(100);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +1,70 @@
|
|||
#/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# "Bash strict mode", see http://redsymbol.net/articles/unofficial-bash-strict-mode/
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
print() { printf '%s' "$1"; }
|
||||
println() { printf '%s\n' "$1"; }
|
||||
|
||||
# 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")"
|
||||
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
|
||||
println "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
|
||||
print "Name of module (e.g. Oil gauge): "
|
||||
read module_name
|
||||
|
||||
# Determine a "clean" module name: lowercase, no spaces
|
||||
module="${module_name,,}"
|
||||
module="${module// /_}"
|
||||
module="${module//\'/}"
|
||||
# Determine a "clean" module name for paths: lowercase, no spaces
|
||||
module="`print "$module_name" | tr [A-Z] [a-z] | sed "s/ /_/g;s/'//g"`"
|
||||
|
||||
# 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
|
||||
if [ -e "$module_dir" ]; then
|
||||
println "$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
|
||||
print "How would you like to be credited? Your name: "
|
||||
read author
|
||||
|
||||
# Copy the template directory
|
||||
cp -r -T template_module "$module_dir"
|
||||
cd "$module_dir"
|
||||
cp -r -- template_module "$module_dir"
|
||||
cd -- "$module_dir"
|
||||
|
||||
# Disallow % in fields that will be used in %-delimited ed substitution
|
||||
assert_no_percent() {
|
||||
case "$1" in
|
||||
*"%"*) println "$2 must not contain %" >&2; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
assert_no_percent "$author" "Author name"
|
||||
assert_no_percent "$module_name" "Module name"
|
||||
assert_no_percent "$module" "Module path name"
|
||||
|
||||
# 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"
|
||||
# `sed -i` is not portable so we create something like it ourselves
|
||||
reced() {
|
||||
for file in "$1"/*; do
|
||||
if [ -f "$file" ]; then
|
||||
ed "$file" <<HERE
|
||||
%s/{YEAR}/$(date +%Y)/
|
||||
%s%{AUTHOR}%$author%
|
||||
%s%{MODULE_NAME}%$module_name%
|
||||
%s%{MODULE}%$module%
|
||||
wq
|
||||
HERE
|
||||
elif [ -d "$file" ]; then
|
||||
reced "$file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
reced .
|
||||
|
||||
echo "The basic structure for your module is now ready in $module_dir"
|
||||
# Arduino IDE requires .ino sketches to have the same name as their directory
|
||||
mv -- main.ino "$module.ino"
|
||||
|
||||
println "The basic structure for your module is now ready in $module_dir"
|
||||
|
|
Loading…
Reference in a new issue