1. Introduction
Increasingly, sensor data is used in application development to enable new use cases such a geolocation, counting steps or head-tracking. This is especially true on mobile devices where new sensors are added regularly. It is also increasinly common in networked objects which are part of the Internet of Things.
Exposing sensor data to the Web has so far been both slow-paced and ad-hoc. Few sensors are already exposed to the Web. When they are, it is often in ways that limit their possible use cases (for example by exposing abstractions that are too high-level and which don’t perform well enough). APIs also vary greatlty from one sensor to the next which increases the cognitive burden of Web application developers and slows development.
The goal of the Generic Sensor API is to promote consistency across sensor APIs, enable advanced use cases thanks to performant low-level APIs, and increase the pace at which new sensors can be exposed to the Web by simplifying the specification and implementation processes.
2. Terminology
A sensor measure different physical quantities and provide corresponding output data which is a source of information about the user and their environment.
Known, predictable discrepancies between sensor output data and the corresponding physical quantities being measured are corrected through calibration,
Known but unpredictable discrepancies need to be addressed dynamically through a process called sensor fusion.
Different sensor types measure different physical quantities such as temperature, air pressure, heart-rate, or luminosity.
For the purpose of this specification we distinguish between high-level and low-level sensor types.
Sensor types which are characterized by their implementation are refered to as low-level sensors. For example a Gyroscope is a low-level sensor type.
Sensors named after their output data,
regarless of the implementation,
are said to be high-level sensors.
For instance, geolocation sensors provide information about the user’s location,
but the precise means by which this data is obtained
is purposefully left opaque
(it could come from a GPS chip, network cell triangulation,
wifi networks, etc. or any combination of the above)
and depends on various, implementation-specific heuristics. High-level sensors are generally the fruits of
applying algorithms to low-level sensors—
That said, the distinction between high-level and low-level sensor types
is somewhat arbitrary and the line between the two is often blurred.
For instance, a barometer, which measures air pressure,
would be considered low-level for most common purposes.
As, even though it is the product of the sensor fusion of
resistive piezo-electric pressure and temperature sensors,
exposing the sensors that compose it would serve no practical purpose
(who cares about the temperature of a piezo-electric sensor?).
A pressure-altimeter would probably fall in the same category,
while a non-descript altimeter—
Because the distinction is somewhat blurry, extensions to this specification (see §7 Extensibility) are encouraged to provide domain-specific definitions of high-level and low-level sensors for the given sensor types they are targeting.
The output data of sensors can be combined with the output of other sensors through a process called sensor fusion. This process provides higher-level or more accurate data (often at the cost of increased latency). For example, the output of a three-axis magnetometer needs to be combined with the output of an accelerometer to provide a correct bearing. Sensor fusion can be carried out at either the hardware or software level.
Note: sensors created through sensor fusion are sometimes
called virtual or synthetic sensors.
However, the specification doesn’t make any practical differences between them,
preferring instead to differentiate sensors as to whether they describe
the kind of output data produced--these are high-level sensors—
TODO: add a section about reading from a sensor and how this is exposed as an asynchonous operation.
3. An note on Feature Detection of Hardware Features
This section is non-normative.
Feature detection is an established Web development best practice. Resources on the topic are plentiful on and offline and the purpose of this section is not to discuss it further, but rather to put it in the context of detecting hardware-dependent features.
Consider the below feature detection examples:
if (typeof Gyroscope === "function") { // run in circles... } if ("PromimitySensor" in window) { // watch out! } if (window.AmbientLightSensor) { // go dark... } // etc.
All of these tell you something about the presence and possible characteristics of an API. They do not tell you anything, however, about whether that API is actually connected to a real hardware sensor, whether that sensor works, if its still connected, or even whether the user is going to allow you to access it. Note you can check the latter using the Permissions API [permissions].
In an ideal world, information about the underlying status would be available upfront. The problem with this is twofold. First, getting this information out of the hardware is costly, in both performance and battery time, and would sit in the critical path. Secondly, the status of the underlying hardware can elvolve over time. The user can revoke permission, the connection to the sensor be severed, the operating system may decide to limit sensor usage below a certain battery threshold, etc.
Therefore, an effective strategy is to combine feature detection, which checks whether an API for the sought-after sensor actually exists, and defensive programming which includes:
-
checking for error thrown when instantiating a Sensor object,
-
listening to errors emitted by it,
-
setting an appropriate timeout for your particular usecase,
-
handling all of the above graciously so that the user’s experienced is enhanced by the possible usage of a sensor, not degraded by its absence.
if (typeof GeolocationSensor === "function") { try { let sensor = new GeolocationSensor({ timeout: 3 * 1000 // 3 seconds }); sensor.onerror = error => gracefullyDegrade(error); sensor.onchange = data => updatePosition(data.coords); } catch(error) { gracefullyDegrade(error); } } else { gracefullyDegrade(); }
4. Model
This section is non-normative.
The Generic Sensor API is designed to make the most common use cases straightforward while still enabling more complex use cases.
Most devices deployed today do not carry more than one sensor of each type. This shouldn’t come as a surprise since use cases for more than a sensor of a given type are rare and generally limited to specific sensor types such as proximity sensors.
The API therefore makes it easy to interact with the device’s default (and often unique) sensor for each type simply by instantiating the corresponding Sensor subclass.
Indeed, without specific information identifying a particular sensor of a given type, the default sensor is chosen.
let sensor = new GeolocationSensor({ accuracy: "high" }); sensor.onchange = function(event) { var coords = [event.data.latitude, event.data.longitude]; updateMap(null, coords, event.data.accuracy); }; sensor.onerror = function(error) { updateMap(error); };
Similarly, getting a single output data sample should be a simple process, and it is:
GeolocationSensor.requestData({ accuracy: "high" }) .then(reading => { displayCoords(reading.coords); }) .catch(err => console.log(err));
Note: extension to this specification may choose not to define a default sensor when doing so wouldn’t make sense. For example, it might be difficult to agree on an obvious default sensor for proximity sensors.
In cases where multiple sensors of the same type may coexist on the same device, specification extension will have to define ways to uniquely identify each one.
DirectTirePressureSensor.requestData({ position: "rear", side: "left" }) .then(reading => { display(reading.pressure); }) .catch(err => console.log(err));
4.1. Enumerating Sensors
Sensor discoverability becomes particularly important as the number of sensor grows. Web application developers need to find out what kind of sensor is on a given device and what their capabilities are.This feature is at risk and might be moved to a subsequent version of this specification.
5. API
5.1. The Sensor Interface
A Sensor object has an associated sensor.
A Sensor object observes the changes in its associated sensor at regular intervals and reports those values by firing DOM events.
frequency is measured in hertz (Hz).
TODO: define the following concepts
-
[Constructor(optional SensorOptions sensorOptions), Exposed=(Window,Worker)] interface Sensor : EventTarget { static Promise<SensorReading> requestData(RequestDataOptions requestDataOptions); attribute double frequency; attribute boolean batch; readonly attribute SensorInfo info; attribute TresholdCallback? treshold; attribute double timeout; attribute boolean wakeup; readonly attribute SensorReading? value; readonly attribute SensorReading[]? values; attribute EventHandler onerror; attribute EventHandler ondata; attribute EventHandler onchange; attribute EventHandler oncalibration; }; dictionary SensorOptions { DOMString? sensorId; double? frequency; boolean? batch = false; TresholdCallback? treshold; double? timeout; }; dictionary RequestDataOptions { DOMString? sensorId; double? frequency; boolean? batch = false; double? timeout; boolean? fromCache = false; }; callback TresholdCallback = boolean (SensorReading currentValue, SensorReading newValue);
5.1.1. Sensor Constructor
The Sensor() constructor must run these steps:
-
If the incumbent settings object is not a secure context, then:
-
throw a SecurityError.
-
-
If sensorOptions.sensorId is specified, then:
-
Otherwise, if identifying parameters in sensorOptions are set, then:
-
If these identifying parameters allow a unique sensor to be identified, then:
-
Otherwise, throw a TypeError.
-
-
Otherwise, if a default sensor exists for this sensor type:
-
Otherwise, throw a TypeError.
-
Set sensor’s reading attribute to
null
. -
return sensor.
-
Run these substeps in parallel:
-
If permission is not granted, queue a task to fire an event named error on sensor, and terminate these substeps.
-
If cached SensorReadings are available,
-
let latest_reading be the most recent of those SensorReadings.
-
set the value of sensor’s reading attribute to latest_reading.
-
-
run the read steps for sensor.
-
5.1.2. Sensor.frequency
The frequency at which the read steps are run.
Copy pasted from @domenic's original issue rwaldron/sensors#5
For games and other animation frame loop-based situations (off the top of my head, accelerometer-based scrolling comes to mind) you want one value per animation frame.
Chrome is actually doing work specifically to integrate our scheduling of input events and animation frames, see Blink Scheduler design doc.
Off the top of my head,
frequency: "animationframe"
might be good. The contract being that, if you pass that in to the constructor, then givenrequestAnimationFrame(function frame() { console.log(mySensor.value); requestAnimationFrame(frame); });you will get an updated value every frame.
I don't think
frequency: 60
is quite the same thing (but I could be wrong; will try to tag in some Blink engineers) since animation frames often vary away from 60 Hz and so you will quickly get out of sync.Posted to blink-dev: https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/fLucJ2QH3fA unsure whether people will follow-up here or there.
Proposed resolution: Enabling developers to tie the frequency of sensor updates to animation framerate is a desirable feature. Further research is needed to see whether or not that is implementable.
Actions:
- research whether sensor update frequency can be tied to animation framerate.
- design the API so that developers can indicate the sensors update frequency must be framerate dependent.
5.1.3. Sensor.batch
true
if batch mode was requested, false otherwise.
5.1.4. Sensor.info
Returns the related SensorInfo object.
5.1.5. Sensor.treshold
5.1.6. Sensor.timeout
5.1.7. Sensor.wakeup
5.1.8. Sensor.value
The value attribute of must always point to the latest SensorReading whatever the frequency so that the value attribute of two instances of the same Sensor interface associated with the same sensor hold the same SensorReading during a single event loop turn.
5.1.9. Sensor.values
Even when doing realtime processing (eg. say at 60 Hz), there is a benefit to having more than one new value every time you process (eg. Oculus polls a gyroscope at 1000 Hz for head tracking). In this case, every time you process, you'll have [1000/60] ~= 16 new sensor data.
This seems like a desirable feature. How would you handle this though?
Is this (yet) an extra option you set? How does that work with sensors with multiple values, etc.?
/cc @borismus
Proposed resolutions: Some use cases have high data frequency requirements which might cause performance and/or memory constraints. Further research is needed to assess and determine how to satisfy those requirements.
Further actions:
- List use cases with high data frequency requirements.
- Research the performance costs of crossing the JS context to justify batching requirements (talk with implementors).
- Decide on a solution to expose the batching in the Sensor API.
- Devise a strategy to handle huge backlog of historical data.
- Decide whether performance and/or memory constraints justify making the batch mode opt-in or whether it can just be the normal operational mode.
5.1.10. Sensor.onerror
5.1.11. Sensor.ondata
5.1.12. Sensor.onchange
5.1.13. Sensor.oncalibration
5.1.14. Event handlers
The following are the event handlers (and their corresponding event handler event types) that MUST be supported as attributes by the objects implementing the Sensor interface:
event handler | event handler event type |
---|---|
ondata
| data
|
onchange
| change
|
onerror
| error
|
oncalibration
| calibration
|
5.2. The SensorReading Interface
A SensorReading represents the state of a sensor at a given point in time.
interface SensorReading { readonly attribute DOMHighResTimeStamp timeStamp; readonly attribute SensorInfo info; };
5.2.1. SensorReading.timeStamp
Returns a timestamp of the time at which the read steps was carried out expressed in milliseconds that passed since the time origin.
5.2.2. SensorReading.info
Returns the sensor object the reading is taken from.
5.3. The Sensors Interface
This feature is at risk.
The Sensors interface represents a container for a list of SensorInfo objects.
It is exposed on Window and Workers as the Window.sensors
and WorkerGlobalScope.sensors
attribute respectively.
TODO: make this explicietly about local sensors and allow supporting enumeration of remote sensors through added parameters.
[Constructor, Exposed=(Window,Worker)] interface Sensors { Promise<sequence<SensorInfo>> matchAll(optional MatchAllOptions options); }; partial interface Window { [SameObject] readonly attribute Sensors sensors; }; partial interface WorkerGlobalScope { [SameObject] readonly attribute Sensors sensors; }; dictionary MatchAllOptions { DOMString type; boolean? remote = false; };
5.3.1. Sensors.matchAll
Returns a promise which resolves to an array of SensorInfo objects representing all available local(?) sensors.
sensors.matchAll({ type: "proximity", position: "rear" }).then(function(sensors) { let sensor_info = sensors[0]; if (!sensor_info) return; let sensor = new ProximitySensor({ sensorId: sensor_info.id }); sensor.onchange = dostuff; });
5.4. The SensorInfo Interface
This feature is at risk.
The SensorInfo interface is a lightweight object that represents an actual physical sensor. Concrete sensor implementation will need to subclass it.
[Constructor(optional DOMString id, optional SensorInit sensorInitDic), Exposed=(Window,Worker)] interface SensorInfo { readonly attribute DOMString id; readonly attribute boolean isDefault; }; dictionary SensorInit { boolean isDefault; };
5.4.1. SensorInfo.id
Returns the id of the sensor. This is an opaque DOMString.
5.4.2. SensorInfo.isDefault
Returns true if the sensor is the default sensor of that type on the device, false otherwise.
6. Security and privacy considerations
TODO: gather general security and privacy risks common to all sensors.
6.1. Secure Context
Sensor data is explicitely flagged by the Secure Contexts specification [powerful-features] as a high-value target for network attackers. As such, sensor data should only be available within a secure context.
6.2. Obtaining Explicit User Permission
Make sure the API is compatible with sensors which need to ask the user's permission before being used.
Proposed resolutions:
- The Generic Sensor API must be compatible with an async permissioning model.
Actions:
- Identify the best solution to communicate that the permission to access the sensor has been denied by the user agent.
7. Extensibility
This section is non-normative.
Its purpose is to describe how this specification can be extended to speficy APIs for different sensor types.
Extension specifications are encouraged to focus on a single sensor type, exposing both high and low level as appropriate.
7.1. Naming
Sensor interfaces for low-level sensors should be
named after their associated sensor.
So for example, the interface associated with a gyroscope
should be simply named Gyroscope
. Sensor interfaces for high-level sensors should be
named by combining the physical quantity the sensor measures
with the "Sensor" suffix.
For example, a sensor measuring
the distance at which an object is from it
may see its associated interface called ProximitySensor
.
Attributes of the SensorReading subclass that
hold output data should be named after the full name of this output data.
For example, the TemperatureSensorReading
interface should hold
the value of the sensor’s output data in
a temperature
attribute (and not a value
or temp
attribute).
7.2. Exposing High-Level vs. Low-Level Sensors
So far, specifications exposing sensors to the Web platform have focused on high-level sensors APIs. [geolocation-API] [orientation-event]
This was a reaosnable approach for a number of reasons. Indeed, high-level sensors:
-
convey developer intent clearly,
-
do not require intimate knowledge of how the underlying hardware sensors functions,
-
are easy to use,
-
may enable the User Agent to make significant performance and battery life improvements,
-
help avoid certain privacy and security issues by decreasing the amount and type of information exposed.
However, an increasing number of use cases such as virtual and augmented reality [sensor-use-cases] require low-level access to sensors, most notably for performance reasons.
Providing low-level access enables Web application developers to leverage domain-specific constraints and design more performant systems.
Following the precepts of the Extensible Web Manifesto [EXTENNNNSIBLE], extension specifications should focus primarily on exposing low-level sensor APIs, but should also expose high-level APIs when they are clear benefits in doing so.
7.3. When is Enabling Multiple Sensors of the Same Type Not the Right Choice?
TODO: provide guidance on when to:
-
allow multiple sensors of the same type to be instantiated,
-
create different interfaces that inherit from Sensor,
-
add constructor parameters to tweak sensors settings (e.g. setting required accuracy).
7.4. Defining a default
TODO: provide guidance on how and when to set a default sensor.
7.5. Calibration
Output data emitted by Sensor objects should always be calibrated.
7.6. Extending the Permission API
Provide guidance on how to extend the Permission API [permissions] for each sensor types.
Accessing sensor data has multiple privacy implications and is therefore subject to permissioning. The Permission API allows developers to check whether the permission to access a given API was already granted (or denied) by the user on a per origin basis.
Currently, exposing that status through the Permissions API requires a modification of the Permission API spec itself. Ideally, we'd want sped editors authoring concrete sensor specs to be able to extend the Permission API within the same document.
This needs concertation with the editors of the Permissions API.
Actions:
- Liaise with @mounirlamouri and @marcoscaceres
- Investigate relationship to Permissions API.
- Try to find a solution that doesn't imply a Permissions API update for every concrete Sensor spec.
-
Email @public-script-coord about
partial enum
proposal - Wait for @public-script-coord feedback and document it in Extensibility section accordingly.
7.7. Example WebIDL
Here’s example WebIDL for a possible extension of this specification for proximity sensors.
[Constructor(optional ProximitySensorOptions proximitySensorOptions), Exposed=(Window,Worker)] interface ProximitySensor : Sensor { readonly attribute ProximitySensorReading? value; readonly attribute ProximitySensorReading[]? values; }; interface ProximitySensorReading : SensorReading { readonly attribute unrestricted double distance; }; dictionary ProximitySensorOptions : SensorOptions { double? min = -Infinity; double? max = Infinity; ProximitySensorPosition? position; ProximitySensorDirection? direction; }; enum ProximitySensorPosition { "top-left", "top", "top-right", "middle-left", "middle", "middle-right", "bottom-left", "bottom", "bottom-right" }; enum ProximitySensorDirection { "front", "rear", "left", "right", "top", "bottom" };
8. Acknowledgements
The following people have greatly contributed to this specification through extensive discussions on GitHub: Anssi Kostiainen, Boris Smus, Claes Nilsson, Dave Raggett, davidmarkclements, Domenic Denicola, Dominique Hazael-Massieux, fhirsch, Francesco Iovine, gmandyam, Jafar Husain, Johannes Hund, Kris Kowal, Marcos Caceres, Mats Wichmann, Matthew Podwysocki, pablochacin, Remy Sharp, Rich Tibbett, Rick Waldron, Rijubrata Bhaumik, robman, Sean T. McBeth, smaug----, and zenparsing.
We’d also like to thank Anssi Kostiainen,Erik Wilde, and Michael[tm] Smith for their editorial input.
Conformance
Document conventions
Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. 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 RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.
All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]
Examples in this specification are introduced with the words "for example"
or are set apart from the normative text with class="example"
,
like this:
Because this document doesn’t itself define APIs for specific sensor types—
Informative notes begin with the word "Note" and are set apart from the
normative text with class="note"
, like this:
Note, this is an informative note.
Conformant Algorithms
Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.
Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.
Conformance Classes
A conformant user agent must implement all the requirements listed in this specification that are applicable to user agents.