ganehag / pyMeterBus

Pure Python implementation of the Meter-Bus (M-Bus EN13757-3) protocol.
BSD 3-Clause "New" or "Revised" License
77 stars 40 forks source link

Would be nice to add a prefix to the request #11

Open kolucciy opened 5 years ago

kolucciy commented 5 years ago

It looks like my meter replies to the command only if its sent with a prefix of 210:

\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x5b\x00\x5b\x16

instead of \x10\x5b\x00\x5b\x16 sent by: meterbus.send_request_frame(ser, address)

Was wondering if there is a way to add this prefix? For now I run:

ser.write(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x5b\x00\x5b\x16') frame = meterbus.load(meterbus.recv_frame(ser)) print(frame.to_JSON())

ganehag commented 5 years ago

Is this over wired M-Bus or over an optical connection?

It is quite common for heat meters to only reply over opto after a preamble of some sort. Typical preambles are a series of "0x00" or "0x55" characters.

kolucciy commented 5 years ago

It's over an optical connection.

ganehag commented 5 years ago

I will take a look and see if I can come up with something elegant.

Memphiz commented 3 years ago

@kolucciy any chance you are talking about a Landis gyr uh30 (T330) here (same prefix using optical mbus interface)? If so - did you succeed in reading it out? I am at the beginning of all the m-bus stuff atm and get responses already and try to figure out how to parse them in the best way.

ganehag commented 3 years ago

In an old C code solution that I have, which was written to read data from M-Bus meter over the Optical interface, there is an opto_wake function. Using "8n1", it sends a bunch of 0x55 ('U') characters (01010101) where the count is based on the formula;

(ispeed / 10) * sleeptime

Where the speed is the baudrate and sleeptime is 2 seconds.

I can't recall what this code is based on. Probably something in a datasheet for one or more energy meters.

I don't think that any Opto wake solution should be part of the library. But it may be added the CLI tools.

Memphiz commented 3 years ago

There seems to be a difference between prefix (the leading 0x0 or 0x55 bytes) and the wakeup of the interface though.

For my heat meter the 210 0x0 bytes need to be prefixed to all m bus telegrams - but after some time of not using the optical interface it seems to go into a sleep mode where it does not respond to any telegrams.

It seems it is woken up by raising dtr and cts lines in a special manner but I have no idea how this would affect the ir interface at all (cause it has only tx and rx).

ATM I try to reverse engineer the wake up process from the manufacturer software - once woken up via original software the approach with the leading zeros works for me (until it goes to sleep again)

kolucciy commented 3 years ago

Hi @Memphiz,

That's what I ended up doing to read the meter for my HA setup.

Hope that helps

import meterbus
import serial
import logging

from homeassistant.const import ENERGY_KILO_WATT_HOUR
from homeassistant.helpers.entity import Entity

_LOGGER = logging.getLogger(__name__)

def setup_platform(hass, config, add_devices, discovery_info=None):
    """Setup the sensor platform."""
    add_devices([T230Sensor()])

class T230Sensor(Entity):
    """Representation of a Sensor."""

    def __init__(self):
        """Initialize the sensor."""
        self._state = None
        self.update()

    @property
    def name(self):
        """Return the name of the sensor."""
        return 'ULTRAHEAT T230'

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement."""
        return ENERGY_KILO_WATT_HOUR

    def update(self):
        """Fetch new state data for the sensor.
        This is the only method that should fetch new data for Home Assistant.
        """
        ser = serial.Serial('/dev/ttyUSB0', 2400, 8, 'E', 1, 4)

        try:
            """Ping... 5 times"""
            connected = False
            for i in range(0, 4):
                ser.write(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x40\x00\x40\x16')
                try:
                    frame = meterbus.load(meterbus.recv_frame(ser, 1))
                    if isinstance(frame, meterbus.TelegramACK):
                        connected = True
                        break
                except meterbus.MBusFrameDecodeError:
                    pass

            """Read out"""
            if connected:
                ser.write(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x5b\x00\x5b\x16')
                frame = meterbus.load(meterbus.recv_frame(ser))
                #hass.components.mqtt.publish("t230_heating", frame.records[2].parsed_value/1000)
                self._state = frame.records[2].parsed_value/1000

                """Read remaining frames"""
                read_buffer = b''
                chunk_size = 200    
                while True:
                    # Read in chunks. Each chunk will wait as long as specified by
                    # timeout. Increase chunk_size to fail quicker
                    byte_chunk = ser.read(size=chunk_size)
                    read_buffer += byte_chunk
                    if not len(byte_chunk) == chunk_size:
                        break
        except meterbus.MBusFrameDecodeError as e:
           _LOGGER.error("Could not fetch meter readings. Frame: %s", e.value)

        ser.close()
Memphiz commented 3 years ago

Thx for sharing your code. I had something similar in place too. I think the retries made the difference - though those look like a workaround for doing something not as intended. It works ok as it seems. I have read for similar wake up procedures that there is a wait time after sending the zeros and before sending the first telegram (snd_nke in this code example). Not sure if that is the reason why it needs retries sometimes.

T230 and T330 share the init process in the "ultra assist" software as well so that's we it works here too I guess.

For pyMeterBus, to come back to topic, there should be no need to add anything for this. We can send the zeros first (only the zeros) and afterwards use the pyMeterBus APIs to send telegrams.

As I also think that this wakeup stuff is not mbus related but ZVEI (or how that optical interface is called) related.

Sorry for spamming this issue - but really glad for your replies - infinally can start optimizing my heating now that I have all the meter data inside fhem :)