-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
What illuminance value should be reported for stopped sensor? #15
Comments
SensorReading values are frozen and cannot and should not be modified. They have a timestamp, so that'd be wrong if the value of the illuminance attribute would be modified at any point in time. We should probably clarify this in the spec, so I leave this issue open for that. |
That's actually a generic sensor issue. As @anssiko pointed out, the answer is that a sensor reading object should be immutable. |
My understanding is that |
@tobie, sure, readings are immutable. My question was about expected behaviour for cached readings. As Rick mentioned, als.reading would be null, that is fine, we've implemented as spec says. However, spec doesn't specify what happens if developer caches reading and accesses it after stop is called. Should we return data from the last reading before stop was called? Or maybe we should return initial values that are specified for SensorReadingInit dictionary? |
Well, the SensorReading object is immutable, so the value of its properties stay the same as when they were set. |
So, you mean that Sensor.reading getter would always make a copy of a current reading when sensor is active? |
No. I mean that for each new sensor reading you must construct a new |
Why would it? I would expect JavaScript semantics: let raw = 1;
let reading = {
get illuminance() {
return raw;
}
};
let als = {
reading
};
let cached = als.reading;
console.log(cached.illuminance); // 1
als.reading = null;
console.log(cached.illuminance); // 1 |
@alexshalamov, yeah, the UA must generate a new immutable let als = new AmbientLightSensor();
als.start({frequency: 60 });
let als2 = new AmbientLightSensor();
als2.start({ frequency: 25 });
let cachedReading1 = als.reading; // null
// time passes
let cachedReading2 = als.reading; // { illuminance: 23, timestamp: 87249 }
let cachedReading3 = als.reading; // { illuminance: 23, timestamp: 87249 }
let als2cachedReading = als2.reading; // { illuminance: 23, timestamp: 87249 }
// more time passes
let cachedReading4 = als.reading; // { illuminance: 344, timestamp: 3497979 }
als.stop();
als2.stop();
console.log(cachedReading1.illuminance);
// Uncaught TypeError: Cannot read property 'illuminance' of null(…)
console.log(cachedReading1 === cachedReading2);
// false
console.log(cachedReading2.illuminance);
// 23
console.log(cachedReading3.illuminance);
// 23
console.log(cachedReading2 === cachedReading3);
// true
console.log(als2cachedReading.illuminance);
// 23
console.log(cachedReading2 === als2cachedReading);
// true
console.log(cachedReading4.illuminance);
// 344
console.log(cachedReading3 === cachedReading4);
// false |
@tobie, I understand how you want API to behave, it is just bit confusing, maybe Sensor.reading section needs to be reformulated somehow. Spec says that Sensor.reading points to current reading. Reading is an object, therefore if I write |
Heh. yeah. :) There's a lot of reformulation needed. That said, the update current reading algorithm is quite clear about this.
That's a good point in terms of meeting developer expectations. @rwaldron, WDYT? Should we use method instead, e.g.: sensor.read();
That's not what happens, because if it did: let current_reading = null;
let als = {
get reading() {
return current_reading;
}
};
// poll sensor
current_reading = {
get illuminance() {
return 23;
},
get timestamp() {
return 87249;
}
}
// poll sensor later
current_reading = {
get illuminance() {
return 344;
},
get timestamp() {
return 3497979;
}
} |
@tobie If |
Yes. |
I think the language shouldn't use the word "current", for two reasons:
W/r to |
That's not a term that's exposed to developers, and it's defined in the spec. But I'm happy to change if you find it confusing.
No, I meant sensor.read() as a way to access the latest reading instead of |
+1 for |
@alexshalamov Why would you want to make it return a clone, though? Imho, SensorReading is a similar pattern as events. You want the same reading to be ===. |
@tobie As I mentioned, I would like to avoid to have references to internal data of a sensor object. But either way is fine for me (cloning or making reference). I have no objections if getter will return latest reading and in next update local reference would be If you are planning to change Sensor.reading to sensor.getLatestReading(), semantics will be slightly different, for example, after stop() is called, should sensor.getLatestReading() return null? If not, what happens if permission is revoked. Anyways, your explanation about how you want API to behave was helpful and few fixes need to be made to implementation to align with the spec. Thanks. |
I'm waiting for feedback from @rwaldron on having this be a method rather than an attribute getter. |
Attributes should not return a new object every time (pls. see https://w3ctag.github.io/design-principles/#attributes-like-data ) so method looks more preferable. |
@pozdnyakov third list item of the linked section of the API design doc says the following (emphasis mine):
This describes pretty much exactly the wanted behavior, where the 'logical "reset"' corresponds to a new sensor reading. |
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 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: // 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 Empirically speaking, I doubt that devs will write code like 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 (Warning: I'm only presenting this as a thought exercise, not as a proposed solution) // 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:
|
The design principles you mention actually describe precisely what it is you're suggesting below. Incidentally, that's also how it's already spec'ed. :)
Yup. There's a new I'm glad we're all on the same page. I'll edit the spec to clarify what's already there. |
Ok, that's what I thought forever... I must've turned myself around while I was reading this thread, the spec text and writing my way-too-long response. 0_o |
Consider the case when sensor is started and then developer caches reading object.
At the moment I've implemented it to return 0.0, is this expected behaviour?
The text was updated successfully, but these errors were encountered: