Skip to content

Instantly share code, notes, and snippets.

@Symbitic
Last active September 17, 2024 16:50
Show Gist options
  • Save Symbitic/7e7f0722da6357aaa2d5a2a2df999c4d to your computer and use it in GitHub Desktop.
Save Symbitic/7e7f0722da6357aaa2d5a2a2df999c4d to your computer and use it in GitHub Desktop.
Sensor Discovery API

Sensor Discovery API

This describes a generic framework for discovering and using sensors and devices. It is composed primarily of two parts: a new SensorBackend class for providing sensor values, and a SensorRegistry class for registering backends. It also proposes several related interfaces: SensorReading, SensorBackend, SensorBackendConstructor, and extensions to the Generic Sensor API to support backends.

This is a follow-up to my previous Sensor Discovery API proposal which should no longer be actively considered.

Names

Naming things is considered one of the hardest problems in computer science, and this preliminary spec was no exception. I had to decide on a name for the API, whether or not to use Sensor to mean any device and not just a sensor, whether to create a separate Device interface and use a term that unifies Sensor and Device, and if so, what its name should be.

I eventually decided to use Sensor as this document describes extensions and additions to the existing Generic Sensor API. I forsee names being changed or new interfaces being designed (such as Device to encapsulate both sensor devices and actuator devices).

Next, I had to decide on a term for the interface between a Sensor and a Sensor Discovery API. Some potential candidates were: SensorDriver, SensorSource, SensorManager, and SensorController. After my previous proposal, this redesigned spec is designed to be less like low-level drivers and something more high-level and secure. I eventually choose the name SensorBackend as the primary interface name.

SensorReading

SensorReading provides the interface between a SensorBackend and Sensor. Simply put, a SensorReading is the data provided by each Sensor instance. As such, it will be unique for every sensor. SensorReading provides data to populate the members of the Sensor class the Backend provides.

There is only one property shared by all SensorReading interfaces: timestamp, which provides the time of the most recent sensor reading.

Some example SensorReading definitions for current Sensor classes:

interface SensorReading {
    timestamp: DOMHighResTimeStamp;
}

interface AccelerometerReading extends SensorReading {
    x: number;
    y: number;
    z: number;
}

interface GyroscopeReading extends SensorReading {
    x: number;
    y: number;
    z: number;
}

interface OrientationReading extends SensorReading {
    quaternion: [number, number, number, number];
}

interface AmbientLightReading extends SensorReading {
    illuminance: number;
}

A SensorReading will be provided to a Sensor instance using the setReading method of the SensorBackend class. If the reading value does not match the format expected by the sensor, an error should be thrown.

SensorBackend

A SensorBackend class provides data to a Sensor using its setReading class. When it does so, the Sensor class will update its properties (x, y, z, etc) and emit the "reading" event.

This interface has two primary methods: setReading and setError. setReading indicates to the Sensor that a reading is available and should be handled accordingly. setError indicates to the Sensor that an error has occured and should be handled accordingly.

There is only one event: "activate", which tells the backend the sensor has started or stopped. The Sensor class sends it to the SensorBackend whenever the sensor has been activated or is no longer activated. There should also be a corresponding optional onactivate member.

declare class SensorBackend<T extends SensorReading = SensorReading> extends EventTarget {
    onactivate: () => void;
    setReading(reading: T): void;
    setError(error: SensorErrorEvent): void;
}

Generic Sensor extensions

For this to work, the Generic Sensor API should be extended so that it and every interface that extends it should support an optional backend parameter in the constructor parameter. It should also provide a setBackend method to allow changing the backend that will provide SensorReading values. This will allow existing global sensor classes such as Accelerometer to continue functioning while also being compatible with the new Sensor Discovery features.

If setBackend is used after a sensor has already been activated, it should throw an error. If backendName has not been registered with SensorRegistry when a backend is set, an error should be thrown. The backend property is readonly and may only be changed by the Sensor.

There should be a new SensorType for the type property of the sensor class. type is readonly and should only be set by the Sensor class. It is used to differentiate different classes of sensors when constructing a backend.

type SensorType = "Accelerometer" | "Gyroscope" | "Battery" | DOMString;

interface SensorOptions {
    frequency?: number | undefined;
    backend?: DOMString | undefined;
}

declare class Sensor extends EventTarget {
    readonly type: SensorType;
    readonly backend: string;

    constructor(options?: SensorOptions);
    
    setBackend(backendName: string): void;
}

SensorRegistry

A SensorRegistry handles registering SensorBackends and providing them to sensors.

There is only method in SensorRegistry: register, which registers an interface for constructing a backend. It also has only one readonly property: backend, which contains a list of every backendName which has been registered.

register accepts a SensorBackendConstructor interface which takes care of providing SensorBackend instances on-demand when requested by the Sensor. Its primary method is createBackend, which returns a SensorBackend for the SensorType requested and throws an error if a backend cannot be constructed. There is also a backendTypes method which returns the types property of a SensorBackendConstructor interface. When there is no backend matching the name given, it should return an empty array.

In addition, SensorBackendConstructor also provides a readonly backends array listing every backendName that has been registered thus far. Applications may call backendTypes for every value listed in this array.

Similar to CustomElements, there should be a global sensorRegistry instance of SensorRegistry.

interface SensorBackendConstructor {
    readonly types: SensorType[];

    createBackend(type: SensorType): Promise<SensorBackend>;
}

interface SensorRegistry {
    readonly backends: string[];
    
    backendTypes(backendName): string[];

    register(backendName: string, backendConstructor: SensorBackendConstructor);
}

Example

Here is an example of defining a custom Accelerometer backend and registering it.

class DummyAccelerometerBackend extends SensorBackend {
    constructor(interval) {
        this.interval = interval;
    }

    onactivate() {
        this.timerId = setInterval(() => {
	    const reading = {
	        x: 0,
		y: 9.5,
		z: 0,
		timestamp: performance.now()
	    };
	    this.setReading(reading);
	}, this.interval);
    }
}

class DummyAccelerometer extends SensorBackendConstructor {
    constructor() {}

    readonly types: string[] = ["Accelerometer"];
    
    async createBackend(type: SensorType) {
        if (sensorType === "Accelerometer") {
	    return new DummyAccelerometerBackend(100);
	}
	throw new Error(`Cannot create a backend for sensor type "${type}"`);
    }
}

sensorRegistry.register("dummy", new DummyAccelerometer);

const accelerometer = new Accelerometer({ backend: "dummy" });
// OR
const accelerometer = new Accelerometer();
accelerometer.setBackend("dummy");

Future Work

I admit I'm not a fan of the name SensorBackendConstructor. I'd prefer something besides Constructor.

The current proposal has SensorType being passed as the only parameter to createBackend, but it might be better to just pass the whole Sensor instance.

Should SensorBackendConstructor be an interface or an actual ctor passed to register()?

Acknowledgements

Much of the API for this revised proposal comes from the excellent Qt Sensors module. It provided the inspiration for a much safer and more user-friendly Web API than the last proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment