- From: Rick Waldron via GitHub <sysbot+gh@w3.org>
- Date: Wed, 02 Nov 2016 16:51:38 +0000
- To: public-device-apis-log@w3.org
> No, I meant sensor.read() as a way to access the latest reading
instead of Sensor.reading
> ...
> sensor.getLatestReading();
Using a method of the sensor instance to get a data value that already
exists (it must, because the value was delivered by whatever
subsystem provides it and an event was emitted to signal that
delivery), then the API becomes misleading: for `sensor.read()` you
could imagine someone seeing that call and thinking that it was
actually making a new "read" measurement and returning that value.
Subjectively, `sensor.getLatestReading()` is an eyesore—one that
doesn't pay for itself if you consider the context.
J5 avoids creating this accessor reference "problem" by defining the
accessor property that will expose the latest reading value directly
on the instance, eg. if applied here: `sensor.illuminance`. The event
objects contain a property whose value is just a copy of the
measurement value (no accessor). J5 attempts to clearly separate data
and behavior, as properties and methods which are defined directly on
the instance (not on a "nested" property). For instances of `Multi`
class, which represent chips that provide a "collection" of sensors,
we expose the component sensor instances as values of getter accessor
properties, ie:
```js
// The BME280 provides barometer, hygrometer and thermometer sensors
var multi = new Multi({ controller: "BME280" });
// In the following, the properties: barometer, hygrometer and
thermometer,
// are all getter accessors that return an instance of their
respective sensor class:
multi.barometer instanceof Barometer;
multi.hygrometer instanceof Hygrometer;
multi.thermometer instanceof Thermometer;
// And the data properties of each of those instances are also getter
accessors,
// which return the relevant data value:
multi.barometer.pressure;
multi.hygrometer.relativeHumidity;
multi.thermometer.celsius;
```
I'm not offering this as a suggestion to solve the issue at hand, but
instead to lay the groundwork for my next statement: The `IMU` and
`Multi` class (which are designed as shown above, were introduced in
December 2014, and were rolled out at a workshop (CodeMash 2015)
attended by ~90 people (a recap:
http://briangenisio.com/software/2015/01/12/codemash-nodebots-recap.html).
Code that uses instances of these classes has appeared in tutorials
and articles, including several on Sparkfun.com. In that
almost-two-years time, no issues have been filed, no support questions
in gitter, no twitter critics, no colleagues, no core (or fringe)
committers, not one single mention of the accessor properties being
somehow unexpected behavior, or creating surprising references.
Empirically speaking, I doubt that devs will write code like `let
cache = sensor.reading`, when the data they actually want to cache is
at `let cache = sensor.reading.theActualThingICareAbout`.
If none of that is compelling enough to "leaving it as is", another
approach might be considered: implementations don't need to use
traditional attribute getter accessor properties (which can used to
produce a reference) to expose a value that's asynchronously updated.
If you're willing to make a minor break from the dogmatic "API Design
Principles", and allow the `sensor.reading` property to return a new
`SensorReading` object when there is a new measurement, then sensors
can be specified and implemented in terms of Proxy objects:
(_Warning: I'm only presenting this as a thought exercise, not as a
proposed solution_)
```js
// Start: Imaginary data source mechanism
const timeOfOrigin = Date.now();
const imaginaryDataSource = new Map();
const simulateMeasurementCreateSensorReading = (illuminance) => {
imaginaryDataSource.set("reading", new SensorReading({ illuminance
}));
};
class SensorReading {
constructor(data) {
this.timeStamp = Date.now() - timeOfOrigin;
Object.assign(this, data);
}
}
// End: Imaginary data source mechanism
// Will be used for some private state
const priv = new WeakMap();
// Partial simulator/host implementation
class Sensor {
constructor(settings) {
let reading = null;
const state = {
isActive: false,
};
const proxy = new Proxy(this, {
get: (target, name) => {
if (target === this && name === "reading") {
// Only updates when active...
if (state.isActive) {
reading = imaginaryDataSource.get("reading");
}
return reading;
}
// Proxy trap handlers for prototype methods
if (typeof target[name] === "function") {
return target[name];
}
// console.log(target);
},
});
// Register private state in weakmap side table
priv.set(proxy, state);
return proxy;
}
start() {
priv.get(this).isActive = true;
}
stop() {
priv.get(this).isActive = false;
}
}
let cached1 = null;
let cached2 = null;
let cached3 = null;
let sensor = new Sensor();
sensor.start();
// initial reading is null
simulateMeasurementCreateSensorReading(null);
// Proxy objects "present" themselves as their target would:
console.log(sensor instanceof Sensor);
console.log(Object.getPrototypeOf(sensor) === Sensor.prototype);
console.log(typeof sensor.reading === "object");
console.log(sensor.reading instanceof SensorReading);
// Even though there's no actual reading data, these are still the
// same object for the life of the initial execution turn.
console.log(sensor.reading === sensor.reading);
// And the value of "illuminance" is null when first initialized...
console.log(typeof sensor.reading.illuminance === "object");
console.log(sensor.reading.illuminance === null);
// Some turn later, an illuminance reading has been taken and
// delivered by the host...
/* ---- Execution turn boundary ----- */
simulateMeasurementCreateSensorReading(1);
// ...And now, this value is a number...
console.log(sensor.reading.illuminance === 1);
console.log(typeof sensor.reading.illuminance === "number");
// So a program could cache this attribute's value, which
// is an object representing "reading"; a "reading" is
// a SensorReading object containing the latest measurement
// data delivered by the host.
cached1 = sensor.reading;
// And it would match throughout _this_ turn.
console.log(cached1.illuminance === sensor.reading.illuminance);
// In this turn, sensor.reading attribute's value will always be the
same SensorReading object
console.log(sensor.reading === sensor.reading);
// Some execution turn later, the data source is updated with data
from a new measurement.
// A new measurement should intuitively create a new SensorReading
object...
// Which opposes the design rules stated here:
// https://w3ctag.github.io/design-principles/#attributes-like-data
/* ---- Execution turn boundary ----- */
simulateMeasurementCreateSensorReading(2);
// The previously cached SensorReading object does not contain a copy
of any
// get accessor, so the value of that cached SensorReading object's
illuminance property
// is the value that was present when the cache assignment occurred.
console.log(cached1.illuminance !== sensor.reading.illuminance);
// cached1.illuminance is still 1
console.log(cached1.illuminance === 1);
// And the new SensorReading object's illuminance property has a value
of 2
console.log(sensor.reading.illuminance === 2);
// I would also expect that a new measurement would produce a new
SensorReading object,
// so intuitively, cached !== sensor.reading
console.log(cached1 !== sensor.reading);
// But until a new reading In this turn, sensor.reading attribute's
value will always be the same SensorReading object
console.log(sensor.reading === sensor.reading);
// Since there is no set() trap for "reading",
// this is meaningless:
sensor.reading = 1;
// Cache this SensorReading object...
cached2 = sensor.reading;
// Is it possible for more then one measurement to occur in a
// single execution?
simulateMeasurementCreateSensorReading(3);
// Since a _new_ measurement was delivered by the host, sensor.reading
// produces a _new_ SensorReading object.
console.log(cached2 !== sensor.reading);
// Of course that will remain equal to itself until a new measurement
// is delivered...
console.log(sensor.reading === sensor.reading);
// cached2.illuminance is still 2
console.log(cached2.illuminance === 2);
// And the new SensorReading object's illuminance property has a value
of 3
console.log(sensor.reading.illuminance === 3);
// So, let's cache this last reading before we proceed...
cached3 = sensor.reading;
// But then, _this_ sensor is stopped...
sensor.stop();
// ...And also a new execution turn has been entered...
/* ---- Execution turn boundary ----- */
simulateMeasurementCreateSensorReading(4);
// But sensor.reading is no longer returning new
// SensorReading objects, even though the host
// just delivered a new measurement...
console.log(cached3 === sensor.reading);
// cached3.illuminance is still 3
console.log(cached3.illuminance === 3);
// And sensor.reading still returns the SensorReading object
// from before the sensor was stopped.
console.log(sensor.reading.illuminance === 3);
```
Notes about the above code:
- Execute in a JS runtime that supports the thing necessary modern ES
things.
- All of the expressions will evaluate to `true`.
--
GitHub Notification of comment by rwaldron
Please view or discuss this issue at
https://github.com/w3c/ambient-light/issues/15#issuecomment-257926023
using your GitHub account
Received on Wednesday, 2 November 2016 16:51:44 UTC