""" NMEA2000-Gerät - auf dem Bus erkannte Geräte - für das eigene Gerät steht initUid() zur Verfügung TODO - logging """ import time import struct from . import lookup def set_to_lines(values, maxlen, indent=0): spaces = ' ' * indent lines = [] current = "" for v in values: part = str(v) emax = maxlen - indent if not current: current = part elif len(current) + 1 + len(part) <= emax: current += ',' + part else: lines.append(spaces + current + ',') current = part if current: lines.append(spaces + current) return lines class Device(): def __init__(self, address): # WIP: Felder können sich noch ändern! self.address = address # Kann sich zur Laufzeit ändern self.lastseen = time.time() self.lastpinfo = None # Wann letztes Mal Productinfo erhalten? self.lastcinfo = None # Wann letztes Mal Configurationinfo erhalten? self.has_cinfo = True # Weitere Abfragen können verhindert werden self.has_pgnlist = False # PGN-Listen Tx,Rx vorhanden # Device info self.NAME = 0 # Wird über getNAME (address claim) gefüllt, 64bit Integer self.uniqueid = None # Z.B. aus der Geräteseriennummer abgeleitet self.manufacturercode = 2046 # Open Boat Projects self.instance = 0 # default 0 self.instlower = 0 # 3bit, ISO ECU Instance self.instupper = 0 # 5bit, ISO Function Instance self.sysinstance = 0 # used with bridged networks, default 0 self.devicefunction = None self.deviceclass = None # Product info self.product = None # Product name (ModelID) self.productcode = None # Product code (16bit unsigned) self.serial = None # Device serial number self.modelvers = None # Hardware Version self.softvers = None # Current Software Version self.n2kvers = None # NMEA2000 Network Message Database Version self.certlevel = None # NMEA2000 Certification Level self.loadequiv = None # NMEA2000 LEN # Configuration info self.instdesc1 = None self.instdesc2 = None self.manufinfo = None # PGN lists, fill with functions defined below self.pgns_transmit = set() self.pgns_receive = set() # Additional data self.customname = None # User defined device name def _ISOtime(self, epoch): if not epoch: return "n/a" return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(epoch)) def initUid(self): # Initialize unique id from raspi cpu id and return 21bit value with open("/sys/firmware/devicetree/base/serial-number", "r") as f: hexid = f.read().rstrip('\x00') return int(hexid, 16) & 0x001FFFFF # 21bit mask def getNAME(self): """ NAME is unique on bus Returns bytearray and sets integer NAME for later easy and fast access """ data = bytearray() data.extend(struct.pack(' 0.25: # recv ist blocking. if timeout reached msg should be None msg = bus.recv(0.25) if msg: baseid = (msg.arbitration_id & 0x3ffff00) >> 8 pgn = baseid & 0xffff00 if not pgn == 60928: continue dest = baseid & 0x0000ff if dest == 0xff: print("Claim detected for 0xff (broadcast)") if self.NAME > struct.unpack_from(' 0.25): print("claim seems ok after 250ms") return claim_ok # TODO String handling # Spec says ASCII is only 0x20 to 0x7E. But some devices use full # Latin-1 / ISO-8859-1 code set. # Unicode: # UTF-16 Little Endian # UTF-8 eventually used in some PGNs? def getStringFix(self, text, maxlength=32, filler=b' '): if not text: text = '' if text.isascii(): # text.encode('latin1') # text.encode('ascii') can throw errors # text.encode('ascii', 'replace') sets question mark for unknown codes #return b'x01' + bytes(text or '', 'ascii').ljust(maxlength, b' ') return b'x01' + text.encode('ascii', 'ignore').ljust(maxlength, filler) else: # convert to UTF-16 Little Endian return b'x00' + text.encode('utf-16-le').ljust(maxlength, filler) def getProductInfo(self): """ Returns data for msg prepared to be sent out as fast packet """ print("Set Productinfo:", self.product) data = bytearray() data.extend(struct.pack('