Dear Future Reader,
This README was written to preserve the hashtag institutional knowledge of the technological geniuses in AEPi. It includes how to set up Raspberry Pis minimally for booth, installing TypeScript and related tools, and an explanation and rationale for our heavy use of Socket.io.
- Components
- Setting up your Pi
- Setting up TypeScript
- Developing for the Pi
- Socket.io
- Type Safety
- Misc tidbits and tips
The base booth tech kit comes with:
- Raspberry Pi
- Micro SD card
- Micro SD card reader
- Power supply
- Micro USB cable
You will need all of these things to continue.
If you were given a Pi that was already set up, you can skip this section.
If you are the one setting up the Pis, you might find this section helpful.
These instructions were written specifically for Mac.
- Download Etcher
- Download Raspbian (it may be faster to use the torrent download)
- If you don't need the desktop, I recommend going with lite.
- Put the Micro SD card into the reader
- Plug the reader into your computer
- Open Etcher
- Select the zip of Raspbian
- Select the reader as the storage device
- Flash!
- Plug a keyboard (via USB), mouse (via USB), screen (via HDMI), and the Micro SD card into your pi.
- The micro SD card should be taken out of the reader and put into the pie directly. The slot is on the bottom.
- If you are at CMU and plan to use the internet on the Pi, you'll need to register its MAC address
- Click Enter
- Click Register New Machine
- In the first dropdown, select Legacy Wireless Network and then click continue
- Type whatever you want for the hostname.
- On the Pi, open up the Terminal and type
ifconfig
- Type the MAC address (should look like AA:BB:CC:DD) into the Hardware address box
- Select Student Organization under affiliation
- Click continue
- In about 30 minutes, you should be able to access the internet from the Pi.
- Try to open chromium on your pi. If it doesn't let you access the internet after waiting for 30 minutes, follow this SO answer.
- Instead of internationalistation options, you'll want localisation options.
- Set up the Pi SSH and VNC servers
- Run
sudo rapsi-config
in a terminal window - Select
Interfacing Options
- Navigate to and select
SSH
- Choose
Yes
- Select
Ok
- Do the same for
VNC
- Choose
Finish
- Now you can ssh onto the pi by connecting to it via ethernet and
running
ssh [email protected]
. The password israspberry
by default. - You can also connect to the Pi with VNC. See
https://www.raspberrypi.org/documentation/remote-access/vnc/README.md
for more information.
- Run
- Set up typescript + node on the Pi
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
- Install node,
sudo apt-get install node
- Install TypeScript,
sudo npm install -g typescript
- Install nodemon,
sudo npm install -g nodemon
- Set sshfs for editing code
- On your laptop run sudo apt-get install sshfs
- On your laptop run mkdir pi
- log in with "sshfs pi@HOSTNAME:/home/pi pi" and type in the password
- When you enter the pi directory you will be able to see and edit the pi's files with whatever text editors you have installed on your laptop.
- Set up remote VSCode on your machine
- Open VSCode (see next section)
- Click the extensions icon (it looks like a square with a square inside it)
- Search Remote VSCode
- Click install
- On the installation page, it will have suggested user settings. Add these to your VSCode user settings.
- Add the following to your ~/.ssh/config:
HostName <pi_address> User pi ForwardAgent yes RemoteForward 52698 127.0.0.1:52698``` 7. Restart VSCode 8. `ssh` into your pi, navigate to the file you'd like to edit, and `rcode <filename>`. The file should open in VSCode.
You now have a linux machine on which you may begin developing.
TypeScript is a type-safe superset of JavaScript with a badass community and development environment. All of the booth programming can be done via ssh with remote VSCode, so you should follow these instructions on your local machine.
We use remote VSCode to ease the development process. I recommend connecting to the pi via ethernet, but you can also access it using its IP address. Your pi should also be connected to wifi so it can download external dependencies.
You'll want to use VS Code to edit TypeScript files. I know I know, you really wanted to use EDITORNAMEHERE
to work on booth,
but trust me, VS Code is great.
Since we set up remote VSCode, you should be able to open up the project on your pi inside VS Code. Any changes you make to these files will be reflected via ssh to your pi.
Within VS Code, open up a terminal window and ssh onto your pi (ssh pi@<pi name>.local
).
cd
into your project folder. You'll want to make sure that you install dependencies while on the Pi.
With this setup, you'll be able to use your local environment to develop while still getting to run your code on the pi!
Socket IO is essentially a distributed implementation of the observer design pattern. The basic premise is that instead of being responsible for repeatedly checking the state of a server, you can just set up "listeners" (which are just functions) that react to certain messages. An example of a message is the message that comes in when a user connects to the server. Something listening to that message may add that user to a list of active users, send the user a message, or continue listening to messages from that user.
Let's take a look at an example:
server.ts
import Express = require('express');
import Http = require('http');
import IO = require('socket.io');
var app = Express();
var http = new Http.Server(app);
var io = IO(http);
// When a user connects, wait for them to identify themselves. Print
// to the console when they disconnect.
// Note: 'connect' and 'disconnect' are built in messages that are automatically
// sent when a connection is created and disconnected, respectively
io.on('connect', function(socket: SocketIO.Socket){
var name: null | string = null;
console.log('a user connected: ' + socket.id);
socket.on('disconnect', function () {
if (name) console.log(name + ' disconnected');
else console.log(socket.id + ' disconnected');
});
// 'identification' is our own custom message. We expect a string to be supplied.
socket.on('identification', function (data: string) {
if (name === null) {
console.log('user ' + socket.id + ' identified as ' + data);
name = data;
}
});
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
This code doesn't just execute top down like you may expect. This code tells the server to continually listen for these messages. Specifically, always listen for a user to connect. When they do, always listen for them to identify themselves, and note when they disconnect. We do not need to put our code in a loop -- this code says to start listening and then proceeds on asynchronously.
The client to a server may look something like this:
client.ts
import Socket = require('socket.io-client');
import Readline = require('readline');
var socket = Socket('http://localhost:3000');
const rl = Readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('What is your name? ', (answer) => {
// Sends an identification message to the server.
socket.emit('identification', answer);
rl.close();
});
This client connects to the server, takes a name as input from STDIN, and tells the server the name. On the server side,
it received a 'connect'
message, since the client made a connection to the server, and an 'identification'
message that
the client explicitly sends.
Traditionally (by this, I of course mean the 2017 & 2018 golden years of booth game), the master server in the booth keeps track of
game state while a bunch of Rasperry Pis control several mini-games throughout the booth react to changes in the game state. Some Raspberry Pis will also send messages to the server, and are the driving force behind many of these game state changes. This kind
of design is extremely well-modeled by socket.io -- just listen for a gamestate changed
message and react accordingly; no need to
poll periodically! Similarly, to update the gamestate the server can just listen for updates and propagate those updates by broadcasting
a gamestate changed
message.
Sure, TypeScript is unsound by design (I'm more of a Flow guy myself), but that doesn't mean we can't use its type system to ease our development process. TypeScript allows us to do some real black magic: type safe messages between different machines. Process that for a second. Think about how ridiculously robust that would make an application.
Take a look at messages.ts. This file specifies all of the ways we use socket.io to send and receive messages.
All of these functions are just simple wrappers around the socket.on
function, which specifies the type of the message being sent
or received. As long as everyone only uses the type safe methods to communicate between machines, we can statically guarantee that
all of the different programs are sending messages whose types are expected. If you ever want to create a new message, you MUST add it
to messages.ts. Since it's in common, every sub-project can reference it, meaning all of the types are
synchronized across all of the sub-projects.
- You will not be able to use anything that requries Internet access at runtime-- we will only have access to each other's machines via our LAN network.
- The most important thing is robustness. Make sure your pi will not crash under any circumstances.