moonraker icon indicating copy to clipboard operation
moonraker copied to clipboard

Feature: Generic sensor support and data logging

Open mjonuschat opened this issue 2 years ago • 3 comments

This feature implements a sensor component that can be used to track/log generic sensors from multiple sources. Each sensor can have properties like unit of measurement, accuracy and a display name that help frontends display the tracked measurements.

Some supported sources could be locally connected I2C, SPI or 1-wire sensors as well as remote sensor providers like MQTT or REST API driven sensor.

Example of the component in use with a ESPHome device connected through MQTT:

[sensor.py:component_init()] - Initializing sensor component
[sensor.py:initialize()] - Registered sensor 'Chamber Temperature'
[sensor.py:initialize()] - Registered sensor 'Chamber Humidity'
[sensor.py:initialize()] - Registered sensor 'Power Usage'
[sensor.py:component_init()] - All sensors have been initialized
...
[mqtt.py:__call__()] - MQTT Subscriptions Acknowledged
 Topic: devvm-mjonuschat.sharks-with-lasers.net/moonraker/api/request | Granted QoS 0
 Topic: hank/sensor/chamber_temperature/state | Granted QoS 0
 Topic: hank/sensor/chamber_humidity/state | Granted QoS 0
 Topic: hank/sensor/pzem-004t_v3_power/state | Granted QoS 0
[sensor.py:_on_state_update()] - Context: {'payload': '27.7'}
[sensor.py:_on_state_update()] - Received updated sensor value for Chamber Temperature: 27.7°C
[sensor.py:_on_state_update()] - Context: {'payload': '44.9'}
[sensor.py:_on_state_update()] - Received updated sensor value for Chamber Humidity: 44.9%
[sensor.py:_on_state_update()] - Context: {'payload': '1.60'}
[sensor.py:_on_state_update()] - Received updated sensor value for Power Usage: 1.6W
...

Querying the aggregated endpoint to retrieve sensor values:

{
  "result": {
    "chamber_temperature": {
      "source": "hank/sensor/chamber_temperature/state",
      "name": "Chamber Temperature",
      "unit_of_measurement": "°C",
      "accuracy_decimals": 1,
      "measurements": [
        0,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7,
        27.7
      ]
    },
    "chamber_humidity": {
      "source": "hank/sensor/chamber_humidity/state",
      "name": "Chamber Humidity",
      "unit_of_measurement": "%",
      "accuracy_decimals": 1,
      "measurements": [
        0,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9,
        44.9
      ]
    },
    "power_usage": {
      "source": "hank/sensor/pzem-004t_v3_power/state",
      "name": "Power Usage",
      "unit_of_measurement": "W",
      "accuracy_decimals": 2,
      "measurements": [
        0,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6,
        1.6
      ]
    }
  }
}

Signed-off-by: Morton Jonuschat [email protected]

mjonuschat avatar Sep 18 '22 22:09 mjonuschat

Thanks. I'm open to discussing generic sensor support, however the aioesphomeapi dependency will create an issue. It depends on noiseprotocol, which depends on cryptography. Unfortunately the Rust bindings in cryptography will cause a problem on SBCs that don't have prebuilt wheels available (ie: Armbian). The wheel won't build and Moonraker will fail to update. Initial support for ESPHome devices would probably need to be accomplished via MQTT. In the future when Moonraker reaches stable we could revisit this, as distribution and updates for stable releases would include prebuilt dependencies.

Separately, I think this can be accomplished though a single component. Its acceptable to break the component into separate modules if necessary, that could be accomplished by making the component a package (see the file_manager and update_manager components as examples). I don't believe we need a separate data store module though, it seems that each base sensor could store whatever data is necessary, then sensor.py could expose endpoints for querying the data.

Arksine avatar Sep 22 '22 10:09 Arksine

Thanks for the feedback - I didn't think about the crypto dependencies and will happily change to the proposed MQTT implementation as well as integrate the data storage into the base sensor which would get rid of the data store component. The esphome component made sense (to me) as I am currently working on also integrating it to control WLED style LEDstrips and switches/relays connected to the ESPhome device - at that point it seemed sensible to have a single connection that you configure and the downstream implementations would use that. Moving to MQTT will make this obsolete so it shouldn't be an issue and everything can be contained in the sensor component.

mjonuschat avatar Sep 22 '22 14:09 mjonuschat

I've implemented the changes (storing the data in the sensor, not adding any new dependencies and providing a MQTT based implementation). I'll add some documentation over the weekend.

mjonuschat avatar Sep 24 '22 01:09 mjonuschat

Thanks, the configuration documentation provided me with a more generalized idea of what you are looking to accomplish. I think this module adds value, and I have added some review comments.

With regard to the API, we probably need a couple of endpoints. One that returns current device info (including its current value), and one that returns the queue stored values for each device. I touched on what that might look like in the review comments. The info endpoint should have an option for consumers to specify a specific device.

Additionally, most clients want changes in the value pushed to them over the websocket. In the review I mentioned having a single timer that updates the queue for each registered sensor. This timer could also push emit a sensor_update event that is registered as a websocket notification. This event would only include sensors that detected a change, if no sensor values change then it wont be emitted. A one second timer is probably appropriate.

Arksine avatar Feb 09 '23 01:02 Arksine

@Arksine : I think I've addressed most of the comments / implemented the suggested changes. Have not yet tested this fully, wanted to check in to see that this is going in the direction you were thinking. In regards to the notifications I have implemented those with the only notify when readings changed but it feels a bit counter-intuitive for sensors where you would expect to get a time-series with predictable intervals between the readings, even if say the temperature didn't change (unless it's expected that the clients "fill the gaps").

mjonuschat avatar Feb 12 '23 05:02 mjonuschat

Tested and verified working with the example configuration and notification have been added

mjonuschat avatar Feb 16 '23 02:02 mjonuschat

It is close, just a couple of minor issues and the "notification bundling". Additionally the APIs and websocket notification will need documentation, then it will be ready to merge.

Arksine avatar Feb 18 '23 20:02 Arksine

Other than the missing sensor event name the changes to the source look good. You need to rebase your branch against master to resolve the merge conflicts with api_changes.md, frankly you don't need to update that if you don't want. That is mostly for breaking changes. Very soon I plan to keep a single changelog.

The websocket notification needs to be documented in web_api.md. Since you basically have it documented in api_changes you could move it with a few tweaks.

Arksine avatar Feb 19 '23 12:02 Arksine

Requested changes have been addressed and changes have been rebased. Let me know if you want me to squash the changes before merging.

mjonuschat avatar Feb 20 '23 17:02 mjonuschat

Thanks. I found some issues with the docs, and apparently I missed a problem with the measurements request in my last review. I also proposed refactoring the name of /server/sensors/sensor to /server/sensors/info, as the /server/sensors/sensor?sensor= is a bit extreme on the use of the term "sensor".

Arksine avatar Feb 20 '23 19:02 Arksine

Sorry for the force-push, forgot to add the Signed-off comment to a bunch of the commit messages.

Tested the endpoint changes and the bugfix:

pi@moonraker-dev:~ $ curl -s "http://localhost:7125/server/sensors/measurements" | jq
{
  "result": {
    "powermeter": {
      "power": [
        0,
        0,
        0
      ],
      "voltage": [
        115.7,
        115.7,
        115.7
      ],
      "current": [
        0,
        0,
        0
      ],
      "energy": [
        0,
        0,
        0
      ]
    }
  }
}
pi@moonraker-dev:~ $ curl -s "http://localhost:7125/server/sensors/measurements?sensor=powermeter" | jq
{
  "result": {
    "powermeter": {
      "power": [
        0,
        0
      ],
      "voltage": [
        115.4,
        115.4
      ],
      "current": [
        0,
        0
      ],
      "energy": [
        0,
        0
      ]
    }
  }
}
pi@moonraker-dev:~ $ curl -s "http://localhost:7125/server/sensors/info?sensor=powermeter"|jq
{
  "result": {
    "id": "powermeter",
    "friendly_name": "powermeter",
    "type": "mqtt",
    "values": {
      "power": 0,
      "voltage": 115.4,
      "current": 0,
      "energy": 0
    }
  }
}

mjonuschat avatar Feb 20 '23 21:02 mjonuschat

Thanks.

Arksine avatar Feb 20 '23 22:02 Arksine