view zbmux.tac @ 22:c26eaa20b70d

Keep 10 5MB log files.
author Daniel O'Connor <darius@dons.net.au>
date Tue, 16 Apr 2013 08:14:56 +0930
parents 7a2ce1c1f176
children 08535b12504f
line wrap: on
line source

#
# Mux the ZB module to TCP ports
#
# Run me like so...
# twistd -l zbmux.log --pidfile=zbmux.pid -y zbmux.tac
#
# 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 = '/home/darius/projects/ZigBee/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 = 5 * 1024 * 1024, backupCount = 10)

        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: ***