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
Handle explicit Modbus NaN values #90800
Conversation
Hey there @adamchengtkc, @janiversen, @vzahradnik, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
9c3d48e
to
d7efd54
Compare
So what I'm unsure about is what the return value should be in case of NaN. Should it be 0 or 'unavailable' or none? In the PR I opted for 0, just because it is of the same return type. There's also some discussions here: |
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
You have a CI problem, please solve that. |
Furthermore your PR is marked as draft, please mark it as ready for review when you want a formal review. |
yes will do |
Let’s see if your CI problem is solved. This PR is still marked as draft, so we cannot review/approve it. |
a739f18
to
62ab9cd
Compare
dbd35e3
to
efce9bd
Compare
polite question, is something missing from my side for a revised review? |
A polite answer you have not answered my last review comments…and if I remember right your doc. PR also have issues (I might remember wrong ), it is marked as draft, so not ready for review. |
actually answered to all of them, incorporated some changes in the code. After a few days I clicked on 'resolve comment' since I thought this was part of the review process, but I think this just made your comments and my answers vanish :/ |
The current status is the PR is "changes requested" so it is hard to know that they are solved. |
Just controlled there are 6 review comments without response. You might have solved all 6 in the commits you made, but how do you expect me to know that |
I really don't know what to do now. I only see 2 requests for changes from you above, and I changed the code and responded to the latter. I definitely shouldn't have squashed the commits, therefore the original lines of code on which you commented is now gone. I'm eager to do whatever else needs to be done for this PR. It's my first one for HA in general. |
@janiversen Those comments in your review screenshots, have the Meaning you still need to submit them as a review. You are the only one that sees those. ../Frenck |
thanks @frenck for spotting, @janiversen I just wanted to say I don't see any of them and was waiting for something to happen. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry my fault.
@@ -105,6 +107,7 @@ def get_optional_numeric_config(config_name: str) -> int | float | None: | |||
|
|||
self._min_value = get_optional_numeric_config(CONF_MIN_VALUE) | |||
self._max_value = get_optional_numeric_config(CONF_MAX_VALUE) | |||
self._nan_value = entry.get(CONF_NAN_VALUE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if nan value is not present in the config (this line fails)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should work with a default None
value from the input Schema validation in __init__.py
. Forgot the default value there and will add with a commit. I could also do a proper hex string enforcement using voluptuous in the init. What do you think?
@@ -161,6 +164,10 @@ def __init__(self, hub: ModbusHub, config: dict) -> None: | |||
self._scale = config[CONF_SCALE] | |||
self._offset = config[CONF_OFFSET] | |||
self._count = config[CONF_COUNT] | |||
if self._nan_value is not None: | |||
self._nan_value = struct.unpack( | |||
self._structure, bytes.fromhex(self._nan_value[2:]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the doc it is a hex string (without specific definition) but it seems the code assumes something special for the first 2 chars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also you cannot use _structure, that will give wrong results or break when configuring a custom format.
why no simply do something like:
‘’’
x = int("deadbeef", 16)
‘’’
that works with 0x as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1st comment
yes, the special 0x
. Could do prettier things like a split after 0x
, which is less performant. But if format is enforced with the preceding 0x
, will this be ok?
2nd comment
this doesn't work, as x
should have different values when using e.g. uint16
or int16
, which again is set with the struct from _structure
. int("deadbeef", 16)
will always convert it to the same int value in base 16.
@@ -161,6 +164,10 @@ def __init__(self, hub: ModbusHub, config: dict) -> None: | |||
self._scale = config[CONF_SCALE] | |||
self._offset = config[CONF_OFFSET] | |||
self._count = config[CONF_COUNT] | |||
if self._nan_value is not None: | |||
self._nan_value = struct.unpack( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the user configures "jan was here" which are allowed since it is defined as a string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above comment, could do hex string enforcement using voluptuous in the __init__.py
or also just preceding here with a regex check with proper error message is hex format is incorrect?
@@ -161,6 +164,10 @@ def __init__(self, hub: ModbusHub, config: dict) -> None: | |||
self._scale = config[CONF_SCALE] | |||
self._offset = config[CONF_OFFSET] | |||
self._count = config[CONF_COUNT] | |||
if self._nan_value is not None: | |||
self._nan_value = struct.unpack( | |||
self._structure, bytes.fromhex(self._nan_value[2:]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_structure is optional, why use that to unpack your hex string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, otherwise pythons int("hex_string")
yields the integer representation of "hex_string"
regardless of the modbus datatype, which can be uint16
, int16
, uint32
, int32
, float32
,... and all as either little endian or big endian.
The "hex_string"
NaN value has to be decoded in the same way as the actual incoming modbus sensor value is decoded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above this breaks with custom formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't find a good solution yet
def __process_raw_value(self, entry: float | int | str) -> float | int | str: | ||
"""Process value from sensor with NaN handling, scaling, offset, min/max etc.""" | ||
if self._nan_value and entry == self._nan_value: | ||
return STATE_UNAVAILABLE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should return a value not a state, this might have a number of side effects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my initial version had return value 0
, and was wondering if "unavailable"
was better. For this, you suggested it should be "unavailable"
.
Or is your issue STATE_UNAVAILABLE
vs. "unavailable"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you have any guidance here?
( | ||
{ | ||
CONF_DATA_TYPE: DataType.INT32, | ||
CONF_NAN_VALUE: "0x80000000", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You miss tests:
- nan_value configured as "jan was here"
- nan_value configured as a hex string (no 0x) allowed according to the doc
- nan_value, configured but no match
- nan_value match (check that raw_value is not broken).
In general you need to look at the coverage output and secure the lines you have changed are covered by tests.
@frenck good catch, thanks. |
Any progress on this, there are a number of open review comments and it is out of date with the dev branch. In case of no progress it will be closed at a later date. |
will commit updated unittests, and the things where I know what to do. Still some open questions, see above |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added extra to make it work.
f89b2a3
to
8a2ede2
Compare
8a2ede2
to
b71212d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (with the extras).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for picking this up, but I think this has issues when the modbus configuration variable swap
is used with swapped order of bytes/words. This was my motivation to use the above struct.unpack()
with the same self._structure
in homeassistant/components/modbus/base_platform.py
used to encode the actual incoming modbus values. Since you mentioned this is not a good idea, I didn't how to proceed.
@@ -225,6 +230,10 @@ def unpack_structure_result(self, registers: list[int]) -> str | None: | |||
# the conversion only when it's absolutely necessary. | |||
if isinstance(val_result, int) and self._precision == 0: | |||
return str(val_result) | |||
if isinstance(val_result, str): | |||
if val_result == "nan": | |||
val_result = STATE_UNAVAILABLE # pragma: no cover |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
STATE_UNAVAILABLE
should never be set directly as state. We use the entity property available
set to False to set an entity state as unavailable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MartinHjelmare
Something is not right, when doing it I get:
File "/Users/jan/repos/core/homeassistant/components/modbus/base_platform.py", line 193, in __process_raw_value
self.available = False
^^^^^^^^^^^^^^
AttributeError: property 'available' of 'ModbusRegisterSensor' object has no setter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a property, so it can't be set as an attribute. It needs to return a value. You can use self._attr_available
if you want to set the value directly.
https://developers.home-assistant.io/docs/core/entity#property-implementation
Ok will change it in a followup PR. |
Proposed change
Add optional modbus config for explicit NaN values specified by the device vendor. Without this option, template sensore are required to remove the NaN values. See related issue #69656
Type of change
Additional information
fixes specify Modbus NaN values per sensor #69656
Checklist
black --fast homeassistant tests
)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest
.requirements_all.txt
.Updated by running
python3 -m script.gen_requirements_all
..coveragerc
.To help with the load of incoming pull requests: