Skip to content

Commit

Permalink
chore: setup jsr
Browse files Browse the repository at this point in the history
  • Loading branch information
hansSchall committed Jun 6, 2024
1 parent 0464dd7 commit ffec953
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* eol=lf
36 changes: 0 additions & 36 deletions .github/workflows/deno.yml

This file was deleted.

28 changes: 28 additions & 0 deletions .github/workflows/jsr.yml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 11 additions & 4 deletions deno.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
2 changes: 1 addition & 1 deletion lib/dmxAddr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

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] {
Expand Down
2 changes: 0 additions & 2 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/// <reference lib="deno.unstable" />

/*
* LMGU-Technik sACN-Deno
Expand Down
37 changes: 28 additions & 9 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ 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

```typescript
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
Expand All @@ -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;
}
```
Expand All @@ -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
Expand All @@ -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 ...
Expand Down Expand Up @@ -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)

Expand Down
92 changes: 71 additions & 21 deletions src/receiver.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,20 +17,33 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

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<void>;
Expand All @@ -40,12 +53,18 @@ interface MulticastV4Membership {
setTTL: (ttl: number) => Promise<void>;
}

/**
* 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;
Expand Down Expand Up @@ -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<boolean> {
if (this.multicast.has(universe)) {
return false;
Expand All @@ -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<boolean> {
const membership = this.multicast.get(universe);

if (!membership) {
Expand All @@ -112,16 +135,25 @@ export class Receiver {
return true;
}

// all currently active sources
readonly sources = new Set<sACNSource>();
/**
* all currently active sources
*/
readonly sources: Set<sACNSource> = new Set<sACNSource>();

// stores last packet of sources // performance.now()
/**
* stores last packet of sources as performance.now() timestamp
*/
private readonly sourceTimeout = new WeakMap<sACNSource, number>();

// last sequence number
/**
* last sequence number for each source and universe
* Map<Source, Map<Universe, Sequence>>
*/
private readonly sequence = new WeakMap<sACNSource, Map<number, number>>();

// finds source by given cid
/**
* Source by CID
*/
getSource(cid: Uint8Array): sACNSource | null {
for (const source of this.sources) {
if (bufferEqual(source.cid, cid)) {
Expand All @@ -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<Packet, void, void> {
for await (const [chunk] of this.socket) {
const packet = parsePacket(chunk);
Expand Down Expand Up @@ -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) {
Expand All @@ -193,17 +230,24 @@ export class Receiver {
}
}

// sACNSource => (Universe => [Data, Priority])
/**
* sACNSource => (Universe => [Data, Priority])
*/
private readonly lastSourceData = new WeakMap<
sACNSource,
Map<number, [Uint8Array, number]>
>();

// Chan(global) => Value
/**
* Chan(global) => Value
*/
private readonly lastChanData = new Map<number, number>();

// Merges Channels
// AsyncIterator<[Chan(glob), Value]>
/**
* Merges Channels
* AsyncIterator<[Chan(glob), Value]>
* Only use once!!
*/
async *[Symbol.asyncIterator](): AsyncIterator<readonly [number, number]> {
for await (const packet of this.onPacket()) {
const packetSource = this.getSource(packet.cid)!;
Expand Down Expand Up @@ -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();
}
Expand Down

0 comments on commit ffec953

Please sign in to comment.