rize-the-flag / ArtNetLib

0 stars 0 forks source link

Cool project #1

Closed oligriffiths closed 1 month ago

oligriffiths commented 3 months ago

Hi

This is nice project are you still developing it?

Would you consider breaking out the artnet packets into a separate package? I am building my own artnet relay and have been using the packets from this project, but I have to import them using:

import { DmxPacket } from "./node_modules/@rtf-dm/artnet-lib/build/core/packets/dmx-packet.js";
import { SyncPacket } from "./node_modules/@rtf-dm/artnet-lib/build/core/packets/sync-packet.js";

Which is a bit cumbersome.

Thoughts?

rize-the-flag commented 1 month ago

Hi Oli, Sorry for the late reply. Was busy with home problems... Yes, I still developing this project. But I fully concentrated on UI part on the internal company users. Let me release a new version of the artnet-lib, where I separate protocol packets into separate package. Please let me know if this issue still relevant for you. If you have any questions and suggestions please fill free to ask me. 

rize-the-flag commented 1 month ago

I've moved all packets to separate package: "@rtf-dm/artnet-packets" now you can use it as :

npm i @rtf-dm/artnet-packets;

import {DmxPacket} from "@rtf-dm/artnet-packets";
import {SyncPacket} from "@rtf-dm/artnet-packets";

const dmxPacket = new DmxPacket({
    net: 1,
    subNet: 2,
    length: 10,
    dmxData: [
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    ]
});

const buffer = dmxPacket.encode();
const payload = dmxPacket.decode(buffer)
oligriffiths commented 1 month ago

@rize-the-flag Amazing thank you. Is there a utility to pass in the incoming data packet and have it decoded to whichever packet type it corresponds to?

rize-the-flag commented 1 month ago

@oligriffiths I think you are talking about @rtf-dm/protocol package. It allows you to create any packets definition with encode/decode functionality.

1) install package

npm i @rtf-dm/protocol

2) Describe type of your packet payload:

type TestPacketPayload = {
    ID: string,
    opCode: number,
    foo: number,
    bar: number[]
}

3) create a class which is extends abstract class Packet

class TestPacket extends Packet<TestPacketPayload> {
    constructor(payload: Partial<TestPacketPayload>) {

        const schema: PacketSchemaPublic<TestPacketPayload> = [
            ['opCode', {length: 2, type: 'number', byteOrder: 'LE'}],
            ['ID', {length: 4, type: 'string'}],
            ['foo', {length: 2, type: 'number', byteOrder: 'BE'}],
            ['bar', {length: 10, type: 'array'}]
        ];

        const testPacketPayload: TestPacketPayload = {
            ID: "Test",
            opCode: 0xFA,
            foo: 20,
            bar: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            ...payload
        }

        super(testPacketPayload, schema);
    }
}

4) Describe the packet schema and initial payload

        const schema: PacketSchemaPublic<TestPacketPayload> = [
            ['opCode', {length: 2, type: 'number', byteOrder: 'LE'}],
            ['ID', {length: 4, type: 'string'}],
            ['foo', {length: 2, type: 'number', byteOrder: 'BE'}],
            ['bar', {length: 10, type: 'array'}]
        ];

        const testPacketPayload: TestPacketPayload = {
            ID: "Test",
            opCode: 0xFA,
            foo: 20,
            bar: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
            ...payload
        }

5) Use Your packet

const packet = new TestPacket({
    foo: 10,
    bar: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
});

const buf = packet.encode();
const payload = packet.decode(buf);

console.log(payload.ID);
console.log(payload.opCode);
console.log(payload.bar);
console.log(payload.foo);
oligriffiths commented 1 month ago

Ah perhaps I wasn't clear.

I am referring to incoming UDP packets and decoding them via a single function that returns the relevant class instance.

rize-the-flag commented 1 month ago

This function called decode, it construct payload object by schema. However construction a relevant class is little bit more complex problem.

Reasons:

  1. Different class implementation for the same payload type. for example you may have more than one class to handle TestPayload (e.g. TestPacket1 and TestPacket2) with their own logic.
  2. Some packets have fixed bytes count and other packet may have variable bytes count. e.g. DMX data packets field "dmxData" array have variable length and it's mean that two DMX data packets will have different schema to decode it. So the only concrete instance know how to decode it's payload. In other words factory function must know the schema of entire packet but concrete packet class instance owns the schema...

You can use

export function decode<TPayload extends PacketPayload>(
    buffer: Buffer,
    schema: Schema<TPayload>,
): TPayload 

One of possible solution is to add some kind of static factory method e.g. make to each packet. Check if UDP packet has valid ID/opCode and than just create new packet.


type TestPayload = {
    ID: string,
    opCode: number
}

class TestPacket extends Packet<TestPayload> {

    public static schemaDefault: PacketSchemaPublic<TestPayload> = [
        ['ID', {length: 4, type: "string", byteOrder: 'LE'}],
        ['opCode', {length: 2, type: "number", byteOrder: 'BE'}]
    ]

    constructor(payload: TestPayload) {
        super(payload, TestPacket.schemaDefault);
    }

    static isTestPacket(data: Buffer) {
        return data.subarray(0, 4).toString() === 'TEST'
    }

    static make(data: Buffer, schema: PacketSchemaPublic<TestPayload> = TestPacket.schemaDefault) {
        return new TestPacket(decode(data, new Schema<TestPayload>(schema)));
    }
}

const packet1 = new TestPacket({
    ID: 'TEST',
    opCode: 42
});

const data = packet1.encode(); // recieved UDP packet

if (TestPacket.isTestPacket(data)) { // check if entire data is TestPacket
    const packet = TestPacket.make(data) // build test packet using default packet schema
}

I want to say that instantiatiation of a concrete packet depends on many factors (protocol rules, project needs e.t.c.) and it's not obvious for me how to develop a good generic interface for such functionality. This should be done on client side of library. However i open for suggestions =)