From f1c0281f0827699b5990c5dd21908d2384407578 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Wed, 2 Apr 2008 23:02:59 +0200 Subject: * host/proto: - added proto interface. --- host/proto/proto.py | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 host/proto/proto.py (limited to 'host/proto/proto.py') diff --git a/host/proto/proto.py b/host/proto/proto.py new file mode 100644 index 00000000..e6d3ab32 --- /dev/null +++ b/host/proto/proto.py @@ -0,0 +1,165 @@ +# proto - Proto interface. {{{ +# +# Copyright (C) 2008 Nicolas Schodet +# +# APBTeam: +# Web: http://apbteam.org/ +# Email: team AT apbteam DOT org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# }}} +"""Proto interface module.""" +import binascii, struct + +START = 0 +BANG = 1 +CMD = 2 +ARG = 3 + +class Proto: + + def __init__ (self, file, date, timeout, log = None): + """Initialise and set file (serial port, pty, socket...), date + function and timeout value.""" + self.file = file + self.date = date + self.last_send = None + self.timeout = timeout + self.send_queue = [ ] + self.state = START + self.log = log + self.handlers = { } + + def send (self, *frame): + """Queue a frame to send.""" + if not self.send_queue: + self.last_send = None + self.send_queue.append (Frame (*frame)) + + def read (self): + """Read from file and receive frames.""" + for f in self.recv (): + if self.log: + self.log ('recv %s' % f) + if self.send_queue and f == self.send_queue[0]: + del self.send_queue[0] + if self.send_queue: + self.send_head () + else: + self.dispatch (f) + + def sync (self): + """Send frames, return True if all is sent.""" + if self.send_queue and (self.last_send is None + or self.last_send + self.timeout < self.date ()): + self.send_head () + return not self.send_queue + + def register (self, command, fmt, handler): + """Register a handler for the specified command and format. The + handler will receive decoded arguments.""" + key = (command, struct.calcsize ('!' + fmt)) + assert key not in self.handlers + self.handlers[key] = (handler, fmt) + + def fileno (self): + """Return file descriptor, for use with select.""" + return self.file.fileno () + + def send_head (self): + """Send first frame from the send queue.""" + if self.log: + self.log ('send %s' % self.send_queue[0]) + self.file.write (self.send_queue[0].data ()) + self.last_send = self.date () + + def recv (self): + """Receive a frame, used as a generator.""" + for c in self.file.read (1): + if c == '!': + self.state = BANG + else: + if self.state == START: + pass + elif self.state == BANG: + if c.isalpha (): + self.recv_command = c + self.recv_args = '' + self.state = CMD + else: + self.recv_error () + elif self.state == CMD: + if c == '\r': + f = Frame (self.recv_command) + f.args = binascii.unhexlify (self.recv_args) + yield f + elif (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f'): + self.recv_args += c + self.state = ARG + else: + self.recv_error () + else: + assert self.state == ARG + if (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f'): + self.recv_args += c + self.state = CMD + else: + self.recv_error () + + def recv_error (self): + """Handle reception errors.""" + self.state = START + if self.log: + self.log ('error') + # Resend now. + if self.send_queue: + self.send_head () + + def dispatch (self, frame): + """Pass a received frame to the correct handler.""" + key = (frame.command, len (frame.args)) + if key in self.handlers: + h = self.handlers[key] + h[0] (*(frame.decode (h[1]))) + +class Frame: + + def __init__ (self, command = None, fmt = '', *args): + """Initiliase a frame. If command is given, the frame is constructed + using a struct.pack like fmt string.""" + if command: + assert len (command) == 1 and command.isalpha () + self.command = command + self.args = struct.pack ('!' + fmt, *args) + else: + self.command = None + self.args = '' + + def data (self): + """Get a frame representation ready to send.""" + return '!' + self.command + binascii.hexlify (self.args) + '\r' + + def decode (self, fmt): + """Decode using a struct.unpack like fmt string.""" + return struct.unpack ('!' + fmt, self.args) + + def __eq__ (self, other): + """Compare for equality.""" + return self.command == other.command and self.args == other.args + + def __str__ (self): + """Convert to string.""" + return '!' + self.command + binascii.hexlify (self.args) -- cgit v1.2.3