summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/ebus_dump4
-rw-r--r--dist/ebus-0.2_r1652-py2.5.eggbin10430 -> 9707 bytes
-rw-r--r--ebus/__init__.py219
-rw-r--r--test.py14
4 files changed, 86 insertions, 151 deletions
diff --git a/bin/ebus_dump b/bin/ebus_dump
index 3a86ee8..41efa33 100644
--- a/bin/ebus_dump
+++ b/bin/ebus_dump
@@ -4,6 +4,10 @@ import asyncore
from ebus import EbusReader
class MyEbusReader(EbusReader):
+ def __init__(self,*args,**kwargs):
+ EbusReader.__init__(self,*args,**kwargs)
+ self.debug=True
+
def handle_ebus(self,ebus_packet):
if ebus_packet.values() != dict():
print ebus_packet
diff --git a/dist/ebus-0.2_r1652-py2.5.egg b/dist/ebus-0.2_r1652-py2.5.egg
index 52e26c9..fba3dc6 100644
--- a/dist/ebus-0.2_r1652-py2.5.egg
+++ b/dist/ebus-0.2_r1652-py2.5.egg
Binary files differ
diff --git a/ebus/__init__.py b/ebus/__init__.py
index 71d6397..0813b53 100644
--- a/ebus/__init__.py
+++ b/ebus/__init__.py
@@ -1,27 +1,18 @@
-# -*- coding:utf8 -*-
+# -*- coding: utf-8 -*-
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'},
-]
+import os
+from lxml import objectify
+from lxml import etree
"""ebus specific data field formats"""
class fields:
class DataField(object):
- def __init__(self, offset):
+ def __init__(self, name, offset):
+ self.name = name
self.offset = offset
def value(self,data):
raise NotImplemented()
@@ -81,9 +72,10 @@ class fields:
class ByteEnum(DataField):
- def __init__(self, offset, values):
+ def __init__(self, name, offset, values):
+ fields.DataField.__init__(self, name, offset)
self.values = values
- fields.DataField.__init__(self, offset)
+
def value(self, data):
value = ord(data[self.offset])
if self.values.has_key(value):
@@ -91,127 +83,84 @@ class fields:
else:
return None
-#Pflichfelder: primary, secondary, description, name, format (auch leer)
-packetDescription = [
- # Service 0x05 (Brennersteuerbefehle)
- {'primary':0x5, 'secondary':0x3, 'description':'Betriebsdaten des Feuerungsautomaten an den Regler Block1','name':'betriebsdatenRegler1', 'format': {
- 'blocknumber':fields.Byte(0),
- #('errorCode':fields.Byte(1),
- 'stellgradKesselleistung':fields.Byte(3),
- 'kesseltemperatur':fields.Data1c(4),
- 'ruecklaufTemperatur':fields.Byte(5),
- 'boilerTemperatur':fields.Byte(6),
- 'ausenTemperatur':fields.Byte(7),
- #if ord(fields.Byte(0)) == 0x01 else
- #'abgasTemperatur':fields.Data2c(1),
- #'BWW-Vorlauftemperatur':fields.Data1c(3),
- #'momentaneRelativeKesselTemperatur':fields.Data1c(4),
- #'gemeinsameVorlaufTemperatur':fields.Data1c(5)),
- }},
-
- {'primary':0x5, 'secondary':0x7, 'description':'Betriebsdaten des Reglers an den Feuerungsautomaten','name':'betriebsdatenFeuerungsautomat','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),
- 'stellgrad':fields.Data1c(6),
- 'brauchwasserSollwert':fields.Data1c(7)
- }},
- # Service 0x07 (Systemdatenbefehle)
- {'primary':0x7, 'secondary':0x0, 'description':'Datum/Zeit - Meldung eines eBUS Masters', 'name':'datumZeit', 'format': {
- 'aussenTemperatur':fields.Data2b(0),
- 'sekunden':fields.Bcd(2),
- 'minuten':fields.Bcd(3),
- 'stunden':fields.Bcd(4),
- 'tag':fields.Bcd(5),
- 'monat':fields.Bcd(6),
- 'wochentag':fields.Bcd(7),
- 'jahr':fields.Bcd(8)}},
-
- {'primary':0x7, 'secondary':0x4, 'description':'Identifikation', 'name':'identifikation', 'format':{
- }},
-
- # Service 0x08 (Reglerbefehle)
- {'primary':0x8, 'secondary':0x0, 'description':'Sollwertübertragung des Reglers an andere Regler', 'name':'sollwertÜbertragungRegler', 'format':{
- }},
-
- # 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, 'description':'Gesamtbrennstoffmengenzähler lesen', 'name':'brenstoffMengeLesen', 'format':{
- }},
-
- {'primary':0x50, 'secondary':0x17, 'description':'Solar Daten', 'name': 'solarDaten', 'format':{
- 'solarPumpe':fields.Bit(0),
- 'tempKollektor':fields.Data2c(2),
- 'tempWarmwasserSolar':fields.Data2c(4)}},
-]
+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))
-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):
+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 name(self):
- if self._definition():
- return self._definition()['name']
-
- def description(self):
- if self._definition():
- return self._definition()['description']
-
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), \
+ 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 _definition(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):
- definition = self._definition()
- if definition and definition.has_key('format'):
- #XXX self.data only in subclass
- return dict( map(lambda name: (name, definition['format'][name].value(self.data) ), definition['format'].keys()) )
- else:
- return dict()
+ 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):
@@ -235,8 +184,6 @@ class EbusBroadcast(EbusPacket):
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)
@@ -277,13 +224,9 @@ class EbusReader(asynchat.async_chat):
print "GAGA"
return
source = ord(data[0])
+ sourceType = EbusXMLMixin.address_to_type(source)
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
+ destinationType = EbusXMLMixin.address_to_type(destination)
if len(data) < 9:
print >>sys.stderr, "Unvollständige Daten"
@@ -293,23 +236,23 @@ class EbusReader(asynchat.async_chat):
secondaryCommand = ord(data[3])
payloadLength = ord(data[4])
payload = data[5:5+payloadLength]
- ### DEBUG ###
+
if self.debug:
- "\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))
+ 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 sourceDevice[0]['type'] == 'master' and destinationDevice[0]['type'] == 'master':
+ if sourceType == 'master' and destinationType == 'master':
p = EbusMasterMaster(source, destination, primaryCommand, secondaryCommand, payload)
- self.handle_ebus(p)
- elif sourceDevice[0]['type'] == 'master' and destinationDevice[0]['type'] == 'slave':
+ elif sourceType == 'master' and destinationType == 'slave':
p = EbusMasterSlave(source, destination, primaryCommand, secondaryCommand, payload, None) #FIXME
- self.handle_ebus(p)
- elif sourceDevice[0]['type'] == 'master' and destinationDevice[0]['type'] == 'broadcast':
+ elif sourceType == 'master' and destinationType == 'broadcast':
p = EbusBroadcast(source, destination, primaryCommand, secondaryCommand, payload)
- self.handle_ebus(p)
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"
diff --git a/test.py b/test.py
index 292ac42..2558be8 100644
--- a/test.py
+++ b/test.py
@@ -1,16 +1,4 @@
#!/usr/bin/env python2.5
-import os
-from lxml import objectify
-from lxml import etree
-class EbusProtocol:
- doc = objectify.parse(open(
- os.path.join( os.path.dirname(__file__), "ebus/ebus_specification.xml")))
- @staticmethod
- def get_packet(primary, secondary):
- return (EbusProtocol.doc.xpath("/ebus/packets/packet[@primary=$primary and @secondary=$secondary]",
- primary=primary,secondary=secondary) or [None])[0]
-p=EbusProtocol.get_packet("0x05","0x07")
-for field in p.fields.iterchildren():
- print field.tag,field.get("name"), "l"
+