168 lines
5.8 KiB
Python
Executable File
168 lines
5.8 KiB
Python
Executable File
#!/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)
|
|
|