diff options
Diffstat (limited to 'ebus')
-rw-r--r-- | ebus/__init__.py | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/ebus/__init__.py b/ebus/__init__.py new file mode 100644 index 0000000..d0d3497 --- /dev/null +++ b/ebus/__init__.py @@ -0,0 +1,252 @@ +# -*- coding:utf8 -*- +import asynchat +import asyncore +import socket +import sys + +deviceDescription = [ + {'address':0x03, 'type':'master', 'description':'Feuerungsautomat'}, + {'address':0x10, 'type':'master', 'description':'Heizungsregler #2'}, + {'address':0x30, 'type':'master', 'description':'Heizkreisregler 1'}, + {'address':0x70, 'type':'master', 'description':'Heizkreisregler 2'}, + {'address':0x71, 'type':'master', 'description':'Heizungsregler #9'}, + {'address':0xf1, 'type':'master', 'description':'Heizungsregler #10'}, + {'address':0x50, 'type':'slave', 'description':'Mischer 1'}, + {'address':0x51, 'type':'slave', 'description':'Mischer 2'}, + {'address':0x90, 'type':'slave', 'description':'Raumgeräte/Fernsteller 1'}, + {'address':0x91, 'type':'slave', 'description':'Raumgeräte/Fernsteller 2'}, + {'address':0xfe, 'type':'broadcast', 'description':'Broadcast'}, +] + +"""ebus specific data field formats""" +class fields: + class DataField(object): + def __init__(self, offset): + self.offset = offset + def value(self,data): + raise NotImplemented() + + class Data2c(DataField): + """ + Beispiel für die Berechnung: + if ((x & 8000h) == 8000h) // y negativ + y = - [dez(High_Byte(!x)) 16 + dez(High_Nibble (Low_Byte (!x))) + + (dez(Low_Nibble (Low_Byte (!x))) +1 ) / 16] + else // y positiv + y = dez(High_Byte(x)) 16 + dez(High_ Nibble (Low Byte (x))) + + dez(Low_ Nibble (Low Byte (x))) / 16 + """ + def value(self,data): + highByte = ord(data[self.offset+1]) + lowByte= ord(data[self.offset]) + if (0x8000 & (highByte<<8 | lowByte)) == 0x8000: + return (-1) * ( (0xff^highByte)*16 + (0xf^(lowByte>>4)) + (0x0f^(0x0&flowByte) + 1)/16.0 ) + else: + return highByte*16 + (lowByte>>4) + (lowByte&0xf)/16.0 + + class Data2b(DataField): + """ + if ((x&8000h) == 8000h) // y negativ + y = - [dez(High_Byte(!x)) + (dez(Low_Byte(!x)) + 1) / 256] + else // y positiv + y = dez(High_Byte (x)) + dez(Low_Byte (x)) / 256 + """ + def value(self,data): + highByte = ord(data[self.offset+1]) + lowByte= ord(data[self.offset]) + if (0x8000 & (highByte<<8 | lowByte)) == 0x8000: + return (-1) * ((0xff^highByte) + (0xff^lowByte+1)/256.0) + else: + return highByte + lowByte/256.0 + + class Bit(DataField): + def value(self, data): + return ord(data[self.offset]) == 0x1 + + class ByteEnum(DataField): + def __init__(self, offset, values): + self.values = values + fields.DataField.__init__(self, offset) + def value(self, data): + value = ord(data[self.offset]) + if self.values.has_key(value): + return self.values[value] + else: + return None + +packetDescription = [ + # Service 0x05 (Brennersteuerbefehle) + {'primary':0x5, 'secondary':0x3, 'name':'Betriebsdaten des Feuerungsautomaten an den Regler Block1'}, + {'primary':0x5, 'secondary':0x7, 'name':'Betriebsdaten des Reglers an den Feuerungsautomaten','format': { + 'betriebszustand':fields.ByteEnum(0, { + 0x00:'Brenner Abschalten', + 0x01:'Keine Aktion', + 0x55:'Brauchwassbereitung', + 0xaa:'Heizbetrieb', + 0xcc:'Emmissionskontroll', + 0xdd:'TÜV-Funktion', + 0xee:'Regler Stopp', + 0x66:'Brauchwasserbereitung bei Reglerstoppfunktion', + 0xbb:'Brauchwasserbereitung bei Heizbetrieb', + 0x44:'Reglerstoppfunktion bei stufigem Betrieb'}), + 'betriebszustand2':fields.ByteEnum(1, { + 0x00:'Keine Aktion', + 0x01:'Ausschalten Kesselpumpe', + 0x02:'Einschalten Kesselpumpe', + 0x03:'Ausschalten variable Verbraucher', + 0x04:'Einschalten variabler Verbraucher'}), + 'kesselSollwertTemperatur':fields.Data2c(2), + 'kesselSollwertDruck':fields.Data2b(4), + }}, + # Service 0x07 (Systemdatenbefehle) + {'primary':0x7, 'secondary':0x0, 'name':'Datum/Zeit - Meldung eines eBUS Masters'}, + {'primary':0x7, 'secondary':0x4, 'name':'Identifikation'}, + # Service 0x08 (Reglerbefehle) + {'primary':0x8, 'secondary':0x0, 'name':'Sollwertübertragung des Reglers an andere Regler'}, + + # Response + #p[0] = Einheit (1=>Liter, 2=>Kubik) + #p[1] = 10^0 + #p[2] = 10^2 + #p[3] = 10^4 + #p[4] = 10^6 + {'primary':0x3, 'secondary':0x8, 'name':'Gesamtbrennstoffmengenzähle lesen'}, + {'primary':0x50, 'secondary':0x17, 'name':'Solar Daten', 'format':{ + 'solarPumpe':fields.Bit(0), + 'tempKollektor':fields.Data2c(2), + 'tempWarmwasserSolar':fields.Data2c(4)}}, +] + +def formatHex(data): + return " ".join(map(lambda byte: "%.2x"%ord(byte), data)) + +def getDsc(address): + dev=filter(lambda dev: dev['address'] == address, deviceDescription) + if len(dev)>0: + return dev[0]['description'] + else: + return None + +class EbusPacket(object): + def __init__(self, source, destination, primary_command, secondary_command): + self.source = source + self.destination = destination + self.primary_command = primary_command + self.secondary_command = secondary_command + + def name(self): + if self.description(): + return self.description()['name'] + + def __str__(self): + #XXX self.length only in subclasses + return "<%-18s name=\"%-15s\" source=\"%s\" destination=\"%s\" primary=0x%x secondary=0x%x length=0x%x %s>" % \ + (self.__class__.__name__, self.name(), getDsc(self.source), getDsc(self.destination), \ + self.primary_command, self.secondary_command, self.length, \ + " ".join(map(lambda name: "%s=%s" % (name, self.values()[name]),self.values()))) + + def description(self): + desc = filter(lambda p: p['primary'] == self.primary_command \ + and p['secondary'] == self.secondary_command, packetDescription) + if len(desc) > 0: + return desc[0] + else: + return None + + def values(self): + desc = self.description() + if not desc or not desc.has_key('format'): + return dict() + else: + #XXX self.data only in subclass + return dict( map(lambda name: (name, desc['format'][name].value(self.data) ), desc['format'].keys()) ) + +class EbusMasterMaster(EbusPacket): + def __init__(self, source, destination, primary_command, secondary_command, data): + EbusPacket.__init__(self, source, destination, primary_command, secondary_command) + self.length = len(data) + self.data = data + +class EbusMasterSlave(EbusPacket): + def __init__(self, source, destination, primary_command, secondary_command, request, response): + EbusPacket.__init__(self, source, destination, primary_command, secondary_command) + self.length = len(request) + self.request = request + self.response = response + +class EbusBroadcast(EbusPacket): + def __init__(self, source, destination, primary_command, secondary_command, data): + EbusPacket.__init__(self, source, destination, primary_command, secondary_command) + self.length = len(data) + self.data = data + + +class EbusReader(asynchat.async_chat): + def __init__(self): + self.buffer = "" + + asynchat.async_chat.__init__(self) + self.set_terminator("") + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.connect(("10.2.2.200", 7970)) + + self.packetIndex = 0 + self.buf = "" + + def collect_incoming_data(self,data): + for it in range(len(data)): + if data[it] == "\xaa": + if it+1 < len(data) and data[it+1] != "\xaa": + self._parse(self.buf) + self.buf = "" + self.packetIndex = 0 + else: + self.buf += data[it] + self.packetIndex = self.packetIndex + 1 + #print "%.2x [%d]" % (ord(data[it]),self.packetIndex) + + def _parse(self,data): + if len(data) < 2: + print >>sys.stderr, "GAGA" + return + source = ord(data[0]) + destination = ord(data[1]) + + sourceDevice = filter(lambda dev: dev['address'] == source, deviceDescription) + destinationDevice = filter(lambda dev: dev['address'] == destination, deviceDescription) + if len(sourceDevice) == 0 or len(destinationDevice) == 0: + print >>sys.stderr, "Unbekanntes Paket: source=%x destination=%x" % (source, destination) + return + + if len(data) < 9: + print >>sys.stderr, "Unvollständige Daten" + return + + primaryCommand = ord(data[2]) + secondaryCommand = ord(data[3]) + payloadLength = ord(data[4]) + payload = data[5:6+payloadLength] + #delete 0x50 packets for devel + #if (primaryCommand == ord("\x50")): +# return + #print "PR SC NN D0 D1 D2 D3 D4 D5 D6 D7 ..." + #print "%.2x" % (primaryCommand,) + #print "%.2x %.2x %.2x %s" % (primaryCommand,secondaryCommand,payloadLength,"bla") + #print "%.2x %.2x %.2x %s" % (primaryCommand,secondaryCommand,payloadLength,formatHex(payload)) + + p = None + if sourceDevice[0]['type'] == 'master' and destinationDevice[0]['type'] == 'master': + p = EbusMasterMaster(source, destination, primaryCommand, secondaryCommand, payload) + self.handle_ebus(p) + elif sourceDevice[0]['type'] == 'master' and destinationDevice[0]['type'] == 'slave': + p = EbusMasterSlave(source, destination, primaryCommand, secondaryCommand, payload, None) #FIXME + self.handle_ebus(p) + elif sourceDevice[0]['type'] == 'master' and destinationDevice[0]['type'] == 'broadcast': + p = EbusBroadcast(source, destination, primaryCommand, secondaryCommand, payload) + self.handle_ebus(p) + else: + print >>sys.stderr, "KOMISCHES ZEUG" + return + + def handle_ebus(self,ebus_packet): + print >>sys.stderr, "unhandled ebus_packet" |