Mercurial > code > home > repos > light9
changeset 1776:b680d6f50a93
start sequencer web report. WIP
Ignore-this: e97eadee6190d2c90cfb81f3542f4f2f
author | drewp@bigasterisk.com |
---|---|
date | Sun, 03 Jun 2018 21:04:00 +0000 |
parents | 64db0e6b2be9 |
children | 299d49de85a8 |
files | bin/effectsequencer light9/effect/sequencer.html light9/effect/sequencer.py |
diffstat | 3 files changed, 220 insertions(+), 7 deletions(-) [+] |
line wrap: on
line diff
--- a/bin/effectsequencer Sun Jun 03 18:46:36 2018 +0000 +++ b/bin/effectsequencer Sun Jun 03 21:04:00 2018 +0000 @@ -12,7 +12,7 @@ import optparse, sys, logging import cyclone.web from rdflib import URIRef -from light9.effect.sequencer import Sequencer, sendToCollector +from light9.effect.sequencer import Sequencer, sendToCollector, Updates from light9 import clientsession class App(object): @@ -38,9 +38,13 @@ settings)) self.cycloneApp = cyclone.web.Application(handlers=[ + (r'/()', cyclone.web.StaticFileHandler, + {"path" : "light9/effect/", "default_filename" : "sequencer.html"}), + (r'/updates', Updates), (r'/stats', StatsForCyclone), ], debug=True, + seq=self.seq, graph=self.graph, stats=self.stats) reactor.listenTCP(networking.effectSequencer.port, self.cycloneApp)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/effect/sequencer.html Sun Jun 03 21:04:00 2018 +0000 @@ -0,0 +1,167 @@ +<!doctype html> +<html> + <head> + <title>effect sequencer</title> + <meta charset="utf-8" /> + <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script> + <link rel="import" href="/lib/polymer/polymer.html"> + <link rel="import" href="/lib/iron-ajax/iron-ajax.html"> + <link rel="import" href="../rdfdb-synced-graph.html"> + <link rel="import" href="../resource-display.html"> + <script src="/node_modules/n3/n3-browser.js"></script> + <script src="/lib/async/dist/async.js"></script> + <script src="/lib/underscore/underscore-min.js"></script> + + <link rel="stylesheet" href="/style.css"> + <style> + td { white-space: nowrap; } + </style> + </head> + <body> + + <dom-module id="light9-collector-device"> + <template> + <style> + :host { + display: block; + break-inside: avoid-column; + } + h3 { + margin-top: 12px; + margin-bottom: 0; + } + td.nonzero { + background: #310202; + color: #e25757; + } + td.full { + background: #2b0000; + color: red; + font-weight: bold; + } + </style> + <h3><resource-display graph="{{graph}}" uri="{{uri}}"></resource-display></h3> + <table class="borders"> + <tr> + <th>output attr</th> + <th>value</th> + <th>output chan</th> + </tr> + <template is="dom-repeat" items="{{attrs}}"> + <tr> + <td>{{item.attr}}</td> + <td class$="{{item.valClass}}">{{item.val}} →</td> + <td>{{item.chan}}</td> + </tr> + </template> + + </template> + <script> + HTMLImports.whenReady(function () { + Polymer({ + is: "light9-collector-device", + properties: { + graph: {type: Object, notify: true}, + uri: {type: Object, notify: true}, + attrs: {type: Array, notify: true}, + }, + observers: [ + "initUpdates(updates)", + ], + initUpdates: function(updates) { + updates.addListener(function(msg) { + if (msg.outputAttrsSet && msg.outputAttrsSet.dev == this.uri.value) { + this.set('attrs', msg.outputAttrsSet.attrs); + this.attrs.forEach(function(row) { + row.valClass = row.val == 255 ? 'full' : (row.val ? 'nonzero' : ''); + }); + } + }.bind(this)); + }, + }); + }); + </script> + </dom-module> + + + <dom-module id="light9-collector-ui"> + <template> + <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph> + + <h1>Collector <a href="stats">[stats]</a></h1> + + <h2>Devices</h2> + <div style="column-width: 18em"> + <template is="dom-repeat" items="{{devices}}"> + <light9-collector-device + graph="{{graph}}" updates="{{updates}}" + uri="{{item}}"></light9-collector-device> + </template> + </div> + </template> + <script> + class Updates { + constructor() { + this.listeners = []; + + } + addListener(cb) { + this.listeners.push(cb); + } + onMessage(msg) { + this.listeners.forEach(function(lis) { + lis(msg); + }); + } + } + HTMLImports.whenReady(function () { + Polymer({ + is: "light9-collector-ui", + properties: { + graph: {type: Object, notify: true}, + updates: {type: Object, notify: true}, + devices: {type: Array}, + }, + observers: [ + 'onGraph(graph)', + ], + ready: function() { + this.updates = new Updates(); + var sock = new WebSocket( + window.location.href.replace(/^http/, 'ws') + 'updates'); + sock.onmessage = function(ev) { + this.updates.onMessage(JSON.parse(ev.data)); + }.bind(this); + }, + onGraph: function(graph) { + this.graph.runHandler(this.findDevices.bind(this), 'findDevices'); + }, + findDevices: function() { + var U = function(x) { + return this.graph.Uri(x); + }; + this.set('devices', []); + + let classes = this.graph.subjects(U('rdf:type'), U(':DeviceClass')); + _.uniq(_.sortBy(classes, 'value'), true).forEach(function(dc) { + _.sortBy(this.graph.subjects(U('rdf:type'), dc), 'value').forEach(function(dev) { + this.push('devices', dev); + }.bind(this)); + }.bind(this)); + } + }); + }); + </script> + </dom-module> + + list notes of song, time with note, eff attrs, function stuff + update rates, stutters from reloads. + <script> + + var source = new EventSource('updates'); + source.onmessage = function(e) { + console.log(JSON.parse(e.data)); + }; + </script> + </body> +</html>
--- a/light9/effect/sequencer.py Sun Jun 03 18:46:36 2018 +0000 +++ b/light9/effect/sequencer.py Sun Jun 03 21:04:00 2018 +0000 @@ -12,6 +12,7 @@ import time from twisted.internet.inotify import INotify from twisted.python.filepath import FilePath +from louie import dispatcher from light9 import networking from light9.namespaces import L9, RDF @@ -131,15 +132,21 @@ def outputSettings(self, t): """ - list of (device, attr, value) + list of (device, attr, value), and a report for web """ + report = {'note': str(self.uri)} effectSettings = self.baseEffectSettings.copy() effectSettings[L9['strength']] = self.evalCurve(t) - return self.effectEval.outputFromEffect( + report['effectSettings'] = dict( + (str(k), str(v)) + for k,v in sorted(effectSettings.items())) + out = self.effectEval.outputFromEffect( effectSettings.items(), songTime=t, # note: not using origin here since it's going away noteTime=t - self.points[0][0]) + print 'out', out.asList() + return out, report class CodeWatcher(object): @@ -207,19 +214,54 @@ self.recentUpdateTimes = self.recentUpdateTimes[-20:] + [now] stats.recentFps = len(self.recentUpdateTimes) / (self.recentUpdateTimes[-1] - self.recentUpdateTimes[0] + .0001) if now > self.lastStatLog + 10: - log.info("%.2f fps", stats.recentFps) + dispatcher.send('state', update={'recentFps': stats.recentFps}) self.lastStatLog = now reactor.callLater(1 / self.fps, self.update) musicState = self.music.getLatest() song = URIRef(musicState['song']) if musicState.get('song') else None + dispatcher.send('state', update={'song': str(song)}) if 't' not in musicState: return t = musicState['t'] settings = [] + songNotes = sorted(self.notes.get(song, [])) + noteReports = [] + for note in songNotes: + s, report = note.outputSettings(t) + noteReports.append(report) + settings.append(s) + dispatcher.send('state', update={'songNotes': noteReports}) + self.sendToCollector(DeviceSettings.fromList(self.graph, settings)) + +import cyclone.sse +class Updates(cyclone.sse.SSEHandler): + def __init__(self, application, request, **kwargs): + cyclone.sse.SSEHandler.__init__(self, application, request, + **kwargs) + self.state = {} + dispatcher.connect(self.updateState, 'state') + self.numConnected = 0 + + def updateState(self, update): + self.state.update(update) - for note in self.notes.get(song, []): - settings.append(note.outputSettings(t)) - self.sendToCollector(DeviceSettings.fromList(self.graph, settings)) + def bind(self): + print 'new client', self.settings.seq + self.numConnected += 1 + + if self.numConnected == 1: + self.loop() + + def loop(self): + if self.numConnected == 0: + return + self.sendEvent(self.state) + reactor.callLater(2, self.loop) + + def unbind(self): + self.numConnected -= 1 + print 'bye', self.numConnected +