- 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