WebUSB ♥ Arduino
by Andrea Stagi
API for secure access to USB devices from web pages.
Let's plug our device!
A notification appears!
Let's click on it!
And in the website you can choose your device
The device's firmware itself must specify which URLs can access it.
This is similar to the CORS mechanism in HTTP
The URLs must be secured with HTTPS (except for localhost)
Talk is cheap.
Let's code!
WebUSB API
Hardware requirements
I can't use my old Arduino UNO
So I bought an Arduino Leonardo
ATmega32u4
VS
ATmega328
ATmega32u4 has the ability to talk USB natively
Software requirements
In Chrome enable "Experimental Web Platform Features" flag
Arduino IDE >= 1.6.11
Go to avr/cores/arduino/USBCore.h
Find the line
#define USB_VERSION 0x200
and change 0x200 to 0x210.
/* ..... */
#define MSC_SUBCLASS_SCSI 0x06
#define MSC_PROTOCOL_BULK_ONLY 0x50
#ifndef USB_VERSION
#define USB_VERSION 0x210
#endif
/* ..... */
Include WebUSB Arduino library
Our first firmware
#include <WebUSB.h>
const WebUSBURL URLS[] = {
{ 1, "astagi.github.io/webusb/" },
{ 0, "localhost:8000" },
};
const uint8_t ALLOWED_ORIGINS[] = { 1, 2 };
WebUSB WebUSBSerial(URLS, 2, 2, ALLOWED_ORIGINS, 2);
#define Serial WebUSBSerial
sketch.ino
typedef struct {
uint8_t scheme;
const char* url;
} WebUSBURL;
WebUSBURL Structure
WebUSB(
const WebUSBURL* urls,
uint8_t numUrls,
uint8_t landingPage,
const uint8_t* allowedOrigins,
uint8_t numAllowedOrigins
);
WebUSB Constructor
You can now use it like the Serial interface to read and write!
#define Serial WebUSBSerial
void loop() {
if (Serial && Serial.available()) {
command = Serial.read();
/* ELABORATE!! */
Serial.flush();
}
}
PluggableUSB.h
Arduino library to expose low-level USB functionalities
WebUSB::WebUSB(
const WebUSBURL* urls, uint8_t numUrls,
uint8_t landingPage,
const uint8_t* allowedOrigins,
uint8_t numAllowedOrigins)
: PluggableUSBModule(2, 1, epType),
urls(urls), numUrls(numUrls),
landingPage(landingPage),
allowedOrigins(allowedOrigins),
numAllowedOrigins(numAllowedOrigins)
{
epType[0] = EP_TYPE_BULK_OUT;
epType[1] = EP_TYPE_BULK_IN;
PluggableUSB().plug(this);
}
How Arduino announces WebUSB support
You must implement getDescriptor method
int WebUSB::getDescriptor(USBSetup& setup) {
if (USB_BOS_DESCRIPTOR_TYPE == setup.wValueH) {
if (setup.wValueL == 0 && setup.wIndex == 0) {
int whole_size = 0;
// SEND BOS DESCRIPTOR PREFIX
// SEND LANDING PAGE VALUE
if (USB_SendControl(0, &landingPage, 1) < 0)
return -1;
whole_size += 1;
// SEND BOS DESCRIPTOR SUFFIX
return whole_size;
}
}
return 0;
}
const uint8_t BOS_DESCRIPTOR_PREFIX[] PROGMEM = {
0x05, // Length
0x0F, // Binary Object Store descriptor
0x39, 0x00, // Total length
0x02, // Number of device capabilities
// WebUSB Platform Capability descriptor
// (bVendorCode == 0x01).
0x18, // Length
0x10, // Device Capability descriptor
0x05, // Platform Capability descriptor
0x00, // Reserved
0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47,
0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65,
0x00, 0x01, // Version 1.0
0x01, // Vendor request code
};
You need to implement setup() method
Manage WebUSB requests
#define WEBUSB_REQUEST_GET_ALLOWED_ORIGINS 0x01
if (setup.bRequest == 0x01 && setup.wIndex == WEBUSB_REQUEST_GET_ALLOWED_ORIGINS)
{
uint8_t allowedOriginsPrefix[] = {
// Allowed Origins Header, bNumConfigurations = 1
0x05, 0x00, 0x0c + numAllowedOrigins, 0x00, 0x01,
// Configuration Subset Header, bNumFunctions = 1
0x04, 0x01, 0x01, 0x01,
// Function Subset Header, bFirstInterface = pluggedInterface
0x03 + numAllowedOrigins, 0x02, pluggedInterface
};
if (USB_SendControl(
0, &allowedOriginsPrefix,
sizeof(allowedOriginsPrefix)
) < 0)
return false;
return USB_SendControl(0, allowedOrigins, numAllowedOrigins) >= 0;
}
#define
WEBUSB_REQUEST_GET_URL
0x02
else if (setup.bRequest == 0x01 && setup.wIndex == WEBUSB_REQUEST_GET_URL)
{
if (setup.wValueL == 0 || setup.wValueL > numUrls)
return false;
const WebUSBURL& url = urls[setup.wValueL - 1];
uint8_t urlLength = strlen(url.url);
uint8_t descriptorLength = urlLength + 3;
if (USB_SendControl(0, &descriptorLength, 1) < 0)
return false;
uint8_t descriptorType = 3;
if (USB_SendControl(0, &descriptorType, 1) < 0)
return false;
if (USB_SendControl(0, &url.scheme, 1) < 0)
return false;
return USB_SendControl(0, url.url, urlLength) >= 0;
}
Implement getInterface()
int WebUSB::getInterface(uint8_t* interfaceCount) {
*interfaceCount += 1; // uses 1 interface
WebUSBDescriptor webUSBInterface = {
D_INTERFACE(pluggedInterface, 2, 0xff, 0, 0),
D_ENDPOINT(
USB_ENDPOINT_OUT(pluggedEndpoint),
USB_ENDPOINT_TYPE_BULK, 0x40, 0
),
D_ENDPOINT(
USB_ENDPOINT_IN (pluggedEndpoint + 1),
USB_ENDPOINT_TYPE_BULK, 0x40, 0
)
};
return USB_SendControl(
0, &webUSBInterface,
sizeof(webUSBInterface)
);
}
JS App
requestDevice()
It must be called via a user gesture like a touch or mouse click
const filters = [
{ 'vendorId': 0x2341, 'productId': 0x8036 },
{ 'vendorId': 0x2341, 'productId': 0x8037 },
];
return navigator.usb.requestDevice (
{ 'filters': filters }
).then(
device_ => device = device_;
);
the open() function
return this.device.open()
.then(() => {
if (device.configuration === null) {
return device.selectConfiguration(1);
}
})
.then(() => device.claimInterface(2))
.then(() => device.controlTransferOut({
'requestType': 'class',
'recipient': 'interface',
'request': 0x22, // CDC_SET_CONTROL_LINE_STATE
'value': 0x01,
'index': 0x02}))
.then(() => {
readLoop();
});
Reading from device
let readLoop = () => {
device.transferIn(5, 64).then(result => {
console.log(result.data);
readLoop();
}, error => {
console.log(error);
});
};
Writing to the device
device.transferOut(4, data);
Demo time!
What about Android?
Another Nanpy?
from nanpy import ArduinoApi
a = ArduinoApi()
a.pinMode(13, a.OUTPUT)
a.digitalWrite(13, a.HIGH)
from nanpy import DallasTemperature
sensors = DallasTemperature(5)
n_sensors = sensors.getDeviceCount()
addresses = []
for i in range(n_sensors):
addresses.append(sensors.getAddress(i))
sensors.setResolution(12)
while True:
sensors.requestTemperatures()
for i in range(n_sensors):
temp = sensors.getTempC(i)
stagi.andrea@gmail.com
twitter: @4stagi
github: @astagi
Presentation available at
WebUSB ♥ Arduino
By Andrea Stagi
WebUSB ♥ Arduino
WebUSB and Arduino playground
- 4,594