# -*- coding: utf-8 -*- import asynchat import asyncore import socket import sys import os from lxml import objectify from lxml import etree """ebus specific data field formats""" class fields: class DataField(object): def __init__(self, name, offset): self.name = name self.offset = offset def value(self,data): raise NotImplemented() class Data1c(DataField): def value(self,data): return ord(data[self.offset])/2.0 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 + (0xff^(lowByte>>4)) + (0x0ff^(0xf&lowByte) + 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 Bcd(DataField): """ y = dez(High_Nibble(x))*10 + dez(Low_Nibble(x)) """ def value(self, data): byte = ord(data[self.offset]) return (byte >> 4) * 10 + (byte & 0xf) class Byte(DataField): def value(self, data): return ord(data[self.offset]) class ByteEnum(DataField): def __init__(self, name, offset, values): fields.DataField.__init__(self, name, offset) self.values = values def value(self, data): value = ord(data[self.offset]) if self.values.has_key(value): return self.values[value] else: return None class EbusXMLMixin(object): EBUS_SPECIFICATION = os.path.join(os.path.dirname(__file__), "ebus_specification.xml") ebus_xml = objectify.parse(open(EBUS_SPECIFICATION)) @staticmethod def address_to_type(address): d=[dev.get("type") for dev in EbusXMLMixin.ebus_xml.xpath("/ebus/devices/device[@address=$address]", address="0x%.2x"%address)] return len(d)>0 and d[0] or None def _get_packet(self): p=EbusXMLMixin.ebus_xml.xpath("/ebus/packets/packet[@primary=$primary and @secondary=$secondary]", primary="0x%.2x"%self.primary_command, secondary="0x%.2x"%self.secondary_command) return len(p)>0 and p[0] or None def _get_source(self): s=EbusXMLMixin.ebus_xml.xpath("/ebus/devices/device[@address=$address]", address="0x%.2x"%self.source) return len(s)>0 and s[0] or None def _get_destination(self): d=EbusXMLMixin.ebus_xml.xpath("/ebus/devices/device[@address=$address]", address="0x%.2x"%self.destination) return len(d)>0 and d[0] or None def name(self): return self._get_packet().get("name") def description(self): return self._get_packet().get("description") def format(self): packet = self._get_packet() if packet is not None: for field in packet.fields.iterchildren(): name = field.get("name") offset = field.get("offset") if not name: continue elif field.tag == "bit": yield fields.Bit( name, int(offset)) elif field.tag == "data1c": yield fields.Data1c( name, int(offset) ) elif field.tag == "data2b": yield fields.Data2b( name, int(offset) ) elif field.tag == "data2c": yield fields.Data2c( name, int(offset) ) elif field.tag == "bcd": yield fields.Bcd( name, int(offset) ) elif field.tag == "byteEnum": options = dict(map(lambda opt: ( int(opt.get("value"),16), opt.get("name")), field.xpath("./option"))) yield fields.ByteEnum( name, int(offset), options) else: print "error: %s in %s" % (name, self.name()) def formatHex(data): return " ".join(map(lambda byte: "%.2x"%ord(byte), data)) class EbusPacket(EbusXMLMixin): 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 __str__(self): #XXX self.length only in subclasses return "<%-18s name=\"%s\" source=\"0x%.2x\" destination=\"0x%.2x\" primary=0x%x secondary=0x%x length=0x%x %s>" % \ (self.__class__.__name__, self.name(), self.source, self.destination, \ self.primary_command, self.secondary_command, self.length, \ " ".join(map(lambda name: "%s=%s" % (name, self.values()[name]),self.values()))) def values(self): return dict( map(lambda format: (format.name, format.value(self.data) ), self.format() )) 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): 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 = "" self.debug = False 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,dataRaw): #0xaa bug i = 0 data = "" while (i+1) < len(dataRaw): if ord(dataRaw[i]) == 0xaa and ord(dataRaw[i+1]) == 0x01: data += "\xaa" i = i + 1 elif ord(dataRaw[i]) == 0xa9 and ord(dataRaw[i+1]) == 0x00: data += "\xa9" i = i + 1 else: data += dataRaw[i] i = i + 1 if len(data) < 2: print "GAGA" return source = ord(data[0]) sourceType = EbusXMLMixin.address_to_type(source) destination = ord(data[1]) destinationType = EbusXMLMixin.address_to_type(destination) 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:5+payloadLength] if self.debug: print "\033[1;31m%.2x %.2x\033[1;m \033[1;33m%.2x %.2x\033[1;m \033[1;30m%.2x\033[1;m \033[1;45m%s\033[1;m" % (source,destination,primaryCommand,secondaryCommand,payloadLength,formatHex(payload)) p = None if sourceType == 'master' and destinationType == 'master': p = EbusMasterMaster(source, destination, primaryCommand, secondaryCommand, payload) elif sourceType == 'master' and destinationType == 'slave': p = EbusMasterSlave(source, destination, primaryCommand, secondaryCommand, payload, None) #FIXME elif sourceType == 'master' and destinationType == 'broadcast': p = EbusBroadcast(source, destination, primaryCommand, secondaryCommand, payload) else: print >>sys.stderr, "KOMISCHES ZEUG" print >>sys.stderr, "source=%s sourceType=%s destination=%s destType=%s" % \ (source, sourceType, destination, destinationType) return self.handle_ebus(p) def handle_ebus(self,ebus_packet): print >>sys.stderr, "unhandled ebus_packet"