BlackZork / mqmgateway

MQTT gateway for modbus networks
GNU Affero General Public License v3.0
44 stars 20 forks source link

Problem reading float values from a device #47

Closed fencepost-error closed 5 months ago

fencepost-error commented 5 months ago

I'm working with some devices that report everything as a float32. It's a supported type (as 'float') with the docs saying all arguments are optional. However when I try it with option A enabled:

      state:
        - name: voltage
A         converter: std.float
B         converter: std.int32
          register: 1
          register_type: input
        - name: current
A         converter: std.float
B         converter: std.int32
          register: 7
          register_type: input

I get:

[CRITICAL] config error(line 26): Supply converter spec in form: plugin.converter(arg1, arg2, <E2><80><A6>)

If I use option B I get:

[ERROR] 1 error(s) when reading register 10.0, last error: libmodbus: read fn 0 failed with return code -1: Invalid data

If I use neither A nor B it works, I just get some random 16-bit value that's half the float32 I should be getting.

What's the correct way to read a float32 value from a device?

BlackZork commented 5 months ago

You still need parentheses like in this example

fencepost-error commented 5 months ago

Thanks, but even with that it's still not working:

[CRITICAL] config error(line 26): Converter std.float not found

It looks like the type should actually be float32, with that it starts but then reports:

[CRITICAL] Cannot read 32-bit float from single register

Trying to fix that by adding a count (which doesn't seem like the right thing to do, but since it's complaining about a single register...) gives:

[CRITICAL] config error(line 25): state register list entry cannot contain count

BlackZork commented 5 months ago

A list in state:

state:
  - name: voltage
  [...]
  - name: current
  [...]

is supposed to output a json dict in format { "voltage": "...", "current":"..."} under a single topic. Unfortunately you cannot use separate converters for dict components in current version.

You can publish current and voltage as separate topics this way:

  objects:
    - topic: my_device/current
      state:
        converter: std.float32()
        register: 1
        register_type: input
        count: 2
    - topic: my_device/voltage
      state:
        converter: std.float32()
        register: 2
        register_type: input
        count: 2

float32 needs two 16-bit registers. Your device stores current and voltage as FP16?

fencepost-error commented 5 months ago

Damn, I was hoping to reduce the size of the YAML file (see my comment in the templates thread). Thanks, that's fixed it, just needed to add network and slave values as well.

The devices are Eastron power meters, they store everything as float32. And I mean everything, baud rate, device ID, number of stop bits... you know, in case you want to use 6.022e23 stop bits at some point. Makes them a real pain to first set up since you need to calculate things like the IEEE 754 hex encoding of the baud rate just to change the device settings.

Oh, and a minor request, it'd be nice to be able to specify in the 'slaves' section whether a particular slave uses 0- or 1-based numbering, half the devices I've got seem to use 0 and the other half 1, it would make the register numbering follow the documentation rather than have off-by-one values for some devices.

BlackZork commented 5 months ago

Oh, and a minor request, it'd be nice to be able to specify in the 'slaves' section whether a particular slave uses 0- or 1-based numbering, half the devices I've got seem to use 0 and the other half 1, it would make the register numbering follow the documentation rather than have off-by-one values for some devices.

Not configurable, but if you specify register number as 0x1, 0x10 then it is interpreted as 0-based numbering. Your documentation for 0-based numbering uses decimal or hex values?

fencepost-error commented 5 months ago

Yeah, I saw that in the docs. I'm actually playing with multiple devices (they're pretty cheap off Aliexpress :-) and they all seem to document things differently. For example the Eastrons, which have excellent documentation, use +1 and then the standard base of 30000 for input registers, so 30085 is actually 84. Some family of temperature/humidity sensors that everyone on Aliexpress seems to clone uses holding registers for everything, so 40001 is actually 0, however they also give 0-based hex values. Another pressure sensor only gives 0-based hex values but they specify 03 and 06 for reads and writes so presumably holding registers. Another device gives both the holding register form, 40201, and what looks like a hex form, 0200, but is actually decimal. Another one is hex but gives values like 4x8000, which from memory evaluated to holding register 32768, (which isn't input register 2768) that took trial and error to sort out. There were one or two others where it was just guesswork reading different options until you got a known value that you could map to a register address.

Anyway, that's why an explicit start:0 or start:1 would be useful, to document what's in the YAML vs. whats in the device docs.

BlackZork commented 5 months ago

is supposed to output a json dict in format { "voltage": "...", "current":"..."} under a single topic. Unfortunately you cannot use separate converters for dict components in current version.

Errata: converter for single register can be added, but int32, float32 cannot. I added it as a bug #49

BlackZork commented 5 months ago

Anyway, that's why an explicit start:0 or start:1 would be useful, to document what's in the YAML vs. whats in the device docs.

Seems reasonable. Added as #50

fencepost-error commented 5 months ago

For anyone else using Eastron power meters who's found this thread via Google, here's the definitions. This is only a fraction of the registers, I've tried to limit it to the more useful ones to keep the size down and reduce modbus traffic on each read. To adapt this to your setup, replace 'XX' with the device name and 'YY' with the device address.

    - topic: XX/voltage
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 1
        register_type: input
        count: 2
    - topic: XX/current
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 7
        register_type: input
        count: 2
    - topic: XX/active_power
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 13
        register_type: input
        count: 2
    - topic: XX/frequency
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 71
        register_type: input
        count: 2
    - topic: XX/import_energy
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 73
        register_type: input
        count: 2
    - topic: XX/export_energy
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 75
        register_type: input
        count: 2
    - topic: XX/total_demand
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 85
        register_type: input
        count: 2
    - topic: XX/positive_demand
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 89
        register_type: input
        count: 2
    - topic: XX/reverse_demand
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 93
        register_type: input
        count: 2
    - topic: XX/current_demand
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 259
        register_type: input
        count: 2
    - topic: XX/max_current_demand
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 265
        register_type: input
        count: 2
    - topic: XX/total_energy
      network: switchboard
      slave: YY
      state:
        converter: std.float32()
        register: 343
        register_type: input
        count: 2