changeset 808:867f59c83dba

moved from proj/pixel Ignore-this: 97cafcfb96fd33ea70fcd467659ee31a darcs-hash:20110808045021-312f9-be4547f197b49b893d6e873f9c96f30491d0bdc7.gz
author drewp <drewp@bigasterisk.com>
date Sun, 07 Aug 2011 21:50:21 -0700
parents 4713bb87e34e
children bebb8f7c5a3e
files service/sba/sba.html service/sba/sba.py
diffstat 2 files changed, 275 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/sba/sba.html	Sun Aug 07 21:50:21 2011 -0700
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:n="http://nevow.com/ns/nevow/0.1">
+  <head>
+    <title>shiftweb</title>
+    <style type="text/css" media="all">
+      /* <![CDATA[ */
+      body {
+	  background: #000000;
+	  color: #888;
+      }
+      h1 {
+	  font-size:100%;
+      }
+      div.manual {
+	  margin: 10px;
+	  padding: 10px;
+	  background: #222;
+	  width:19em;
+      }
+      div.credit {
+	  font-size: 80%;
+	  margin-top:41px;
+      }
+      .colorpicker {
+	  display: inline-block;
+      }
+      /* ]]> */
+    </style>
+    <meta name="viewport" content="width=320; initial-scale=1.3; maximum-scale=2.0; user-scalable=1;"/>
+
+    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+    <script type="text/javascript" src="http://bigasterisk.com/bathroomLight/static/farbtastic/farbtastic.js"></script>
+    <link rel="stylesheet" href="http://bigasterisk.com/bathroomLight/static/farbtastic/farbtastic.css" type="text/css" />
+  </head>
+  <body>
+    <h1>Light color</h1>
+    <div class="colorpicker" id="p0"></div>
+    <div class="colorpicker" id="p1"></div>
+    <div class="colorpicker" id="p2"></div>
+    <div class="colorpicker" id="p3"></div>
+    <div class="colorpicker" id="p4"></div>
+    
+    <script type="text/javascript">
+      $(document).ready(function() {
+
+	  $.each([
+	      ["#p0", "brite/0"],
+	      ["#p1", "brite/1"],
+	      ["#p2", "brite/2"],
+	      ["#p3", "brite/3"],
+	      ["#p4", "brite/4"],
+	  ], function (i, r) {
+	      var elem=r[0], url=r[1];
+
+	      var picker = $.farbtastic(elem, function (newColor) {
+		  $.ajax({
+                      type: "put",
+                      url: url,
+                      contentType: "text/plain",
+                      data: newColor
+		  });
+	      });
+	      
+	      $.get(url, picker.setColor);
+	  });
+      });
+    </script>
+    
+    <div class="credit">
+      Using the 
+      <a href="http://acko.net/dev/farbtastic">Farbtastic color picker</a>
+    </div>
+
+    <hr/>
+
+    <div class="manual">
+      <form method="POST" action="brite/0">
+	<div>
+	  Channel: <input type="text" name="channel" value="0" size="3"/>
+	  Color: <input type="text" name="color" value="#ffffff" size="7"/>
+	</div>
+	<div><input type="submit" value="Post color"/></div>
+      </form>
+    </div>
+
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/sba/sba.py	Sun Aug 07 21:50:21 2011 -0700
@@ -0,0 +1,185 @@
+from __future__ import division
+import serial, time, jsonlib, sys, cgi, argparse
+import cyclone.web
+from twisted.python import log
+from twisted.internet import reactor
+
+class Sba(object):
+    def __init__(self, port="/dev/ttyACM0"):
+        self.port = port
+        self.reset()
+
+    def reset(self):
+        log.msg("reopening port")
+        self.s = serial.Serial(self.port, baudrate=115200)
+        log.msg(str(self.s.__dict__))
+        self.sendControl()
+
+    def sendControl(self):
+        controlBits = [0, 1,
+                       0, 0, 0,
+                       1, 1, 1, 1, 1, 1, 1, # b correction
+                       0, 0, 0,
+                       1, 1, 1, 1, 1, 1, 1, # g correction
+                       0,
+                       0, 0, # clock mode 00=internal
+                       1, 1, 1, 1, 1, 1, 1, # r correction
+                       ]
+
+        control = reduce(lambda a, b: a<<1 | b,
+                         #controlBits
+                         reversed(controlBits)
+                         )
+        self.send("C" + hex(control)[2:].zfill(8))
+        self.send("E0")
+        
+    def send(self, cmd, getResponse=True):
+        """
+        send a command using the protocol from http://engr.biz/prod/SB-A/
+
+        we will attach the carriage return, cmd is just a string like 'V'
+
+        Returns the response line, like '+OK'
+        """
+        try:
+            self.s.write(cmd + "\r")
+        except OSError:
+            self.reset()
+            
+        if getResponse:
+            return self.s.readline().strip()
+
+    def rgbs(self, rgbs):
+        """
+        send a set of full rgb packets. Values are 0..1023.
+        """
+        t1 = time.time()
+        for (r,g,b) in rgbs:
+            packed = (b & 0x3ff) << 20 | (r & 0x3ff) << 10 | (g & 0x3ff)
+            self.send("D%08x" % packed, getResponse=False)
+
+        self.send("L1", getResponse=False)
+        self.send("L0", getResponse=False)
+        sends = time.time() - t1
+        # doing all the reads together triples the transmission rate
+        t2 = time.time()
+        [self.s.readline() for loop in range(2 + len(rgbs))]
+        reads = time.time() - t2
+
+        log.msg("%.1f ms for sends, %.1f ms for reads" % (
+            1000 * sends, 1000 * reads))
+
+class BriteChain(object):
+    def __init__(self, sba):
+        self.sba = sba
+        self.colors = []
+
+    def setColor(self, pos, color):
+        """color is (r,g,b) 10-bit int. The highest position you ever
+        set is how many channels we'll output"""
+        if len(self.colors) <= pos:
+            self.colors.extend([(0,0,0)]*(pos - len(self.colors) + 1))
+        self.colors[pos] = color
+        self.refresh()
+        
+    def getColor(self, pos):
+        try:
+            return self.colors[pos]
+        except IndexError:
+            return (0,0,0)
+
+    def refresh(self):
+        self.sba.rgbs(self.colors[::-1])
+
+class IndexHandler(cyclone.web.RequestHandler):
+    def get(self):
+        self.set_header("Content-type", "text/html")
+        self.write(open("sba.html").read())
+
+class BriteHandler(cyclone.web.RequestHandler):
+    """
+    /brite/0 is the first shiftbrite on the chain. Put a text/plain
+    color like #ffffff (8-bit) or a application/json value like
+    {"rgb10":[1023,1023,1023]} (for 10-bit). GET (with accept, to pick
+    your format) to learn the current color. 
+
+    /brite/1 affects the second shiftbrite on the chain, etc 
+    """
+    def put(self, pos):
+        d = self.request.body
+        ctype = self.request.headers.get("Content-Type")
+        if ';' in ctype: 
+            ctype = ctype.split(';')[0].strip()
+        if ctype == 'text/plain':
+            color = decode8bitHexColor(d)
+        elif ctype == 'application/json':
+            color = jsonlib.read(d)['rgb10']
+        elif ctype == 'application/x-www-form-urlencoded':
+            color = decode8bitHexColor(cgi.parse_qs(d)['color'][0])
+        else:
+            self.response.set_status(415, "use text/plain, application/json, "
+                                     "or application/x-www-form-urlencoded")
+            return
+
+        self.settings.chain.setColor(int(pos), color)
+        self.set_header("Content-Type", "text/plain")
+        self.write("set %s\n" % pos)
+
+    def post(self, pos):
+        self.put(pos)
+        self.redirect("..")
+
+    def get(self, pos):
+        # todo: content neg
+        color = self.settings.chain.getColor(int(pos))
+        self.set_header("Content-Type", "text/plain")
+        self.write(encode8bitHexColor(color))
+
+def decode8bitHexColor(s):
+    return [4 * int(s.lstrip('#')[i:i+2], 16) for i in [0, 2, 4]]
+def encode8bitHexColor(color):
+    return "#%02X%02X%02X" % (color[0] // 4, color[1] // 4, color[2] // 4)
+
+class Application(cyclone.web.Application):
+    def __init__(self, chain):
+        handlers = [
+            (r"/", IndexHandler),
+            (r"/brite/(\d+)", BriteHandler),
+            ]
+
+        settings = {
+            "static_path": "./static",
+            "template_path": "./template",
+            "chain" : chain,
+        }
+
+        cyclone.web.Application.__init__(self, handlers, **settings)
+
+def main():
+    parser = argparse.ArgumentParser(description='drive sba lights')
+    parser.add_argument('-v', '--verbose', action="store_true", help='logging')
+    args = parser.parse_args()
+
+    try:
+        sba = Sba()
+    except serial.SerialException:
+        sba = Sba("/dev/ttyACM1")
+
+    chain = BriteChain(sba)
+
+    if 0: # todo: stick test patterns like this on some other resource
+        while 1:
+            t1 = time.time()
+            steps = 0
+            for x in range(0, 1024, 5):
+                steps += 1
+                sba.rgbs([(x, x, x)] * 2)
+            print steps / (time.time() - t1)
+
+    if args.verbose:
+        log.startLogging(sys.stdout)
+    reactor.listenTCP(9060, Application(chain))
+    reactor.run()
+
+if __name__ == "__main__":
+    main()