W3C home > Mailing lists > Public > public-device-apis-log@w3.org > November 2016

Re: [ambient-light] What illuminance value should be reported for stopped sensor?

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
Message-ID: <issue_comment.created-257926023-1478105496-sysbot+gh@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

This archive was generated by hypermail 2.4.0 : Monday, 4 July 2022 12:47:52 UTC