view zbmux.py @ 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
children ac60a9244bdf
line wrap: on
line source

#
# 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()