From: Leandro Lucarella Date: Tue, 1 Nov 2005 00:57:34 +0000 (+0000) Subject: Agrega implementación del protocolo y un server (emulador del dispositivo) de prueba. X-Git-Tag: 0.1-recibe-matriz-raw-por-udp~58 X-Git-Url: https://git.llucax.com/z.facultad/66.09/etherled.git/commitdiff_plain/7abe2bbcea5cea86b3c47d6bd16fd69c9a16b17e Agrega implementación del protocolo y un server (emulador del dispositivo) de prueba. --- diff --git a/cliente/etherled/__init__.py b/cliente/etherled/__init__.py new file mode 100644 index 0000000..040f8d4 --- /dev/null +++ b/cliente/etherled/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vim: set expandtab tabstop=4 shiftwidth=4 : +#---------------------------------------------------------------------------- +# Etherled +#---------------------------------------------------------------------------- +# This file is part of etherled. +# +# etherled 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. +# +# etherled 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 etherled; if not, write to the Free Software Foundation, Inc., 59 +# Temple Place, Suite 330, Boston, MA 02111-1307 USA +#---------------------------------------------------------------------------- +# Creado: sáb oct 29 21:56:59 ART 2005 +# Autores: Leandro Lucarella +#---------------------------------------------------------------------------- + +from packet import * +from protocol import * + diff --git a/cliente/etherled/packet.py b/cliente/etherled/packet.py new file mode 100644 index 0000000..2f38145 --- /dev/null +++ b/cliente/etherled/packet.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vim: set expandtab tabstop=4 shiftwidth=4 : +#---------------------------------------------------------------------------- +# Etherled +#---------------------------------------------------------------------------- +# This file is part of etherled. +# +# etherled 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. +# +# etherled 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 etherled; if not, write to the Free Software Foundation, Inc., 59 +# Temple Place, Suite 330, Boston, MA 02111-1307 USA +#---------------------------------------------------------------------------- +# Creado: sáb oct 29 00:45:52 ART 2005 +# Autores: Leandro Lucarella +#---------------------------------------------------------------------------- + +# Tipos de operación +TYPE_GET = 0 +TYPE_SET = 1 + +# Variables +VAR_MATRIX = 0 + +# Limites +MAX_ID = 8 +MAX_VAR = 8 + +class ParityError(ValueError): + pass + +class Packet(object): + + def __init__(self, type, var=None, id=None, data=None): + if isinstance(type, str): + self.fromStr(type) + else: + self.type = type + self.var = var + self.id = id + self.data = data + + def fromStr(self, string): + header = ord(string[0]) + self.type = header >> 7 + self.var = (header & 0x70) >> 4 + self.id = (header & 0x0E) >> 1 + par = header & 0x01 + if self.par != par: + raise ParityError + self.data = string[1:] or None + + def __str__(self): + header = self._header_to_int() + data = '' + if self.data is not None: + data = self.data + return chr(header) + data + + def __repr__(self): + return "Packet(type=%d, var=%d, id=%d, data=%s)" \ + % (self.type, self.var, self.id, repr(self.data)) + + def __len__(self): + return len(str(self)) + + def __eq__(self, p): + return self.type == p.type and self.var == p.var and self.id == p.id + + def _header_to_int(self): + res = (self.type << 7) + (self.var << 4) + (self.id << 1) + return res + self.par + + def _getPar(self): + par = self.type + for i in xrange(3): + par += int((self.var & (1 << i)) != 0) + for i in xrange(3): + par += int((self.id & (1 << i)) != 0) + return par % 2 + + def _getType(self): + return self._type + + def _setType(self, type): + if type != TYPE_GET and type != TYPE_SET: + raise ValueError, "type debe ser Packet.GET o Packet.SET" + self._type = type + + def _getVar(self): + return self._var + + def _setVar(self, var): + if var < 0 and var >= self.MAX_VAR: + raise ValueError, "var debe estar entre 0 y %d" % self.MAX_VAR-1 + self._var = var + + def _getId(self): + return self._id + + def _setId(self, id): + if id < 0 and id >= self.MAX_ID: + raise ValueError, "id debe estar entre 0 y %d" % self.MAX_ID-1 + self._id = id + + type = property(_getType, _setType, doc="Tipo de operación") + var = property(_getVar, _setVar, doc="Variable con la cual operar") + id = property(_getId, _setId, doc="Identificador del paquete") + par = property(_getPar, doc="Paridad de la cabecera del paquete") + +class ClientPacket(Packet): + + def __init__(self, type, var=None, id=None, data=None): + if isinstance(type, str): + self.fromStr(type) + else: + Packet.__init__(self, type, var, id, data) + if type == TYPE_GET and data is not None: + raise ValueError, "El paquete a enviar por el cliente no " \ + "puede contener datos si es de tipo GET" + + def fromStr(self, string): + Packet.fromStr(self, string) + if self.type == TYPE_SET and self.data is not None: + raise ValueError, "El paquete recibido por el cliente no " \ + "puede contener datos si es de tipo SET" + +class ServerPacket(Packet): + + def __init__(self, type, var=None, id=None, data=None): + if isinstance(type, str): + self.fromStr(type) + else: + Packet.__init__(self, type, var, id, data) + if type == TYPE_SET and data is not None: + raise ValueError, "El paquete a enviar por el servidor no " \ + "puede contener datos si es de tipo SET" + + def fromStr(self, string): + Packet.fromStr(self, string) + if self.type == TYPE_GET and self.data is not None: + raise ValueError, "El paquete recibido por el cliente no " \ + "puede contener datos si es de tipo GET" + +# Prueba +if __name__ == '__main__': + assert str(Packet(1, 7, 7)) == '\xFF' + assert str(Packet(0, 0, 0)) == '\x00' + assert str(Packet(1, 1, 1)) == '\x93' + assert str(Packet(TYPE_SET, VAR_MATRIX, 2)) == '\x84' + assert str(Packet(TYPE_GET, 4, 0)) == 'A' + assert str(Packet(TYPE_GET, 4, 0, 'hola')) == 'Ahola' + p = Packet(TYPE_GET, 4, 0, 'hola') + assert Packet(str(p)) == p + print "OK!" + diff --git a/cliente/etherled/protocol.py b/cliente/etherled/protocol.py new file mode 100644 index 0000000..56bb34a --- /dev/null +++ b/cliente/etherled/protocol.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- +# vim: set expandtab tabstop=4 shiftwidth=4 : +#---------------------------------------------------------------------------- +# Etherled +#---------------------------------------------------------------------------- +# This file is part of etherled. +# +# etherled 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. +# +# etherled 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 etherled; if not, write to the Free Software Foundation, Inc., 59 +# Temple Place, Suite 330, Boston, MA 02111-1307 USA +#---------------------------------------------------------------------------- +# Creado: sáb oct 29 00:45:52 ART 2005 +# Autores: Leandro Lucarella +#---------------------------------------------------------------------------- + +import socket +import packet + +__all__ = ('SendError', 'RecvError', 'Client', 'NetworkedDevice', 'DummyServer') + +# Tamaño del buffer +_BUFSIZ = 65536 + +class SendError(socket.error): + pass + +class RecvError(socket.error): + pass + +class Client(object): + + def __init__(self, host='localhost', port=38437, timeout=3.0): + self._host = host + self._port = port + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.connect(self.addr) + self._sock.settimeout(timeout) + self._ids = {} + self._drop = 0 + + def send(self, type, var, data=None): + pkt = packet.ClientPacket(type, var, self._getId(type, var), data) + try: + sent = self._sock.send(str(pkt)) + except socket.timeout: + raise SendError, "Tiempo de espera agotado" + if sent != len(pkt): + raise SendError, "Sólo se enviaron %d bytes de %d" \ + % (sent, len(pkt)) + while True: + try: + msg = self._sock.recv(_BUFSIZ) + except socket.timeout: + raise RecvError, "Tiempo de espera agotado" + pkt_r = packet.ClientPacket(msg) + if pkt == pkt_r: + break # paquete ok + self._drop += 1 + return pkt_r.data + + def get(self, var): + return self.send(packet.TYPE_GET, var) + + def set(self, var, data): + self.send(packet.TYPE_SET, var, data) + + def _getId(self, type, var): + id = self._ids.get((type, var), 0) + self._ids[type, var] = (id + 1) % packet.MAX_ID + return id + + def _getHost(self): + return self._host + + def _getPort(self): + return self._port + + def _getAddr(self): + return (self._host, self._port) + + def _getDrop(self): + return self._drop + + host = property(_getHost, doc='Host al cual enviar datos') + port = property(_getPort, doc='Puerto al cual enviar datos') + addr = property(_getAddr, doc='Tupla (host, port)') + drop = property(_getDrop, doc='Cantidad de paquetes descartados') + +class NetworkedDevice(Client): + + LED_BYTES = 2 + + def _getMatrix(self): + stream = self.get(packet.VAR_MATRIX) + return self._stream2Matrix(stream) + + def _setMatrix(self, matrix): + stream = self._matrix2Stream(matrix) + self.set(packet.VAR_MATRIX, stream) + + def _stream2Matrix(self, stream): + cols = ord(stream[0]) # Obtiene tamaño + stream = stream[1:1+cols*self.LED_BYTES] # me quedo con el resto + matrix = {} + for col in xrange(cols-1, -1, -1): + for row_byte in xrange(self.LED_BYTES): + byte = ord(stream[(cols-col-1)*self.LED_BYTES+row_byte]) + for i in xrange(8): + shift = 8 - i - 1 + matrix[col, row_byte*8+i] = (byte >> shift) & 1 + return matrix + + def _matrix2Stream(self, matrix): + cols = len(matrix) / (self.LED_BYTES*8) + stream = chr(cols) # primero va el tamaño + for col in xrange(cols-1, -1, -1): + for i in xrange(self.LED_BYTES): + byte = 0 + for row in xrange(8): + shift = 8 - row - 1 + byte += matrix[col,row] << shift + stream += chr(byte) + return stream + + matrix = property(_getMatrix, _setMatrix, doc='Matriz de leds') + +class DummyServer: + + def __init__(self, host='localhost', port=38437, timeout=3.0): + self._host = host + self._port = port + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._sock.bind(self.addr) + self._vars = [None for i in xrange(8)] + + def run(self): + while True: + (msg, addr) = self._sock.recvfrom(_BUFSIZ) + pkt = packet.ServerPacket(msg) + if pkt.type == packet.TYPE_GET: + pkt.data = self._vars[pkt.var] + elif pkt.type == packet.TYPE_SET: + self._vars[pkt.var] = pkt.data + pkt = packet.ServerPacket(pkt.type, pkt.var, pkt.id) + sent = self._sock.sendto(str(pkt), addr) + if sent != len(pkt): + raise SendError, "Sólo se enviaron %d bytes de %d" \ + % (sent, len(packet)) + + def _getHost(self): + return self._host + + def _getPort(self): + return self._port + + def _getAddr(self): + return (self._host, self._port) + + host = property(_getHost, doc='Host al cual enviar datos') + port = property(_getPort, doc='Puerto al cual enviar datos') + addr = property(_getAddr, doc='Tupla (host, port)') + +def _print_matrix(matrix): + for row in xrange(NetworkedDevice.LED_BYTES*8): + for col in xrange(len(matrix)/(NetworkedDevice.LED_BYTES*8)): + print matrix[row,col], + print + print + +def _print_stream(stream): + for c in stream: + print '0x%02X' % ord(c), + print + print + +# Prueba +if __name__ == '__main__': + import os, sys, time + pid = os.fork() + if pid: + time.sleep(0.1) + # Creo dispositivo por red + dev = NetworkedDevice() + # Creo matriz + matrix = {} + for col in xrange(16): + for row in xrange(16): + matrix[row,col] = row % 2 + # Mando matriz + print 'Matriz enviada:' + _print_matrix(matrix) + dev.matrix = matrix + print 'Matriz recibida:' + _print_matrix(dev.matrix) + # Verifico resultado + assert matrix == dev.matrix + ########################### + # Creo matriz + matrix = {} + for col in xrange(16): + for row in xrange(16): + matrix[row,col] = col % 2 + # Mando matriz + print 'Matriz enviada:' + _print_matrix(matrix) + dev.matrix = matrix + print 'Matriz recibida:' + _print_matrix(dev.matrix) + # Verifico resultado + assert matrix == dev.matrix + ########################### + # Creo matriz + matrix = {} + for col in xrange(16): + for row in xrange(16): + matrix[row,col] = (col+row) % 2 + # Mando matriz + print 'Matriz enviada:' + _print_matrix(matrix) + dev.matrix = matrix + print 'Matriz recibida:' + _print_matrix(dev.matrix) + # Verifico resultado + assert matrix == dev.matrix + # Matamos al servidor + os.kill(pid, 15) + else: + server = DummyServer() + server.run() + sys.exit(0) + print "OK!" +