changeset 12:a3ec3f2988ac

Initial commit of zbmux. This program demultiplexes packets from remote ZB modules to feed into a log file. Also allows user(s) to telnet to a port to talk to a particular module. Needs more work. eg - TCP connections should be in raw mode, not line. - There is no way to access statistics for each module.
author darius@Inchoate
date Tue, 13 Jan 2009 12:17:02 +1030
parents 75f785a09e2e
children 729f2393f296
files zbmux.py
diffstat 1 files changed, 148 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zbmux.py	Tue Jan 13 12:17:02 2009 +1030
@@ -0,0 +1,148 @@
+#
+# Mux the ZB module to TCP ports
+#
+# Copyright (c) 2009
+#      Daniel O'Connor <darius@dons.net.au>.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+from twisted.internet.serialport import SerialPort
+from twisted.internet.protocol import Protocol
+from twisted.protocols import basic
+from twisted.internet import protocol, reactor
+from twisted.python import log
+import sys, zb, logging, logging.handlers
+
+portname = '/dev/cuaU0'
+baudrate = 38400
+lognamebase = '/tmp/zbmux-%d.log'
+baseport = 1080
+zbids = [1, 2]
+
+class ZBClient(basic.LineReceiver):
+    """Client for the TCP connection"""
+
+    def connectionMade(self):
+        log.msg("Got new client")
+        self.factory.clients.append(self)
+        if self.factory.lastline != None:
+            self.message(self.factory.lastline)
+        
+    def connectionLost(self, reason):
+        log.msg("Lost a client")
+        self.factory.clients.remove(self)
+
+    def lineReceived(self, line):
+        """Got a line - send it to our ZB module"""
+        #log.msg("Got line " + line)
+        self.factory.proto.sendData(self.factory.zbid, line + '\n')
+
+    def message(self, message):
+        """Called to write a mesage to our client"""
+        self.transport.write(message)
+        
+class ZBFactory(protocol.ServerFactory):
+    """Factory for a ZB module
+
+Represents a remote ZB module and has zero or more clients and a log file.
+"""
+    protocol = ZBClient
+    
+    def __init__(self, zbid, proto, lognamebase):
+        self.zbid = zbid
+        self.proto = proto
+        self.clients = []
+        self.tmpline = ""
+        self.lastline = None
+
+        # Open logger
+        self.logger = logging.getLogger('Zigbee-%d' % (zbid))
+        self.logger.setLevel(logging.DEBUG)
+
+        # Add the log message handler to the logger
+        handler = logging.handlers.RotatingFileHandler(
+            lognamebase % (zbid), maxBytes = 20 * 1024, backupCount = 5)
+
+        self.logger.addHandler(handler)
+
+    def message(self, zbid, message):
+        """Called when we get a message, check it's for us - if it is log it and write to our clients"""
+        if zbid != self.zbid:
+            return
+        
+        for c in self.clients:
+            c.message(message)
+
+        # Logger is line oriented, convert from packet oriented here
+        self.tmpline = self.tmpline + message
+        tmp = self.tmpline.split('\n')
+        for l in tmp[0:-1]:
+            self.lastline = l # Stores last seen line for new clients
+            self.logger.debug(l.replace('\n', ''))
+        self.tmpline = tmp[-1]
+
+class ZBProto(Protocol):
+    """Protocol to handle packets from the ZB module on the serial port"""
+    def __init__(self):
+        self.pkts = zb.Packets()
+        self.factories = []
+        
+    def dataReceived(self, data):
+        """Parses data from ZB module into packets, calls each factory if a RX packet is received"""
+        #log.msg("Read data " + data)
+        if self.pkts.processstr(data) > 0:
+            while len(self.pkts.pktq) > 0:
+                a = self.pkts.pktq.pop(0)
+                #log.msg("type is " + str(type(a)))
+                if type(a) == type(zb.RX_16_Bit()):
+                    #log.msg("Rx'd from %d => %s" % (a.sender, a.payloadstr))
+                    for f in self.factories:
+                        f.message(a.sender, a.payloadstr)
+                    
+    def sendData(self, zbid, data):
+        """Sends a chunk of data to our ZB module"""
+        #log.msg("%d <= %s" % (zbid, data))
+
+        # Chop up data into pieces the module can handle
+        maxsz = 10
+        #maxsz = zb.TX_16_Bit.PKT_MAX_PAYLOAD
+        for i, j in zip(range(0, len(data), maxsz), range(maxsz, len(data) + maxsz, maxsz)):
+            p = zb.TX_16_Bit(zbid, data[i:j])
+            self.transport.write(p.Pack())
+            #log.msg("sent " + str(p))
+        
+if __name__ == '__main__':
+    logFile = sys.stdout
+    log.startLogging(logFile)
+
+    # ZigBee serial protocol handler
+    zbproto = ZBProto()
+    SerialPort(zbproto, portname, reactor, baudrate = 38400)
+
+    # Per-module TCP listener
+    for id in zbids:
+        zbfactory = ZBFactory(id, zbproto, lognamebase)
+        zbproto.factories.append(zbfactory)
+        reactor.listenTCP(baseport + id, zbfactory)
+
+    reactor.run()