--- a/bin/dmx-web.py Mon Apr 21 00:20:27 2014 +0300
+++ b/bin/dmx-web.py Thu May 01 23:34:20 2014 +0300
@@ -9,6 +9,16 @@
import optparse
+def dmx_heads (options) :
+ from qmsk.dmx import heads
+
+ return {
+ 'ledpar': heads.Stairville_LEDPar56(1),
+ 'par1': heads.Dimmer(5),
+ 'par2': heads.Dimmer(6),
+ 'ledbar': heads.AmericanDJ_MegaTri60_Mode2(10),
+ }
+
def main (argv) :
"""
DMX web control.
@@ -18,7 +28,7 @@
parser.add_option_group(pvl.args.parser(parser))
parser.add_option_group(pvl.web.args.parser(parser))
- parser.add_option('--dmx-serial', default=qmsk.dmx.DMX.SERIAL,
+ parser.add_option('--dmx-serial', default=None,
help="Path to /dev/tty*")
options, args = parser.parse_args(argv[1:])
@@ -26,11 +36,14 @@
pvl.args.apply(options)
# dmx
- dmx = qmsk.dmx.DMX.open(options.dmx_serial)
- dmx.zero()
+ if options.dmx_serial :
+ dmx = qmsk.dmx.DMX.open(options.dmx_serial)
+ #dmx.zero()
+ else :
+ dmx = None
# app
- app = qmsk.dmx.web.DMXWebApplication(dmx)
+ app = qmsk.dmx.web.DMXWebApplication(dmx, dmx_heads(options))
pvl.web.args.main(options, app)
--- a/qmsk/dmx/control.py Mon Apr 21 00:20:27 2014 +0300
+++ b/qmsk/dmx/control.py Thu May 01 23:34:20 2014 +0300
@@ -2,6 +2,10 @@
import logging; log = logging.getLogger('qmsk.dmx.control')
import serial
+"""
+ Low-level DMX channel output.
+"""
+
class DMXError (Exception) :
def __init__ (self, **kwargs) :
self.kwargs = kwargs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qmsk/dmx/heads.py Thu May 01 23:34:20 2014 +0300
@@ -0,0 +1,82 @@
+"""
+ High-level DMX output.
+"""
+
+class Head (object) :
+ CHANNELS = None
+
+ def __init__ (self, channel) :
+ self.channel = channel
+ self.channels = self.init()
+ self.attrs = { }
+
+ def init (self) :
+ if self.CHANNELS :
+ return self.CHANNELS
+ else :
+ raise NotImplementedError()
+
+ def __setitem__ (self, attr, value) :
+ if attr in self.channels :
+ self.attrs[attr] = value
+ else :
+ raise KeyError(attr)
+
+ def __getitem__ (self, attr) :
+ if attr in self.channels :
+ return self.attrs.get(attr, 0)
+ else :
+ raise KeyError(attr)
+
+ def alpha (self, alpha=None) :
+ if alpha is not None :
+ self['alpha'] = alpha
+
+ if 'alpha' in self.channels :
+ return dict(alpha=self['alpha'])
+ else :
+ return None
+
+ def color (self, red=None, green=None, blue=None) :
+ if red is not None :
+ self['red'] = red
+ if green is not None :
+ self['green'] = green
+ if blue is not None :
+ self['blue'] = blue
+
+ if 'red' in self.channels and 'green' in self.channels and 'blue' in self.channels :
+ return dict(red=self['red'], green=self['green'], blue=self['blue'])
+ else :
+ return None
+
+ def __iter__ (self) :
+ for attr in self.channels :
+ yield self.attrs.get(attr, 0)
+
+ def __call__ (self, dmx) :
+ dmx[self.channel] = tuple(self)
+
+class Stairville_LEDPar56 (Head) :
+ CHANNELS = [
+ 'control1',
+ 'red',
+ 'green',
+ 'blue',
+ 'control2',
+ ]
+
+class Dimmer (Head) :
+ CHANNELS = [
+ 'alpha',
+ ]
+
+class AmericanDJ_MegaTri60_Mode2 (Head) :
+ CHANNELS = [
+ 'red',
+ 'green',
+ 'blue',
+ 'control',
+ 'alpha',
+ ]
+
--- a/qmsk/dmx/web.py Mon Apr 21 00:20:27 2014 +0300
+++ b/qmsk/dmx/web.py Thu May 01 23:34:20 2014 +0300
@@ -3,6 +3,39 @@
import logging; log = logging.getLogger('qmsk.dmx.web')
+def colorize (x, red, green, blue, alpha=1.0) :
+ return x(style='background-color: rgba({red}, {green}, {blue}, {alpha:0.2f})'.format(red=red, green=green, blue=blue, alpha=alpha))
+
+def input (head, name, value) :
+ return html.input(
+ type = 'text',
+ name = '-'.join([head, name]),
+
+ id = '-'.join([head, name]),
+ class_ = 'form-control dmx-input dmx-input-{name}'.format(name=name),
+
+ placeholder = name,
+ value = '{v:d}'.format(v=value) if value else None,
+ )
+
+def color_input (head, c, value) :
+ color = dict(red=0, green=0, blue=0, alpha=0)
+
+ color[c] = 255
+ if value :
+ color[alpha] = value / 255.0
+
+ return colorize(input(head, c, value), **color)
+
+def slider (head, name) :
+ return html.div(id='-'.join([head, name, 'slider']), class_='dmx-slider dmx-slider-{name}'.format(name=name))
+
+def color_slider (head, c) :
+ return slider(head, c)
+
+def head_color (head, value) :
+ return html.div(class_='dmx-color-background')(colorize(html.div(id='-'.join([head, 'color']), class_='dmx-color')(' '), **value))
+
class Handler (pvl.web.Handler) :
# Bootstrap
DOCTYPE = 'html'
@@ -11,6 +44,8 @@
CSS = (
'//code.jquery.com/ui/1.10.4/themes/ui-darkness/jquery-ui.css',
'//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css',
+
+ '/static/dmx.css',
)
JS = (
'//code.jquery.com/jquery-2.1.0.js',
@@ -18,125 +53,71 @@
'//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js',
'/static/color-slider.js',
+ '/static/dmx.js',
)
- STYLE = """
-body {
- padding-top: 2em;
- text-align: center;
-}
-
-.container {
- padding: 2em 1em;
- text-align: left;
-}
-
-.panel {
- width: 30em;
- margin: 1em auto;
-}
-
-input.color-control {
- width: 5em;
-}
-
-div#color {
- width: 5em;
- height: 5em;
-
- margin: 1em auto;
-}
-
-div.color-slider {
- margin: 1em;
-}
-
-div.color-slider#slider-r .ui-slider-range {
- background: #ff0000;
-}
-
-div.color-slider#slider-g .ui-slider-range {
- background: #00ff00;
-}
-
-div.color-slider#slider-b .ui-slider-range {
- background: #0000ff;
-}
- """
-
# test
TITLE = u"DMX Control"
def process (self) :
if self.request.method == 'POST' :
- self.color = tuple((int(x, 16) if x else 0) for x in (
+ # XXX
+ r, g, b = tuple((int(x, 16) if x else 0) for x in (
self.request.form.get('r'),
self.request.form.get('g'),
self.request.form.get('b'),
))
- r, g, b = self.color
-
self.app.dmx_color(r, g, b, 255)
- else :
- self.color = None
-
- log.info("%s", self.color)
-
- def render (self) :
- if self.color :
- r, g, b = self.color
+ def render_head (self, name, head) :
+ if head.alpha() is None :
+ head_input = head_slider = None
else :
- r = g = b = None
-
- def color_swatch () :
- return html.div(id='color',
- style='background-color: rgb({r}, {g}, {b})'.format(r=r, g=g, b=b)
- )(' '),
-
- def color_input (name, value) :
- color = dict(r=0, g=0, b=0)
- bgcolor = dict(r=0, g=0, b=0)
-
- if value :
- color[name] = value
- alpha = value / 255.0
- else :
- alpha = 0
-
- bgcolor[name] = 255
+ head_input = input(name, 'alpha', head.alpha()['alpha'])
+ head_slider = slider(name, 'alpha')
- return html.input(type='text', name=name,
- class_ = 'form-control color-control',
- placeholder = name,
- id = name,
+ rowspan = 1
- value = '{v:02x}'.format(v=value) if value else None,
- style = 'background-color: rgba({r}, {g}, {b}, {a:0.2f})'.format(a=alpha, **bgcolor),
+ if head.color() is None :
+ colors = { }
+ color = None
+ else :
+ colors = head.color()
+ color = head_color(name, colors)
+ rowspan += 3
+
+ yield html.tr(
+ html.th(rowspan=rowspan)(name),
+ html.td(head_input),
+ html.td(head_slider),
+ html.td(rowspan=rowspan)(color),
+ )
+
+ for c in colors :
+ yield html.tr(
+ html.td(
+ color_input(name, c, colors[c]),
+ ),
+ html.td(
+ color_slider(name, c),
+ ),
)
-
+
+ def render (self) :
return html.div(class_='container')(
- html.div(class_='panel')(
- color_swatch(),
- html.div(id='slider-r', class_='color-slider')(' '),
- html.div(id='slider-g', class_='color-slider')(' '),
- html.div(id='slider-b', class_='color-slider')(' '),
- html.form(action='.', method='POST', class_='form-inline')(
- #html.label(for_='color', class_='control-label')("Color"),
- html.div(class_='form-group')(
- color_input('r', r),
+ html.form(action='.', method='POST')(
+ html.table(class_='dmx')(
+ html.thead(
+ html.th(class_='dmx-head')(u"Head"),
+ html.th(class_='dmx-value')(u"DMX"),
+ html.th(class_='dmx-control')(u"Control"),
+ html.th(class_='dmx-head-control')(u"Head Control"),
),
- html.div(class_='form-group')(
- color_input('g', g),
- ),
- html.div(class_='form-group')(
- color_input('b', b),
- ),
- html.div(class_='form-group')(
- html.button(type='submit', class_='btn btn-primary')("Go"),
- ),
- )
+ html.tbody(self.render_head(name, head) for name, head in sorted(self.app.heads.iteritems())),
+ ),
+
+ html.button(type='submit', class_='btn btn-primary')("Go"),
)
)
@@ -145,17 +126,8 @@
urls.rule('/', Handler),
))
- def __init__ (self, dmx, **opts) :
+ def __init__ (self, dmx, heads, **opts) :
super(DMXWebApplication, self).__init__(**opts)
self.dmx = dmx
-
- def dmx_color (self, r, g, b, a=255) :
- # Stairville LED Par56
- self.dmx[1] = (0, r, g, b, 0)
-
- # 4ch dimmer
- self.dmx[5] = (a, a, a, a)
-
- # American DJ - Mega Tri 60 - Mode 2
- self.dmx[10] = (r, g, b, 0, a)
+ self.heads = heads
--- a/static/color-slider.js Mon Apr 21 00:20:27 2014 +0300
+++ b/static/color-slider.js Thu May 01 23:34:20 2014 +0300
@@ -6,59 +6,6 @@
if (a == undefined)
a = 1.0;
- this.css('background', 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')');
+ this.css('background', 'rgba(' + (r * 255) + ', ' + (g * 255) + ', ' + (b * 255) + ', ' + a + ')');
},
});
-
-function color_slider_slide (event, ui) {
- // $(this).css('background', 'rgba(0, 0, 0, ' + (ui.value / 255) + ')');
-
- $('#color').background_color(
- $('#slider-r').slider('value'),
- $('#slider-g').slider('value'),
- $('#slider-b').slider('value')
- );
-
- $(['r', 'g', 'b']).each(function (i, c) {
- var value = $('#slider-' + c).slider('value');
- var input = $('#' + c);
- var color = {r: 0, g: 0, b: 0};
-
- color[c] = 255;
-
- input.val(value.toString(16));
- input.background_color(color.r, color.g, color.b, value / 255);
- });
-}
-
-function color_slider_input (input, c) {
- var value;
-
- if (input.val())
- value = parseInt(input.val(), 16);
- else
- value = 0;
-
- $('#slider-' + c).slider('value', value);
-}
-
-$(function () {
- $('.color-slider').slider({
- orientation: 'horizontal',
- range: 'min',
- min: 0,
- max: 255,
-
- slide: color_slider_slide,
- });
-
- $(['r', 'g', 'b']).each(function (i, c) {
- var input = $('#' + c);
-
- // bind
- input.change(function () { color_slider_input($(this), this.id); });
-
- // initialize
- color_slider_input(input, c);
- });
-});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/dmx.css Thu May 01 23:34:20 2014 +0300
@@ -0,0 +1,84 @@
+body {
+ padding-top: 2em;
+ text-align: center;
+}
+
+.container {
+ padding: 2em 1em;
+ text-align: left;
+}
+
+/*
+ * Table layout
+ */
+table.dmx {
+ margin: 1em auto;
+ width: 80%;
+
+ border-collapse: collapse;
+}
+
+table.dmx tr
+{
+ border: 1px dotted #aaa;
+}
+
+table.dmx th
+{
+ text-align: center;
+}
+
+th.dmx-head { width: 5em; }
+th.dmx-value { width: 5em; }
+th.dmx-control { }
+th.dmx-head-control { width: 5em; }
+
+
+
+
+/* Controls */
+input.dmx-input {
+
+}
+
+div.dmx-color-background {
+ background-color: rgb(0, 0, 0);
+}
+
+div.dmx-color {
+ width: 5em;
+ height: 5em;
+
+ margin: 1em auto;
+
+ border: 1px dotted #aaa;
+}
+
+div.dmx-slider {
+ margin: 1em;
+}
+
+div.dmx-slider-alpha .ui-slider-range,
+div.dmx-slider-alpha .ui-slider-handle
+{
+ background: #ffffff;
+}
+
+div.dmx-slider-red .ui-slider-range,
+div.dmx-slider-red .ui-slider-handle
+{
+ background: #ff0000;
+}
+
+div.dmx-slider-green .ui-slider-range,
+div.dmx-slider-green .ui-slider-handle
+{
+ background: #00ff00;
+}
+
+div.dmx-slider-blue .ui-slider-range,
+div.dmx-slider-blue .ui-slider-handle
+{
+ background: #0000ff;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/dmx.js Thu May 01 23:34:20 2014 +0300
@@ -0,0 +1,92 @@
+function dmx_input (head, attr) {
+ var value = $('.dmx-input#' + head + '-' + attr).val();
+
+ if (value == undefined )
+ return undefined;
+
+ if (value == "")
+ return 0; // default
+
+ return parseInt(value);
+}
+
+/*
+ * Update color for head.
+ */
+function dmx_color (head) {
+ var alpha = dmx_input(head, 'alpha');
+
+ if (alpha)
+ alpha = alpha / 255;
+
+ $('.dmx-color#' + head + '-color').background_color(
+ dmx_input(head, 'red') / 255,
+ dmx_input(head, 'green') / 255,
+ dmx_input(head, 'blue') / 255,
+ alpha
+ );
+}
+
+/*
+ * Update slider from <input>.
+ */
+function _slider_input (input, slider) {
+ var value;
+
+ if (input.val())
+ value = parseInt(input.val());
+ else
+ value = 0;
+
+ slider.slider('value', value);
+}
+
+/*
+ * Bind given <input> to given slider.
+ */
+function slider_input (input, slider) {
+ // bind
+ input.change(function () { _slider_input(input, slider); });
+
+ // initialize
+ _slider_input(input, slider);
+}
+
+$(function () {
+ $('.dmx-input').each(function () {
+ var attr = this.id;
+ var head = attr.split('-', 1)[0];
+ var input = $(this);
+ var slider = $('.dmx-slider#' + attr + '-slider');
+ var color = $('.dmx-color#' + head + '-color');
+
+ // slider control
+ slider.slider({
+ orientation: 'horizontal',
+ range: 'min',
+ min: 0,
+ max: 255,
+
+ slide: function () {
+ var value = slider.slider('value');
+
+ // update input
+ input.val(value.toString());
+
+ if (color) {
+ // update color value
+ dmx_color(head);
+ }
+
+ },
+ });
+
+ // update slider from <input>
+ slider_input(input, slider);
+
+ // init
+ if (color) {
+ dmx_color(head);
+ }
+ });
+});