feat: Define measurement units using topic metadata#3776
feat: Define measurement units using topic metadata#3776didier-wenzek merged 5 commits intothin-edge:mainfrom
Conversation
Robot Results
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
| supported_operations: SupportedOperations, | ||
| pub operation_handler: OperationHandler, | ||
|
|
||
| units: Units, |
There was a problem hiding this comment.
Wondering if we should keep a meta wrapper at this level which internally contains the unit definitions, instead of keeping units directly here, since units is just one aspect of the metadata.
There was a problem hiding this comment.
Possibly. However, this can easily be added later when actually required.
| pub fn set_unit(&mut self, measurement: String, meta: serde_json::Value) { | ||
| if let serde_json::Value::String(unit) = meta { | ||
| // "Temperature": "°C" | ||
| self.units.insert(measurement, unit); |
There was a problem hiding this comment.
Not quite sure about this case where we assume the value to be units, if we expect meta to be expanded beyond units in future. But, if we're pretty confident that unit is the most reasonable "default" meta for a measurement, then this is fine.
There was a problem hiding this comment.
Yeah. Better to remove support for this shortcut. Otherwise we will have to support it sooner or later, even if not documented ;-)
| #[derive(Default)] | ||
| pub struct Units { | ||
| units: HashMap<String, String>, | ||
| groups: HashMap<String, Units>, |
There was a problem hiding this comment.
| groups: HashMap<String, Units>, | |
| group_units: HashMap<String, Units>, |
There was a problem hiding this comment.
I think we need to react to invalid data in the meta topic somehow, which is my only blocker. Otherwise if in the future we need to do something similar we need to be careful not to add more parameters to from_thin_edge_json but to do some refactoring of generalizing this pattern of "take some json key/values from one place and apply it to many messages"
| if let Some(unit) = meta.get("unit") { | ||
| // "Temperature": {"unit": "°C"}, | ||
| if let serde_json::Value::String(unit_name) = unit { | ||
| self.units.insert(measurement, unit_name.to_owned()); | ||
| } | ||
| } else { | ||
| // "Climate": { "Temperature": {"unit": "°C"}, "Humidity": {"unit": "%RH"} } | ||
| let group = measurement; | ||
| self.set_group_units(group, meta); | ||
| } |
There was a problem hiding this comment.
question: So, for a payload like {"unit: "u", "Temperature": {"unit": "°C"}, "Humidity": {"unit": "%RH"}} we only grab the "unit" and ignore the others?
I think we should only allow only one and print an error and don't add any units if both are present, ideally also adding this case to the tests. Or really, for any garbage data that can't be parsed published on these topics we should emit an ERROR event?
Also perhaps TRACE for when we actually set a new unit, but it's not as important.
There was a problem hiding this comment.
There is a tension here between flexibility and adherence to thin-edge JSON conventions.
- We want the freedom to add not only units but also other info about measurements such as precision as with
"temperature": { "unit": "°C", "precision": "±0.1°C" } - We want to be able to use different units for
"Temperature","Climate": { "Temperature" }and"Engine": { "Temperature" }, e.g. with"Climate": { "Temperature": {"unit": "°C"} }
However, this makes things a bit complicated. As raised by @Bravo555, "Foo": {"unit: "u", "Temperature": {"unit": "°C"}} can then be understood as metadata assigning units to "Foo":20 values as well as "Foo" : { "Temperature": 20 } (the current code ignoring the latter, though).
An alternative is to flatten the structure of (measurement, info) pairs where group measurements are identified by a path.
"Temperature": { "unit": "°C", "precision": "±0.1°C" },
"Climate.Temperature": { "unit": "°C", "precision": "±0.1°C" },
"Engine.Temperature": { "unit": "°F", "precision": "±1°F" },
=> I will give a try to this idea.
There was a problem hiding this comment.
=> I will give a try to this idea.
rina23q
left a comment
There was a problem hiding this comment.
I have some comments. I'll test it manually tomorrow.
Releasing blocker for {"unit: "u", "Temperature": {"unit": "°C"}, "Humidity": {"unit": "%RH"}}, decided during daily that the current behaviour is well-defined.
| self.group_units | ||
| .entry(group.to_owned()) | ||
| .or_default() | ||
| .set_unit(measurement.to_owned(), meta); |
There was a problem hiding this comment.
question: If the messages have flat key-value structures, with no nesting, do we still need separate hashmaps for group units? Can't we put all the keys in a single hashmap?
There was a problem hiding this comment.
Indeed, not required. This would simplify the mapping and its memory footprint. However this would also implies building paths (concatenating group and measurement names) each time a grouped measurement value is received.
| Execute Command tedge mqtt pub te/device/main///m/t6 '{ "temperature": 25 }' | ||
| Execute Command tedge mqtt pub -r te/device/main///m/t6/meta '' | ||
| Execute Command tedge mqtt pub te/device/main///m/t6 '{ "temperature": 298.15 }' |
There was a problem hiding this comment.
@didier-wenzek Do you think this might introduce a flakey test if the meta topic is not processed before receiving the second temperature measurement?
There was a problem hiding this comment.
If so, it might be worth doing two measurement assertions (one to check that the units are present, and the second to check that the units are not present)
There was a problem hiding this comment.
@didier-wenzek Do you think this might introduce a flakey test if the meta topic is not processed before receiving the second temperature measurement?
Good point. The mapper sequentially processes all the measurement related messages. But the broker might change the message order as they are not published on the same topic.
There was a problem hiding this comment.
Actually, not flaky.
$ invoke flake-finder --test-name "Measurement units can be cleared" --iterations 100 --outputdir output_ff --clean
------------------------------
Overall: PASSED
Results: 100 iterations, 100 passed, 0 failed
Elapsed time: 0:20:33.596564
There was a problem hiding this comment.
I will remove that useless commit when re-basing 92c40f6
There was a problem hiding this comment.
Yeah I was also surprised that it didn't seem to be flakey, though I pushed a slightly refactored test that also passed 50 flake finder runs (and the test removes the potential race condition)
commit: 40bea55
40bea55 to
664137c
Compare
Signed-off-by: Didier Wenzek <didier.wenzek@free.fr>
Signed-off-by: Didier Wenzek <didier.wenzek@free.fr>
Signed-off-by: Didier Wenzek <didier.wenzek@free.fr>
Signed-off-by: Didier Wenzek <didier.wenzek@free.fr>
Mimicing the structure of grouped measurements as in `"Foo": {"unit: "u", "Temperature": {"unit": "°C"}}`
leads to confusion as units are assigned to `"Foo":20` values as well as `"Foo" : { "Temperature": 20 }`.
Using a flat structure is less confusing as units for `"Foo"` and `"Foo.Temperature"`
are now defined using two independent property sets.
Signed-off-by: Didier Wenzek <didier.wenzek@free.fr>
664137c to
c6cd05a
Compare
|
To ease the discoverability of this feature, wouldn't it be better to mention the metadata support in https://github.com/thin-edge/thin-edge.io/blob/main/docs/src/start/send-measurements.md as well, rather than "hidden" in a few reference docs? |
Good point. I will push a PR to improve that. |
Proposed changes
te/+/+/+/+/m/+/metatopics to set measurement unitsTypes of changes
Paste Link to the issue
Checklist
just prepare-devonce)just formatas mentioned in CODING_GUIDELINESjust checkas mentioned in CODING_GUIDELINESFurther comments