#!/usr/bin/python from __future__ import print_function import argparse import serial import time import os import stat import simplejson as json import yaml from decimal import Decimal try: import meterbus except ImportError: import sys sys.path.append('./pyMeterBus/') import meterbus def ping_address(ser, address, retries=5, read_echo=False): for i in range(0, retries + 1): meterbus.send_ping_frame(ser, address, read_echo) try: frame = meterbus.load(meterbus.recv_frame(ser, 1)) if isinstance(frame, meterbus.TelegramACK): return True except meterbus.MBusFrameDecodeError as e: pass time.sleep(0.5) return False def do_reg_file(args): with open(args.device, 'rb') as f: frame = meterbus.load(f.read()) if frame is not None: print(frame.to_JSON()) def do_char_dev(args): address = None try: address = int(args.address) if not (0 <= address <= 254): address = args.address except ValueError: address = args.address.upper() try: ibt = meterbus.inter_byte_timeout(args.baudrate) with serial.serial_for_url(args.device, args.baudrate, 8, 'E', 1, inter_byte_timeout=ibt, timeout=1) as ser: frame = None if meterbus.is_primary_address(address): if ping_address(ser, address, args.retries, args.echofix): meterbus.send_request_frame(ser, address, read_echo=args.echofix) frame = meterbus.load( meterbus.recv_frame(ser, meterbus.FRAME_DATA_LENGTH)) else: print("no reply") elif meterbus.is_secondary_address(address): if ping_address(ser, meterbus.ADDRESS_NETWORK_LAYER, args.retries, args.echofix): meterbus.send_select_frame(ser, address, args.echofix) try: frame = meterbus.load(meterbus.recv_frame(ser, 1)) except meterbus.MBusFrameDecodeError as e: frame = e.value # Ensure that the select frame request was handled by the slave assert isinstance(frame, meterbus.TelegramACK) frame = None meterbus.send_request_frame( ser, meterbus.ADDRESS_NETWORK_LAYER, read_echo=args.echofix) time.sleep(0.3) frame = meterbus.load( meterbus.recv_frame(ser, meterbus.FRAME_DATA_LENGTH)) else: print("no reply") if frame is not None and args.output != 'dump': recs = [] for rec in frame.records: recs.append({ 'value': rec.value, 'unit': rec.unit }) ydata = { 'manufacturer': frame.body.bodyHeader.manufacturer_field.decodeManufacturer, 'identification': ''.join(map('{:02x}'.format, frame.body.bodyHeader.id_nr)), 'access_no': frame.body.bodyHeader.acc_nr_field.parts[0], 'medium': frame.body.bodyHeader.measure_medium_field.parts[0], 'records': recs } if args.output == 'json': print(json.dumps(ydata, indent=4, sort_keys=True)) elif args.output == 'yaml': def float_representer(dumper, value): if int(value) == value: text = '{0:.4f}'.format(value).rstrip('0').rstrip('.') return dumper.represent_scalar(u'tag:yaml.org,2002:int', text) else: text = '{0:.4f}'.format(value).rstrip('0').rstrip('.') return dumper.represent_scalar(u'tag:yaml.org,2002:float', text) # Handle float and Decimal representation yaml.add_representer(float, float_representer) yaml.add_representer(Decimal, float_representer) print(yaml.dump(ydata, default_flow_style=False, allow_unicode=True, encoding=None)) elif frame is not None: print(frame.to_JSON()) except serial.serialutil.SerialException as e: print(e) if __name__ == '__main__': # meterbus.ADDRESS_NETWORK_LAYER parser = argparse.ArgumentParser( description='Request data over serial M-Bus for devices.') parser.add_argument('-d', action='store_true', help='Enable verbose debug') parser.add_argument('-b', '--baudrate', type=int, default=2400, help='Serial bus baudrate') parser.add_argument('-a', '--address', type=str, default=meterbus.ADDRESS_BROADCAST_REPLY, help='Primary or secondary address') parser.add_argument('-r', '--retries', type=int, default=3, help='Number of ping retries for each address') parser.add_argument('-o', '--output', default="dump", help='Output format [dump,json,yaml]') parser.add_argument('--echofix', action='store_true', help='Read and ignore echoed data from target') parser.add_argument('device', type=str, help='Serial device, URI or binary file') args = parser.parse_args() meterbus.debug(args.d) try: mode = os.stat(args.device).st_mode if stat.S_ISREG(mode): do_reg_file(args) else: do_char_dev(args) except OSError: do_char_dev(args)