-
Notifications
You must be signed in to change notification settings - Fork 0
Adding an IO Arduino
One of the killer features of the BOCS is that Arduinos controlling BOCS inputs and outputs can easily be added. Only a few basic requirements need to be satisfied before your new Arduino can simply be plugged into the Raspberry Pi running the BOCS. The requirements are fourfold:
- Perform a handshake by saying hello to the computer and accepting a hello in return
- Transmit input messages back to the Raspberry Pi encoded in a standard JSON format
- Accept string messages sent over serial to control any output
- Send a "ba-dump" message (a heartbeat) over serial to the computer at least once per 10 seconds
When the BOCS first starts up, it will enter a pairing mode with the Arduinos. The Raspberry Pi will repeatedly transmit Hello from computer until it receives the appropriate response from an Arduino. That response must be in the format Hello from <arduino_name>. The value for <arduino_name> will be what the BOCS uses to identify that Arduino when sending messages, so be sure you use the same name when listing your Arduino in io_states.py (more on that later).
Sending input messages back to the computer is quite straightforward. Simply send a JSON string over serial with an "event_id" and a "data" attribute. Here's an example (remove the newlines in your implementation -- they've just been added here for readability):
{
"event_id": "2",
"data": "3241"
}The value for event_id will be the event identifier. It will be used when writing the puzzle to identify which input was triggered when the event callback is fired. You can check which IDs are already being used by viewing the ArduinoCommEventType in raspi/available_io.py. Once you've settled on an unused ID, you should add an alias to yours in the ArduinoCommEventType class definition. This will help to make your puzzle code more readable later.
Messages sent from the Raspberry Pi to the Arduinos are not encoded as JSON due to the extremely slow JSON-decoding performance of an Arduino. Rather, messages begin with an identifier character followed by the message "payload" (a string encoding data in any format you care to parse).
The Raspberry Pi code also uses a layer of abstraction between the I/O devices and the raw text that is sent to the Arduinos. For an example, we can look at the KeypadState class definition, which controls the visibility of the numeric keypad.
from raspi.io_states.io_state import IOState
class KeypadState(IOState):
def __init__(self, visible=False, old_state_json=False):
IOState.__init__(self, old_state_json)
self._data['visible'] = visible
def set_visible(self, is_visible):
"""
Shows and hides the input to/from the player.
:param is_visible: True to make the input visible, False to hide it
"""
self._data['visible'] = is_visible
def get_arduino_message(self):
return 'k1' if self._data['visible'] else 'k0'An instance of this state object is what you would use when writing your puzzle (more on the details of that later). The key part, for this section, is the last bit: the definition of get_arduino_message(). Here, you can see that the Raspberry Pi will be transmitting one of two messages: k1 if the keypad should be visible or k0 if it should be hidden. Here, k is the identifier and 1 is the payload.
Due to the fact that the Arduinos register themselves by their name upon pairing, message identifiers must not be unique across all connected Arduinos. That is, multiple Arduinos can use k as a message identifier because only the intended Arduino will receive the message.
With the k message, the payload is quite simple: 1 for open or 0 for closed. For a more sophisticated payload example, take a look at the Trellis state. There, the payload encodes light configuration, display time, and whether or not the pattern should repeat. It's of the format light_enc1,time1;light_enc2,time2; and so on, where light_enc is the 16-bit binary encoding of which lights should be on and time is the time to keep the lights in that configuration. If there's an R at the end, the pattern repeats.
This is the easiest part. A l you need to do is transmit "ba-dump" (a heartbeat sound) over serial at least once every 10 seconds (every 3 seconds is a good number). If the computer doesn't receive a message within that timeframe, it will go back into handshake mode. (See the section below for an implementation example.)
For an example, here is the code running the keypad, Trellis, and telegraph inputs:
if (handshakeCompleted) {
// Check for messages from the computer and act on them, if appropriate (main logic here)
checkSerialForMessages();
// Heartbeat stuff
unsigned long curTime = millis();
if (curTime > nextBroadcastTime) {
Serial.println("ba-dump"); // Send heartbeat message
nextBroadcastTime += BROADCAST_INTERVAL;
}
if (curTime > expectedHeartbeatByTime && usingHandshake) {
// Haven't heard heartbeat from computer in too long
handshakeCompleted = false; // Go back into pairing mode
}
} else { // We have yet to establish a connection with the computer
if (Serial.available() > 0) { // The computer is responding
String msg = Serial.readString();
handshakeCompleted = msg.equals("Hello from computer\n");
Serial.println(handshakeCompleted ? "Connected" : "Not connected");
expectedHeartbeatByTime = millis() + HEARTBEAT_TIMEOUT;
} else if (millis() > nextBroadcastTime) { // Cry out for a computer
Serial.println("Hello from arduino_drawer");
nextBroadcastTime = millis() + BROADCAST_INTERVAL;
}
}