Changeset - 2b8a2a25b154
[Not reviewed]
default
0 3 0
drewp@bigasterisk.com - 20 months ago 2023-05-28 00:56:28
drewp@bigasterisk.com
logging
3 files changed with 5 insertions and 4 deletions:
0 comments (0 inline, 0 general)
light9/collector/collector.py
Show inline comments
 
import logging
 
import time
 
from typing import Dict, List, Set, Tuple, cast
 
from light9.typedgraph import typedValue
 

	
 
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 
from rdflib import URIRef
 

	
 
from light9.collector.device import resolve, toOutputAttrs
 
from light9.collector.output import Output as OutputInstance
 
from light9.collector.weblisteners import WebListeners
 
from light9.effect.settings import DeviceSettings
 
from light9.namespaces import L9, RDF
 
from light9.newtypes import (ClientSessionType, ClientType, DeviceAttr, DeviceClass, DeviceSetting, DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr,
 
                             OutputRange, OutputUri, OutputValue, UnixTime, VTUnion, uriTail)
 

	
 
log = logging.getLogger('collector')
 

	
 

	
 
def makeDmxMessageIndex(base: DmxIndex, offset: DmxIndex) -> DmxMessageIndex:
 
    return DmxMessageIndex(base + offset - 1)
 

	
 

	
 
def _outputMap(graph: SyncedGraph, outputs: Set[OutputUri]) -> Dict[Tuple[DeviceUri, OutputAttr], Tuple[OutputUri, DmxMessageIndex]]:
 
    """From rdf config graph, compute a map of
 
       (device, outputattr) : (output, index)
 
    that explains which output index to set for any device update.
 
    """
 
    ret = cast(Dict[Tuple[DeviceUri, OutputAttr], Tuple[OutputUri, DmxMessageIndex]], {})
 

	
 
    for dc in graph.subjects(RDF.type, L9['DeviceClass']):
 
        log.info('mapping devices of class %s', dc)
 
        log.info('  mapping devices of class %s', dc)
 
        for dev in graph.subjects(RDF.type, dc):
 
            dev = cast(DeviceUri, dev)
 
            log.info('  💡 mapping device %s', dev)
 
            log.info('    💡 mapping device %s', dev)
 
            universe = typedValue(OutputUri, graph, dev, L9['dmxUniverse'])
 
            if universe not in outputs:
 
                raise ValueError(f'{dev=} is configured to be in {universe=}, but we have no Output for that universe')
 
            try:
 
                dmxBase = typedValue(DmxIndex, graph, dev, L9['dmxBase'])
 
            except ValueError:
 
                raise ValueError('no :dmxBase for %s' % dev)
 

	
 
            for row in sorted(graph.objects(dc, L9['attr']), key=str):
 
                outputAttr = typedValue(OutputAttr, graph, row, L9['outputAttr'])
 
                offset = typedValue(DmxIndex, graph, row, L9['dmxOffset'])
 
                index = makeDmxMessageIndex(dmxBase, offset)
 
                ret[(dev, outputAttr)] = (universe, index)
 
                log.info(f'      {uriTail(outputAttr):15} maps to {uriTail(universe)} index {index}')
 
    return ret
 

	
 

	
 
class Collector:
 
    """receives setAttrs calls; combines settings; renders them into what outputs like; calls Output.update"""
 

	
 
    def __init__(self, graph: SyncedGraph, outputs: List[OutputInstance], listeners: WebListeners, clientTimeoutSec: float = 10):
 
        self.graph = graph
 
        self.outputs = outputs
 
        self.listeners = listeners
 
        self.clientTimeoutSec = clientTimeoutSec
 

	
 
        self._initTime = time.time()
 
        self._outputByUri: Dict[OutputUri, OutputInstance] = {}
 
        self._deviceType: Dict[DeviceUri, DeviceClass] = {}
 
        self.remapOut: Dict[Tuple[DeviceUri, OutputAttr], OutputRange] = {}
 

	
 
        self.graph.addHandler(self._compile)
 

	
 
        # rename to activeSessons ?
 
        self.lastRequest: Dict[Tuple[ClientType, ClientSessionType], Tuple[UnixTime, Dict[Tuple[DeviceUri, DeviceAttr], VTUnion]]] = {}
 

	
 
        # (dev, devAttr): value to use instead of 0
 
        self.stickyAttrs: Dict[Tuple[DeviceUri, DeviceAttr], VTUnion] = {}
 

	
 
    def _compile(self):
 
        log.info('Collector._compile:')
 
        self._outputByUri = self._compileOutputByUri()
 
        self._outputMap = _outputMap(self.graph, set(self._outputByUri.keys()))
 

	
 
        self._deviceType.clear()
 
        self.remapOut.clear()
 
        for dc in self.graph.subjects(RDF.type, L9['DeviceClass']):
 
            dc = cast(DeviceClass, dc)
 
            for dev in self.graph.subjects(RDF.type, dc):
 
                dev = cast(DeviceUri, dev)
 
                self._deviceType[dev] = dc
 
                self._compileRemapForDevice(dev)
 

	
 
    def _compileOutputByUri(self) -> Dict[OutputUri, OutputInstance]:
 
        ret = {}
 
        for output in self.outputs:
 
            ret[OutputUri(output.uri)] = output
 
        return ret
 

	
 
    def _compileRemapForDevice(self, dev: DeviceUri):
 
        for remap in self.graph.objects(dev, L9['outputAttrRange']):
 
            attr = typedValue(OutputAttr, self.graph, remap, L9['outputAttr'])
 
            start = typedValue(float, self.graph, remap, L9['start'])
 
            end = typedValue(float, self.graph, remap, L9['end'])
 
            self.remapOut[(dev, attr)] = OutputRange((start, end))
 

	
 
    def setAttrs(self, client: ClientType, clientSession: ClientSessionType, settings: DeviceSettings, sendTime: UnixTime):
 
        """
 
        Given DeviceSettings, we resolve conflicting values,
 
        process them into output attrs, and call Output.update
 
        to send the new outputs.
 

	
 
        client is a string naming the type of client.
 
        (client, clientSession) is a unique client instance.
 
        clientSession is deprecated.
 

	
 
        Each client session's last settings will be forgotten
 
        after clientTimeoutSec.
 
        """
 
        # todo: cleanup session code if we really don't want to be able to run multiple sessions of one client
 
        clientSession = ClientSessionType("no_longer_used")
 

	
 
        now = UnixTime(time.time())
 
        self._warnOnLateRequests(client, now, sendTime)
 

	
 
        self._forgetStaleClients(now)
 

	
 
        self.lastRequest[(client, clientSession)] = (now, self._resolvedSettingsDict(settings))
 

	
light9/live/Light9AttrControl.ts
Show inline comments
 
@@ -92,90 +92,90 @@ export class Light9AttrControl extends L
 
  }
 

	
 
  private onValueProperty() {
 
    if (this.deviceAttrRow === null) throw new Error();
 
    if (this.effect !== null && this.graph !== undefined) {
 
      const p = this.effect.edit(this.deviceAttrRow.device, this.deviceAttrRow.uri, this.value);
 
      if (p.adds.length || p.dels.length) {
 
        log("Effect told us to graph.patch", p, "to", this.graph);
 
        this.graph.applyAndSendPatch(p);
 
      }
 
    }
 
  }
 

	
 
  private onEffectProperty() {
 
    if (this.effect === null) throw new Error();
 
    // effect will read graph changes on its own, but emit an event when it does
 
    this.effect.settingsChanged.subscribe(() => {
 
      this.effectSettingsChanged();
 
    });
 
    this.effectSettingsChanged();
 
  }
 

	
 
  private effectSettingsChanged() {
 
    // something in the settings graph is new
 
    if (this.deviceAttrRow === null) throw new Error();
 
    if (this.effect === null) throw new Error();
 
    // log("graph->ui on ", this.deviceAttrRow.device, this.deviceAttrRow.uri);
 
    const v = this.effect.currentValue(this.deviceAttrRow.device, this.deviceAttrRow.uri);
 
    this.onGraphValueChanged(v);
 
  }
 

	
 
  private onDeviceAttrRowProperty() {
 
    if (this.deviceAttrRow === null) throw new Error();
 
    const d = this.deviceAttrRow.dataType;
 
    if (d.equals(makeType("scalar"))) {
 
      this.dataType = "scalar";
 
    } else if (d.equals(makeType("color"))) {
 
      this.dataType = "color";
 
    } else if (d.equals(makeType("choice"))) {
 
      this.dataType = "choice";
 
    }
 
  }
 

	
 
  onValueInput(ev: CustomEvent) {
 
    if (ev.detail === undefined) {
 
      // not sure what this is, but it seems to be followed by good events
 
      return;
 
    }
 
    log(ev.type, ev.detail.value);
 
    // log(ev.type, ev.detail.value);
 
    this.value = ev.detail.value;
 
    // this.graphToControls.controlChanged(this.device, this.deviceAttrRow.uri, ev.detail.value);
 
  }
 

	
 
  onGraphValueChanged(v: ControlValue | null) {
 
    if (this.deviceAttrRow === null) throw new Error();
 
    // log("change: control must display", v, "for", this.deviceAttrRow.device.value, this.deviceAttrRow.uri.value);
 
    // this.enableChange = false;
 
    if (this.dataType == "scalar") {
 
      if (v !== null) {
 
        this.value = v;
 
      } else {
 
        this.value = 0;
 
      }
 
    } else if (this.dataType == "color") {
 
      this.value = v;
 
    }
 
  }
 

	
 
  goBlack() {
 
    this.value = "#000000";
 
  }
 

	
 
  onChoice(value: any) {
 
    // if (value != null) {
 
    //   value = this.graph.Uri(value);
 
    // } else {
 
    //   value = null;
 
    // }
 
  }
 

	
 
  onChange(value: any) {
 
    // if (typeof value === "number" && isNaN(value)) {
 
    //   return;
 
    // } // let onChoice do it
 
    // //log('change: control tells graph', @deviceAttrRow.uri.value, value)
 
    // if (value === undefined) {
 
    //   value = null;
 
    // }
 
  }
 
}
light9/rdfdb/service.py
Show inline comments
 
import logging
 
import os
 
from pathlib import Path
 

	
 
from light9.run_local import log
 

	
 
import rdfdb.service
 
from rdflib import URIRef
 

	
 
from light9 import showconfig
 
logging.getLogger('rdfdb').setLevel(logging.INFO)
 
logging.getLogger('rdfdb.file').setLevel(logging.INFO)
 
logging.getLogger('rdfdb.graph').setLevel(logging.INFO)
 
logging.getLogger('rdfdb.net').setLevel(logging.DEBUG)
 
logging.getLogger('rdfdb.net').setLevel(logging.INFO)
 
rdfRoot = Path(os.environ['LIGHT9_SHOW'].rstrip('/') + '/')
 
showUri = URIRef(showconfig.showUri() + '/')
 

	
 
app = rdfdb.service.makeApp(  #
 
    dirUriMap={rdfRoot: showUri},
 
    prefixes={
 
        'show': showUri,
 
        '': URIRef('http://light9.bigasterisk.com/'),
 
        'rdf': URIRef('http://www.w3.org/1999/02/22-rdf-syntax-ns#'),
 
        'rdfs': URIRef('http://www.w3.org/2000/01/rdf-schema#'),
 
        'xsd': URIRef('http://www.w3.org/2001/XMLSchema#'),
 
        'effect': URIRef('http://light9.bigasterisk.com/effect/'),
 
        'dev': URIRef('http://light9.bigasterisk.com/theater/skyline/device/'),
 
    })
0 comments (0 inline, 0 general)