class Lansen2MBus(object): def __init__(self, libpath=None): self._mbus = MBus(host="localhost", port=8888, libpath=libpath) self._mbus_frame = MBusFrame() def getxml(self, buf): _l_field = buf[0] log.debug("L-field: %d", _l_field) if _l_field != len(buf): log.debug("buf: %s", binascii.hexlify(buf)) raise Exception("L-field %d != length of frame %d", _l_field, len(buf)) if buf[10] != 0x7a: raise Exception("only short frame is supported") _d_field = buf[15:] self._mbus._libmbus.parse_set_debug(1) self._mbus_frame.start1 = MBUS_FRAME_LONG_START self._mbus_frame.length1 = len(_d_field) + sizeof(MBusDataVariableHeader) + 3 self._mbus_frame.length2 = self._mbus_frame.length1 self._mbus_frame.start2 = self._mbus_frame.start1 self._mbus_frame.control = MBUS_CONTROL_MASK_RSP_UD self._mbus_frame.address = 0x00 self._mbus_frame.control_information = MBUS_CONTROL_INFO_RESP_VARIABLE self._mbus_frame.stop = MBUS_FRAME_STOP self._mbus_frame.type = MBUS_FRAME_TYPE_LONG header = MBusDataVariableHeader() header.id_bcd[0] = buf[4] header.id_bcd[1] = buf[5] header.id_bcd[2] = buf[6] header.id_bcd[3] = buf[7] header.manufacturer[0] = buf[2] header.manufacturer[1] = buf[3] header.version = buf[8] header.medium = buf[9] header.access_no = buf[11] header.status = buf[12] header.signature[0] = buf[13] header.signature[1] = buf[14] memmove(self._mbus_frame.data, addressof(header), sizeof(header)) self._mbus_frame.data_size = sizeof(MBusDataVariableHeader) + len(_d_field) self._mbus_frame.data[12:self._mbus_frame.data_size] = [x for x in _d_field] self._mbus_frame.checksum = self._mbus_frame.control + self._mbus_frame.address + self._mbus_frame.control_information for i in range(0, self._mbus_frame.data_size): self._mbus_frame.checksum += self._mbus_frame.data[i] if self._mbus._libmbus.frame_verify(self._mbus_frame): raise Exception("mbus verification error: " + str(self._mbus._libmbus.error_str())) mbus_frame_data = self._mbus.frame_data_parse(self._mbus_frame) xml_result = self._mbus._libmbus.frame_data_xml(mbus_frame_data) xml = cast(xml_result, c_char_p).value.decode('ISO-8859-1') return xml
class TaskRunner(object): def __init__(self, loop): self.mbusexecutor = ThreadPoolExecutor(1) # separate thread for MBus operations over serial interface (slow) self.loop = loop self.arguments = {} self.mbusConn = None def __del__(self): self.closeMbusConnection() def get_argument( self, name, default, strip=True ): # mimicing HTTPRequestHander object make function usable both on HTTPRequestHandler- and TaskRunner-objects return self.arguments[name] if name in self.arguments else default @gen.coroutine def bgheartbeat(): out = StringIO() _scheduler.print_jobs(out=out) _log.info("%s" % out.getvalue()) @gen.coroutine def bgsetup(self): firstrun = datetime.fromtimestamp( divmod(datetime.now().timestamp(), bgloop_syncto)[0] * bgloop_syncto + bgloop_syncto ) _log.info("Background jobs first run at %s" % firstrun) _scheduler.add_job(TaskRunner.bgheartbeat, "interval", seconds=60, next_run_time=firstrun) _scheduler.add_job(TaskRunner.readAndStoreMbusData, "interval", args=[self], seconds=60, next_run_time=firstrun) _scheduler.add_job(PowerPoller.getPowerData, "interval", seconds=5, next_run_time=firstrun) out = StringIO() _scheduler.print_jobs(out=out) _log.info("%s" % out.getvalue()) return @gen.coroutine def dbUpsertMbusSlaveInfo(self, slaveinfo, addr): _log.info("Updating (or inserting) mbus slave metadata to database") result = 0 sqlbuf = StringIO() sqlbuf.write( "UPDATE OR IGNORE aMbusSlaveInfo SET\n" ) # update fails intentionally when row not existing, then do insert sqlbuf.write(" manufacturer = '%s',\n" % slaveinfo["Manufacturer"]) sqlbuf.write(" version = %d,\n" % int(slaveinfo["Version"])) sqlbuf.write(" productName = '%s',\n" % slaveinfo["ProductName"]) sqlbuf.write(" medium = '%s',\n" % slaveinfo["Medium"]) sqlbuf.write(" accessNumber = %d,\n" % int(slaveinfo["AccessNumber"])) sqlbuf.write(" signature = '%s',\n" % slaveinfo["Signature"]) sqlbuf.write(" lastStatus = '%s',\n" % slaveinfo["Status"]) sqlbuf.write(" rowUpdatedTimestamp = '%s'\n" % datetime.utcnow().strftime(df)) sqlbuf.write("WHERE address = %d AND id = %s" % (addr, slaveinfo["Id"])) _log.debug("SQL:\n%s" % sqlbuf.getvalue()) # cur.execute(sqlbuf.getvalue()) try: result = yield DBHelperSQLITE.dbExecute( _dbHelper, sqlbuf.getvalue(), parseresp=None, script=False, returnrowcount=True ) except Exception as e: _log.warning("Error during database write : %s" % str(e)) pass if not result: # no rows where updated so insert instead _log.warning("Inserting new row for mbus slave metadata, should only happen when data is recreated") sqlbuf.truncate(0) sqlbuf.seek(0) sqlbuf.write( "INSERT INTO aMbusSlaveInfo(address,id,manufacturer,version,productName,medium,accessNumber,signature,lastStatus,rowCreatedTimestamp)\n" ) sqlbuf.write( " VALUES (%d,'%s','%s',%d,'%s','%s',%d,'%s','%s','%s');\n" % ( addr, slaveinfo["Id"], slaveinfo["Manufacturer"], int(slaveinfo["Version"]), slaveinfo["ProductName"], slaveinfo["Medium"], int(slaveinfo["AccessNumber"]), slaveinfo["Signature"], slaveinfo["Status"], datetime.utcnow().strftime(df), ) ) _log.debug("SQL:\n%s" % sqlbuf.getvalue()) # cur.execute(sqlbuf.getvalue()) try: result = yield DBHelperSQLITE.dbExecute( _dbHelper, sqlbuf.getvalue(), parseresp=None, script=False, returnrowcount=True ) except Exception as e: _log.warning("Error during database write : %s" % str(e)) pass # self.bgDbConn.commit() return result @gen.coroutine def dbInsertMbusDataRecords(self, mbusdata, addr): _log.info("Inserting mbus slave data records to database") sqlbuf = StringIO() sqlbuf2 = StringIO() s2len = 0 slaveid = mbusdata["SlaveInformation"]["Id"] accessnum = mbusdata["SlaveInformation"]["AccessNumber"] mbusdf = "%Y-%m-%dT%H:%M:%S" # 2016-01-28T06:48:13 (In UTC) creationtime = datetime.utcnow().strftime(df) result = 0 for record in mbusdata["DataRecord"]: _log.debug( "Inserting mbus slave data records (address = %d , id = %d) to database" % (addr, int(record["@id"])) ) sqlbuf.write( "INSERT INTO \ aMbusDataRecord(address,id,recordId,recordFunction,recordStorageNumber,recordUnit,recordValue,recordTimestampRaw,recordTimestamp,rowCreatedTimestamp,accessNumber)\n" ) sqlbuf.write( " VALUES (%d,'%s',%d,'%s',%d,'%s','%s','%s','%s','%s',%d);\n" % ( addr, slaveid, int(record["@id"]), record["Function"], int(record["StorageNumber"]), record["Unit"], record["Value"], record["Timestamp"], datetime.strptime(record["Timestamp"], mbusdf).replace(tzinfo=timezone.utc).strftime(df), datetime.utcnow().strftime(df), int(accessnum), ) ) optionals = { k: record.get(k, None) for k in ("@frame", "Tariff", "Device") if k in record } # checking to see if optional data available in record if len(optionals): m = {"@frame": "recordFrame", "Tariff": "recordTariff", "Device": "recordDevice"} _log.debug("Mbus response contains optionals: %s" % optionals) s2len += sqlbuf2.write("UPDATE aMbusDataRecord SET") for i, key in enumerate(optionals): if i != 0: s2len += sqlbuf2.write(" AND") s2len += sqlbuf2.write(" %s = '%s'" % (m[key], optionals[key])) s2len += sqlbuf2.write( " WHERE address=%d AND id='%s' AND recordId='%s' AND recordTimestamp='%s';\n" % (addr, slaveid, record["@id"], creationtime) ) _log.debug("SQL:\n%s" % sqlbuf.getvalue()) # cur.executescript(sqlbuf.getvalue()) try: result = yield DBHelperSQLITE.dbExecute( _dbHelper, sqlbuf.getvalue(), parseresp=None, script=True, returnrowcount=True ) except Exception as e: _log.warning("Error during database write : %s" % str(e)) pass if s2len > 0: _log.debug("SQL(Optionals):\n%s" % sqlbuf2.getvalue()) # cur.executescript(sqlbuf2.getvalue()) try: result = yield DBHelperSQLITE.dbExecute( _dbHelper, sqlbuf2.getvalue(), parseresp=None, script=True, returnrowcount=True ) except Exception as e: _log.warning("Error during database write : %s" % str(e)) pass # self.bgDbConn.commit() return result @gen.coroutine def readAndStoreMbusData(self): mbus_addresses = [1, 15, 31] # m-bus primary addresses to request data from # mbus_addresses = [1] # m-bus primary addresses to request data from reply_dict = {} if not self.mbusConn: _log.info("M-bus connnection not opened previously, opening now..") self.mbusConn = yield self.openMbusConnection() for addr in mbus_addresses: try: res = yield self.readMbus(addr) _log.debug("address: %d resp:\n%s" % (addr, json.dumps(res, indent=4))) if res: reply_dict[addr] = res rowsUpdated = yield self.dbUpsertMbusSlaveInfo( reply_dict[addr]["MBusData"]["SlaveInformation"], addr ) if rowsUpdated: # only try to add records if previous sql was successful rowsUpdated = yield self.dbInsertMbusDataRecords(reply_dict[addr]["MBusData"], addr) except Exception as e: _log.error( "Exception in m-bus read or data insert: addr: %s, type %s, desc: %s" % (addr, type(e), str(e)) ) pass if len(reply_dict): _log.info("Successful m-bus read, aggregating MC302 records") res = yield self.aggregateMC302DataRecords() # for k,resp in enumerate(reply_dict): # _log.info("i: %s key:%s value:\n%s" % (k,resp,reply_dict[resp])) else: _log.info("No data received over m-bus") # mbusConn = yield self.closeMbusConnection() # FIX: don't close all the time return @run_on_executor(executor="mbusexecutor") def openMbusConnection(self): try: _log.info("Opening m-bus connection on device '%s'" % mbusSerialPort) self.mbusConn = MBus(device=mbusSerialPort) self.mbusConn.connect() except Exception as e: _log.error("Exception while connecting to m-bus : type %s, desc: %s" % (type(e), str(e))) raise Exception(e) return self.mbusConn @run_on_executor(executor="mbusexecutor") def closeMbusConnection(self): try: _log.info("Closing m-bus connection") self.mbusConn.disconnect() self.mbusConn = None except Exception as e: _log.error("Exception while closing connection to m-bus : type %s, desc: %s" % (type(e), str(e))) raise Exception(e) return self.mbusConn @run_on_executor(executor="mbusexecutor") def readMbus(self, addr=0): reply = None try: self.mbusConn.send_request_frame(addr) reply = xmltodict.parse( self.mbusConn.frame_data_xml(self.mbusConn.frame_data_parse(self.mbusConn.recv_frame())) ) # xmltodict works directly on the byte-string xml from libmbus except Exception as e: _log.error( "Exception while requesting data from m-bus : mbus-address: %d, type %s, desc: %s" % (addr, type(e), str(e)) ) raise Exception(e) return reply def getFactor(self, exp=""): factor = 1.0 if exp == "m": factor = float(1 / 1000) elif exp == "my": factor = float(1 / 1000000) elif exp == "10": factor = float(10) elif exp == "100": factor = float(100) elif exp == "k": factor = float(1000) elif exp == "10 k": factor = float(10000) elif exp == "100 k": factor = float(100000) elif exp == "M": factor = float(1000000) elif exp == "T": factor = float(1000000000) elif exp.startswith("1e"): factor = float(exp) return factor @gen.coroutine def aggregateMC302DataRecords(self): # merge 30 mbus data record rows into 1 row and update SlaveInfo _log.info("Aggregating mc302 records...") _log.debug("Get timestamp of latest MC302 record update...") # Find all mbus slaves and their addresses sql = "select distinct id,address from aMbusSlaveInfo" result = [] try: result = yield DBHelperSQLITE.dbExecute(_dbHelper, sql, parseresp=None, script=False, returnrowcount=False) except Exception as e: _log.warning("Error during database operation : %s" % str(e)) pass slaves = {} for row in result: slaves[row[0]] = {} slaves[row[0]]["address"] = row[1] # Find all last aMbusMC302Record-update for the slaves sql = "select id,max(recordTimestamp) from aMbusMC302Record group by id" result = [] try: result = yield DBHelperSQLITE.dbExecute(_dbHelper, sql, parseresp=None, script=False, returnrowcount=False) except Exception as e: _log.warning("Error during database operation : %s" % str(e)) pass for row in result: _log.info(row) if slaves[row[0]]: slaves[row[0]]["lastUpdate"] = row[1] sqlbuf = StringIO() # Loop over slave aggregating data for id, val in slaves.items(): # TABLE aMbusDataRecord # rowid INTEGER PRIMARY KEY NOT NULL, # address INTEGER NOT NULL, /* Mbus primary address*/ # id TEXT NOT NULL, /* Mbus slave (SlaveInformation) id*/ # recordId INTEGER NOT NULL, /* XML: /MBusData/Record@id */ # recordFrame TEXT, /* XML: /MBusData/Record@frame (optional), not clear when this data is received but libmbus xml has reserved space for this */ # recordFunction TEXT NOT NULL, /* XML: /MBusData/Record/Function */ # recordStorageNumber INTEGER, /* XML: /MBusData/Record/StorageNumber */ # recordTariff TEXT, /* XML: /MBusData/Record/Tariff (optional) */ # recordDevice TEXT, /* XML: /MBusData/Record/Device (optional) */ # recordUnit TEXT NOT NULL, /* XML: /MBusData/Record/Unit */ # recordValue INTEGER NOT NULL, /* XML: /MBusData/Record/Value */ # recordTimestampRaw TEXT NOT NULL, /* XML: /MBusData/Record/Timestamp */ # recordTimestamp TEXT NOT NULL, /* Parsed from /MBusData/Record/Timestamp */ # rowCreatedTimestamp INTEGER NOT NULL, # accessNumber INTEGER, sql = "SELECT distinct id,address,recordTimestamp,accessNumber FROM aMbusDataRecord WHERE id = '%s'" % id if "lastUpdate" in val: sql += " AND recordTimestamp > '%s'" % val["lastUpdate"] sql += ";\n" timestamps = [] try: timestamps = yield DBHelperSQLITE.dbExecute( _dbHelper, sql, parseresp=None, script=False, returnrowcount=False ) except Exception as e: _log.warning("Error during database operation : %s" % str(e)) pass creationtime = datetime.utcnow().strftime(df) for ts in timestamps: curr_id = ts[0] curr_addr = ts[1] curr_ts = ts[2] curr_acc = ts[3] # TABLE aMbusMC302Record # rowId INTEGER PRIMARY KEY NOT NULL, # accessNumber INTEGER NOT NULL, /* Slave MBus response sequence number */ # address INTEGER NOT NULL, /* Mbus primary address*/ # id TEXT NOT NULL, /* Mbus slave (SlaveInformation) id*/ # recordTimestampRaw TEXT, /* same for all data records in single Mbus response*/ # recordTimestamp TEXT, /* parsed value of raw */ # heatingEnergy INTEGER, /* Heating energy (cumulative, non-resettable) converted to Wh from 1|Instantaneous value|0|Energy;100;Wh */ # coolingEnergy INTEGER, /* Cooling energy (cumulative, non-resettable) converted to Wh from 2|Instantaneous value|0|Energy;100;Wh */ # energyM3T1 INTEGER, /* Energy mˆ3 * T1 (cumulative Volume * Temperature) from 3|Instantaneous value|0|Manufacturer specific */ # energyM3T2 INTEGER, /* Energy mˆ3 * T2 (cumulative Volume * Temperature) from 4|Instantaneous value|0|Manufacturer specific */ # volume INTEGER, /* Current volume (cumulative converted to dm3) from 5|Instantaneous value|0|Volume;m;m^3 */ # hourCounter INTEGER, /* Hour counter (non-resettable) from 6|Instantaneous value|0|On time (hours) */ # errorHourCounter INTEGER, /* Error hour counter (cumul hours in error, no resettable) from 7|Value during error state|0|On time (hours) */ # temp1 REAL, /* Current (Flow) Temperature T1 (converted to deg.Celsius) from 8|Instantaneous value|0|Flow temperature;1e-2;deg C */ # temp2 REAL, /* Current (Return) Temperature T2 (converted to deg.Celsius) from 9|Instantaneous value|0|Return temperature;1e-2;deg C */ # deltaT1T2 REAL, /* Temperature difference T1-T2 (converted to deg.Celsius) from 10|Instantaneous value|0|Temperature Difference;1e-2;deg C */ # power INTEGER, /* Current power (converted to W) from 11|Instantaneous value|0|Power;100;W */ # powerMax INTEGER, /* Maximum power since XX (converted to Watt) from 12|Maximum value|0|Power;100;W */ # flow INTEGER, /* Current water flow (converted to dm3/h) from 13|Instantaneous value|0|Volume flow;m;m^3/h */ # flowMax INTEGER, /* Maximum water flow since XX (converted to dm3/h) 14|Maximum value|0|Volume flow;m;m^3/h */ # errorFlags TEXT, /* Error flags from 15|Instantaneous value|0|Error flags */ # timePoint TEXT, /* Date+Time from 16|Instantaneous value|0|Time Point (time & date) */ # targetHeatingEnergy INTEGER,/* Heating energy since targetTimepoint (in Wh) from 17|Instantaneous value|1|Energy;100;Wh */ # targetCoolingEnergy INTEGER, /* Cooling energy since targetTimepoint (in Wh) from 18|Instantaneous value|1|Energy;100;Wh */ # targetEnergyM3T1 INTEGER, /* Energy mˆ3 * T1 since targetTimepoint from 19|Instantaneous value|1|Manufacturer specific */ # targetEnergyM3T2 INTEGER, /* Energy mˆ3 * T2 since targetTimepoint from 20|Instantaneous value|1|Manufacturer specific */ # targetVolume INTEGER, /* Volume since targetTimepoint (in dm3) from 21|Instantaneous value|1|Volume;m;m^3 */ # targetPowerMax INTEGER, /* Max power since targetTimepoint (in Watt) from 22|Maximum value|1|Power;100;W */ # targetFlowMax INTEGER, /* Max flow since targetTimepoint (in dm3/h) from 23|Maximum value|1|Volume flow;m;m^3/h */ # targetTimepoint TEXT, /* Target time point (date) from 24|Instantaneous value|1|Time Point (date) */ # rowCreatedTimestamp TEXT, # UNIQUE (accessNumber,id,recordTimestamp) ON CONFLICT REPLACE # Insert row sql = ( "INSERT INTO aMbusMC302Record(id,address,recordTimestamp,accessNumber,rowCreatedTimestamp) VALUES ('%s',%d,'%s',%d,'%s');\n" % (curr_id, curr_addr, curr_ts, curr_acc, creationtime) ) try: result = yield DBHelperSQLITE.dbExecute( _dbHelper, sql, parseresp=None, script=False, returnrowcount=True ) except Exception as e: _log.warning("Error during database operation : %s" % str(e)) pass _log.debug(sql) sql = ( "SELECT address,id,recordId,recordStorageNumber,recordUnit,recordValue FROM aMbusDataRecord WHERE id = '%s' AND recordTimestamp = '%s';\n" % (curr_id, curr_ts) ) records = [] try: records = yield DBHelperSQLITE.dbExecute( _dbHelper, sql, parseresp=None, script=False, returnrowcount=False ) except Exception as e: _log.warning("Error during database operation : %s" % str(e)) pass # reuse sqlbuf, empty it first truncate+seek sqlbuf.truncate(0) sqlbuf.seek(0) tmpbuf = StringIO() for record in records: tmpbuf.truncate(0) tmpbuf.seek(0) curr_recid = record[2] curr_recstor = record[3] curr_recunit = record[4] curr_value = record[5] factor = 1.0 if ";" in curr_recunit: parts = curr_recunit.split(";") factor = self.getFactor(str(parts[1])) tmpbuf.write("UPDATE aMbusMC302Record SET \n") if curr_recid == 0: # skip "Firmware" _log.debug("Skipping 0 = Firmware for now") continue elif curr_recid == 1: # tmpbuf.write("heatingEnergy = %d\n" % int(factor * curr_value)) elif curr_recid == 2: # tmpbuf.write("coolingEnergy = %d\n" % int(factor * curr_value)) elif curr_recid == 3: # tmpbuf.write("energyM3T1 = %d\n" % int(curr_value)) elif curr_recid == 4: # tmpbuf.write("energyM3T2 = %d\n" % int(curr_value)) elif curr_recid == 5: # tmpbuf.write( "volume = %d\n" % int(factor * curr_value * 1000) ) # volume converted from m3 to dm3 elif curr_recid == 6: # tmpbuf.write("hourCounter = %d\n" % int(curr_value)) elif curr_recid == 7: # tmpbuf.write("errorHourCounter = %d\n" % int(curr_value)) elif curr_recid == 8: # tmpbuf.write("temp1 = %f\n" % float(factor * curr_value)) elif curr_recid == 9: # tmpbuf.write("temp2 = %f\n" % float(factor * curr_value)) elif curr_recid == 10: # tmpbuf.write("deltaT1T2 = %f\n" % float(factor * curr_value)) elif curr_recid == 11: # tmpbuf.write("power = %d\n" % int(factor * curr_value)) elif curr_recid == 12: # tmpbuf.write("powerMax = %d\n" % int(factor * curr_value)) elif curr_recid == 13: # tmpbuf.write( "flow = %d\n" % int(factor * curr_value * 1000) ) # flow converted from m3/h to dm3/h elif curr_recid == 14: # tmpbuf.write( "flowMax = %d\n" % int(factor * curr_value * 1000) ) # maxflow converted from m3/h to dm3/h elif curr_recid == 15: # tmpbuf.write("errorFlags = '%s'\n" % str(curr_value)) elif curr_recid == 16: # tmpbuf.write("timePoint = '%s'\n" % str(curr_value)) elif curr_recid == 17: # tmpbuf.write("targetHeatingEnergy = %d\n" % int(factor * curr_value)) elif curr_recid == 18: # tmpbuf.write("targetCoolingEnergy = %d\n" % int(factor * curr_value)) elif curr_recid == 19: # tmpbuf.write("targetEnergyM3T1 = %d\n" % int(curr_value)) elif curr_recid == 20: # tmpbuf.write("targetEnergyM3T2 = %d\n" % int(curr_value)) elif curr_recid == 21: # tmpbuf.write( "targetVolume = %d\n" % int(factor * curr_value * 1000) ) # volume converted from m3 to dm3 elif curr_recid == 22: # tmpbuf.write("targetPowerMax = %d\n" % int(factor * curr_value)) elif curr_recid == 23: # tmpbuf.write("targetFlowMax = %d\n" % int(factor * curr_value)) elif curr_recid == 24: # tmpbuf.write("targetTimepoint = '%s'\n" % str(curr_value)) elif curr_recid == 25: # _log.debug("Skipping record id 25") continue elif curr_recid == 26: # _log.debug("Skipping record id 26") continue elif curr_recid == 27: # _log.debug("Skipping record id 27") continue elif curr_recid == 28: # _log.debug("Skipping record id 28") continue elif curr_recid == 29: # _log.debug("Skipping record id 29") continue else: continue tmpbuf.write("WHERE id = '%s' AND recordTimestamp = '%s';\n" % (curr_id, curr_ts)) sqlbuf.write(tmpbuf.getvalue()) _log.debug(sqlbuf.getvalue()) result = [] try: result = yield DBHelperSQLITE.dbExecute( _dbHelper, sqlbuf.getvalue(), parseresp=None, script=True, returnrowcount=True ) except Exception as e: _log.warning("Error during database operation : %s" % str(e)) pass _log.info("Aggregation done") return
from mbus.MBus import MBus debug = True address = 1 mbus = MBus(host="mbus-gw1", port=8888) if debug: print("mbus = " + str(mbus)) mbus.connect() if debug: print("mbus = " + str(mbus)) mbus.send_request_frame(address) reply = mbus.recv_frame() if debug: print("reply =", reply) reply_data = mbus.frame_data_parse(reply) if debug: print("reply_data =", reply_data) xml_buff = mbus.frame_data_xml(reply_data) print("xml_buff =", xml_buff)
class MBusSerial(object): def __init__(self, msgbus, port, interval=1, ids=[]): self._msgbus = msgbus self._port = port self._interval = interval self._get_next_id = itertools.cycle(ids).__next__ self._ioloop = tornado.ioloop.IOLoop.current() self._executor = ThreadPoolExecutor(max_workers=1) self._mbus = None self._ioloop.add_callback(self._read_mbus) def _read_mbus(self): self._executor.submit(self._runner).add_done_callback(self._done) def _reconnect(self): if self._mbus: try: self._mbus.disconnect() self._mbus = None except Exception as ex: log.error("MBus close exception %s", ex) if not self._mbus: try: self._mbus = MBus(device=self._port, libpath='./libmbus.so') except Exception as ex: log.error("MBus create exception %s", ex) return try: self._mbus.connect() except Exception as ex: log.error("MBus open exception %s", ex) self._mbus = None def _runner(self): id = self._get_next_id() if not self._mbus: self._reconnect() if not self._mbus: log.error("MBus reconnect failed") return try: self._mbus.select_secondary_address(id) except Exception as ex: log.error("MBus select exception %s", ex) self._reconnect() return try: self._mbus.send_request_frame(MBUS_ADDRESS_NETWORK_LAYER) except Exception as ex: log.error("MBus send exception %s", ex) self._reconnect() return try: reply = self._mbus.recv_frame() except Exception as ex: log.error("MBus receive exception %s", ex) try: reply_data = self._mbus.frame_data_parse(reply) xml = self._mbus.frame_data_xml(reply_data) self._ioloop.add_callback(self._msgbus.publish, 'mbus_data_received', { 'port': self._port, 'id': id, 'xml': xml }) except Exception as ex: log.error("MBus parse exception %s", ex) def _done(self, _): time.sleep(self._interval) self._ioloop.add_callback(self._read_mbus)
from mbus.MBus import MBus #import xmltodict debug = True mbus_addresses = [1,15,31] # m-bus primary addresses to request data from mbus = MBus(device=b'/dev/ttyUSB0') # C-library expects byte strings, Python3 string is unicode -> prefix string with 'b' #reply_dict = {} reply_xml = {} try: mbus.connect() for adr in mbus_addresses: try: mbus.send_request_frame(adr) reply_xml[adr] = mbus.frame_data_xml(mbus.frame_data_parse(mbus.recv_frame())) #reply_dict[adr] = xmltodict.parse(mbus.frame_data_xml(mbus.frame_data_parse(mbus.recv_frame()))) # xmltodict works directly on the byte-string xml from libmbus except Exception as e: print ("Exception while requesting data from m-bus : type %s, desc: %s" % (type(e),str(e))) pass mbus.disconnect() except Exception as e: print ("Exception while requesting data from m-bus : type %s, desc: %s" % (type(e),str(e))) pass # if reply_dict: # for k,resp in enumerate(reply_dict): # print("i: %s key:%s value:\n%s" % (k,resp,reply_dict[resp])) if reply_xml: for k,resp in enumerate(reply_xml): print("i: %s key:%s value:\n%s" % (k,resp,reply_xml[resp]))