# HG changeset patch
# User drewp@bigasterisk.com
# Date 1716099720 25200
# Node ID 66a4db80ce6eb4bf8c7aa8e79a477d50a4d5afac
# Parent 44fc477970bfc8c64f90de449630294de7f5aafb# Parent 6697a68800d29cfc9ee29eb30983c48231a0c531
keep 44fc
diff -r 6697a68800d2 -r 66a4db80ce6e pdm.lock
--- a/pdm.lock Fri May 17 17:48:26 2024 -0700
+++ b/pdm.lock Sat May 18 23:22:00 2024 -0700
@@ -5,7 +5,7 @@
groups = ["default", "dev"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.1"
-content_hash = "sha256:5393d5c679935ba9f042f2b4f4d6efd58dbf03519b2e9f25e08f1e9d421e52f1"
+content_hash = "sha256:6fac24ed6ab93fd328a74d22973a01c77d9de5b4a0b22cd111a884afd99f235f"
[[package]]
name = "aiohttp"
@@ -2315,19 +2315,6 @@
]
[[package]]
-name = "zmq"
-version = "0.0.0"
-summary = "You are probably looking for pyzmq."
-groups = ["default"]
-dependencies = [
- "pyzmq",
-]
-files = [
- {file = "zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9"},
- {file = "zmq-0.0.0.zip", hash = "sha256:21cfc6be254c9bc25e4dabb8a3b2006a4227966b7b39a637426084c8dc6901f7"},
-]
-
-[[package]]
name = "zope-interface"
version = "6.3"
requires_python = ">=3.7"
diff -r 6697a68800d2 -r 66a4db80ce6e pyproject.toml
--- a/pyproject.toml Fri May 17 17:48:26 2024 -0700
+++ b/pyproject.toml Sat May 18 23:22:00 2024 -0700
@@ -38,7 +38,6 @@
"scipy>=1.9.3",
"braillegraph>=0.6",
"tenacity>=8.2.2",
- "zmq>=0.0.0",
"mido>=1.2.10",
"alsa-midi>=1.0.1",
"treq>=22.2.0",
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/collector/collector.py
--- a/src/light9/collector/collector.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/collector.py Sat May 18 23:22:00 2024 -0700
@@ -1,24 +1,38 @@
import logging
import time
from typing import Dict, List, Set, Tuple, cast
-from light9.typedgraph import typedValue
from prometheus_client import Summary
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)
+from light9.newtypes import (
+ ClientSessionType,
+ ClientType,
+ DeviceAttr,
+ DeviceClass,
+ DeviceUri,
+ DmxIndex,
+ DmxMessageIndex,
+ OutputAttr,
+ OutputRange,
+ OutputUri,
+ OutputValue,
+ UnixTime,
+ VTUnion,
+ uriTail,
+)
+from light9.typedgraph import typedValue
log = logging.getLogger('collector')
STAT_SETATTR = Summary('set_attr', 'setAttr calls')
+
def makeDmxMessageIndex(base: DmxIndex, offset: DmxIndex) -> DmxMessageIndex:
return DmxMessageIndex(base + offset - 1)
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/collector/collector_client_asyncio.py
--- a/src/light9/collector/collector_client_asyncio.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/collector_client_asyncio.py Sat May 18 23:22:00 2024 -0700
@@ -1,12 +1,12 @@
-import asyncio
import json
import logging
import time
-from light9 import networking
-from light9.effect.settings import DeviceSettings
+
import zmq.asyncio
from prometheus_client import Summary
+from light9.effect.settings import DeviceSettings
+
log = logging.getLogger('coll_client')
ZMQ_SEND = Summary('zmq_send', 'calls')
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/collector/device.py
--- a/src/light9/collector/device.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/device.py Sat May 18 23:22:00 2024 -0700
@@ -1,11 +1,20 @@
import logging
-from typing import Dict, List, Any, TypeVar, cast
-from light9.namespaces import L9
+from typing import Dict, List, cast
+
+import colormath.color_conversions
+from colormath.color_objects import CMYColor, sRGBColor
from rdflib import Literal, URIRef
from webcolors import hex_to_rgb, rgb_to_hex
-from colormath.color_objects import sRGBColor, CMYColor
-import colormath.color_conversions
-from light9.newtypes import VT, DeviceClass, HexColor, OutputAttr, OutputValue, DeviceUri, DeviceAttr, VTUnion
+
+from light9.namespaces import L9
+from light9.newtypes import (
+ DeviceAttr,
+ DeviceClass,
+ HexColor,
+ OutputAttr,
+ OutputValue,
+ VTUnion,
+)
log = logging.getLogger('device')
@@ -50,10 +59,7 @@
return cast(HexColor, rgb_to_hex(tuple(maxes)))
-def resolve(
- deviceType: DeviceClass,
- deviceAttr: DeviceAttr,
- values: List[VTUnion]) -> VTUnion: # todo: return should be VT
+def resolve(deviceType: DeviceClass, deviceAttr: DeviceAttr, values: List[VTUnion]) -> VTUnion: # todo: return should be VT
"""
return one value to use for this attr, given a set of them that
have come in simultaneously. len(values) >= 1.
@@ -82,8 +88,10 @@
def toOutputAttrs(
deviceType: DeviceClass,
- deviceAttrSettings: Dict[DeviceAttr, VTUnion # TODO
- ]) -> Dict[OutputAttr, OutputValue]:
+ deviceAttrSettings: Dict[
+ DeviceAttr,
+ VTUnion # TODO
+ ]) -> Dict[OutputAttr, OutputValue]:
return dict((OutputAttr(u), OutputValue(v)) for u, v in untype_toOutputAttrs(deviceType, deviceAttrSettings).items())
@@ -134,10 +142,12 @@
return {L9['red']: r, L9['green']: g, L9['blue']: b}
elif deviceType == L9['LedPar90']:
r, g, b = rgbAttr(L9['color'])
- return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: 0}
+ w = _8bit(floatAttr(L9['white']))
+ return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: w}
elif deviceType == L9['LedPar54']:
r, g, b = rgbAttr(L9['color'])
- return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: 0, L9['strobe']: 0}
+ w = _8bit(floatAttr(L9['white']))
+ return {L9['master']: 255, L9['red']: r, L9['green']: g, L9['blue']: b, L9['white']: w, L9['strobe']: 0}
elif deviceType == L9['SimpleDimmer']:
return {L9['level']: _8bit(floatAttr(L9['brightness']))}
elif deviceType == L9['MegaFlash']:
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/collector/weblisteners.py
--- a/src/light9/collector/weblisteners.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/collector/weblisteners.py Sat May 18 23:22:00 2024 -0700
@@ -1,13 +1,11 @@
import asyncio
import io
-import json
import logging
import time
from typing import Any, Awaitable, Dict, List, Protocol, Tuple
import fastavro
from fastavro.schema import load_schema
-from light9.collector.output import Output as OutputInstance
from light9.newtypes import (DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr, OutputUri, OutputValue)
import starlette.websockets
import websockets
@@ -97,5 +95,4 @@
out = io.BytesIO()
fastavro.schemaless_writer(out, self.CollectorUpdateSchema, {'OutputAttrsSet': {'dev': dev, 'attrs': attrRows}})
msg = out.getvalue()
- log.info(f'made update message {len(msg)=}')
return msg
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/effect/edit.py
--- a/src/light9/effect/edit.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/edit.py Sat May 18 23:22:00 2024 -0700
@@ -1,11 +1,11 @@
-from rdflib import URIRef, Literal
+import treq
+from rdfdb.patch import Patch
+from rdflib import Literal, URIRef
from twisted.internet.defer import inlineCallbacks, returnValue
-import treq
from light9 import networking
from light9.curvecalc.curve import CurveResource
from light9.namespaces import L9, RDF, RDFS
-from rdfdb.patch import Patch
def clamp(x, lo, hi):
@@ -53,13 +53,10 @@
] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
elif L9['Submaster'] in droppedTypes:
quads.extend([
- (effect, L9['code'], Literal('out = %s * env' % dropped.n3()),
- ctx),
+ (effect, L9['code'], Literal('out = %s * env' % dropped.n3()), ctx),
])
else:
- raise NotImplementedError(
- "don't know how to add an effect from %r (types=%r)" %
- (dropped, droppedTypes))
+ raise NotImplementedError("don't know how to add an effect from %r (types=%r)" % (dropped, droppedTypes))
_maybeAddMusicLine(quads, effect, song, ctx)
@@ -90,8 +87,7 @@
if L9['Effect'] in droppedTypes:
musicStatus = yield getMusicStatus()
songTime = musicStatus['t']
- note = _makeNote(graph, song, note, quads, ctx, dropped, songTime,
- event, fade)
+ note = _makeNote(graph, song, note, quads, ctx, dropped, songTime, event, fade)
else:
raise NotImplementedError
@@ -99,8 +95,7 @@
def _point(ctx, uri, t, v):
- return [(uri, L9['time'], Literal(round(t, 3)), ctx),
- (uri, L9['value'], Literal(round(v, 3)), ctx)]
+ return [(uri, L9['time'], Literal(round(t, 3)), ctx), (uri, L9['value'], Literal(round(v, 3)), ctx)]
def _finishCurve(graph, note, quads, ctx, songTime):
@@ -110,9 +105,7 @@
pt2 = graph.sequentialUri(curve + 'p')
pt3 = graph.sequentialUri(curve + 'p')
- quads.extend([(curve, L9['point'], pt2, ctx)] +
- _point(ctx, pt2, songTime - origin, 1) +
- [(curve, L9['point'], pt3, ctx)] +
+ quads.extend([(curve, L9['point'], pt2, ctx)] + _point(ctx, pt2, songTime - origin, 1) + [(curve, L9['point'], pt3, ctx)] +
_point(ctx, pt3, songTime - origin + .5, 0))
@@ -199,7 +192,5 @@
for spoc in quads:
if spoc[1] == L9['code'] and 'music' in spoc[2]:
- quads.extend([(effect, L9['code'],
- Literal('music = %s' % musicCurveForSong(song).n3()),
- ctx)])
+ quads.extend([(effect, L9['code'], Literal('music = %s' % musicCurveForSong(song).n3()), ctx)])
break
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/effect/effect_functions.py
--- a/src/light9/effect/effect_functions.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/effect_functions.py Sat May 18 23:22:00 2024 -0700
@@ -1,5 +1,6 @@
import logging
import random
+from typing import cast
from PIL import Image
from webcolors import rgb_to_hex
@@ -7,13 +8,14 @@
from light9.effect.scale import scale
from light9.effect.settings import DeviceSettings
from light9.namespaces import L9
+from light9.newtypes import HexColor
random.seed(0)
log = logging.getLogger('effectfunc')
-def sample8(img, x, y, repeat=False):
+def sample8(img, x, y, repeat=False) -> tuple[int, int, int]:
if not (0 <= y < img.height):
return (0, 0, 0)
if 0 <= x < img.width:
@@ -54,10 +56,10 @@
) -> DeviceSettings:
x = int((songTime / period) * image.width)
out = []
- for y, (d, da, v) in enumerate(devs.asOrderedList()):
+ for y, (d, da, v) in enumerate(devs.asList()):
if da != L9['color']:
continue
color8 = sample8(image, x, y, repeat=True)
- color = rgb_to_hex(tuple(color8))
- out.append((d, da, scale(color, strength * v)))
- return DeviceSettings(devs.graph, out)
\ No newline at end of file
+ color = HexColor(rgb_to_hex(color8))
+ out.append((d, da, scale(color, strength * cast(float, v))))
+ return DeviceSettings(devs.graph, out)
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/effect/effecteval2.py
--- a/src/light9/effect/effecteval2.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/effecteval2.py Sat May 18 23:22:00 2024 -0700
@@ -1,6 +1,6 @@
-import traceback
import inspect
import logging
+import traceback
from dataclasses import dataclass
from typing import Callable, List, Optional
@@ -11,7 +11,14 @@
from light9.effect.effect_function_library import EffectFunctionLibrary
from light9.effect.settings import DeviceSettings, EffectSettings
from light9.namespaces import L9
-from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, EffectFunction, EffectUri, VTUnion)
+from light9.newtypes import (
+ DeviceAttr,
+ DeviceUri,
+ EffectAttr,
+ EffectFunction,
+ EffectUri,
+ VTUnion,
+)
from light9.typedgraph import typedValue
log = logging.getLogger('effecteval')
@@ -92,7 +99,7 @@
for arg in c.funcArgs:
if arg.annotation == DeviceSettings:
v = c.devSettings
- if v is None: # asked for ds but we have none
+ if v is None: # asked for ds but we have none
log.debug("%s asked for devs but we have none in config", self.uri)
return DeviceSettings(self.graph, [])
elif arg.name == 'songTime':
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/effect/scale.py
--- a/src/light9/effect/scale.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/scale.py Sat May 18 23:22:00 2024 -0700
@@ -1,9 +1,12 @@
+import logging
from decimal import Decimal
from webcolors import hex_to_rgb, rgb_to_hex
from light9.newtypes import VTUnion
+log = logging.getLogger('scale')
+
def scale(value: VTUnion, strength: float):
if isinstance(value, Decimal):
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/effect/sequencer/eval_faders.py
--- a/src/light9/effect/sequencer/eval_faders.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/sequencer/eval_faders.py Sat May 18 23:22:00 2024 -0700
@@ -1,6 +1,6 @@
-import traceback
import logging
import time
+import traceback
from dataclasses import dataclass
from typing import List, Optional, cast
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/effect/sequencer/service.py
--- a/src/light9/effect/sequencer/service.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/sequencer/service.py Sat May 18 23:22:00 2024 -0700
@@ -14,8 +14,8 @@
from starlette.routing import Route
from starlette_exporter import PrometheusMiddleware, handle_metrics
+from light9 import networking
from light9.background_loop import loop_forever
-from light9 import networking
from light9.collector.collector_client_asyncio import sendToCollector
from light9.effect.effect_function_library import EffectFunctionLibrary
from light9.effect.sequencer.eval_faders import FaderEval
@@ -51,10 +51,10 @@
def main():
graph = SyncedGraph(networking.rdfdb.url, "effectSequencer")
logging.getLogger('sse_starlette.sse').setLevel(logging.INFO)
-
+
logging.getLogger('autodepgraphapi').setLevel(logging.INFO)
logging.getLogger('syncedgraph').setLevel(logging.INFO)
-
+
logging.getLogger('effecteval').setLevel(logging.INFO)
logging.getLogger('seq.fader').setLevel(logging.INFO)
diff -r 6697a68800d2 -r 66a4db80ce6e src/light9/effect/settings.py
--- a/src/light9/effect/settings.py Fri May 17 17:48:26 2024 -0700
+++ b/src/light9/effect/settings.py Sat May 18 23:22:00 2024 -0700
@@ -19,7 +19,7 @@
from light9.collector.device import resolve
from light9.localsyncedgraph import LocalSyncedGraph
from light9.namespaces import L9, RDF
-from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, HexColor, VTUnion)
+from light9.newtypes import DeviceAttr, DeviceUri, EffectAttr, HexColor, VTUnion
log = logging.getLogger('settings')
diff -r 6697a68800d2 -r 66a4db80ce6e web/TiledHome.ts
--- a/web/TiledHome.ts Fri May 17 17:48:26 2024 -0700
+++ b/web/TiledHome.ts Sat May 18 23:22:00 2024 -0700
@@ -1,25 +1,15 @@
import debug from "debug";
import * as FlexLayout from "flexlayout-react";
-import { LitElement, html } from "lit";
+import { LitElement, TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import * as React from "react";
import { createRoot } from "react-dom/client";
import { getTopGraph } from "./RdfdbSyncedGraph";
import { SyncedGraph } from "./SyncedGraph";
+import { panelDisplayName, panelElementNames, panelUrl } from "./panels";
export { RdfdbSyncedGraph } from "./RdfdbSyncedGraph";
-export { Light9CollectorUi } from "./collector/Light9CollectorUi";
-export { Light9FadeUi } from "./fade/Light9FadeUi";
-export { Light9DeviceSettings } from "./live/Light9DeviceSettings";
const log = debug("home");
-const config: FlexLayout.IJsonModel = {
- global: {
- tabEnableRename: false,
- },
- borders: [],
- layout: fullLayout(persistedLayout) as FlexLayout.IJsonRowNode,
-};
-
// see https://github.com/lit/lit/tree/main/packages/labs/react
// Store flexlayout panels in per-browser localstorage.
@@ -31,7 +21,7 @@
{
type: "tabset",
weight: 50,
- children: [{ type: "tab", name: "devsettings", component: "light9-device-settings" }],
+ children: [{ type: "tab", name: "fade", component: "light9-fade-ui" }],
},
{
type: "tabset",
@@ -53,6 +43,9 @@
}
}
+// This lets lit call a method on a react element.
+let addTab: (component: string) => void;
+
class Main extends React.Component {
state: { model: FlexLayout.Model; persistence: PersistentLayout };
constructor(props: any) {
@@ -75,6 +68,15 @@
};
render() {
+ addTab = (component) => {
+ const name = panelDisplayName(component);
+ if (name === undefined) throw new Error("no such panel: " + component);
+ const newTab = { type: "tab", name: name, component: component };
+ const firstTabSet = this.state.model.getRoot().getChildren()[0];
+ const action = FlexLayout.Actions.addNode(newTab, firstTabSet.getId(), FlexLayout.DockLocation.LEFT, 0);
+ this.state.model.doAction(action);
+ };
+
return React.createElement(FlexLayout.Layout, {
model: this.state.model,
realtimeResize: true,
@@ -92,15 +94,31 @@
export class Light9HomeStatus extends LitElement {
graph!: SyncedGraph;
render() {
- return html`
- metrics `;
+ return html`
+
+ metrics
+ Open tab or new window: ${panelElementNames().map((elem) => this.linkToPanelPage(elem))}
+ `;
}
+
+ linkToPanelPage(elem: string): TemplateResult {
+ return html` ${panelDisplayName(elem)} `;
+ }
+
constructor() {
super();
getTopGraph().then((g) => {
this.graph = g;
});
}
+
+ onClickPanelLink(ev: MouseEvent) {
+ ev.preventDefault();
+
+ const a = ev.target as HTMLAnchorElement;
+ const elem = a.dataset.panelElem!;
+ addTab(elem);
+ }
}
const root = createRoot(document.getElementById("container")!);
diff -r 6697a68800d2 -r 66a4db80ce6e web/panels.ts
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/panels.ts Sat May 18 23:22:00 2024 -0700
@@ -0,0 +1,29 @@
+export { Light9AscoltamiUi } from "./ascoltami/Light9AscoltamiUi";
+export { Light9CollectorUi } from "./collector/Light9CollectorUi";
+export { Light9EffectListing } from "./effects/Light9EffectListing";
+export { Light9FadeUi } from "./fade/Light9FadeUi";
+export { Light9DeviceSettings } from "./live/Light9DeviceSettings";
+
+const panels: Map> = new Map([
+ ["light9-ascoltami-ui", ["ascoltami", "ascoltami"]],
+ ["light9-collector-ui", ["collector", "collector"]],
+ ["light9-device-settings", ["device-settings", "live"]],
+ ["light9-effect-listing", ["effect-listing" , "effectListing"]],
+ ["light9-fade-ui", ["fade", "fade"]],
+]);
+
+export function panelElementNames(): string[] {
+ return Array.from(panels.keys());
+}
+
+export function panelUrl(elem: string): string {
+ const row = panels.get(elem);
+ if (!row) throw new Error("No panel: " + elem);
+ return "/" + row[1] + "/";
+ }
+
+export function panelDisplayName(elem: string): string {
+ const row = panels.get(elem);
+ if (!row) throw new Error("No panel: " + elem);
+ return row[0];
+}
\ No newline at end of file