From ffec953fc2e2fcb0137dafc31c9753ae3842d7d6 Mon Sep 17 00:00:00 2001 From: Hans Schallmoser <99032404+hansSchall@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:52:01 +0200 Subject: [PATCH] chore: setup jsr --- .gitattributes | 1 + .github/workflows/deno.yml | 36 --------------- .github/workflows/jsr.yml | 28 ++++++++++++ deno.json | 15 +++++-- lib/dmxAddr.ts | 2 +- mod.ts | 2 - readme.md | 37 +++++++++++---- src/receiver.ts | 92 +++++++++++++++++++++++++++++--------- 8 files changed, 140 insertions(+), 73 deletions(-) create mode 100644 .gitattributes delete mode 100644 .github/workflows/deno.yml create mode 100644 .github/workflows/jsr.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fae8897 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* eol=lf diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml deleted file mode 100644 index cc17f1a..0000000 --- a/.github/workflows/deno.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Deno - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Setup repo - uses: actions/checkout@v3 - - - name: Setup Deno - # uses: denoland/setup-deno@v1 - uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31 # v1.1.2 - with: - deno-version: v1.x - - - name: Verify formatting - run: deno fmt --check - - - name: Run linter - run: deno lint - - - name: Run TSC - run: deno check mod.ts - - - name: Run tests - run: deno test --allow-net --unstable diff --git a/.github/workflows/jsr.yml b/.github/workflows/jsr.yml new file mode 100644 index 0000000..7dae303 --- /dev/null +++ b/.github/workflows/jsr.yml @@ -0,0 +1,28 @@ +name: JSR Publish +on: + push: + branches: main + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write + + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Install Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + + - name: Test + run: deno task check-ci + + - name: Publish package to JSR + run: deno publish diff --git a/deno.json b/deno.json index 6791450..d2ca0c3 100644 --- a/deno.json +++ b/deno.json @@ -1,8 +1,15 @@ { - "tasks": { - "test": "deno fmt && deno lint && deno check mod.ts && deno test --allow-net --unstable" - }, + "name": "@deno-plc/sacn", + "version": "1.1.0", + "exports": "./mod.ts", "fmt": { "indentWidth": 4 - } + }, + "tasks": { + "check": "deno fmt && deno lint && deno check mod.ts && deno publish --dry-run --allow-dirty && deno test --unstable-net --allow-net", + "check-ci": "deno fmt --check && deno lint && deno check mod.ts && deno test --unstable-net --allow-net" + }, + "unstable": [ + "net" + ] } diff --git a/lib/dmxAddr.ts b/lib/dmxAddr.ts index 15fd643..37620c1 100644 --- a/lib/dmxAddr.ts +++ b/lib/dmxAddr.ts @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -export function dmxToGlobal(universe: number, addr: number) { +export function dmxToGlobal(universe: number, addr: number): number { return (universe - 1) * 512 + addr; } export function globalToDmx(global: number): [universe: number, addr: number] { diff --git a/mod.ts b/mod.ts index 1aa133d..ca9eb1d 100644 --- a/mod.ts +++ b/mod.ts @@ -1,5 +1,3 @@ -/// - /* * LMGU-Technik sACN-Deno diff --git a/readme.md b/readme.md index 88c90f2..9b44149 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ consoles (e.g. [ETC Eos](https://www.etcconnect.com/), This is a Deno.js port of https://github.com/k-yle/sACN, but it is NOT API compatible (especially events). Most parts are rewritten from ground up using modern APIs (ArrayBuffer, TypedArray, AsyncIterator, ...). Unlike k-yle/sACN -this one natively supports merging channel priorities. +this one natively supports merging multiple senders (HTP with Priorities). ## Install @@ -16,6 +16,15 @@ this one natively supports merging channel priorities. import {...} from "https://deno.land/x/sacn/mod.ts" ``` +or + +[@deno-plc/sacn ![JSR](https://jsr.io/badges/@deno-plc/sacn)](https://jsr.io/@deno-plc/sacn) + +## Deno CLI flags + +- `--unstable-net`, because UDP support in Deno is still unstable. +- `--allow-net`, this is a networking library ;-) + ## Receiver API ```typescript @@ -37,15 +46,20 @@ for await (const [chan, value] of receiver) { ### `new Receiver(options: ReceiverOptions)` ```typescript -interface ReceiverOptions { - // nearly every implementation uses this port - // defaults to 5568 +export interface ReceiverOptions { + /** + * @default 5568 // nearly every implementation uses this port + */ readonly port: number; - // network interface to listen on - // defaults to all (0.0.0.0) + /** + * network interface to listen on, + * @default "0.0.0.0" // listen to all interfaces + */ readonly iface: string; - // drop all non-zero start code packets - // defaults to true + /** + * drop all non-zero start code packets + * @default true + */ readonly dmxAOnly: boolean; } ``` @@ -64,6 +78,8 @@ one. Used to obtain value changes +Note: use only once + ```typescript for await (const [chan, value] of receiver) { // chan is a global address, this helper function can be used to split into universe and address @@ -76,6 +92,9 @@ for await (const [chan, value] of receiver) { Advanced: Used to obtain bare packets +Note: use only once and not in conjunction with +`Receiver.[Symbol.asyncIterator]()` + ```typescript for await (const packet of receiver.onPacket()) { // do stuff ... @@ -124,7 +143,7 @@ the Entertainment Services and Technology Association (ESTA). ## License -(c) 2023 Hans Schallmoser +(c) 2023 - 2024 Hans Schallmoser Licensed under the terms of the GNU General public license (see LICENSE file) diff --git a/src/receiver.ts b/src/receiver.ts index 317b0ec..18326f5 100644 --- a/src/receiver.ts +++ b/src/receiver.ts @@ -1,7 +1,7 @@ /* * LMGU-Technik sACN-Deno -* Copyright (C) 2023 Hans Schallmoser +* Copyright (C) 2023 - 2024 Hans Schallmoser * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,20 +17,33 @@ * along with this program. If not, see . */ -import { Packet, parsePacket } from "./packet.ts"; +import { type Packet, parsePacket } from "./packet.ts"; import { multicastGroup } from "../lib/util.ts"; import { bufferEqual } from "../lib/util.ts"; +/** + * Configure the receiver + */ export interface ReceiverOptions { - // defaults to 5568 + /** + * @default 5568 // nearly every implementation uses this port + */ readonly port: number; - // network interface to listen on // defaults to all (0.0.0.0) + /** + * network interface to listen on, + * @default "0.0.0.0" // listen to all interfaces + */ readonly iface: string; - // drop all non-zero start code packets // defaults to true + /** + * drop all non-zero start code packets + * @default true + */ readonly dmxAOnly: boolean; } -// from Deno/std // unexported +/** + * from Deno/std, unexported + */ interface MulticastV4Membership { /** Leaves the multicast group. */ leave: () => Promise; @@ -40,12 +53,18 @@ interface MulticastV4Membership { setTTL: (ttl: number) => Promise; } +/** + * Represents one sACN source (e.g. a light console or control software) + */ export interface sACNSource { readonly cid: Uint8Array; readonly label: string; priority: number; } +/** + * sACN Receiver + */ export class Receiver { readonly socket: Deno.DatagramConn; readonly options: ReceiverOptions; @@ -78,7 +97,9 @@ export class Receiver { MulticastV4Membership | null >(); - // returns true if successful, false if already listening to universe + /** + * returns true if successful, false if already listening to universe + */ public async addUniverse(universe: number): Promise { if (this.multicast.has(universe)) { return false; @@ -96,8 +117,10 @@ export class Receiver { return true; } - // returns true if successful, false if not listening to universe - public async removeUniverse(universe: number) { + /** + * returns true if successful, false if not listening to universe + */ + public async removeUniverse(universe: number): Promise { const membership = this.multicast.get(universe); if (!membership) { @@ -112,16 +135,25 @@ export class Receiver { return true; } - // all currently active sources - readonly sources = new Set(); + /** + * all currently active sources + */ + readonly sources: Set = new Set(); - // stores last packet of sources // performance.now() + /** + * stores last packet of sources as performance.now() timestamp + */ private readonly sourceTimeout = new WeakMap(); - // last sequence number + /** + * last sequence number for each source and universe + * Map> + */ private readonly sequence = new WeakMap>(); - // finds source by given cid + /** + * Source by CID + */ getSource(cid: Uint8Array): sACNSource | null { for (const source of this.sources) { if (bufferEqual(source.cid, cid)) { @@ -131,7 +163,10 @@ export class Receiver { return null; } - // get bare packets // checks sequence + /** + * get bare packets, checks sequence + * USE ONLY ONCE AND NOT IN CONJUNCTION WITH `Receiver.[Symbol.asyncIterator]()` + */ async *onPacket(): AsyncGenerator { for await (const [chunk] of this.socket) { const packet = parsePacket(chunk); @@ -181,8 +216,10 @@ export class Receiver { } } - // removes all sources whose last packet was sent more than 5s ago - // called every 5s // interval setup in constructor + /** + * removes all sources whose last packet was sent more than 5s ago, + * called every 5s (interval setup in constructor) + */ private cleanupSources() { const clearPoint = performance.now() - 5000; for (const source of this.sources) { @@ -193,17 +230,24 @@ export class Receiver { } } - // sACNSource => (Universe => [Data, Priority]) + /** + * sACNSource => (Universe => [Data, Priority]) + */ private readonly lastSourceData = new WeakMap< sACNSource, Map >(); - // Chan(global) => Value + /** + * Chan(global) => Value + */ private readonly lastChanData = new Map(); - // Merges Channels - // AsyncIterator<[Chan(glob), Value]> + /** + * Merges Channels + * AsyncIterator<[Chan(glob), Value]> + * Only use once!! + */ async *[Symbol.asyncIterator](): AsyncIterator { for await (const packet of this.onPacket()) { const packetSource = this.getSource(packet.cid)!; @@ -271,11 +315,17 @@ export class Receiver { } } + /** + * cleanup + */ dispose() { clearInterval(this.cleanupInterval); this.socket.close(); } + /** + * alias to .dispose(), needed for `using` keyword + */ [Symbol.dispose]() { this.dispose(); }