diff zbmux.tac @ 17:b0fc5f8da118

Rename to zbmux.tac, and use application framework. Add emacs magic so it gets edited in Python mode. Make log file rotate at 1Mb.
author darius@Inchoate
date Sun, 18 Jan 2009 13:32:29 +1030
parents zbmux.py@ce3712110055
children 0baf9538a1b6
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/zbmux.tac	Sun Jan 18 13:32:29 2009 +1030
@@ -0,0 +1,185 @@
+#
+# 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.application import internet, service
+from twisted.internet.serialport import SerialPort
+from twisted.internet.protocol import Protocol
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.insults import insults
+from twisted.protocols import basic
+from twisted.internet import protocol, reactor
+from twisted.python import log
+import sys, zb, logging, logging.handlers, string
+
+portname = '/dev/cuaU0'
+baudrate = 38400
+lognamebase = '/tmp/zbmux-%d.log'
+baseport = 1080
+zbids = [1, 2]
+
+class ZBClient(insults.TerminalProtocol):
+    """Client for the TCP connection"""
+    #: Time to wait before sending pending data (seconds)
+    QUEUE_TIME = 0.1
+    
+    def connectionMade(self):
+        log.msg("Got new client")
+        self.terminal.eraseDisplay()
+        self.terminal.resetPrivateModes([])
+        
+        # Send the last whole line we've seen out
+        for l in self.factory.lastlines:
+            self.message(l + '\n')
+            
+        self.pending = ""
+        self.pendtimer = None
+        self.factory.clients.append(self)
+        
+    def connectionLost(self, reason):
+        log.msg("Lost a client")
+        self.factory.clients.remove(self)
+        
+    def keystrokeReceived(self, keyID, modifier):
+        """Got some data, add it to the pending output queue"""
+        if modifier != None:
+            print "Received unhandled modifier: %s" % (str(modifier))
+            return
+        #if keyID not in string.printable:
+        #    print "Received unhandled keyID: %r" % (keyID,)
+        #    return
+        #log.msg("Got key ->%s<-" % (keyID))
+        self.pending = self.pending + keyID
+        if self.pendtimer == None:
+            self.pendtimer = reactor.callLater(self.QUEUE_TIME, self.sendData)
+            
+    def sendData(self):
+        """Send pending data to module"""
+        #log.msg("sending " + self.pending)
+        self.factory.zbproto.sendData(self.factory.zbid, self.pending)
+        self.pending = ""
+        self.pendtimer = None
+        
+    def message(self, message):
+        """Called to write a mesage to our client"""
+        self.terminal.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, lognamebase):
+        self.zbid = zbid
+        self.clients = []
+        self.tmpline = ""
+        self.lastlines = []
+
+        # 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 = 1 * 1024 * 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.lastlines.append(l)
+            self.lastlines = self.lastlines[-5:]
+            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)
+                if type(a) == type(zb.TX_Status()):
+                    #log.msg("Tx status for frame %d is %s" % (a.frameid, a.statusMsg))
+                    pass
+                
+    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 = 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))
+            
+application = service.Application('zbmux')
+
+# ZigBee serial protocol handler
+zbproto = ZBProto()
+SerialPort(zbproto, portname, reactor, baudrate = 38400)
+
+# Per-module TCP listener
+for id in zbids:
+    f = ZBFactory(id, lognamebase)
+    f.zbproto = zbproto
+    f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+                                         insults.ServerProtocol,
+                                         ZBClient)
+    zbproto.factories.append(f)
+    internet.TCPServer(baseport + id, f).setServiceParent(
+        service.IServiceCollection(application))
+
+######################################################################
+#
+# These lines tell Emacs to edit this file in Python mode
+#;;; Local Variables: ***
+#;;; mode:python ***
+#;;; End: ***