Changeset - ae4b90efb55a
[Not reviewed]
default
0 3 6
drewp@bigasterisk.com - 8 months ago 2024-05-20 08:28:12
drewp@bigasterisk.com
start calibration tool
9 files changed with 481 insertions and 8 deletions:
0 comments (0 inline, 0 general)
package.json
Show inline comments
 
@@ -17,24 +17,25 @@
 
    "@types/async": "^3.2.24",
 
    "@types/d3": "^7.4.3",
 
    "@types/debug": "^4.1.12",
 
    "@types/fpsmeter": "^0.3.34",
 
    "@types/n3": "^1.16.4",
 
    "@types/node": "^20.12.11",
 
    "@types/reconnectingwebsocket": "^1.0.10",
 
    "@types/underscore": "^1.11.15",
 
    "async": "^3.2.5",
 
    "avro-js": "^1.11.3",
 
    "d3": "^7.9.0",
 
    "debug": "^4.3.4",
 
    "echarts": "^5.5.0",
 
    "flexlayout-react": "^0.7.15",
 
    "fpsmeter": "^0.3.1",
 
    "immutable": "^4.3.5",
 
    "knockout": "^3.5.1",
 
    "lit": "^2.8.0",
 
    "n3": "^1.17.3",
 
    "onecolor": "^4.1.0",
 
    "parse-prometheus-text-format": "^1.1.1",
 
    "react": "^18.3.1",
 
    "react-dom": "^18.3.1",
 
    "reconnectingwebsocket": "^1.0.0",
 
    "sub-events": "^1.9.0",
pnpm-lock.yaml
Show inline comments
 
@@ -41,33 +41,33 @@ dependencies:
 
  '@types/reconnectingwebsocket':
 
    specifier: ^1.0.10
 
    version: 1.0.10
 
  '@types/underscore':
 
    specifier: ^1.11.15
 
    version: 1.11.15
 
  async:
 
    specifier: ^3.2.5
 
    version: 3.2.5
 
  avro-js:
 
    specifier: ^1.11.3
 
    version: 1.11.3
 
  avsc:
 
    specifier: ^5.7.7
 
    version: 5.7.7
 
  d3:
 
    specifier: ^7.9.0
 
    version: 7.9.0
 
  debug:
 
    specifier: ^4.3.4
 
    version: 4.3.4
 
  echarts:
 
    specifier: ^5.5.0
 
    version: 5.5.0
 
  flexlayout-react:
 
    specifier: ^0.7.15
 
    version: 0.7.15(react-dom@18.3.1)(react@18.3.1)
 
  fpsmeter:
 
    specifier: ^0.3.1
 
    version: 0.3.1
 
  immutable:
 
    specifier: ^4.3.5
 
    version: 4.3.5
 
  knockout:
 
    specifier: ^3.5.1
 
    version: 3.5.1
 
@@ -1077,29 +1077,24 @@ packages:
 
    resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
 
    engines: {node: '>= 0.4'}
 
    dependencies:
 
      possible-typed-array-names: 1.0.0
 
    dev: true
 

	
 
  /avro-js@1.11.3:
 
    resolution: {integrity: sha512-B1b0wI5iwSkVwj3RQWRzW99/LGoYl6df9j1kWime8r8b0dXCdKU7t7mEOFkOpQFaT/I9JXaL7KAIxM+/3TUk1A==}
 
    dependencies:
 
      underscore: 1.13.6
 
    dev: false
 

	
 
  /avsc@5.7.7:
 
    resolution: {integrity: sha512-9cYNccliXZDByFsFliVwk5GvTq058Fj513CiR4E60ndDwmuXzTJEp/Bp8FyuRmGyYupLjHLs+JA9/CBoVS4/NQ==}
 
    engines: {node: '>=0.11'}
 
    dev: false
 

	
 
  /base64-js@1.5.1:
 
    resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
 

	
 
  /blocking-elements@0.1.1:
 
    resolution: {integrity: sha512-/SLWbEzMoVIMZACCyhD/4Ya2M1PWP1qMKuiymowPcI+PdWDARqeARBjhj73kbUBCxEmTZCUu5TAqxtwUO9C1Ig==}
 
    dev: false
 

	
 
  /blueimp-md5@2.19.0:
 
    resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==}
 
    dev: false
 

	
 
  /bn.js@4.12.0:
 
@@ -1642,24 +1637,31 @@ packages:
 
    resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
 
    dependencies:
 
      bn.js: 4.12.0
 
      miller-rabin: 4.0.1
 
      randombytes: 2.1.0
 
    dev: true
 

	
 
  /domain-browser@4.23.0:
 
    resolution: {integrity: sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==}
 
    engines: {node: '>=10'}
 
    dev: true
 

	
 
  /echarts@5.5.0:
 
    resolution: {integrity: sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==}
 
    dependencies:
 
      tslib: 2.3.0
 
      zrender: 5.5.0
 
    dev: false
 

	
 
  /elliptic@6.5.5:
 
    resolution: {integrity: sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==}
 
    dependencies:
 
      bn.js: 4.12.0
 
      brorand: 1.1.0
 
      hash.js: 1.1.7
 
      hmac-drbg: 1.0.1
 
      inherits: 2.0.4
 
      minimalistic-assert: 1.0.1
 
      minimalistic-crypto-utils: 1.0.1
 
    dev: true
 

	
 
@@ -2541,24 +2543,28 @@ packages:
 
    engines: {node: '>=14.0.0'}
 
    dev: false
 

	
 
  /tinyspy@2.2.1:
 
    resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
 
    engines: {node: '>=14.0.0'}
 
    dev: false
 

	
 
  /tslib@1.14.1:
 
    resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
 
    dev: false
 

	
 
  /tslib@2.3.0:
 
    resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
 
    dev: false
 

	
 
  /tslib@2.6.2:
 
    resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
 
    dev: false
 

	
 
  /tty-browserify@0.0.1:
 
    resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==}
 
    dev: true
 

	
 
  /type-detect@4.0.8:
 
    resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
 
    engines: {node: '>=4'}
 
    dev: false
 
@@ -2785,12 +2791,18 @@ packages:
 
    engines: {node: '>=0.4'}
 
    dev: true
 

	
 
  /yocto-queue@0.1.0:
 
    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
 
    engines: {node: '>=10'}
 
    dev: true
 

	
 
  /yocto-queue@1.0.0:
 
    resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
 
    engines: {node: '>=12.20'}
 
    dev: false
 

	
 
  /zrender@5.5.0:
 
    resolution: {integrity: sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==}
 
    dependencies:
 
      tslib: 2.3.0
 
    dev: false
web/calibrate/Light9Calibrate.ts
Show inline comments
 
new file 100644
 
import debug from "debug";
 
import { css, html, LitElement } from "lit";
 
import { customElement, query, state } from "lit/decorators.js";
 
import { CollectorClient } from "../collector/CollectorClient";
 
import { getTopGraph } from "../RdfdbSyncedGraph";
 
import { SyncedGraph } from "../SyncedGraph";
 
import { Light9Camera } from "./Light9Camera";
 
import { XyPlot } from "./XyPlot";
 
export { RdfdbSyncedGraph } from "../RdfdbSyncedGraph";
 
export { Light9Camera } from "./Light9Camera";
 
export { XyPlot } from "./XyPlot";
 
debug.enable("*");
 
const log = debug("calibrate");
 

	
 
async function sleep(ms: number) {
 
  return new Promise((resolve) => setTimeout(resolve, ms));
 
}
 

	
 
@customElement("light9-calibrate")
 
export class Light9Calibrate extends LitElement {
 
  graph!: SyncedGraph;
 
  static styles = [
 
    css`
 
      button {
 
        min-height: 3em;
 
        min-width: 10em;
 
      }
 
    `,
 
  ];
 
  collector: CollectorClient = new CollectorClient("calibrate");
 
  @query("light9-camera", true) cam?: Light9Camera;
 
  @query("xy-plot", true) plot?: XyPlot;
 
  @state() device: string;
 
  constructor() {
 
    super();
 
    this.device = "http://light9.bigasterisk.com/theater/vet/device/parR3";
 
    getTopGraph().then((g) => {
 
      this.graph = g;
 
    });
 
  }
 

	
 
  render() {
 
    return html`<rdfdb-synced-graph></rdfdb-synced-graph>
 
      <h1>Calibrate</h1>
 
      <light9-camera></light9-camera>
 

	
 
      <p>Device to calibrate: [ ${this.device} ]</p>
 

	
 
      <ol>
 
        <li><button @click=${this.setToFull}>Set to full</button></li>
 
        <li><button @click=${this.findSafeExposure}>Find safe exposure</button></li>
 
        <li><button @click=${this.setToZero}>Set to 0</button></li>
 
        <li><button @click=${this.markTare}>Mark tare</button></li>
 
        <li><button @click=${this.calibrateLoop}>Calibrate loop</button></li>
 
      </ol>
 
      <xy-plot label="zebra pixels vs exposure"></xy-plot>
 
      r/g/b/r*g*b lines ,x=send y=seen `;
 
  }
 

	
 
  async withButtonSpinner(ev: MouseEvent, fn: () => Promise<void>) {
 
    const btn = ev.target as HTMLButtonElement;
 
    try {
 
      btn.disabled = true;
 
      await fn();
 
    } finally {
 
      btn.disabled = false;
 
    }
 
  }
 
  async setToFull(ev: MouseEvent) {
 
    await this.withButtonSpinner(ev, async () => {
 
      this.collector.updateSettings([
 
        /// device,attr,value
 
        [this.device, "http://light9.bigasterisk.com/color", "#ffffff"],
 
        [this.device, "http://light9.bigasterisk.com/white", 1],
 
      ]);
 
    });
 
  }
 

	
 
  async findSafeExposure(ev: MouseEvent) {
 
    await this.withButtonSpinner(ev, async () => {
 

	
 
      const gatherSample = async (expo: number) => {
 
        await this.cam?.set("exposureTime", expo);
 
        const settleUntil = Date.now() + 1000;
 
        let miny = this.cam?.saturatedPixelCount!;
 
        while (Date.now() < settleUntil) {
 
          await sleep(50);
 
          miny = Math.min(miny, this.cam?.saturatedPixelCount!);
 
        }
 
        this.plot!.insertPoint(expo, miny);
 
      };
 

	
 
      // todo: drive around without big skips, gradually slower, looking for the max workable expo
 
      let fixedSteps = 8;
 
      const expoMin = 1;
 
      const expoMax = 200;
 
      let expo = 0;
 
      const data=this.plot!.data;
 
      while (data.length < 20) {
 
        if (data.length < fixedSteps + 1) {
 
          expo = expoMin + ((expoMax - expoMin) / fixedSteps) * data.length;
 
        } else {
 
          let x2 = data.findIndex(([_, y]) => y > 2);
 
          if (x2 < 1) x2 = 1;
 
          const x1 = x2 - 1;
 
          log(JSON.stringify([x1, data[x1], x2, data[x2]]));
 
          expo = (data[x1][0] + data[x2][0]) / 2;
 
          log(data);
 
        }
 
        await gatherSample(expo);
 
      }
 
    });
 
  }
 
  async setToZero(ev: MouseEvent) {
 
    await this.withButtonSpinner(ev, async () => {
 
      this.collector.updateSettings([
 
        [this.device, "http://light9.bigasterisk.com/color", "#000000"],
 
        [this.device, "http://light9.bigasterisk.com/white", 0],
 
      ]);
 
    });
 
  }
 
  async markTare(ev: MouseEvent) {
 
    await this.withButtonSpinner(ev, async () => {});
 
  }
 
  async calibrateLoop(ev: MouseEvent) {
 
    await this.withButtonSpinner(ev, async () => {});
 
  }
 
}
web/calibrate/Light9Camera.ts
Show inline comments
 
new file 100644
 
import debug from "debug";
 
import { css, html, LitElement, PropertyValueMap, TemplateResult } from "lit";
 
import { customElement, property, state } from "lit/decorators.js";
 

	
 
@customElement("light9-camera")
 
export class Light9Camera extends LitElement {
 
  static styles = [
 
    css`
 
      :host {
 
        display: flex;
 
      }
 
      #video {
 
        display: none;
 
      }
 
      #stack {
 
        position: relative;
 
        width: 640px;
 
        height: 480px;
 
      }
 
      #stack > * {
 
        position: absolute;
 
        left: 0;
 
        top: 0;
 
      }
 
      #stack > :first-child {
 
        position: static;
 
      }
 
      #stack > img {
 
        opacity: 0;
 
        animation: fadeIn 1s 1s ease-in-out forwards;
 
      }
 
      @keyframes fadeIn {
 
        from {
 
          opacity: 0;
 
        }
 
        to {
 
          opacity: 1;
 
        }
 
      }
 
    `,
 
  ];
 
  videoEl!: HTMLVideoElement;
 
  canvas!: HTMLCanvasElement;
 
  ctx!: CanvasRenderingContext2D;
 
  @state()
 
  vtrack: MediaStreamTrack | undefined;
 

	
 
  @property() saturatedPixelCount = 0;
 
  @property() saturatedPixelFraction = 0;
 

	
 
  @property() videoSettings: MediaTrackSettings & any = {};
 

	
 
  render() {
 
    const saturatedCountDisplay = `${this.saturatedPixelCount} (${(this.saturatedPixelFraction * 100).toFixed(2)}%)`;
 

	
 
    return html`
 
      <video id="video"></video>
 

	
 
      <div id="stack">
 
        <img src="zebra.png" />
 
        <canvas id="canvas"></canvas>
 
      </div>
 
      <div id="controls">
 
        <p>saturated pixels: ${saturatedCountDisplay}</p>
 
        <light9-camera-settings-table .cam=${this} .videoSettings=${this.videoSettings}></light9-camera-settings-table>
 
      </div>
 
    `;
 
  }
 

	
 
  protected async firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
 
    this.videoEl = this.shadowRoot!.getElementById("video") as HTMLVideoElement;
 
    this.canvas = this.shadowRoot!.getElementById("canvas") as HTMLCanvasElement;
 
    this.ctx = this.canvas.getContext("2d", { willReadFrequently: true })!;
 

	
 
    const constraints: MediaStreamConstraints = {
 
      video: {
 
        facingMode: { ideal: "environment" },
 
        frameRate: { max: 10 },
 
      },
 
    };
 
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
 
    const t = stream.getVideoTracks()[0];
 
    await t.applyConstraints({
 
      brightness: 0,
 
      contrast: 32,
 
      colorTemperature: 6600,
 
      exposureMode: "manual",
 
      exposureTime: 250,
 
      whiteBalanceMode: "manual",
 
      focusMode: "manual",
 
      focusDistance: 235,
 
    } as MediaTrackConstraints);
 

	
 
    this.vtrack = t;
 
    this.videoEl.srcObject = stream;
 
    this.videoEl.play();
 
    this.videoSettings = this.vtrack.getSettings();
 

	
 
    this.redrawLoop();
 
  }
 

	
 
  redrawLoop() {
 
    if (this.videoEl.videoWidth !== 0 && this.videoEl.videoHeight !== 0) {
 
      this.redraw();
 
    }
 
    // todo: video frames come slower than raf is waiting
 
    requestAnimationFrame(this.redrawLoop.bind(this));
 
  }
 

	
 
  public async set(k: string, v: any) {
 
    if (!this.vtrack) {
 
      throw new Error("vtrack");
 
    }
 
    await this.vtrack.applyConstraints({ [k]: v });
 
    this.videoSettings = this.vtrack.getSettings();
 
  }
 

	
 
  private redraw() {
 
    this.canvas.width = this.videoEl.videoWidth;
 
    this.canvas.height = this.videoEl.videoHeight;
 
    this.ctx.drawImage(this.videoEl, 0, 0);
 
    this.makeSaturatedPixelsTransparent();
 
  }
 

	
 
  private makeSaturatedPixelsTransparent() {
 
    const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
 
    const data = imageData.data;
 
    this.saturatedPixelCount = 0;
 
    for (let i = 0; i < data.length; i += 4) {
 
      if (data[i] === 255 || data[i + 1] === 255 || data[i + 2] === 255) {
 
        this.saturatedPixelCount += 1;
 

	
 
        data[i + 3] = 0;
 
      }
 
    }
 
    this.saturatedPixelFraction = this.saturatedPixelCount / (data.length / 4);
 
    this.ctx.putImageData(imageData, 0, 0);
 
  }
 
}
 

	
 
@customElement("light9-camera-settings-table")
 
export class Light9CameraSettingsTable extends LitElement {
 
  static styles = [
 
    css`
 
      table {
 
        border-collapse: collapse;
 
      }
 
      td {
 
        border: 1px solid gray;
 
        padding: 1px 6px;
 
      }
 
    `,
 
  ];
 

	
 
  boring = [
 
    "aspectRatio",
 
    "backgroundBlur",
 
    "channelCount",
 
    "deviceId",
 
    "displaySurface",
 
    "echoCancellation",
 
    "eyeGazeCorrection",
 
    "faceFraming",
 
    "groupId",
 
    "latency",
 
    "noiseSuppression",
 
    "pointsOfInterest",
 
    "resizeMode",
 
    "sampleRate",
 
    "sampleSize",
 
    "suppressLocalAudioPlayback",
 
    "torch",
 
    "voiceIsolation",
 
  ];
 

	
 
  adjustable: Record<string, { min: string; max: string }> = {
 
    focusDistance: { min: "0", max: "1023" },
 
    brightness: { min: "0", max: "64" },
 
    colorTemperature: { min: "2800", max: "6500" },
 
    exposureTime: { min: "0", max: "400" },
 
  };
 

	
 
  @property() cam!: Light9Camera;
 
  @property() videoSettings: MediaTrackSettings & any = {};
 
  supportedByBrowser: MediaTrackSupportedConstraints;
 
  constructor() {
 
    super();
 
    this.supportedByBrowser = navigator.mediaDevices.getSupportedConstraints();
 
  }
 
  render() {
 
    const rows: TemplateResult<1>[] = [];
 
    for (const key of Object.keys(this.supportedByBrowser)) {
 
      if (!this.boring.includes(key)) {
 
        this.renderRow(key, rows);
 
      }
 
    }
 
    return html`<table>
 
      ${rows}
 
    </table>`;
 
  }
 

	
 
  private renderRow(key: string, rows: any[]) {
 
    let valueDisplay = "";
 
    if (this.videoSettings[key] !== undefined) {
 
      valueDisplay = JSON.stringify(this.videoSettings[key]);
 
    }
 
    let adjuster = html``;
 
    let conf = this.adjustable[key];
 
    if (conf !== undefined) {
 
      adjuster = html`
 
        <input type="range" min="${conf.min}" max="${conf.max}" value="${this.videoSettings[key]}" data-param="${key}" @input=${this.setFromSlider} />
 
      `;
 
    }
 
    rows.push(
 
      html`<tr>
 
        <td>${key}</td>
 
        <td>${valueDisplay}</td>
 
        <td>${adjuster}</td>
 
      </tr>`
 
    );
 
  }
 

	
 
  async setFromSlider(ev: InputEvent) {
 
    const el = ev.target as HTMLInputElement;
 
    await this.cam.set(el.dataset.param as string, parseFloat(el.value));
 
  }
 
}
web/calibrate/XyPlot.ts
Show inline comments
 
new file 100644
 
import debug from "debug";
 
import * as echarts from "echarts";
 
import { css, html, LitElement, PropertyValueMap } from "lit";
 
import { customElement, property } from "lit/decorators.js";
 
debug.enable("*");
 
const log = debug("calibrate");
 

	
 
@customElement("xy-plot")
 
export class XyPlot extends LitElement {
 
  static styles = [
 
    css`
 
      #chart {
 
        width: 800px;
 
        height: 300px;
 
      }
 
    `,
 
  ];
 
  chart!: echarts.ECharts;
 
  @property() label: string = "";
 
  @property() data: number[][] = [];
 
 
 
  render() {
 
    return html`
 
      <fieldset>
 
        <legend>${this.label}</legend>
 
        <div id="chart"></div>
 
      </fieldset>
 
    `;
 
  }
 

	
 
  protected async firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
 
    var chartDom = this.shadowRoot!.getElementById("chart")!;
 
    this.chart = echarts.init(chartDom);
 
    this.chart.setOption({
 
      animation: false,
 
      xAxis: {
 
        type: "value",
 
      },
 
      yAxis: {
 
        type: "value",
 
      },
 
      series: [
 
        {
 
          name: "d",
 
          data: [],
 
          type: "line",
 
        },
 
      ],
 
    });
 
  }
 

	
 
  clear() {
 
    this.data.length = 0;
 
  }
 
  
 
  insertPoint(x: number, y: number) {
 
    this.data.push([x, y]);
 
    this.data.sort((a, b) => a[0] - b[0]);
 
    this.chart.setOption({ series: [{ name: "d", data: this.data }] });
 
  }
 
}
web/calibrate/index.html
Show inline comments
 
new file 100644
 
<!DOCTYPE html>
 
<html>
 
  <head>
 
    <title>calibrate</title>
 
    <meta charset="utf-8" />
 
    <link rel="stylesheet" href="../style.css" />
 
    <script type="module" src="Light9Calibrate"></script>
 
  </head>
 
  <body>
 
    <light9-calibrate></light9-calibrate>
 
  </body>
 
</html>
web/calibrate/zebra.png
Show inline comments
 
new file 100644
 
binary diff not shown
Show images
web/collector/CollectorClient.ts
Show inline comments
 
new file 100644
 
type Settings = Array<[string,string,string|number]>;
 

	
 
export class CollectorClient {
 
  private settings: Settings;
 
  constructor(public clientName:string) {
 
    this.settings = [];
 
    this.putLoop();
 
  }
 
  private async putLoop() {
 
    await this.put();
 
    setTimeout(() => {
 
      this.putLoop();
 
    }, 1000);
 
  }
 
  private async put() {
 
    await fetch("/service/collector/attrs", {
 
      method: "PUT",
 
      body: JSON.stringify({
 
        client: this.clientName,
 
        clientSession: "unused",
 
        sendTime: Date.now() / 1000,
 
        settings: this.settings,
 
      }),
 
    });
 
  }
 
  public async updateSettings(settings: Settings) {
 
    this.settings = settings;
 
    await this.put()
 
  }
 
}
web/panels.ts
Show inline comments
 
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";
 
export { Light9Calibrate } from "./calibrate/Light9Calibrate";
 

	
 
const panels: Map<string, Array<string>> = 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"]],
 
  ["light9-calibrate", ["calibrate", "calibrate"]],
 
]);
 

	
 
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] + "/";
 
  }
 

	
0 comments (0 inline, 0 general)