diff --git a/CHANGELOG.md b/CHANGELOG.md index 59216c28..82479dad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - **Expanded** Shelly emulation logs to report battery detection, inactivity, and reconnection events ([#241](https://github.com/tomquist/astrameter/pull/241)). - **Reduced** throttling output noise by replacing unconditional `print` calls in `ThrottledPowermeter` with structured logging (`logger.debug` for routine wait/fetch/cache messages; failures remain at error level) ([#251](https://github.com/tomquist/astrameter/pull/251)). - **Improved** Shelly UDP server robustness by adding socket timeouts to avoid hangs during shutdown and testing ([#233](https://github.com/tomquist/astrameter/pull/233)). +- **Upgraded** `JSON_PATHS` parsing in the JSON HTTP and MQTT powermeters to the `jsonpath-ng` extended syntax, so values that arrive with a unit suffix (e.g. openHAB `Number:Power` returning `"331.74 W"`) can be sanitized inside the path with `` `split(...)` `` or `` `sub(/regex/, replacement)` `` instead of failing the float conversion ([#349](https://github.com/tomquist/astrameter/pull/349)). ### Fixed - **Fixed** Modbus `UNIT_ID` handling and clarified Home Assistant entity ID configuration in the docs ([#191](https://github.com/tomquist/astrameter/pull/191), [#195](https://github.com/tomquist/astrameter/pull/195)). diff --git a/README.md b/README.md index c6840e64..af0d8f86 100644 --- a/README.md +++ b/README.md @@ -681,6 +681,8 @@ TOPIC = home/powermeter The `JSON_PATH` option is used to extract the power value from a JSON payload. The path must be a [valid JSONPath expression](https://goessner.net/articles/JsonPath/). If the payload is a simple integer value, you can omit this option. +Both `JSON_PATH` and `JSON_PATHS` are parsed with the [`jsonpath-ng` extended syntax](https://github.com/h2non/jsonpath-ng#extensions), so you can chain extensions like `` `split(...)` `` or `` `sub(/regex/, replacement)` `` to massage a payload value before it's converted to a float — for instance `$.state.`split( , 0, -1)`` or `$.state.`sub(/[^0-9.\-]+$/, )`` to strip a unit suffix like `"331.74 W"`. See the [JSON HTTP](#json-http) section below for more examples. + #### Multi-phase MQTT For three-phase setups, there are two options: @@ -717,6 +719,13 @@ PASSWORD = pass (Optional) HEADERS = Authorization: Bearer token ``` +`JSON_PATHS` is parsed with the [`jsonpath-ng` extended syntax](https://github.com/h2non/jsonpath-ng#extensions), so you can chain extensions like `` `split(...)` `` or `` `sub(/regex/, replacement)` `` to massage the value before it's converted to a float. For example, an openHAB `Number:Power` item returns `"331.74 W"` — strip the unit with either of: + +```ini +JSON_PATHS = $.state.`split( , 0, -1)` +JSON_PATHS = $.state.`sub(/[^0-9.\-]+$/, )` +``` + ### TQ Energy Manager ```ini diff --git a/config.ini.example b/config.ini.example index fd613ac9..1fa8aec3 100644 --- a/config.ini.example +++ b/config.ini.example @@ -279,6 +279,10 @@ THROTTLE_INTERVAL = 0 # [JSON_HTTP] #URL = http://example.com/api #JSON_PATHS = $.power +## JSON_PATHS supports jsonpath-ng extensions; strip a unit suffix like +## "331.74 W" with `split` or `sub`: +##JSON_PATHS = $.state.`split( , 0, -1)` +##JSON_PATHS = $.state.`sub(/[^0-9.\-]+$/, )` #USERNAME = user #PASSWORD = pass #HEADERS = Authorization: Bearer token diff --git a/src/astrameter/powermeter/json_http.py b/src/astrameter/powermeter/json_http.py index 1e01e5cc..e1103e64 100644 --- a/src/astrameter/powermeter/json_http.py +++ b/src/astrameter/powermeter/json_http.py @@ -2,7 +2,7 @@ import aiohttp from aiohttp import BasicAuth, ClientTimeout -from jsonpath_ng import parse +from jsonpath_ng.ext import parse from astrameter.config.logger import logger diff --git a/src/astrameter/powermeter/json_http_test.py b/src/astrameter/powermeter/json_http_test.py index 6c5570f0..963e7966 100644 --- a/src/astrameter/powermeter/json_http_test.py +++ b/src/astrameter/powermeter/json_http_test.py @@ -23,6 +23,17 @@ async def test_three_phase(mock_aiohttp_session): await meter.stop() +async def test_strips_unit_suffix_via_jsonpath_ext(mock_aiohttp_session): + mock_aiohttp_session.set_json({"state": "331.74 W"}) + with patch("aiohttp.ClientSession", return_value=mock_aiohttp_session): + meter = JsonHttpPowermeter( + "http://localhost", "$.state.`sub(/[^0-9.\\-]+$/, )`" + ) + await meter.start() + assert await meter.get_powermeter_watts() == [331.74] + await meter.stop() + + async def test_headers_and_auth(mock_aiohttp_session): mock_aiohttp_session.set_json({"power": 50}) with patch("aiohttp.ClientSession", return_value=mock_aiohttp_session) as mock_cls: diff --git a/src/astrameter/powermeter/mqtt.py b/src/astrameter/powermeter/mqtt.py index 6a9521c2..d6098402 100644 --- a/src/astrameter/powermeter/mqtt.py +++ b/src/astrameter/powermeter/mqtt.py @@ -4,7 +4,7 @@ import ssl import aiomqtt -from jsonpath_ng import parse +from jsonpath_ng.ext import parse from astrameter.config.logger import logger