#+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
Not connected to anything.

#+END_SRC