#+TITLE: Notes on Interfaces and Applications
#+AUTHOR:Dylan Holmes
#+OPTIONS: toc:nil num:1
#+HTML:
#+BEGIN_SRC
ffmpeg -i dxh_websockets_switch_large.mp4 -vf scale=400:-2 -vcodec libx264 -crf 20 dxh_websockets_switch.mp4
#+END_SRC
This week, I set up a web-server-based light switch: On a desktop
computer, you activate a web server and webpage with a button; when
you click the button on the page, it sends a message to the server,
which sends a message to a board over a serial FTDI connection, which
makes the board toggle its LED.
* Installing nodejs
For this project, I had to install nodejs (for running Javascript as a
standalone programming environment) and some nodejs packages,
including =serialport= (open serial ports), =express@4.15.2= (host web
server), =http= (serve http), =readline= (keyboard interaction), =ws=
(web sockets) and express-ws (integrate ws and express). I installed
these packages using the nodejs package manager (=npm=).
#+BEGIN_SRC
npm install serialport, express@4.15.2, http, readline, ws, express-ws
#+END_SRC
* Example nodejs programs
Here are some useful helper subroutines I've written. Using nodejs, you
can:
1. Send keypresses over serial (just like Neil's =pyserial= program or =picocom=)
2. Serve a hardcoded string of html as a website.
3. Serve a file as a website.
First I set some global parameters for serial and http; details on
your machine (e.g. the value of =serial_port=) may differ:
#+BEGIN_SRC javascript
const server_port = 8081
const client_address = '127.0.0.1'
const serial_port = "/dev/ttyUSB0"
var baud = 115200
#+END_SRC
Next, I define the relevant subroutines.
** You can send keypresses over serial
(This is just what Neil's =pyserial= or =picocom= program does; you
can try it out with an echo hello world board.)
#+BEGIN_SRC javascript
example_terminal = function() {
// Create a terminal for speaking with a pcb board over serial.
// Performs the same function as term.py (from class) or picocom.
// Set up serial connection
const SerialPort = require('serialport')
const Readline = require('@serialport/parser-readline')
const port = new SerialPort(serial_port,
{baudRate: baud,
databits:8,
dtr: true,
})
// Serial-opening errors will be emitted as an error event
port.on('error', function(err) {
console.log('Error: ', err.message)
})
// Whenever serial data is received, interpret the data as
// keycodes and print as a string.
port.on('data', function(data) {
console.log(data.toString('utf8'))
})
// Listen to keyboard in nodejs terminal
const readline = require('readline')
readline.emitKeypressEvents(process.stdin)
process.stdin.setRawMode(true)
console.log("Ready for input: ")
process.stdin.on('keypress', (str, key) => {
// Ctrl-c exits the terminal
if(key.ctrl && key.name === 'c') process.exit()
var debug = false
if (debug) console.log("You pressed the '",str,"' key.", key)
port.write(key.sequence)
})
};
example_terminal();
#+END_SRC
** You can serve html from a hardcoded string or a file
Run either of these two functions, then visit localhost:8081 to see
the webpage. (If you change =server_port= from 8081 to another value,
the page will be hosted on a different port.)
#+BEGIN_SRC javascript
example_http_server_1 = function() {
// Create an http server on http://localhost:8081/.
// Serve a one-line hardcoded string as an HTML page.
var app = require('express')()
var http = require('http').Server(app)
app.get('/', function(req, res){
res.send('
Hello world
')
})
http.listen(server_port, function(){
console.log('listening on *:'+(server_port).toString());
})
};
example_http_server_2 = function(html_file='/example_page.html') {
// Create an http server on http://localhost:8081/.
// Serve a file as an HTML page.
var app = require('express')()
var http = require('http').Server(app)
app.get('/', function(req, res){
res.sendFile(__dirname + html_file)
});
http.listen(server_port, function(){
console.log('listening on *:'+(server_port).toString())
});
};
#+END_SRC
The second http server serves the simple two-line file named =example_page.html=:
#+BEGIN_SRC web
I am an example page.
I'm an html file located at example_page.html.
#+END_SRC
* Web-enabled serial commands
** You can set up an echo server through websockets
/Web sockets/ are a protocol which allow servers to send commands to
clients and receive them in return. You can use them to make websites
that dynamically update, for example, or that allow users to send chat
messages to each other.
We'll use nodejs to open a web socket that will listen for
messages. We'll also design a webpage that connects to the opened
websocket and allows you to send messages to it. The code for each is
pretty simple.
Here's nodejs code for running a server. If you run it, the page
=http://localhost:8081= will show an html messsage saying that the
echo server is running. More importantly, it will open a web socket at
=http://localhost:8081/echo= that webpages can connect and send
messages to.
#+BEGIN_SRC javascript
example_echo_server = function() {
const express = require('express')
const app = express()
require('express-ws')(app);
app.get('/', (req, res) => res.send('(The echo server is running.)'))
app.listen(server_port, () => console.log(`Listening on port :${server_port}`))
app.ws('/echo', function(ws, req) {
ws.on('message', function(msg) {
ws.send(msg) // echo it back
});
});
}
#+END_SRC
The html page is contained in a single file. It contains html elements
for the inputs, and client-side javascript code for connecting to the
web socket we've just opened.
(! Actually, conveniently, http://websocket.org maintains its own echo
server. If you want to try their server before or while
troubleshooting your own, you can set the websocket to their echo
address; see code below.)
#+BEGIN_SRC web
WebSockets Echo Demo
Not connected to anything.
#+END_SRC
If you run the echo server in node, then open the above html file in
your browser, the page in your browser will enable you to send
messages to the echo server and get them back.
(You can try it with and without your echo server running to see the difference
it makes.)
** You can send serial data over websockets
To put our examples together, let's replace the keyboard input from
the serial example with keyboard input in a page running in the
browser.
Keypresses on the html webpage will be sent over websockets to the
listening nodejs server, which will use our previous function to send
them to the microcontroller over serial.
In the reverse direction, when the microcontroller sends serial data
to the nodejs server, the data will be pushed over websockets into the
waiting webpage.
Here's the code for now; it still has a few weird bugs (can't connect
multiple webpages to serial; refreshing the page breaks
connection. Serial responses lag behind by one message.) but does
work.
Nodejs code:
#+BEGIN_SRC javascript
example_serial_websocket_server = function() {
// Set up the web socket server
const express = require('express')
const app = express()
require('express-ws')(app);
// Connections maintain a list of who has connected to the
// websocket server, so we know who needs to hear serial messages.
var connections = []
app.get('/', (req, res) => res.send('(The sockets-serial server is running.)'))
app.listen(server_port, () => console.log(`Listening on port :${server_port}`))
// Set up serial connection
const SerialPort = require('serialport')
const Readline = require('@serialport/parser-readline')
const port = new SerialPort(serial_port,
{baudRate: baud,
databits:8,
dtr: true,
})
port.on('error', function(err) {
console.log('Error: ', err.message)
})
// Enable communication between sockets and serial. This is a
// little messy (nested), because we have to do the sockets <--
// serial connection in a variable scope where ws is defined.
app.ws('/ftdi', function(ws, req) {
// sockets <-- serial
port.on('data', function(data) {
console.log("serial connection sent:", data.toString('utf8'))
ws.send(data.toString('utf8'))
})
// sockets --> serial
ws.on('message', function(msg) {
console.log("html page sent:", msg)
port.write(Buffer.from(msg))
port.flush()
});
});
}
example_serial_websocket_server()
#+END_SRC
And here's the html page:
#+BEGIN_SRC html
WebSockets-to-Serial Demo
Not connected to anything.
Received:
#+END_SRC
** Toggle board LED every other character
If your hello-world board has an LED, we can turn this
websockets-serial connection into a web based interface for turning it
on and off. We'll alter the C code on the board so that whenever it
receives a character, it toggles the light:
I modify the main function of Neil's =hello.ftdi.44.echo.c= code as
follows. (Note that if your LED pin is not at pin PB2, you'll have to
change all references to =PORTB=, =DDRB=, =PB2= accordingly.)
#+BEGIN_SRC C
int main(void) {
//
// main
//
static char chr;
static char buffer[max_buffer] = {0};
static int index;
int light_on = 0;
set(PORTB, (1 << PB2));
output(DDRB, (1 << PB2));
//
// set clock divider to /1
//
CLKPR = (1 << CLKPCE);
CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
//
// initialize output pins
//
set(serial_port, serial_pin_out);
output(serial_direction, serial_pin_out);
//
// main loop
//
index = 0;
while (1) {
// dxh
clear(PORTB, (1 << PB2));
set(PORTB, (light_on << PB2));
get_char(&serial_pins, serial_pin_in, &chr);
light_on = 1 - light_on;
put_string(&serial_port, serial_pin_out, "hello.ftdi.44.echo.c: you typed \"");
buffer[index++] = chr;
if (index == (max_buffer-1))
index = 0;
put_string(&serial_port, serial_pin_out, buffer);
put_char(&serial_port, serial_pin_out, '\"');
put_char(&serial_port, serial_pin_out, 10); // new line
}
}
#+END_SRC
And since we're ignoring all of the textual information, here's a
streamlined html page that just has a button. It sends the character
='0'= every time the button is pressed; the C code toggles the light
every time it gets a character. (The html page requires the same
=example_serial_websocket_server= nodejs backend as the other
websockets-to-serial demo.)
#+BEGIN_SRC html
WebSockets Light Switch Demo