W3C

Web GPIO API

Draft Report

This Version:
http://
Latest Published Version
http://
Previous Version:
None.
Editors:
Futomi Hatano ( 羽田野 ( はたの ) 太巳 ( ふとみ ) ), Newphoria
Satoru Takagi ( 高木 ( たかぎ ) ( さとる ) ), KDDI

Abstract

This specification defines an API to enable web applications to access GPIO embedded in the underlying device.

Status of this document

This specification was published by the Browsers and Robotics Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1 Introduction

This section is non-normative.

The Web GPIO API provides interfaces that web applications can access GPIO embedded in the underlying devices.

Using this API, web applications can set GPIO direction (input or output), read GPIO value, write GPIO value, watch hardware interrupts, and so on.

This API is intended to be used under the mechanism which maintains the safety of the device by which this API operates and users using it. Note that this API is not intended to be used by web applications running on a general web browser without such considerations. As a mechanism for such consideration, there may be packaged applications running on a browser runtime or web applications running on a browser in some kind of dedicated devices.

2 Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in the normative parts of this document are to be interpreted as described in RFC2119. For readability, these words do not appear in all uppercase letters in this document. [RFC2119]

3 Terminology

The EventTarget, Event, and EventInit interfaces are defined in the Web IDL specification. [DOM4]

The DOMExceptions interfaces is defined in the Web IDL specification. [WEBIDL]

The Promise is defined in ECMA-262 6th Edition. [ES6]

4 Example

This section is non-normative.

The following examples show common GPIO usage in JavaScript. The variable gpio is a GPIOAccess object in the examples blow.

4.1 Getting Access to the GPIO

This example shows how to request access to the set of GPIOs on the underlying system.

var gpio = null;  // global GPIOAccess object

navigator.requestGPIOAccess().then(
    function(gpioAccess) {
        console.log("GPIO ready!");
        gpio = gpioAccess; // store in the global
    },
    function(error) {
        console.log("Failed to get GPIO access: " + error.message);
    }
);

4.2 Listing GPIO ports

This example shows how to list the GPIO ports available on the underlying operating system.

// get a GPIOPortMap object
var ports = gpio.ports;

// show the detailed information for each port
ports.values(function(port) {
    console.log("* Port " + port.portNumber);
    console.log("  - Port name   : " + port.portName);
    console.log("  - PIN name    : " + port.pinName);
});

4.3 Getting a GPIO port

This example shows how to get a GPIO port.

var port = null;  // global GPIOPort object

// get the GPIOPort object representing the GPIO port named "gpio18".
gpio.ports.get(18).then(
    function(gpioPort) {
        port = gpioPort; // store in the global
    },
    function(error) {
        console.log("Failed to get GPIO port: " + error.message);
    }
);

4.4 Activating a GPIO port and read the value

This example shows how to activate the specified GPIO port and read the value.

// export (activate) the port.
port.export("in").then(exportSuccess, gpioError);

// successfully exported
function exportSuccess() {
    window.setInterval(function() {
        port.read().then(readSuccess, gpioError);
    }, 1000);
}

// the value successfully read
function readSuccess(value) {
    console.log(port.portName + ": " + value);
}

// Show an error
function gpioError(error) {
    console.log("Error: " + error.message + "(" + port.portName + ")");
}

4.5 Listening to changes of a GPIO port value

This example shows how to listen to changes of a GPIO port values. In this example, the exported GPIO port will be unexported in 60 seconds.

// export (activate) the port
port.export("in").then(exportSuccess, gpioError);

// successfully exported
function exportSuccess() {
    // set an event handler
    port.onchange = valueChanged;
}

// the value changed
function valueChanged(event) {
    var port = event.port; // GPIOPort object
    var value = event.value; // current value
    console.log(port.portName + ": " + value);
}

// Show an error
function gpioError(error) {
    console.log("Error: " + error.message + "(" + port.portName + ")");
}

// unexport (deactivate) the port in 60 seconds
window.setTimeout(function() {
    if(port.exported === true) {
        port.unexport();
    }
}, 60000);

4.6 Listening to changes of multiple GPIO port values

This example shows how to listen to changes of multiple GPIO port values as a whole. In this example, 3 GPIO ports are listened. When the value of the GPIO port "gpio25" changes to 1, all of the exported GPIO port will be unexported.

// set a listener for all of the exported GPIO ports
gpio.onchange = valueChanged;

// get some GPIOPort objects
var targets = [
    port18,
    port23,
    port25
];

// export (activate) the ports
targets.forEach(function(port) {
    port.export("in").then(exportSuccess, gpioError);
});

// successfully exported
function exportSuccess() {
    console.log(port.portName + " is exported.");
}

// the value changed
function valueChanged(event) {
    var port = event.port; // GPIOPort object
    var value = event.value; // current value
    console.log(port.portName + ": " + value);
    // if the value of the gpio25 was changed to 1, unexport all of the exported GPIO ports
    if(port.portNumber === 25 && value === 1) {
        gpio.unexportAll(unexportSuccess, gpioError);
    }
}

// successfully unexported
function unexportSuccess() {
    console.log("This process has been terminated.");
}

// Show an error
function gpioError(error) {
    console.log("Error: " + error.message + "(" + port.portName + ")");
}

4.7 Writing a value to a GPIO port

This example shows how to write a value to the specified GPIO port. This example writes 0 or 1 one after the other to the GPIO port "gpio18" at a second interval.

// export (activate) the port.
port.export("out").then(writeValue, gpioError);

// the value to be written
var v = 0;

// successfully exported and write a value
function writeValue() {
    v = v ? 0 : 1;
    port.write(v).then(writeSuccess, gpioError);
}

// the value successfully written
function writeSuccess(value) {
    console.log(port.portName + " was set to " + value);
    window.setTimeout(writeValue, 1000);
}

// Show an error
function gpioError(error) {
    console.log("Error: " + error.message + "(" + port.portName + ")");
}

5 Interfaces

partial interface Navigator {
    Promise<GPIOAccess> requestGPIOAccess ();
};

The requestGPIOAccess() method must be visible on the Navigator object if the script's global object is the Window object, or on the WorkerNavigator object if the script's global object is the WorkerUtils. [HTML5] [WEBWORKERS]

The requestGPIOAccess() method is used to get a GPIOAccess object. The requestGPIOAccess() method is invoked, the user agent must run the steps as follows:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. If the underlying operating system does not support GPIO access, then jump to the step labeled failure below.

  4. Create a GPIOPortMap object, then let map be the newly created GPIOPortMap.

  5. Find all of the GPIO ports available on the underlying operating system. For each of the GPIO port, create a GPIOPort object for the GPIO port, then add it to the map.

    How can the UA find the GPIO ports available on the underlying operating system?

  6. Create a GPIOAccess object, then set the ports attribute to the map.

  7. Let access to be the newly created GPIOAccess at the previous step.

  8. success: Call resolver's accept(value) method with access as value argument. Abort these steps.

  9. failure: Let error be a new DOMExceptions. This must be of type "NotSupportedError". Then call resolver's reject(value) method with error as value argument.

5.2 The GPIOAccess interface

interface GPIOAccess : EventTarget {
    readonly attribute GPIOPortMap    ports;
    Promise  unexportAll();

             attribute EventHandler   onchange;
};

The ports attribute must return the GPIOPortMap object representing all of the GPIO ports available on the underlying operating system.

The unexportAll() method is used to release all of the GPIO ports exported by this API. The unexportAll() method is invoked, the user agent must run the steps as follows:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. Let list be the list of the GPIOPort object whose exported attribute is true.

  4. For each of the GPIOPort object in the list, release (unexport) the related GPIO port, then set the exported attribute to false.

  5. Wait until the previous step for all of the GPIOPort in the list finishes.

  6. If all of the exported GPIO port are released successfully, jump to the step labeled success below. Otherwise, jump to the step labeled failure below.

  7. success: Call resolver's accept() method without any argument. Abort these steps.

  8. failure: Let error be a new DOMExceptions. This must be of type "OperationError". Then call resolver's reject(value) method with error as value argument.

Is unexportAll necessary?

Should GPIOAccess have an EventTarget interface?

The onchange attribute is a event handler invoked when the value of one of the exported GPIO ports changes (i.e. the value changes from 1 to 0 or from 0 to 1). Whenever the event handler is to be invoked, the user agent must run the following steps:

  1. Let port be the GPIOPort object.

  2. Let port value be the value of the GPIO port corresponding to the GPIOPort.

  3. Let event be a newly constructed GPIOChangeEvent, with the value attribute set to the port value, and the port attribute set to the port.

  4. Fire an event named change at the GPIOAccess object, using the event as the event object.

Is onChange needed although there is an EventTarget interface?

Before the change event is fired on the GPIOAccess object, another change event is fired on the GPIOPort object. Therefore, the event handler set on the GPIOAccess is invoked after the event handler set on the GPIOPort object is invoked.

5.3 The GPIOPortMap interface

interface GPIOPortMap {
             attribute readonly unsigned short size;
    function keys (void function (portNumber));
    function values (void function (GPIOPort));
    GPIOPort get (unsigned short key);
};

The size attributes is the number of GPIO ports available on the underlying operating system at the current time.

The keys() method is an iterator for keys. The values() method is an iterator for values.

The get() method is a getter for a GPIOPort. When the get() method is invoked, the GPIO port number must be passed as the first argument. If the first argument is not specified, or it is not valid as a port number, or it does not represent any GPIO port available on the underlying operating system, the get() method must return null. Otherwise, the get() method must return the GPIOPort corresponding to the specified port number.

This interface is used to represent all the currently available GPIO ports as a MapClass-like interface. This interface also act as an associative array:

// Iterate for values
ports.keys( function( portNumber ) {
    var port = ports.get(portNumber);
    console.log(port.pinName + ", " + port.portNumber);
});

// or you could express as:
ports.values( function( port ) {
    console.log(port.pinName + ", " + port.portNumber);
});

// or you could express as:
for (port in ports) {
    console.log(port.pinName + ", " + port.portNumber);
}

This should be Iterable

5.4 The GPIOPort interface

interface GPIOPort : EventTarget {

    readonly attribute unsigned short portNumber;
    readonly attribute DOMString      portName;
    readonly attribute DOMString      pinName;

    readonly attribute DirectionMode  direction;
    readonly attribute boolean        exported;

    Promise  export(DirectionMode direction);
    Promise  unexport();

    Promise  read();
    Promise  write(unsigned short value);

             attribute EventHandler   onchange;
};

enum DirectionMode { "", "in", "out" }

The GPIOPort interface represents a GPIO port assigned to a physical GPIO pin.

The portNumber attribute must return the GPIO port number assigned to the GPIOPort object.

The portName attribute must return the name of the GPIO port. If the name is unknown, the portName attribute must return an empty string.

The pinName attribute must return the name of the GPIO pin. If the name is unknown, the pinName attribute must return an empty string.

The value to portNumber attribute is a port number assigned to a physical pin by the underlying operating system. This value is based on the name of the GPIO port. Note that this value is not based on the name of the physical pin.

In most of operating systems, the name of the physical pin is different from the name of the GPIO port. For example, the pin name "P1-12" on the Raspberry Pi is mapped to "gpio18" on Raspbian Linux. In this case, the value of the portNumber attribute is 18, the value of the portName is a string "gpio18", and the value of the pinName attribute is a string "P1-12".

Are portName and pinName needed? May portMap have such a functionality?

The direction attribute must return either an empty string, "in", or "out". This value is initially an empty string. This value is set to "in", or "out" when the export() method is invoked and successfully completed based on the argument passed to the method.

The exported attribute gives whether the GPIO port has been exported or not. If the GPIO port has been exported, the exported attribute must return true, otherwise false.

The export() method activate the related GPIO port. When the export() method is invoked, the user agent must run the steps as follows:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. If the value of the exported attribute is true, jump to the step labeled success below.

  4. Let direction be the value of the first argument passed to the method.

  5. If direction is neither "in" nor "out", jump to the step labeled failure below.

  6. Activate the related GPIO port in the specified direction mode ("in" or "out"). If succeeded, set the exported attribute to true, then jump to the step labeled success below. Otherwise, jump to the step labeled failure below.

  7. success: Call resolver's accept() method without any argument. Abort these steps.

  8. failure: Let error be a new DOMExceptions. This must be of type "InvalidAccessError" if direction was invalid (i.e. neither "in" nor "out"), "SecurityError" if this operation was denied by the operating system because of some kind of security reason, "OperationError" if this operation was failed because of any reasons other than security reason. Then call resolver's reject(value) method with error as value argument.

The unexport() method deactivates the related GPIO port. When the unexport() method is invoked, the user agent must run the steps as follows:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. If the value of the exported attribute is not true, jump to the step labeled success below.

  4. Deactivate the related GPIO port. If succeeded, set the exported attribute to false, then jump to the step labeled success below. Otherwise, jump to the step labeled failure below.

  5. success: Call resolver's accept() method without any argument. Abort these steps.

  6. failure: Let error be a new DOMExceptions. This must be of type "OperationError". Then call resolver's reject(value) method with error as value argument.

Is unexport necessary?

The read() method reads the value from the related GPIO port. When the read() method is invoked, the user agent must run the steps as follows:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. If the value of the exported attribute is not true, jump to the step labeled failure below.

  4. If the value of the direction attribute is not "in", jump to the step labeled failure below.

  5. Read the value of the related GPIO port. If succeeded, let port value be the value of the related GPIO port. Otherwise, jump to the step labeled failure below.

  6. success: Call resolver's accept(value) method with port value as value argument. Abort these steps.

  7. failure: Let error be a new DOMExceptions. This must be of type "InvalidAccessError" if the value of the exported attribute is not true or the value of the direction attribute is not "in", "OperationError" otherwise . Then call resolver's reject(value) method with error as value argument.

The write() method writes the value passed as the first argument to the related GPIO port. The value must be numeric 0 or 1. When the write() method is invoked, the user agent must run the steps as follows:

  1. Let promise be a new Promise object and resolver be its associated resolver.

  2. Return promise and run the following steps asynchronously.

  3. If the value of the exported attribute is not true, jump to the step labeled failure below.

  4. If the value of the direction attribute is not "out", jump to the step labeled failure below.

  5. Let port value be the value of the first argument passed to this method.

  6. If port value is neither numeric 0 nor 1, jump to the step labeled failure below.

  7. write port value to the related GPIO port. If succeeded, jump to the step labeled success below. Otherwise, jump to the step labeled failure below.

  8. success: Call resolver's accept(value) method with port value as value argument. Abort these steps.

  9. failure: Let error be a new DOMExceptions. This must be of type "InvalidAccessError" if the value of the exported attribute is not true or if the value of the direction attribute is not "out" or if port value is neither 0 nor 1, "OperationError" otherwise . Then call resolver's reject(value) method with error as value argument.

The onchange attribute is a event handler invoked when the value of the GPIO port corresponding to the GPIOPort object changes (i.e. the value changes from 1 to 0 or from 0 to 1). Whenever the event handler is to be invoked, the user agent must run the following steps:

  1. Let port be the GPIOPort object.

  2. Let port value be the value of the GPIO port corresponding to the GPIOPort.

  3. Let event be a newly constructed GPIOChangeEvent, with the value attribute set to port value, and the port attribute set to port.

  4. Fire an event named change at the GPIOPort object, using the event as the event object.

After the change event is fired on the GPIOPort object, another change event will be fired on the GPIOAccess object. Therefore, the event handler set on the GPIOPort is invoked before the event handler set on the GPIOAccess object is invoked.

5.5 The GPIOChangeEvent interface

When a value of a GPIO port changes, if the onchange of the GPIOPort object is set to a function, the GPIOChangeEvent object is passed to the event handler function as the first argument . If the onchange attribute of

[Constructor(DOMString type, optional GPIOChangeEventInit eventInitDict)]
interface GPIOChangeEvent : Event {
    readonly    attribute unsign short value;
    readonly    attribute GPIOPort     port;
};

The value attribute must return the value of the GPIO port corresponding to the GPIOPort object where the change event was fired..

The port attribute must return the GPIOPort object where the change event was fired.

Is GPIOChangeEventInit needed?

5.6 The GPIOChangeEventInit interface

dictionary GPIOChangeEventInit : EventInit {
    unsign short value;
    GPIOPort     port;
};

The value attribute is a value of the related GPIO port.

6 Security considerations

TBD

References

[DOM4]
DOM4, Anne van Kesteren, Aryeh Gregor, Ms2ger, Alex Russell, Robin Berjon. W3C.
[ES6]
ECMA-262 6th Edition. Ecma International.
[HTML5]
HTML5, Robin Berjon, Travis Leithead, Erika Doyle Navara, Edward O'Connor, Silvia Pfeiffer, Ian Hickson. W3C.
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels, S. Bradner. IETF.
[WEBIDL]
Web IDL, Cameron McCormack. W3C.
[WEBWORKERS]
Web Workers, Ian Hickson. W3C.