This doc shows you how packaged apps can connect to USB devices and read from and write to a user's serial ports. See also the reference docs for the USB API and the Serial API. The Bluetooth API is also available; we've include a link to a Bluetooth sample below.
API Samples: Want to play with the code? Check out the serial, servo, usb, and zephyr_hxm Bluetooth samples.
You can use the USB API to send messages to connected devices using only JavaScript code. Some devices are not accessible through this API - see the Caveats section below for more details.
The USB API requires a special permission "usb" in the manifest file:
"permissions": [ "usb" ]
Every device in a USB bus is identified
by its vendor and product IDs.
To find a device,
use the findDevices() method
which has two parameters:
chrome.usb.findDevices(FindDeviceOptions, callback)
| Parameter (type) | Description |
|---|---|
| FindDeviceOptions (object) | An object specifying both a vendorId (long) and
productId (long) used to find the correct type of device on
the bus. Your manifest must declare theusbDevices permission
section listing all the vendorId and
deviceId pairs your app wants to access.
|
| callback (function) | Called when the device scan is finished.
The callback will be executed with one parameter, an array of device objects
with three properties: handle,
vendorId,
productId. If the optional permissions for this USB device
were not declared in the extension manifest or the user did not consent
to the permissions requested, the parameter will be null.
If no devices could be found, the array will be empty. |
Example:
var onDeviceFound = function(devices) {
_this.devices=devices;
if (devices) {
if (devices.length > 0) {
console.log("Device(s) found: "+devices.length);
} else {
console.log("Device could not be found");
}
} else {
console.log("Did not request correct permissions");
}
};
chrome.usb.findDevices({"vendorId": vendorId, "productId": productId}, onDeviceFound);
USB protocol defines four types of transfers:
control, bulk, isochronous and interrupt.
Theoretically they can occur in both directions:
device-to-host (inbound) and host-to-device (outbound).
However, due to the nature of the USB protocol, both inbound and outbound messages must be initiated by the host (your computer). For inbound (device-to-host) messages, the host, your JavaScript code, sends a message flagged as "inbound" to the device. The exact contents of the message depends on the device, but usually will have some identification of what you are requesting from it. The device then responds with the requested data. The device's response is handled by Chrome and delivered asynchronously to the callback you specified in the transfer method. An outbound (host-to-device) message is similar, but the response doesn't contain data returned from the device.
For each message from the device, the specified callback will receive an event object with the following properties:
| Property | Description |
|---|---|
| resultCode (integer) | 0 is success; other values indicate failure. An error string can be read from
chrome.extension.lastError when a failure is indicated. |
| data (arraybuffer) | Contains the data sent by the device if transfer was inbound. |
Example:
var onTransferCallback = function(event) {
if (event && event.resultCode === 0 && event.data) {
console.log("got "+event.data.byteLength+" bytes");
}
};
chrome.usb.bulkTransfer(device, transferInfo, onTransferCallback);
Control transfers are generally used to send or receive configuration or command parameters to a USB device. The method is simple and receives three parameters:
chrome.usb.controlTransfer(deviceObj, transferInfo, transferCallback)
| Parameter (types) | Description |
|---|---|
| deviceObj | Object sent in findDevice() callback. |
| transferInfo | Parameter object with values from the table below. Check your USB device protocol specification for specifics. |
| transferCallback() | Invoked when the transfer has completed. |
Values for transferInfo object:
| Value | Description |
|---|---|
| requestType (string) | "vendor", "standard", "class" or "reserved". |
| recipient (string) | "device", "interface", "endpoint" or "other". |
| direction (string) | "in" or "out". "in" direction is used to notify the device that it should send information to the host. All communication in a USB bus is host-initiated, so use an 'in' transfer to allow a device to send information back. |
| request (integer) | Defined by your device's protocol. |
| value (integer) | Defined by your device's protocol. |
| index (integer) | Defined by your device's protocol. |
| length (integer) | Only used when direction is "in". Notifies the device that this is the amount of data the host is expecting in response. |
| data (arraybuffer) | Defined by your device's protocol, required when direction is "out". |
Example:
var transferInfo = {
"requestType": "vendor",
"recipient": "device",
"direction": "out",
"request": 0x31,
"value": 120,
"index": 0,
"data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};
chrome.usb.controlTransfer(deviceObj, transferInfo, optionalCallback);
Isochronous transfers is the most complex type of USB transfers. They are commonly used for streams of data, like video and sound. To initiate an isochronous transfer (either inbound or outbound), you must use:
chrome.usb.isochronousTransfer(deviceObj, isochronousTransferInfo, transferCallback)
| Parameter | Description |
|---|---|
| deviceObj | Object sent on findDevice() callback. |
| isochronousTransferInfo | Parameter object with the values in the table below. |
| transferCallback() | Invoked when the transfer has completed. |
Values for isochronousTransferInfo object:
| Value | Description |
|---|---|
| transferInfo (object) | An object with the following attributes: direction (string): "in" or "out". endpoint (integer): defined by your device. Usually can be found by looking at an USB instrospection tool, like lsusb -vlength (integer): only used when direction is "in". Notifies the device that this is the amount of data the host is expecting in response. Should be AT LEAST packets * packetLengthdata (arraybuffer): defined by your device's protocol; only used when direction is "out". |
| packets (integer) | Total number of packets expected in this transfer. |
| packetLength (integer) | Expected length of each packet in this transfer. |
Example:
var transferInfo = {
"direction": "in",
"endpoint": 1,
"length": 2560
};
var isoTransferInfo = {
"transferInfo": transferInfo,
"packets": 20,
"packetLength": 128
};
chrome.usb.isochronousTransfer(deviceObj, isoTransferInfo, optionalCallback);
Notes: One isochronous transfer will contain isoTransferInfo.packets packets of isoTransferInfo.packetLength bytes. If it is an inbound transfer (your code requested data from the device), the data field in the onUsbEvent will be an ArrayBuffer of size transferInfo.length. It is your duty to walk through this ArrayBuffer and extract the different packets, each starting at a multiple of isoTransferInfo.packetLength bytes. If you are expecting a stream of data from the device, remember that you will have to send one "inbound" transfer for each transfer you expect back. USB devices don't send transfers to the bus unless the host explicitly requests them through "inbound" transfers.
Bulk transfer is an USB transfer type commonly used to transfer a large amount of data in a reliable way. The method has three parameters:
chrome.usb.bulkTransfer(deviceObj, transferInfo, transferCallback)
| Parameter | Description |
|---|---|
| deviceObj | Object sent on findDevice() callback. |
| transferInfo | Parameter object with the values in the table below. |
| transferCallback | Invoked when the transfer has completed. |
Values for transferInfo object:
| Value | Description |
|---|---|
| direction (string) | "in" or "out". |
| endpoint (integer) | Defined by your device's protocol. |
| length (integer) | Only used when direction is "in". Notifies the device that this is the amount of data the host is expecting in response. |
| data (ArrayBuffer) | Defined by your device's protocol; only used when direction is "out". |
Example:
var transferInfo = {
"direction": "out",
"endpoint": 1,
"data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};
Interrupt transfers are used to send important notifications. Since all USB communication is initiated by the host, host code usually polls the device periodically, sending interrupt IN transfers that will make the device send data back if there is anything in the interrupt queue. The method has three parameters:
chrome.usb.interruptTransfer(deviceObj, transferInfo, transferCallback)
| Parameter | Description |
|---|---|
| deviceObj | Object sent on findDevice() callback. |
| transferInfo | Parameter object with the values in the table below. |
| transferCallback | Invoked when the transfer has completed.
Notice that this callback doesn't contain the device's response.
It's just to notify your code that the asynchronous transfer request
has been processed and you can go ahead.
The device's response, if any, will always be sent through
the onEvent() callback set on findDevice().
|
Values for transferInfo object:
| Value | Description |
|---|---|
| direction (string) | "in" or "out". |
| endpoint (integer) | Defined by your device's protocol. |
| length (integer) | Only used when direction is "in". Notifies the device that this is the amount of data the host is expecting in response. |
| data (ArrayBuffer) | Defined by your device's protocol; only used when direction is "out". |
| Parameter | Description |
|---|---|
| portName (string) | If your device's port name is unknown, you can use the getPorts method. |
| options (object) | Parameter object with one single value: bitrate, an integer specifying the desired bitrate used to communicate with the serial port. |
| openCallback | Invoked when the port has been successfully opened. The callback will be called with one parameter, openInfo, that has one attribute, connectionId. Save this id, because you will need it to actually communicate with the port.
|
A simple example:
var onOpen = function(connectionInfo) {
// The serial port has been opened. Save its id to use later.
_this.connectionId = connectionInfo.connectionId;
// Do whatever you need to do with the opened port.
}
// Open the serial port /dev/ttyS01
chrome.serial.open("/dev/ttyS01", {bitrate: 115200}, onOpen);
Closing a serial port is simple but very important. See the example below:
var onClose = function(result) {
console.log("Serial port closed");
}
chrome.serial.close(connectionId, onClose);
The serial API reads from the serial port and
delivers the read bytes as an ArrayBuffer.
There is no guarantee that all the requested bytes, even if available in the port, will be read in one chunk.
The following example can accumulate read bytes, at most 128 at a time, until a new line is read,
and then call a listener with the ArrayBuffer bytes converted to a String:
var dataRead='';
var onCharRead=function(readInfo) {
if (!connectionInfo) {
return;
}
if (readInfo && readInfo.bytesRead>0 && readInfo.data) {
var str=ab2str(readInfo.data);
if (str[readInfo.bytesRead-1]==='\n') {
dataRead+=str.substring(0, readInfo.bytesRead-1);
onLineRead(dataRead);
dataRead="";
} else {
dataRead+=str;
}
}
chrome.serial.read(connectionId, 128, onCharRead);
}
/* Convert an ArrayBuffer to a String, using UTF-8 as the encoding scheme.
This is consistent with how Arduino sends characters by default */
var ab2str=function(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
};
The writing routine is simpler than reading,
since the writing can occur all at once.
The only catch is that if your data protocol is String based,
you have to convert your output string to an ArrayBuffer.
See the code example below:
var writeSerial=function(str) {
chrome.serial.write(connectionId, str2ab(str), onWrite);
}
// Convert string to ArrayBuffer
var str2ab=function(str) {
var buf=new ArrayBuffer(str.length);
var bufView=new Uint8Array(buf);
for (var i=0; i<str.length; i++) {
bufView[i]=str.charCodeAt(i);
}
return buf;
}
You can flush your serial port buffer by issuing the flush command:
chrome.serial.flush(connectionId, onFlush);