Index: LaserDisplay.py
===================================================================
--- LaserDisplay.py	(revision 270)
+++ LaserDisplay.py	(working copy)
@@ -1,14 +1,12 @@
 #!/usr/bin/env python
 
-import time
-import telnetlib
-from numpy import *
-import math
-from random import random
+from LasersimDevice import LasersimDevice
+from LaserDevice import LaserDevice
+from types import *
+import traceback
+import sys
 
-PI=3.1415
-WIDTH=255
-HEIGHT=255
+#TODO: Create a file with colors
 
 RED = [255,0,0]
 GREEN = [0,255,0]
@@ -18,289 +16,6 @@
 YELLOW = [255,255,0]
 WHITE = [255,255,255]
 
-def clamp(value, min, max):
-  if value > max: return max
-  if value < min: return min
-  return int(value)
-
-class LaserDisplayLocalDevice():
-  # Configuration flags
-  ALWAYS_ON = 1
-  SOMETHING = 2
-  def __init__(self):
-    self.messageBuffer = []
-    self.ctm = matrix([[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]])
-    self.flags = self.ALWAYS_ON
-
-    import usb
-    import time
-    self.ReplayInitLog()
-    time.sleep(3)
-
-    # find our device
-    self.usbdev = usb.core.find(idVendor=0x9999, idProduct=0x5555)
-
-    # was it found?
-    if self.usbdev is None:
-        raise ValueError('Device (9999:5555) not found')
-
-    # set the active configuration. With no arguments, the first
-    # configuration will be the active one
-    self.usbdev.set_configuration()
-
-    # get an endpoint instance
-    self.ep = usb.util.find_descriptor(
-            self.usbdev.get_interface_altsetting(),   # first interface
-            # match the first OUT endpoint
-            custom_match = \
-                lambda e: \
-                    usb.util.endpoint_direction(e.bEndpointAddress) == \
-                    usb.util.ENDPOINT_OUT
-        )
-
-    assert self.ep is not None    
-
-  def ReplayInitLog(self):
-    import usb
-   # find our device
-    self.usbdev=None
-    for bus in usb.busses():
-      for dev in bus.devices:
-        if dev.idVendor == 0x3333:
-          self.usbdev = dev
-    
-    # was it found?
-    if self.usbdev is None:
-        raise ValueError('Device (3333:5555) not found')
-
-    handle = self.usbdev.open() 
-  
-    print "Initializing device using data collected with USBSnoop"
-    snifferlog = open("usbinit.log")
-
-    for line in snifferlog.readlines():
-      setup_packet = line.split("|")[0]
-      buf = line.split("|")[-1]
-      if len(buf):
-        values = setup_packet.strip().split(" ")
-        reqType = int(values[0],16)
-        req = int(values[1],16)
- 
-        value = int(values[3],16)*256+int(values[2],16)
-        index = int(values[5],16)*256+int(values[4],16)
-        length = int(values[7],16)*256+int(values[6],16)
-
-        value = int(values[2],16)*256+int(values[3],16)
-        index = int(values[4],16)*256+int(values[5],16)
-        length = int(values[6],16)*256+int(values[7],16)
- 
-        print "=== sending ==="
-        print "bmRequestType: "+hex(reqType)
-        print "bRequest: "+hex(req)
-        print "wValue: "+hex(value)
-        print "wIndex: "+hex(index)
-        print "buffer: "+buf
-
-        buf2 = ""
-        for byte in buf.strip().split(" "):
-          buf2+=chr(int(byte,16))
-
-        handle.controlMsg(reqType,req,buf2,value,index)
-
-    print "done."
-
-  def set_flags(self, flags):
-    self.flags = flags
-
-#routines that generate USB messages (content of URBs):
-
-  def line_message(self, x1,y1,x2,y2):
-    x1+=random()*self.MaxNoise-self.MaxNoise/2
-    y1+=random()*self.MaxNoise-self.MaxNoise/2
-    x2+=random()*self.MaxNoise-self.MaxNoise/2
-    y2+=random()*self.MaxNoise-self.MaxNoise/2
-    
-    x1,y1 = self.apply_context_transforms(x1,y1)
-    x2,y2 = self.apply_context_transforms(x2,y2)
-
-    x1 = clamp(x1,0,255)
-    y1 = clamp(y1,0,255)
-    x2 = clamp(x2,0,255)
-    y2 = clamp(y2,0,255)
-    
-    return [x1, 0x00, y1, 0x00, self.color["R"], self.color["G"], self.color["B"], 0x03, x2, 0x00, y2, 0x00, self.color["R"], self.color["G"], self.color["B"], 0x02]
-
-  def point_message(self, x, y):
-    x+=random()*self.MaxNoise-self.MaxNoise/2
-    y+=random()*self.MaxNoise-self.MaxNoise/2
-    
-    x,y = self.apply_context_transforms(x,y)
-    x = clamp(x,0,255)
-    y = clamp(y,0,255)
-
-    return [x, 0x00, y, 0x00, self.color["R"], self.color["G"], self.color["B"], self.flags]
-    
-  def quadratic_bezier_message(self, points, steps):
-    if len(points) < 3:
-      print "Quadratic Bezier curves have to have at least three points"
-      return
-
-    step_inc = 1.0/(steps)
-
-    message = []
-    self.set_flags(0x03)
-    message += self.point_message(points[0][0], points[0][1])
-    self.set_flags(0x00)
-
-    for i in range(0, len(points) - 2, 2):
-      t = 0.0
-      t_1 = 1.0
-      for s in range(steps):
-        t += step_inc
-        t_1 = 1.0 - t
-        if s == steps - 1 and i >= len(points) - 2:
-          self.set_flags(0x02)
-        message += (self.point_message(t_1 * (t_1 * points[i]  [0] + t * points[i+1][0]) + \
-                                       t   * (t_1 * points[i+1][0] + t * points[i+2][0]),  \
-                                       t_1 * (t_1 * points[i]  [1] + t * points[i+1][1]) + \
-                                       t   * (t_1 * points[i+1][1] + t * points[i+2][1])))
-
-    return message
-
-  def cubic_bezier_message(self, points, steps):
-    if len(points) < 4:
-      print "Cubic Bezier curves have to have at least four points"
-      return
-
-    step_inc = 1.0/(steps)
-
-    self.set_flags(0x03)
-    message = self.point_message(points[0][0], points[0][1])
-    self.set_flags(0x00)
-
-    for i in range(0, len(points) - 3, 2):
-      t = 0.0
-      t_1 = 1.0
-      for s in range(steps):
-        t += step_inc
-        t_1 = 1.0 - t
-        if s == steps - 1 and i >= len(points) - 3:
-          self.set_flags(0x02)
-        message += self.point_message(t_1* (t_1 * (t_1 * points[i]  [0] + t * points[i+1][0]) + \
-                                             t   * (t_1 * points[i+1][0] + t * points[i+2][0])) +
-                                       t * (t_1 * (t_1 * points[i+1]  [0] + t * points[i+2][0]) + \
-                                            t   * (t_1 * points[i+2][0] + t * points[i+3][0])),  \
-                                       t_1 * (t_1 * (t_1 * points[i]  [1] + t * points[i+1][1]) + \
-                                              t   * (t_1 * points[i+1][1] + t * points[i+2][1])) +
-                                       t * (t_1 * (t_1 * points[i+1]  [1] + t * points[i+2][1]) + \
-                                              t   * (t_1 * points[i+2][1] + t * points[i+3][1])))
-    return message
-
-#---------------
-
-  def set_laser_configuration(self, blanking_delay, scan_rate):
-    self.ep.write([blanking_delay, (45000 - scan_rate)/200])
-
-  def set_color(self, c):
-    self.color = {"R": c[0], "G": c[1], "B": c[2]}
-
-  def draw_line(self, x1,y1,x2,y2):    
-    self.schedule(self.line_message(x1, y1, x2, y2))
-
-  def draw_quadratic_bezier(self, points, steps):
-    message = self.quadratic_bezier_message(points, steps)
-    if message:
-      self.schedule(message)
-
-  def draw_cubic_bezier(self, points, steps):
-    message = self.cubic_bezier_message(points, steps)
-    if message:
-      self.schedule(message)
-
-  def show_frame(self):
-    self.ep.write(self.messageBuffer, 0)
-    self.messageBuffer = []
-
-  def schedule(self, message):
-    self.messageBuffer += message
-
-# routines that deal with coordinate system transforms:
-
-  def apply_context_transforms(self, x,y):
-    vector = self.ctm*matrix([x,y,1]).transpose()
-    return vector[0], vector[1]
-
-  def save(self):
-    self.saved_matrix = self.ctm
-
-  def restore(self):
-    self.ctm = self.saved_matrix
-    
-  def rotate(self, angle):
-    self.ctm = matrix([[math.cos(angle), -math.sin(angle), 0.0], [math.sin(angle), math.cos(angle), 0.0], [0.0, 0.0, 1.0]])*self.ctm
-
-  def translate(self, x, y):
-    self.ctm = matrix([[1.0, 0.0, float(x)], [0.0, 1.0, float(y)], [0.0, 0.0, 1.0]])*self.ctm
-  
-  def scale(self, s):
-    self.ctm = matrix([[float(s), 0.0, 0.0], [0.0, float(s), 0.0], [0.0, 0.0, 1.0]])*self.ctm
-
-  def rotate_at(self,cx,cy,angle):
-    self.translate(-cx,-cy)
-    self.rotate(angle)
-    self.translate(cx,cy)
- 
-
-
-
-class LaserDisplayRemoteDevice():
-  def __init__(self, server, port=50000):
-    print "remote laser server config:\n server:%s port:%d" % (server, port)
-    self.remote = telnetlib.Telnet(config["server"], config["port"])
-
-  def set_laser_configuration(self, blanking_delay, scan_rate):
-    self.remote.write("config %d %d\n" % (blanking_delay, scan_rate))
-
-  def set_color(self, c):
-    self.remote.write("color %d %d %d\n" % (c[0], c[1], c[2]))
-
-  def draw_line(self, x1,y1,x2,y2):
-    self.remote.write("line %d %d %d %d\n" % (x1, y1, x2, y2))
-
-  def draw_quadratic_bezier(self, points, steps):
-    msg = "quadratic"
-    for p in points:
-      msg+=" %f %f" % (p[0], p[1])
-    self.remote.write(msg+"\n")
-
-  def draw_cubic_bezier(self, points, steps):
-    msg = "cubic"
-    for p in points:
-      msg+=" %f %f" % (p[0], p[1])
-    self.remote.write(msg+"\n")
-
-  def show_frame(self):
-    self.remote.write("show\n")
-    time.sleep(1.0/24)
-
-  def save(self):
-    self.remote.write("save\n")
-
-  def restore(self):
-    self.remote.write("restore\n")
-
-  def rotate(self, angle):
-    self.remote.write("rotate %d\n" % (angle))
-    
-  def translate(self, x, y):
-    self.remote.write("translate %d %d\n" % (x,y))
-  
-  def scale(self, s):
-    self.remote.write("scale %d\n" % (s))
-
-  def rotate_at(self,cx,cy,angle):
-    self.remote.write("rotateat %d %d %d\n" % (cx, cy, angle))
-
 class LaserDisplay():
   # Shapes for our characher rendering routine
   GLYPHS = {"0": [[191, 130], [194, 194], [127, 191], [65, 191], [62, 129], [64, 62], [125, 62], [195, 65], [192, 131]],
@@ -316,10 +31,8 @@
   ":": []}
 
   def __init__(self, config=None):
-    if config and "server" in config:
-      if not "port" in config:
-        config["port"]=50000
-      self.device = LaserDisplayRemoteDevice(config["server"], config["port"])
+    if config and "simulator" in config:
+      self.device = LasersimDevice(config)
     else:
       self.device = LaserDisplayLocalDevice()
    
@@ -328,10 +41,20 @@
     #default values
     self.blanking_delay = 202
     self.scan_rate = 37000
-    self.set_color(WHITE)   
+    self.call("set_color", WHITE)
     self.MaxNoise = 0
-    self.device.set_laser_configuration(self.blanking_delay, self.scan_rate)
-    
+    self.call("set_laser_configuration", self.blanking_delay, self.scan_rate)
+
+  # Call proxymethod that calls the corresponding method in the underlaying device (if defined)
+  def call(self, method_name, *args):
+    try:
+      method = eval("self.device."+method_name)
+    except AttributeError as exc:
+      print "STUB: Method", method_name, "not defined on class", self.device.__class__.__name__
+      traceback.print_exc()
+      return
+    eval("self.device."+method_name+repr(args))
+
   def adjust_glyphs(self):
     for k in self.GLYPHS.keys():
       self.GLYPHS[k] = map(lambda(p):([p[0]/255.0,p[1]/255.0]),self.GLYPHS[k])
@@ -350,7 +73,7 @@
       value = 45000
       print "maximum allowed scan rate value is 45000"
     self.scan_rate = value
-    self.device.set_laser_configuration(self.blanking_delay, self.scan_rate)
+    self.device.call("set_laser_configuration", self.blanking_delay, self.scan_rate)
     
   def set_blanking_delay(self, value):
     if value<0:
@@ -360,40 +83,45 @@
       value = 255
       print "maximum allowed blanking delay value is 255"
     self.blanking_delay = value
-    self.device.set_laser_configuration(self.blanking_delay, self.scan_rate)
+    self.call("set_laser_configuration", self.blanking_delay, self.scan_rate)
 
+  def set_laser_configuration(self, blanking_delay, scan_rate):
+    self.blanking_delay = blanking_delay
+    self.scan_rate = scan_rate
+    self.call("set_laser_configuration", self.blanking_delay, self.scan_rate)
+
   def draw_point(self, x,y):
-    self.device.draw_line(x,y,x,y)
+    self.call("draw_line",x,y,x,y)
 
   def draw_line(self, x1,y1,x2,y2):
-    self.device.draw_line(x1,y1,x2,y2)
+    self.call("draw_line",x1,y1,x2,y2)
 
   def draw_quadratic_bezier(self, points, steps):
-    self.device.draw_quadratic_bezier(points, steps)
+    self.call("draw_quadratic_bezier", points, steps)
 
   def draw_cubic_bezier(self, points, steps):
-    self.device.draw_cubic_bezier(points, steps)
+    self.call("draw_cubic_bezier", points, steps)
 
   def show_frame(self):
-    self.device.show_frame()
+    self.call("show_frame")
     
   def save(self):
-    self.device.save()
+    self.call("save")
 
   def restore(self):
-    self.device.restore()
+    self.call("restore")
     
   def rotate(self, angle):
-    self.device.rotate(angle)
+    self.call("rotate", angle)
 
   def translate(self, x, y):
-    self.device.translate(x, y)
+    self.call("translate", x, y)
   
   def scale(self, s):
-    self.device.scale(s)
+    self.call("scale", s)
 
   def rotate_at(self,cx,cy,angle):
-    self.device.rotate_at(cx,cy,angle)
+    self.call("rotate_at", cx,cy,angle)
 
   def gen_glyph_data(self, char, x, y, rx, ry):
     glyph_data = []
@@ -404,26 +132,6 @@
   def draw_text(self, string, x, y, size, kerning_percentage = -0.3):
     for char in string:
       glyph_curve = self.gen_glyph_data(char, x, y, size, size*2)
-      self.draw_quadratic_bezier(glyph_curve, 5)
+      self.call("draw_quadratic_bezier", glyph_curve, 5)
       x -= int(size + size * kerning_percentage)
 
-  #TODO: refactor it. It should not be in our API
-  def draw_dashed_circle(self, x,y,r, c1, c2):
-    step = 32
-    for alpha in range(step):
-      if alpha%2:
-        self.set_color(c1)
-      else:
-        self.set_color(c2)
-        
-      self.draw_line(x + r*math.cos(alpha*2*PI/step), y + r*math.sin(alpha*2*PI/step), x + r*math.cos((alpha+1)*2*PI/step), y + r*math.sin((alpha+1)*2*PI/step))
-
-
-class Laser3D(LaserDisplay):
-  def Draw_line(self, x1,y1,z1,x2,y2,z3):
-    self.set_color(RED)
-    self.draw_line(x1,y1,x2,y2)
-    self.set_color(GREEN)
-    self.draw_line(x1+z1,y1,x2+z2,y2)
-    
-
Index: LasersimDevice.py
===================================================================
--- LasersimDevice.py	(revision 0)
+++ LasersimDevice.py	(revision 0)
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+import pygame
+
+RED   = (255, 0, 0)
+GREEN = (0, 255, 0)
+BLUE  = (0, 0, 255)
+BLACK = (0, 0, 0)
+
+WIDTH  = 255
+HEIGHT = 255
+
+DISP_WIDTH  = 512
+DISP_HEIGHT = 512
+
+LINE_WIDTH = 3
+
+MAX_LIFE = 200
+
+SCALE_X = float(DISP_WIDTH)/WIDTH
+SCALE_Y = float(DISP_HEIGHT)/HEIGHT
+
+class Line():
+  def __init__(self, p1, p2, color):
+    self.p1 = p1
+    self.p2 = p2
+    self.color = color
+    self.cur_color = color
+    self.life = 0
+
+  def update(self, millis):
+    self.life += millis
+    factor = 1.0 - self.life / float(MAX_LIFE)
+    if (factor < 0.0):
+      factor = 0.0
+    self.cur_color = (int(self.color[0] * factor), int(self.color[1] * factor), int(self.color[2] * factor))
+
+  def draw(self, surface):
+    pygame.draw.line(surface, self.cur_color, self.p1, self.p2, LINE_WIDTH)
+
+# Simulates what the observer would see on a white wall at night
+class Wall():
+  def __init__(self):
+    self.linelist = []
+
+  def update(self, millis):
+    for line in self.linelist:
+      line.update(millis)
+    cut = 0
+    for line in self.linelist:
+      if line.life >= MAX_LIFE:
+        cut += 1
+      else:
+        break
+    self.linelist = self.linelist[cut:]
+
+  def add_line(self, p1, p2, color):
+    # Coordinate transformation
+    p1 = (DISP_WIDTH - SCALE_X*p1[0], DISP_HEIGHT - SCALE_Y*p1[1])
+    p2 = (DISP_WIDTH - SCALE_X*p2[0], DISP_HEIGHT - SCALE_Y*p2[1])
+
+    self.linelist.append(Line(p1, p2, color))
+
+  def draw(self, surface):
+    surface.fill(BLACK)
+    for line in self.linelist:
+      line.draw(surface)
+
+def clamp(value, min, max):
+  if value > max: return max
+  if value < min: return min
+  return int(value)
+
+class LasersimDevice():
+  def __init__(self, config=None):
+    pygame.init()
+
+    self.surface = pygame.display.set_mode((DISP_WIDTH, DISP_HEIGHT))
+    self.lastpoint = (0,0)
+    self.color = RED
+    self.draw = False
+
+    self.wall = Wall()
+
+    self.messageBuffer = []
+
+  def set_color(self, col):
+    self.color = col
+
+  def write(self, message):
+    if len(message) == 0:
+      return
+    # For now, ignore configuration messages
+    if len(message) < 8:
+      return
+    for i in range(0,len(message),8):
+      newpoint = (message[i+0], message[i+2])
+      if self.draw:
+        self.wall.add_line(self.lastpoint, newpoint, self.color)
+      self.lastpoint = newpoint
+      self.color = (message[i+4], message[i+5], message[i+6])
+      if message[i+7] == 0x03:
+        self.draw = True
+      if message[i+7] == 0x02:
+        self.draw = False
+
+  def set_flags(self, flags):
+    self.flags = flags
+
+  def point_message(self, x, y):
+    x = clamp(x,0,255)
+    y = clamp(y,0,255)
+
+    return [x, 0x00, y, 0x00, self.color[0], self.color[1], self.color[2], self.flags]
+    
+  def quadratic_bezier_message(self, points, steps):
+    if len(points) < 3:
+      print "Quadratic Bezier curves have to have at least three points"
+      return
+
+    step_inc = 1.0/(steps)
+
+    message = []
+    self.set_flags(0x03)
+    message += self.point_message(points[0][0], points[0][1])
+    self.set_flags(0x00)
+
+    for i in range(0, len(points) - 2, 2):
+      t = 0.0
+      t_1 = 1.0
+      for s in range(steps):
+        t += step_inc
+        t_1 = 1.0 - t
+        if s == steps - 1 and i >= len(points) - 2:
+          self.set_flags(0x02)
+        message += (self.point_message(t_1 * (t_1 * points[i]  [0] + t * points[i+1][0]) + \
+                                       t   * (t_1 * points[i+1][0] + t * points[i+2][0]),  \
+                                       t_1 * (t_1 * points[i]  [1] + t * points[i+1][1]) + \
+                                       t   * (t_1 * points[i+1][1] + t * points[i+2][1])))
+
+    return message
+
+  def draw_quadratic_bezier(self, points, steps):
+    message = self.quadratic_bezier_message(points, steps)
+    if message:
+      self.schedule(message)
+
+  def schedule(self, message):
+    self.messageBuffer += message
+
+  def show_frame(self):
+    self.write(self.messageBuffer)
+    self.messageBuffer = []
+    self.wall.draw(self.surface)
+    self.wall.update(50)
+    pygame.display.flip()
+

Property changes on: LasersimDevice.py
___________________________________________________________________
Added: svn:executable
   + *

Index: LaserClient.py
===================================================================
--- LaserClient.py	(revision 0)
+++ LaserClient.py	(revision 0)
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+import telnetlib
+import time
+
+class LaserClient():
+  def __init__(self, config={}):
+    if not "server" in config:
+      config["server"] = server
+    if not "port" in config:
+      config["port"] = port
+    print "remote laser server config:\n server:%s port:%d" % (config["server"], config["port"])
+    self.remote = telnetlib.Telnet(config["server"], config["port"])
+
+  def set_laser_configuration(self, blanking_delay, scan_rate):
+    self.remote.write("config %d %d\n" % (blanking_delay, scan_rate))
+
+  def set_color(self, c):
+    self.remote.write("color %d %d %d\n" % (c[0], c[1], c[2]))
+
+  def draw_line(self, x1,y1,x2,y2):
+    self.remote.write("line %d %d %d %d\n" % (x1, y1, x2, y2))
+
+  def draw_quadratic_bezier(self, points, steps):
+    msg = "quadratic"
+    for p in points:
+      msg+=" %f %f" % (p[0], p[1])
+    self.remote.write(msg+"\n")
+
+  def draw_cubic_bezier(self, points, steps):
+    msg = "cubic"
+    for p in points:
+      msg+=" %f %f" % (p[0], p[1])
+    self.remote.write(msg+"\n")
+
+  def show_frame(self):
+    self.remote.write("show\n")
+    time.sleep(1.0/24)
+
+  def save(self):
+    self.remote.write("save\n")
+
+  def restore(self):
+    self.remote.write("restore\n")
+
+  def rotate(self, angle):
+    self.remote.write("rotate %d\n" % (angle))
+    
+  def translate(self, x, y):
+    self.remote.write("translate %d %d\n" % (x,y))
+  
+  def scale(self, s):
+    self.remote.write("scale %d\n" % (s))
+
+  def rotate_at(self,cx,cy,angle):
+    self.remote.write("rotateat %d %d %d\n" % (cx, cy, angle))
+
Index: laser-server.py
===================================================================
--- laser-server.py	(revision 270)
+++ laser-server.py	(working copy)
@@ -1,8 +1,6 @@
 #!/usr/bin/python
 from LaserDisplay import *
 
-LD = LaserDisplay()
-
 from twisted.internet.protocol import Factory, Protocol
 from twisted.internet.task import LoopingCall 
 from twisted.internet import reactor
@@ -10,6 +8,22 @@
 QUAD_BEZ_QUALITY = 8
 CUBIC_BEZ_QUALITY = 8
 
+# Initialization:
+LD = []
+line = raw_input("Simulator? y/N: ")
+if line == "y" or line == "Y":
+  print "Running in simulator mode"
+  LD = LaserDisplay({"simulator":True})
+else:
+  print "Running in normal mode"
+  try:
+    LD = LaserDisplay()
+  except Exception as e:
+    print "Laser init error:",
+    print e
+    exit(-1)
+print "Laser display initialization complete"
+
 def update_laser (connections):
 #  if len(connections):
 #    print "\n\n"+str(len(connections)) + " user(s) connected!"
@@ -19,8 +33,9 @@
       if len(cmd)==0:
         break
       if cmd[0] == "line":
-        msg = LD.line_message(float(cmd[1]), float(cmd[2]), float(cmd[3]), float(cmd[4]))
-        LD.schedule(msg)
+        msg = LD.call("line_message", float(cmd[1]), float(cmd[2]), float(cmd[3]), float(cmd[4]))
+        if msg:
+          LD.call("schedule", msg)
       elif cmd[0] == "quadratic":
         cmd.pop(0)
         points = []
@@ -28,8 +43,9 @@
           x=float(cmd.pop(0))
           y=float(cmd.pop(0))
           points.append([x,y])
-        msg = LD.quadratic_bezier_message(points, QUAD_BEZ_QUALITY)
-        LD.schedule(msg)
+        msg = LD.call("draw_quadratic_bezier", points, QUAD_BEZ_QUALITY)
+        if msg:
+          LD.call("schedule", msg)
       elif cmd[0] == "cubic":
         cmd.pop(0)
         points = []
@@ -37,25 +53,26 @@
           x=float(cmd.pop(0))
           y=float(cmd.pop(0))
           points.append([x,y])
-        msg = LD.cubic_bezier_message(points, CUBIC_BEZ_QUALITY)
-        LD.schedule(msg)
+        msg = LD.call("draw_cubic_bezier", points, CUBIC_BEZ_QUALITY)
+        if msg:
+          LD.call("schedule", msg)
       elif cmd[0] == "save":
-        LD.save()
+        LD.call("save")
       elif cmd[0] == "restore":
-        LD.restore()
+        LD.call("restore")
       elif cmd[0] == "rotate":
-        LD.rotate(float(cmd[1]))
+        LD.call("rotate", float(cmd[1]))
       elif cmd[0] == "translate":
-        LD.translate(float(cmd[1]), float(cmd[2]))
+        LD.call("translate", float(cmd[1]), float(cmd[2]))
       elif cmd[0] == "scale":
-        LD.scale(float(cmd[1]))
+        LD.call("scale", float(cmd[1]))
       elif cmd[0] == "rotateat":
-        LD.rotate_at(float(cmd[1]),float(cmd[2]),float(cmd[3])):
+        LD.call("rotate_at", float(cmd[1]),float(cmd[2]),float(cmd[3]))
       elif cmd[0] == "color":
-        LD.set_color([int(cmd[1]), int(cmd[2]), int(cmd[3])])
+        LD.call("set_color", [int(cmd[1]), int(cmd[2]), int(cmd[3])])
       elif cmd[0] == "config":
-        LD.set_laser_configuration(int(cmd[1]), int(cmd[2]))
-  LD.show_frame()
+        LD.call("set_laser_configuration", int(cmd[1]), int(cmd[2]))
+  LD.call("show_frame")
 
 connections = []
 loop = LoopingCall(update_laser, connections)
Index: example3.py
===================================================================
--- example3.py	(revision 270)
+++ example3.py	(working copy)
@@ -1,7 +1,9 @@
 #!/usr/bin/env python
 from LaserDisplay import *
+from LaserClient import *
 import math
 import random
+import time
 
 WIDTH = 255
 HEIGHT = 255
@@ -14,6 +16,10 @@
 PROBAB_COLOR_CHANGE=0.05
 COLOR_CHANGE_MAXSTEP = 256
 
+def clamp(value, min, max):
+  if value > max: return max
+  if value < min: return min
+  return int(value)
 
 class Particle:
   def __init__(self, display):
@@ -47,7 +53,8 @@
       self.color[1] = clamp(self.color[1] + random.random()*COLOR_CHANGE_MAXSTEP - COLOR_CHANGE_MAXSTEP/2, 0,255)
       self.color[2] = clamp(self.color[2] + random.random()*COLOR_CHANGE_MAXSTEP - COLOR_CHANGE_MAXSTEP/2, 0,255)    
 
-LD = LaserDisplay({"server":"localhost","port": 50000})
+LD = LaserClient({"server":"localhost","port": 50000})
+#LD = LaserDisplay({"server":"localhost","port": 50000})
 #LD = LaserDisplay()
 
 shapes = []
@@ -59,7 +66,7 @@
     
   shapes.append(particles)
 
-LD.set_noise(NOISE)
+#LD.set_noise(NOISE)
   
 while True:
   for particles in shapes:
Index: LaserDevice.py
===================================================================
--- LaserDevice.py	(revision 0)
+++ LaserDevice.py	(revision 0)
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+
+import time
+
+class LaserDevice():
+  # Configuration flags
+  ALWAYS_ON = 1
+  SOMETHING = 2
+  def __init__(self):
+    self.messageBuffer = []
+    self.ctm = matrix([[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]])
+    self.flags = self.ALWAYS_ON
+
+    import usb
+    import time
+    self.ReplayInitLog()
+    time.sleep(3)
+
+    # find our device
+    self.usbdev = usb.core.find(idVendor=0x9999, idProduct=0x5555)
+
+    # was it found?
+    if self.usbdev is None:
+        raise ValueError('Device (9999:5555) not found')
+
+    # set the active configuration. With no arguments, the first
+    # configuration will be the active one
+    self.usbdev.set_configuration()
+
+    # get an endpoint instance
+    self.ep = usb.util.find_descriptor(
+            self.usbdev.get_interface_altsetting(),   # first interface
+            # match the first OUT endpoint
+            custom_match = \
+                lambda e: \
+                    usb.util.endpoint_direction(e.bEndpointAddress) == \
+                    usb.util.ENDPOINT_OUT
+        )
+
+    assert self.ep is not None    
+
+  def ReplayInitLog(self):
+    import usb
+   # find our device
+    self.usbdev=None
+    for bus in usb.busses():
+      for dev in bus.devices:
+        if dev.idVendor == 0x3333:
+          self.usbdev = dev
+    
+    # was it found?
+    if self.usbdev is None:
+        raise ValueError('Device (3333:5555) not found')
+
+    handle = self.usbdev.open() 
+  
+    print "Initializing device using data collected with USBSnoop"
+    snifferlog = open("usbinit.log")
+
+    for line in snifferlog.readlines():
+      setup_packet = line.split("|")[0]
+      buf = line.split("|")[-1]
+      if len(buf):
+        values = setup_packet.strip().split(" ")
+        reqType = int(values[0],16)
+        req = int(values[1],16)
+ 
+        value = int(values[3],16)*256+int(values[2],16)
+        index = int(values[5],16)*256+int(values[4],16)
+        length = int(values[7],16)*256+int(values[6],16)
+
+        value = int(values[2],16)*256+int(values[3],16)
+        index = int(values[4],16)*256+int(values[5],16)
+        length = int(values[6],16)*256+int(values[7],16)
+ 
+        print "=== sending ==="
+        print "bmRequestType: "+hex(reqType)
+        print "bRequest: "+hex(req)
+        print "wValue: "+hex(value)
+        print "wIndex: "+hex(index)
+        print "buffer: "+buf
+
+        buf2 = ""
+        for byte in buf.strip().split(" "):
+          buf2+=chr(int(byte,16))
+
+        handle.controlMsg(reqType,req,buf2,value,index)
+
+    print "done."
+
+  def set_flags(self, flags):
+    self.flags = flags
+
+#routines that generate USB messages (content of URBs):
+
+  def line_message(self, x1,y1,x2,y2):
+    x1+=random()*self.MaxNoise-self.MaxNoise/2
+    y1+=random()*self.MaxNoise-self.MaxNoise/2
+    x2+=random()*self.MaxNoise-self.MaxNoise/2
+    y2+=random()*self.MaxNoise-self.MaxNoise/2
+    
+    x1,y1 = self.apply_context_transforms(x1,y1)
+    x2,y2 = self.apply_context_transforms(x2,y2)
+
+    x1 = clamp(x1,0,255)
+    y1 = clamp(y1,0,255)
+    x2 = clamp(x2,0,255)
+    y2 = clamp(y2,0,255)
+    
+    return [x1, 0x00, y1, 0x00, self.color["R"], self.color["G"], self.color["B"], 0x03, x2, 0x00, y2, 0x00, self.color["R"], self.color["G"], self.color["B"], 0x02]
+
+  def point_message(self, x, y):
+    x+=random()*self.MaxNoise-self.MaxNoise/2
+    y+=random()*self.MaxNoise-self.MaxNoise/2
+    
+    x,y = self.apply_context_transforms(x,y)
+    x = clamp(x,0,255)
+    y = clamp(y,0,255)
+
+    return [x, 0x00, y, 0x00, self.color["R"], self.color["G"], self.color["B"], self.flags]
+    
+  def quadratic_bezier_message(self, points, steps):
+    if len(points) < 3:
+      print "Quadratic Bezier curves have to have at least three points"
+      return
+
+    step_inc = 1.0/(steps)
+
+    message = []
+    self.set_flags(0x03)
+    message += self.point_message(points[0][0], points[0][1])
+    self.set_flags(0x00)
+
+    for i in range(0, len(points) - 2, 2):
+      t = 0.0
+      t_1 = 1.0
+      for s in range(steps):
+        t += step_inc
+        t_1 = 1.0 - t
+        if s == steps - 1 and i >= len(points) - 2:
+          self.set_flags(0x02)
+        message += (self.point_message(t_1 * (t_1 * points[i]  [0] + t * points[i+1][0]) + \
+                                       t   * (t_1 * points[i+1][0] + t * points[i+2][0]),  \
+                                       t_1 * (t_1 * points[i]  [1] + t * points[i+1][1]) + \
+                                       t   * (t_1 * points[i+1][1] + t * points[i+2][1])))
+
+    return message
+
+  def cubic_bezier_message(self, points, steps):
+    if len(points) < 4:
+      print "Cubic Bezier curves have to have at least four points"
+      return
+
+    step_inc = 1.0/(steps)
+
+    self.set_flags(0x03)
+    message = self.point_message(points[0][0], points[0][1])
+    self.set_flags(0x00)
+
+    for i in range(0, len(points) - 3, 2):
+      t = 0.0
+      t_1 = 1.0
+      for s in range(steps):
+        t += step_inc
+        t_1 = 1.0 - t
+        if s == steps - 1 and i >= len(points) - 3:
+          self.set_flags(0x02)
+        message += self.point_message(t_1* (t_1 * (t_1 * points[i]  [0] + t * points[i+1][0]) + \
+                                             t   * (t_1 * points[i+1][0] + t * points[i+2][0])) +
+                                       t * (t_1 * (t_1 * points[i+1]  [0] + t * points[i+2][0]) + \
+                                            t   * (t_1 * points[i+2][0] + t * points[i+3][0])),  \
+                                       t_1 * (t_1 * (t_1 * points[i]  [1] + t * points[i+1][1]) + \
+                                              t   * (t_1 * points[i+1][1] + t * points[i+2][1])) +
+                                       t * (t_1 * (t_1 * points[i+1]  [1] + t * points[i+2][1]) + \
+                                              t   * (t_1 * points[i+2][1] + t * points[i+3][1])))
+    return message
+
+#---------------
+
+  def set_laser_configuration(self, blanking_delay, scan_rate):
+    self.ep.write([blanking_delay, (45000 - scan_rate)/200])
+
+  def set_color(self, c):
+    self.color = {"R": c[0], "G": c[1], "B": c[2]}
+
+  def draw_line(self, x1,y1,x2,y2):    
+    self.schedule(self.line_message(x1, y1, x2, y2))
+
+  def draw_quadratic_bezier(self, points, steps):
+    message = self.quadratic_bezier_message(points, steps)
+    if message:
+      self.schedule(message)
+
+  def draw_cubic_bezier(self, points, steps):
+    message = self.cubic_bezier_message(points, steps)
+    if message:
+      self.schedule(message)
+
+  def show_frame(self):
+    self.ep.write(self.messageBuffer, 0)
+    self.messageBuffer = []
+
+  def schedule(self, message):
+    self.messageBuffer += message
+
+# routines that deal with coordinate system transforms:
+
+  def apply_context_transforms(self, x,y):
+    vector = self.ctm*matrix([x,y,1]).transpose()
+    return vector[0], vector[1]
+
+  def save(self):
+    self.saved_matrix = self.ctm
+
+  def restore(self):
+    self.ctm = self.saved_matrix
+    
+  def rotate(self, angle):
+    self.ctm = matrix([[math.cos(angle), -math.sin(angle), 0.0], [math.sin(angle), math.cos(angle), 0.0], [0.0, 0.0, 1.0]])*self.ctm
+
+  def translate(self, x, y):
+    self.ctm = matrix([[1.0, 0.0, float(x)], [0.0, 1.0, float(y)], [0.0, 0.0, 1.0]])*self.ctm
+  
+  def scale(self, s):
+    self.ctm = matrix([[float(s), 0.0, 0.0], [0.0, float(s), 0.0], [0.0, 0.0, 1.0]])*self.ctm
+
+  def rotate_at(self,cx,cy,angle):
+    self.translate(-cx,-cy)
+    self.rotate(angle)
+    self.translate(cx,cy)