changeset 11:028ed39aa78f

more ts types; attempted multiplayer but the change events are managed wrong
author drewp@bigasterisk.com
date Sun, 28 Jan 2024 17:31:06 -0800
parents 140633abfa2a
children 7cc004eafb82
files light.py light_bridge.py src/main.ts
diffstat 3 files changed, 74 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/light.py	Sun Jan 28 16:53:35 2024 -0800
+++ b/light.py	Sun Jan 28 17:31:06 2024 -0800
@@ -1,6 +1,7 @@
 import asyncio
 import logging
 from dataclasses import dataclass, field
+from typing import Callable
 
 from color import Color
 
@@ -33,6 +34,8 @@
     online: bool | None = None
     latencyMs: float | None = None
 
+    notifyChanged: Callable | None = None
+
     def __post_init__(self):
         self.requestingDeviceColor = self.deviceColor(self.requestingColor)
 
@@ -54,27 +57,50 @@
         return DeviceColor(r=c.r, g=c.g, b=c.b)
 
     async def setColor(self, c: Color):
+        log.info(f'setColor from {self.requestingColor} to {c}')
+        if c == self.requestingColor:
+            return
         self.requestingColor = c
         self.requestingDeviceColor = self.deviceColor(self.requestingColor)
+        if self.notifyChanged:
+            self.notifyChanged()
 
 
 class Lights:
     _d: dict[str, Light] = {}
+    _changeSleepTask: asyncio.Task | None = None
 
     def __init__(self):
         self.add(Light('do-desk', 'topic1'))
         self.add(Light('do-desk2', 'topic2'))
 
     def add(self, d: Light):
+        d.notifyChanged = self.notifyChanged
         self._d[d.name] = d
 
+        self.notifyChanged()
+
     def byName(self, name: str) -> Light:
         return self._d[name]
 
     async def changes(self):  # yields None on any data change
         while True:
+            log.info('Lights has a change')
             yield None
-            await asyncio.sleep(1)  # todo
+            self._changeSleepTask = asyncio.create_task(self._sleep())
+            try:
+                await self._changeSleepTask
+            except asyncio.CancelledError:
+                pass
+            self._changeSleepTask = None
+
+    async def _sleep(self):
+        await asyncio.sleep(100)
+
+    def notifyChanged(self):
+        log.info('Lights.notifyChanged()')
+        if self._changeSleepTask is not None:
+            self._changeSleepTask.cancel()
 
     def to_dict(self):
         return {'lights': [d.to_dict() for d in sorted(self._d.values(), key=lambda r: r.name)]}
--- a/light_bridge.py	Sun Jan 28 16:53:35 2024 -0800
+++ b/light_bridge.py	Sun Jan 28 17:31:06 2024 -0800
@@ -1,6 +1,7 @@
 """
 replaces a lot of mqtt_to_rdf and rdf_to_mqtt
 """
+import asyncio
 import json
 import logging
 import time
@@ -31,9 +32,15 @@
 
 async def table(lights: Lights, req: Request) -> EventSourceResponse:
 
+    def updateMessage():
+        return json.dumps({'now': time.time()} | lights.to_dict())
+
     async def g():
-        async for ping in lights.changes():
-            yield json.dumps({'now': time.time()} | lights.to_dict())
+        yield updateMessage()
+        async for ping in lights.changes():  # broken if there's more than one caller!
+            log.info('send table event')
+            yield updateMessage()
+            await asyncio.sleep(.5)  # slow down the inf loop
 
     return EventSourceResponse(g())
 
--- a/src/main.ts	Sun Jan 28 16:53:35 2024 -0800
+++ b/src/main.ts	Sun Jan 28 17:31:06 2024 -0800
@@ -1,6 +1,16 @@
 import { LitElement, css, html } from "lit";
 import { customElement, state } from "lit/decorators.js";
 
+interface Light {
+  name: string;
+  address: string;
+  requestingColor: string;
+  requestingDeviceColor: object;
+  emittingColor: string;
+  online: boolean;
+  latencyMs: number;
+}
+
 @customElement("lb-page")
 export class LbPage extends LitElement {
   static styles = [
@@ -39,7 +49,8 @@
   ];
 
   // bug https://github.com/lit/lit/issues/4305
-  @((state as any)()) lights: object[] = [];
+  @((state as any)()) lights: Light[] = [];
+  @((state as any)()) lightByName: Map<string, Light> = new Map();
   @((state as any)()) reportTime: Date = new Date(0);
 
   connectedCallback(): void {
@@ -47,10 +58,18 @@
     const es = new EventSource("api/table");
     es.onmessage = (ev) => {
       const body = JSON.parse(ev.data);
-      this.lights = body.lights;
+      this.lights = body.lights.map((wrappedLight: any) => wrappedLight.light as Light);
+      this.rebuildLightByName();
       this.reportTime = new Date(body.now * 1000);
     };
   }
+  private rebuildLightByName() {
+    this.lightByName = new Map();
+    this.lights.forEach((light: any) => {
+      this.lightByName.set(light.name, light);
+    });
+  }
+
   render() {
     return html`
       <h1>Light-bridge</h1>
@@ -66,18 +85,18 @@
           <th class="col-group-3">latency</th>
         </tr>
         ${this.lights.map(
-          (d: any) => html`
+          (d: Light) => html`
             <tr>
-              <td class="col-group-1">${d.light.name}</td>
-              <td class="col-group-1"><code>${d.light.address}</code></td>
+              <td class="col-group-1">${d.name}</td>
+              <td class="col-group-1"><code>${d.address}</code></td>
               <td class="col-group-2">
-                <code>${d.light.requestingColor}</code>
-                <input type="color" @input=${this.onRequestingColor.bind(this, d.light.name)} .value="${d.light.requestingColor}" />
+                <code>${d.requestingColor}</code>
+                <input type="color" @input=${this.onRequestingColor.bind(this, d.name)} .value="${d.requestingColor}" />
               </td>
-              <td class="col-group-2"><code>${JSON.stringify(d.light.requestingDeviceColor)}</code></td>
-              <td class="col-group-3">${d.light.emittingColor} <span class="color" style="background: ${d.light.emittingColor}"></span></td>
-              <td class="col-group-3">${d.light.online ? "✔" : ""}</td>
-              <td class="col-group-3">${d.light.latencyMs} ms</td>
+              <td class="col-group-2"><code>${JSON.stringify(d.requestingDeviceColor)}</code></td>
+              <td class="col-group-3">${d.emittingColor} <span class="color" style="background: ${d.emittingColor}"></span></td>
+              <td class="col-group-3">${d.online ? "✔" : ""}</td>
+              <td class="col-group-3">${d.latencyMs} ms</td>
             </tr>
           `
         )}
@@ -90,10 +109,17 @@
       <bigast-loginbar></bigast-loginbar>
     `;
   }
-  onRequestingColor(lightName: string, ev: InputEvent) {
+  async onRequestingColor(lightName: string, ev: InputEvent) {
+    const currentRequest = this.lightByName.get(lightName)!.requestingColor;
     const value = (ev.target as HTMLInputElement).value;
+    console.log("LbPage ~ onRequestingColor ~ value === currentRequest:", value, currentRequest);
+    if (value === currentRequest) {
+      return;
+    }
     const url = new URL("api/output", location as any);
     url.searchParams.append("light", lightName);
     fetch(url, { method: "PUT", body: value }); // todo: only run one fetch at a time, per light
+
+    // sometime after this, the SSE table will send us back the change we made (probably)
   }
 }