blob: 2eea329ea74987770f7547b2389621511d62d0bd [file] [log] [blame]
<h1>Accessing Hardware Devices</h1>
<p>
This doc shows you how Chrome Apps can connect to USB devices and read from
and write to a user's serial ports. See also the reference docs for the <a
href="usb.html">USB API</a> and the <a href="serial.html">Serial API</a>.
The <a href="bluetooth.html">Bluetooth API</a> is also available; we've
included a link to a Bluetooth sample below.
</p>
<p class="note">
<b>API Samples: </b> Want to play with the code? Check out the
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/serial">serial</a>,
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/servo">servo</a>,
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/usb">usb</a>,
and
<a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/zephyr_hxm">zephyr_hxm
Bluetooth</a> samples.
</p>
<h2 id="usb">Accessing USB devices</h2>
<p>
You can use the <a href="usb.html">USB API</a> to communicate with USB devices
using only JavaScript code. Some devices are not accessible through this API
&ndash; see <a href="#caveats">Caveats</a> below for details.
</p>
<p>
For background information about USB, see the official
<a href="http://www.usb.org/home">USB specifications</a>. <br/>
<a href="http://www.beyondlogic.org/usbnutshell/usb1.shtml">
<i>USB in a NutShell</i></a>
is a reasonable crash course that you may find helpful.
</p>
<h3 id="manifest">Manifest requirement</h3>
<p>The USB API requires the "usb" permission in the manifest file:</p>
<pre data-filename="manifest.json">
"permissions": [
"usb"
]
</pre>
<p>In addition, in order to prevent
<a href="http://en.wikipedia.org/wiki/Device_fingerprint">finger-printing</a>,
you must declare all the device types you want to access in the manifest file.
Each type of USB device corresponds to a vendor id/product id (VID/PID) pair.
You can use $ref:usb.getDevices to enumerate devices by their VID/PID
pair.
</p>
<p>
You must declare the VID/PID pairs for each type of device you want to use
under the "usbDevices" permission in your app's manifest file, as shown in the
example below:</p>
<pre data-filename="manifest.json">
"permissions": [
"usbDevices": [
{
"vendorId": 123,
"productId": 456
}
]
]
</pre>
<p class="note">Note that only decimal numbers are allowed in JSON format.
You cannot use hexadecimal numbers in these fields.</p>
<h3 id="finding_device">Finding a device</h3>
<p>
To determine whether one or more specific devices are connected to a user's
system, use the $ref:usb.getDevices method:
</p>
<pre>
chrome.usb.getDevices(enumerateDevicesOptions, callback);
</pre>
<br/>
<table class="simple">
<tr>
<th scope="col">Parameter (type)</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>EnumerateDevicesOptions (object)</td>
<td>An object specifying both a <code>vendorId</code> (long) and
<code>productId</code> (long) used to find the correct type of device on
the bus. Your manifest must declare the <code>usbDevices</code> permission
section listing all the <code>vendorId</code> and <code>deviceId</code>
pairs your app wants to access.
</td>
</tr>
<tr>
<td>callback (function)</td>
<td>Called when the device enumeration is finished. The callback will be
executed with one parameter, an array of <code>Device</code> objects with
three properties: <code>device</code>, <code>vendorId</code>,
<code>productId</code>. The device property is a stable identifier for a
connected device. It will not change until the device is unplugged. The
detail of the identifier is opaque and subject to change. Do not rely on
its current type. <br/>
If no devices are found, the array will be empty.
</td>
</tr>
</table>
<p>Example:</p>
<pre>
function onDeviceFound(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("Permission denied.");
}
}
chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, onDeviceFound);
</pre>
<h3 id="usb_open">Opening a device</h3>
<p>
Once the <code>Device</code> objects are returned, you can open a device using
usb.openDevice to obtain a connection handle. You can only
communicate with USB devices using connection handles.
</p>
<table class="simple">
<tr>
<th scope="col">Property</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>device</td>
<td>Object received in $ref:usb.getDevices callback.</td>
</tr>
<tr>
<td>data (arraybuffer)</td>
<td>Contains the data sent by the device if the transfer was inbound.</td>
</tr>
</table>
<p>Example:</p>
<pre>
var usbConnection = null;
var onOpenCallback = function(connection) {
if (connection) {
usbConnection = connection;
console.log("Device opened.");
} else {
console.log("Device failed to open.");
}
};
chrome.usb.openDevice(device, onOpenCallback);
</pre>
<p class="note">
Not every device can be opened successfully. In general, operating systems
lock down many types of USB interfaces (e.g. keyboards and mice, mass storage
devices, webcams, etc.) and they cannot be claimed by user applications.
On Linux (other than Chrome OS), once an interface of a device is locked down by
the OS, the whole device is locked down (because all the interfaces shares the
same device file), even if the other interfaces of the device can be used in
theory. On Chrome OS, you can request access to unlocked interfaces using the
$ref:usb.requestAccess method. If permitted, the permission broker will
unlock the device file for you.
</p>
<p>
To simplify the opening process, you can use the $ref:usb.findDevices
method, which enumerates, requests access, and opens devices in one call:
</p>
<pre>
chrome.usb.findDevices({"vendorId": vendorId, "productId": productId, "interfaceId": interfaceId}, callback);
</pre>
<p>which is equivalent to:</p>
<pre>
chrome.usb.getDevices({"vendorId": vendorId, "productId": productId}, function (devices) {
if (!devices) {
console.log("Error enumerating devices.");
callback();
return;
}
var connections = [], pendingAccessRequests = devices.length;
devices.forEach(function (device) {
chrome.usb.requestAccess(interfaceId, function () {
// No need to check for errors at this point.
// Nothing can be done if an error occurs anyway. You should always try
// to open the device.
chrome.usb.openDevices(device, function (connection) {
if (connection) connections.push(connection);
pendingAccessRequests--;
if (pendingAccessRequests == 0) {
callback(connections);
}
});
});
})
});
</pre>
<h3 id="usb_transfers">USB transfers and receiving data from a device</h3>
<p>
The USB protocol defines four types of transfers:
<a href="#control_transfers">control</a>,
<a href="#bulk_transfers">bulk</a>,
<a href="#isochronous_transfers">isochronous</a>
and <a href="#interrupt_transfers">interrupt</a>.
These transfers are described below.
</p>
<p>
Transfers can occur in both directions: device-to-host (inbound), and
host-to-device (outbound). Due to the nature of the USB protocol,
both inbound and outbound messages must be initiated by the host (the
computer that runs the Chrome app).
For inbound (device-to-host) messages, the host (initiated by your JavaScript
code) sends a message flagged as "inbound" to the device.
The details of the message depend 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 specify in the transfer method.
An outbound (host-to-device) message is similar, but the response doesn't
contain data returned from the device.
</p>
<p>
For each message from the device, the specified callback will receive an event
object with the following properties:
</p>
<br>
<table class="simple">
<tr>
<th scope="col">Property</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>resultCode (integer)</td>
<td>0 is success; other values indicate failure. An error string can be<br/>
read from <code>chrome.extension.lastError</code> when a failure is<br/>
indicated.
</td>
</tr>
<tr>
<td>data (arraybuffer)</td>
<td>Contains the data sent by the device if the transfer was inbound.</td>
</tr>
</table>
<p>Example:</p>
<pre>
var onTransferCallback = function(event) {
if (event && event.resultCode === 0 && event.data) {
console.log("got " + event.data.byteLength + " bytes");
}
};
chrome.usb.bulkTransfer(connectionHandle, transferInfo, onTransferCallback);
</pre>
<h3 id="control_transfers">CONTROL transfers</h3>
<p>Control transfers are generally used to send or receive configuration or
command parameters to a USB device. The controlTransfer method always sends
to/reads from endpoint 0, and no claimInterface is required.
The method is simple and receives three parameters:</p>
<pre>
chrome.usb.controlTransfer(connectionHandle, transferInfo, transferCallback)
</pre>
<br>
<table class="simple">
<tr>
<th scope="col">Parameter (types)</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>connectionHandle</td>
<td>Object received in $ref:usb.openDevice callback.
</td>
</tr>
<tr>
<td>transferInfo</td>
<td>Parameter object with values from the table below. Check your USB
device protocol specification for details.
</td>
</tr>
<tr>
<td>transferCallback()</td>
<td>Invoked when the transfer has completed.</td>
</tr>
</table>
<p>
Values for
<code>transferInfo</code>
object:
</p>
<table class="simple">
<tr>
<th scope="col">Value</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>requestType (string)</td>
<td>"vendor", "standard", "class" or "reserved".</td>
</tr>
<tr>
<td>recipient (string)</td>
<td>"device", "interface", "endpoint" or "other".</td>
</tr>
<tr>
<td>direction (string)</td>
<td>"in" or "out". The "in" direction is used to notify the device that<br/>
it should send information to the host. All communication on a USB<br/>
bus is host-initiated, so use an "in" transfer to allow a device to<br/>
send information back.
</td>
</tr>
<tr>
<td>request (integer)</td>
<td>Defined by your device's protocol.</td>
</tr>
<tr>
<td>value (integer)</td>
<td>Defined by your device's protocol.</td>
</tr>
<tr>
<td>index (integer)</td>
<td>Defined by your device's protocol.</td>
</tr>
<tr>
<td>length (integer)</td>
<td>Only used when direction is "in". Notifies the device that this is
the amount of data the host is expecting in response.
</td>
</tr>
<tr>
<td>data (arraybuffer)</td>
<td>Defined by your device's protocol, required when direction is
"out".
</td>
</tr>
</table>
<p>Example:</p>
<pre>
var transferInfo = {
"requestType": "vendor",
"recipient": "device",
"direction": "out",
"request": 0x31,
"value": 120,
"index": 0,
// Note that the ArrayBuffer, not the TypedArray itself is used.
"data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};
chrome.usb.controlTransfer(connectionHandle, transferInfo, optionalCallback);
</pre>
<h3 id="isochronous_transfers">ISOCHRONOUS transfers</h3>
<p>Isochronous transfers are the most complex type of USB transfer. They are
commonly used for streams of data, like video and sound. To initiate an
isochronous transfer (either inbound or outbound), you must use
the $ref:usb.isochronousTransfer method:</p>
<pre>
chrome.usb.isochronousTransfer(connectionHandle, isochronousTransferInfo, transferCallback)
</pre>
<br/>
<table class="simple">
<tr>
<th scope="col">Parameter</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>connectionHandle</td>
<td>Object received in $ref:usb.openDevice callback.
</td>
</tr>
<tr>
<td>isochronousTransferInfo</td>
<td>Parameter object with the values in the table below.</td>
</tr>
<tr>
<td>transferCallback()</td>
<td>Invoked when the transfer has completed.</td>
</tr>
</table>
<p>
Values for
<code>isochronousTransferInfo</code>
object:
</p>
<table class="simple">
<tr>
<th scope="col">Value</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>transferInfo (object)</td>
<td>An object with the following attributes:<br/>
<b>direction (string): </b>"in" or "out".<br/>
<b>endpoint (integer): </b>defined by your device. Usually can be found by
looking at an USB instrospection tool, like <code>lsusb -v</code><br/>
<b>length (integer): </b>only
used when direction is "in". Notifies the device that this is the amount
of data the host is expecting in response. <br/>
Should be AT LEAST <code>packets</code> &times; <code>packetLength</code>.
<br/>
<b>data (arraybuffer): </b>defined by your device's protocol;
only used when direction is "out".
</td>
</tr>
<tr>
<td>packets (integer)</td>
<td>Total number of packets expected in this transfer.</td>
</tr>
<tr>
<td>packetLength (integer)</td>
<td>Expected length of each packet in this transfer.</td>
</tr>
</table>
<p>Example:</p>
<pre>
var transferInfo = {
"direction": "in",
"endpoint": 1,
"length": 2560
};
var isoTransferInfo = {
"transferInfo": transferInfo,
"packets": 20,
"packetLength": 128
};
chrome.usb.isochronousTransfer(connectionHandle, isoTransferInfo, optionalCallback);
</pre>
<p class="note">
<b>Notes:</b> One isochronous transfer will contain
<code>isoTransferInfo.packets</code> packets of
<code>isoTransferInfo.packetLength</code> bytes.
If it is an inbound transfer (your code requested data from the device), the
<code>data</code> field in the onUsbEvent will be an ArrayBuffer of size
<code>transferInfo.length</code>. It is your duty to walk through this
ArrayBuffer and extract the different packets, each starting at a multiple of
<code>isoTransferInfo.packetLength</code> bytes. <br/>
<br/>
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 USB bus unless the host
explicitly requests them through "inbound" transfers.
</p>
<h3 id="bulk_transfers">BULK transfers</h3>
<p>Bulk transfers are commonly used to transfer a large amount of
non-time-sensitive data in a reliable way.
$ref:usb.bulkTransfer has three parameters:</p>
<pre>
chrome.usb.bulkTransfer(connectionHandle, transferInfo, transferCallback);
</pre>
<br>
<table class="simple">
<tr>
<th scope="col">Parameter</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>connectionHandle</td>
<td>Object received in $ref:usb.openDevice callback.
</td>
</tr>
<tr>
<td>transferInfo</td>
<td>Parameter object with the values in the table below.</td>
</tr>
<tr>
<td>transferCallback</td>
<td>Invoked when the transfer has completed.</td>
</tr>
</table>
<p>
Values for
<code>transferInfo</code>
object:
</p>
<table class="simple">
<tr>
<th scope="col">Value</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>direction (string)</td>
<td>"in" or "out".</td>
</tr>
<tr>
<td>endpoint (integer)</td>
<td>Defined by your device's protocol.</td>
</tr>
<tr>
<td>length (integer)</td>
<td>Only used when direction is "in". Notifies the device that this is
the amount of data the host is expecting in response.
</td>
</tr>
<tr>
<td>data (ArrayBuffer)</td>
<td>Defined by your device's protocol; only used when direction is
"out".
</td>
</tr>
</table>
<p>Example:</p>
<pre>
var transferInfo = {
"direction": "out",
"endpoint": 1,
"data": new Uint8Array([4, 8, 15, 16, 23, 42]).buffer
};
</pre>
<h3 id="interrupt_transfers">INTERRUPT transfers</h3>
<p>Interrupt transfers are used to small amount of time sensitive data.
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 (maintained
by the device).
$ref:usb.interruptTransfer has three parameters:</p>
<pre>
chrome.usb.interruptTransfer(connectionHandle, transferInfo, transferCallback);
</pre>
<br>
<table class="simple">
<tr>
<th scope="col">Parameter</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>connectionHandle</td>
<td>Object received in $ref:usb.openDevice callback.
</td>
</tr>
<tr>
<td>transferInfo</td>
<td>Parameter object with the values in the table below.</td>
</tr>
<tr>
<td>transferCallback</td>
<td>Invoked when the transfer has completed. Notice that this callback
doesn't contain the device's response. The purpose of the callback is
simply to notify your code that the asynchronous transfer requests has
been processed.
</td>
</tr>
</table>
<p>
Values for <code>transferInfo</code> object:
</p>
<table class="simple">
<tr>
<th scope="col">Value</th>
<th scope="col">Description</th>
</tr>
<tr>
<td>direction (string)</td>
<td>"in" or "out".</td>
</tr>
<tr>
<td>endpoint (integer)</td>
<td>Defined by your device's protocol.</td>
</tr>
<tr>
<td>length (integer)</td>
<td>Only used when direction is "in". Notifies the device that this is
the amount of data the host is expecting in response.
</td>
</tr>
<tr>
<td>data (ArrayBuffer)</td>
<td>Defined by your device's protocol; only used when direction is
"out".
</td>
</tr>
</table>
<p>Example:</p>
<pre>
var transferInfo = {
"direction": "in",
"endpoint": 1,
"length": 2
};
chrome.usb.interruptTransfer(connectionHandle, transferInfo, optionalCallback);
</pre>
<h3 id="caveats">Caveats</h3>
<p>Not all devices can be accessed through the USB API. In general, devices
are not accessible because either the Operating System's kernel or a native
driver holds them off from user space code. Some examples are devices with
HID profiles on OSX systems, and USB pen drives.</p>
<p>
On most Linux systems, USB devices are mapped with read-only permissions by
default. To open a device through this API, your user will need to have
write access to it too.
A simple solution is to set a udev rule. Create a file
<code>/etc/udev/rules.d/50-yourdevicename.rules</code>
with the following content:
</p>
<pre>
SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
</pre>
<p>
Then, just restart the udev daemon: <code>service udev restart</code>.
You can check if device permissions are set correctly by following these
steps:
</p>
<ul>
<li>Run <code>lsusb</code> to find the bus and device numbers.</li>
<li>Run <code>ls -al /dev/bus/usb/[bus]/[device]</code>. This file should be
owned by group "plugdev" and have group write permissions.
</li>
</ul>
<p>Your app cannot do this automatically since this this procedure requires root
access. We recommend that you provide instructions to end-users and link to the
<a href="#caveats">Caveats</a> section on this page for an explanation.</p>
<p>On Chrome OS, simply call $ref:usb.requestAccess. The permission
broker does this for you.</p>
<h2 id="serial">Accessing serial devices</h2>
<p>
You can use the serial API to read
and write from a serial device.
</p>
<h3 id="requirement">Manifest requirement</h3>
<p>
You must add the "serial" permission to the manifest file:
</p>
<pre data-filename="manifest.json">
"permissions": [
"serial"
]
</pre>
<h3 id="listing">Listing available serial ports</h3>
<p>
To get a list of available serial ports,
use the <code>getPorts()</code> method. <b>Note:</b> not all serial ports are available. The API uses a heuristic based on the name of the port to only expose serial devices that are expected to be safe.
</p>
<pre>
var onGetPorts = function(ports) {
for (var i=0; i&lt;ports.length; i++) {
console.log(ports[i]);
}
}
chrome.serial.getPorts(onGetPorts);
</pre>
<h3 id="opening">Opening a serial device</h3>
<p>
If you know the serial port name, you can open it for read and write using the <code>open</code> method:
</p>
<pre>
chrome.serial.open(portName, options, openCallback)
</pre>
<table border="0">
<tr>
<th scope="col"> Parameter </th>
<th scope="col"> Description </th>
</tr>
<tr>
<td>portName&nbsp;(string)</td>
<td>If your device's port name is unknown, you can use the <code>getPorts</code> method.</td>
</tr>
<tr>
<td>options&nbsp;(object)</td>
<td>Parameter object with one single value: <code>bitrate</code>, an integer specifying the desired bitrate used to communicate with the serial port.</td>
</tr>
<tr>
<td>openCallback</td>
<td>Invoked when the port has been successfully opened. The callback will be called with one parameter, <code>openInfo</code>, that has one attribute, <code>connectionId</code>. Save this id, because you will need it to actually communicate with the port.
</td>
</tr>
</table>
<p>A simple example:</p>
<pre>
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);
</pre>
<h3 id="closing">Closing a serial port</h3>
<p>
Closing a serial port is simple but very important. See the example below:
</p>
<pre>
var onClose = function(result) {
console.log("Serial port closed");
}
chrome.serial.close(connectionId, onClose);
</pre>
<h3 id="reading">Reading from a serial port</h3>
<p>
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 <code>ArrayBuffer</code> bytes converted to a String:
</p>
<pre>
var dataRead='';
var onCharRead=function(readInfo) {
if (!connectionInfo) {
return;
}
if (readInfo &amp;&amp; readInfo.bytesRead>0 &amp;&amp; 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));
};
</pre>
<h3 id="writing">Writing to a serial port</h3>
<p>
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 <code>ArrayBuffer</code>.
See the code example below:
</p>
<pre>
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&lt;str.length; i++) {
bufView[i]=str.charCodeAt(i);
}
return buf;
}
</pre>
<h3 id="flushing">Flushing a serial port buffer</h3>
<p>
You can flush your serial port buffer by issuing the flush command:
</p>
<pre>
chrome.serial.flush(connectionId, onFlush);
</pre>
<p class="backtotop"><a href="#top">Back to top</a></p>