Giter Site home page Giter Site logo

artnet4j's Introduction

Hi, I'm Florian!

instagram behance

Computer Scientist / Interaction Designer / Immersive Artist
Currently working as Research Associate @ Zurich University of the Arts

artnet4j's People

Contributors

cansik avatar mrexplode avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

artnet4j's Issues

receive event

Hello, thank you for sharing this library. I'm using it with processing. I would like to get an event (callback function) when the artnet packet is received, is this possible?
thank you. Marco

Unnecessary Port Binding for ArtNet Sender

When using the artnet4j library in a Processing 4 application, the library creates a UDP port binding even when acting as an ArtNet Sender. This behavior leads to binding conflicts with other applications that may need to communicate locally, such as DigiShow LINK and QLC+, which do not bind to a specific port when sending ArtNet data.

Expected Behavior

ArtNet Sender applications should use an ephemeral port for sending data and should not bind to a specific port (e.g. 6454). Only ArtNet Receiver applications require a static bind to listen for incoming ArtNet packets.

Observed Behavior

The artnet4j library binds a port regardless of whether it is used as a Sender or Receiver. This causes conflicts when other applications attempt to bind to the same port.

Steps to Reproduce

  1. Condition 1: Sender = Processing 4, Receiver = DigiShow LINK

    • Launch DigiShow LINK before Processing 4: No port conflict.
    • Launch Processing 4 before DigiShow LINK: Port conflict occurs.
  2. Condition 2: Receiver = Processing 4, Sender = DigiShow LINK

    • Launch DigiShow LINK before Processing 4: No port conflict.
    • Launch Processing 4 before DigiShow LINK: No port conflict.
  3. Extra Tests:

    • DigiShow LINK Sender launched in isolation: No binding on port 6454.
    • Processing 4 Sender launched in isolation: Binding occurs on port 6454.

Diagnostic Results

Using lsof on macOS reveals that DigiShow LINK does not create a binding when sending ArtNet, but the Processing 4 application using artnet4j does:

  • DigiShow LINK as Sender (no binding):

    sudo lsof -i :6454

    Output: (empty)

  • Processing 4 as Sender (binding occurs):

    sudo lsof -i :6454
    COMMAND   PID        USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
    java    27353 tylerstraub   21u  IPv6 0xe877803c50f1714e      0t0  UDP *:6454

Validation of Ephemeral Port Usage in DigiShow LINK

After examining the DigiShow LINK source code, it was confirmed that DigiShow LINK uses ephemeral ports for sending ArtNet data. This approach prevents port conflicts and allows the application to coexist with other applications that need to bind as a Reciever.

Key Points from dgs_artnet_interface.cpp:

  1. UDP Port Definition:

    #define ARTNET_UDP_PORT 6454
  2. UDP Socket Initialization:

    m_udp = new QUdpSocket();
  3. Binding the UDP Socket for Input:

    if (m_interfaceInfo.mode == INTERFACE_ARTNET_INPUT) {
        // for artnet receiver
        m_dataAll.clear();
        m_udp->bind(m_udpPort);
        connect(m_udp, SIGNAL(readyRead()), this, SLOT(onUdpDataReceived()));
    }
  4. Setting Up the UDP Socket for Output:

    if (m_interfaceInfo.mode == INTERFACE_ARTNET_OUTPUT) {
        // for artnet sender
        m_sequence = 0;
        const unsigned char bytes[] = {
            0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00,  // Art-Net
            0x00, 0x20, // opcode ARTNET_POLL
            0x00, 0x0e, // version
            0x00, 0x00  // take-to-me
        };
        m_udp->writeDatagram((const char*)bytes, sizeof(bytes),
                             m_udpHost,
                             (quint16)m_udpPort);
        m_timer = new QTimer();
        connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimerFired()));
        m_timer->setTimerType(Qt::PreciseTimer);
        m_timer->setSingleShot(false);
        m_timer->setInterval(1000 / frequency);
        m_timer->start();
    }

Suggested Fix

Modify the artnet4j library to avoid static binding its port when acting solely as a Sender. Instead, it should use an ephemeral port for sending data.

Proposed Code Changes

Here is an example modification to the ArtNetServer class:

public void start(InetAddress networkAddress, boolean isReceiver) throws SocketException, ArtNetException {
    if (broadCastAddress == null) {
        setBroadcastAddress(DEFAULT_BROADCAST_IP);
    }
    if (socket == null) {
        socket = new DatagramSocket(null);
        socket.setReuseAddress(true);
        socket.setBroadcast(true);

        if (isReceiver) {
            if (networkAddress == null)
                networkAddress = socket.getLocalAddress();

            socket.bind(new InetSocketAddress(networkAddress, port));
            logger.info("Art-Net server started at: " + networkAddress.getHostAddress() + ":" + port);
        } else {
            logger.info("Art-Net server started as sender using ephemeral port.");
        }

        for (ArtNetServerListener l : listeners) {
            l.artNetServerStarted(this);
        }
        isRunning = true;
        serverThread = new Thread(this);
        serverThread.start();
    } else {
        throw new ArtNetException(
                "Couldn't create server socket, server already running?");
    }
}

In the application code, specify whether the instance is acting as a Sender or Receiver:

ArtNetServer server = new ArtNetServer();
server.start(null, false); // For sender
// or
server.start(null, true);  // For receiver

Conclusion

By implementing these changes, the artnet4j library will avoid unnecessary port bindings when acting as a Sender, preventing conflicts with other applications and allowing for more flexible execution order.

Receive ArtNet Data

Is it possible to use the library to receive ArtNet data (like a normal ArtNet node would function)?

Exceptions usually are swallowed

The usual behavior of artnet4j is to log Exceptions using Logger instances when they are thrown. This is a design flaw imo since Exceptions are a good thing for devs who use the lib because they can catch them to recover from errors or at least notify the user.

In my use case, I let the user decide on which network interface they want to send Art-Net. When it's not possible to use the chosen interface for whatever reason, I'm not even able to tell this to the user in any way (not a console app). So the UI looks like everything is working but no packets are being sent.

I'd propose to at least being able to disable logging and re-throw Exceptions using flags/environment variables or to resign from logging all together - in favor of catchable Exceptions.

Misundertandings Using the library

Hello! Been a while since you are active in this project, but i was trying to use it and would really appreaciate your help.

Basically im trying to make a java program to simulate a light and render it, controlled by Dot2 (Or any other program you reccomend). What i dont get is how to use the whole library.
I tried to make a basic setup, where i try to print all the bytes to at least see if anything changes, but i have no idea what im doing

        client = new ArtNetClient();
        client.start("0.0.0.0"); // IP Given by Dot2

        for(int i = 0; i < 100; i++) {
            byte[] data = client.readDmxData(0,0);
            System.err.println("Test: " + (data[1] & 0xFF));
            Thread.sleep(100);
        }
        client.stop();

It just prints me

dic 11, 2023 6:01:09 PM ch.bildspur.artnet.ArtNetServer start
INFORMAZIONI: Art-Net server started at: 0.0.0.0:6454

Not any of the bytes ever change even though im changing values on Dot2. Is there some kind of wiki to understand how this library works?

ArtNetServer blocks when used as sender

When using artnet4j only to send broastcast packets using ArtNetClient (as shown in the readme), it is not possible to stop the Client using ArtNetClient.stop() since the underlying ArtNetServer thread uses DatagramSocket.receive(), which according to the Javadocs blocks until a datagram is received.

Wouldn't it be a good idea to make sending possible without the need to listen at the same time? Then there wouldn't be a thread do deal with in the first place.

EDIT: Another approach would be to close the socket in ArtNetServer.stop(). DatagramSocket.receive() would then throw an IOException according to the docs which could be ignored if a corresponding flag is set in ArtNetServer.

discoverNode() should inform listeners if new node is not exactly the same as previously discovered node with same IP

In this code:

public void discoverNode(ArtPollReplyPacket reply) {
    InetAddress nodeIP = reply.getIPAddress();
    ArtNetNode node = discoveredNodes.get(nodeIP);
    if (node == null) {
        logger.info("discovered new node: " + nodeIP);
        node = reply.getNodeStyle().createNode();
        node.extractConfig(reply);
        discoveredNodes.put(nodeIP, node);
        for (ArtNetDiscoveryListener l : listeners) {
            l.discoveredNewNode(node);
        }
    } else {
        // What happens here if the node info in the reply
        // doesn't exactly match the info that we got from
        // discoveredNodes?  It seems like this should be
        // considered as a new node, and l.discoveredNewNode()
        // should be called for each listener, no?
        node.extractConfig(reply);
    }
    lastDiscovered.add(node);
}

In the if clause the code creates a new ArtNetNode, which is populated with the contents of the reply, added
to discoveredNodes, and discoveredNewNode() is called on each listener. However, in the else clause, when
a node with a matching IP address is found, the contents of the reply is copied into the existing node, overwriting
the previous contents. The potential problem that I see is that if the contents of the packet that is copied into
the ArtNetNode from the reply has changed at all from what it was previously, for example if the node was
reconfigured on the fly to change the number of ports, then the listeners do not get notified of the change to the node.

To fix this there needs to be a proper set of equals (and probably hashCode) functions for the ArtNetNode and any
objects it contains, so the code can check for any changes. The default equals function does not work in this case
from what I can see and return false when the data is otherwise the same. Once these functions are implemented
then the code can do something like this in the else clause:

        ArtNetNode newNode = reply.getNodeStyle().createNode();
        newNode.extractConfig(reply);

        if (!newNode.equals(node)) {
            node = newNode;
            discoveredNodes.put(nodeIP, node);
            for (ArtNetDiscoveryListener l : listeners) {
                l.discoveredNewNode(node);
            }
        }

Art-Net 4 node discovery is not fully supported

In the Art-Net 4 protocol release v1.4 doc, in section Art-Net overview/Art-Net 4: is this paragraph:

Art-Net can address over 30,000 universes. Previously, each group of 4 DMX ports were
limited to universes from a consecutive block of 16. Art-Net 4 allows this limit to be
resolved as the developer can choose to identify each DMX port individually. This simply
means encoding a single DMX port in each ArtPollReply. Using this mechanism, all DMX
ports can be assigned a fully independent universe.

What this means is that a device which support ArtNet 4 can send multiple ArtPollReply's for a single
node, which only contains the information for that specific port on that node. The BindIndex contains
the index of the port that the information pertains to. Currently a device which sends this type of
ArtPollReply will cause the ArtNetNode to be updated multiple times for every ArtPoll request, once
for each Reply, with only a single port's worth of information (instead of containing all the ports for
that node), and if listeners are setup it will constantly do multiple callbacks telling the listener that
the node has disconnected and a new node has connected.

I believe that what should happen is that if there is already a Node with a matching IP address, then
the node should use the information in the packet to either add a new port, based on the BinIndex, or
to update an existing port, if the data is different. If a port is added or updated then listeners should be
informed.

Lacking of Documentation

Hello,
I am not able to find out the proper way to run nodes discovery, would it be possible to add an example of it ?

Thanks in advance :)

Return ArtPollReply package

Currently no ArtPollReply package is sent back to the caller. This should be implemented and made editable to the library user.

Short and Long node names are created incorrectly

In the ArtNet protocol it states that both the long and short names are null-terminated, i.e. "C" style, strings. However,
the code that creates the names simply grabs the entire range of bytes from the packet and stuffs it into a String.

This has 2 effects:

  1. If someone calls getShortName or getLongName they get a string with random garbage at the end of it.
  2. The node discovery code relies on the garbage remaining the same to work.

#1 is a problem because if the code uses either name, like printing/logging ArtNetNode.toString(), the output will
contain the garbage.
#2 is a problem because in ArtNetNodeDiscovery the lastDiscovered member variable is a List. There is code in the
thread run function that calls lastDiscovered.contains(node), which invokes either equals() or hashCode() on two objects
to determine if the objects are the same. In either function, if the garbage in either name string has changed the return
value will be false, leading to the code not finding the entry, and making it looks like the node has disconnected, then
reconnected again, with a different name, even though everything else is unchanged.

The way to fix both problems is to strip off everything after the null character when parsing the ArtPollReplyPacket. Here
is a fix that works for me:

private String stripGarbage(String s) {
    int idx = s.indexOf(0);
    if (idx >= 0) {
        s = s.substring(0, idx);
    }
    return s;
}

@Override
public boolean parse(byte[] raw) {
    setData(raw);
    // Extraneous code removed:
    // Both the short and long names are null terminated strings.  When setting
    // the names we have to strip the null char and the garbage after it, otherwise
    // the names will contain that garbage:
    shortName = stripGarbage(new String(data.getByteChunk(null, 26, 17)));
    longName = stripGarbage(new String(data.getByteChunk(null, 44, 63)));

One possible optimization would be making lastDiscovered a ConcurrentHashMap, like discoveredNodes. This would
allow the code to call lastDiscovered.get(node.getIPAddress()) instead of contains(). Then, if a node with the correct IP
address is found, the nodes could be compared using equals().

This also begs the question about whether lastDiscovered, which is a simple List, needs to be thread-safe, or at least
synchronized. It is being manipulated by multiple threads, so I suspect the code should be using a thread-safe collection,
like a ConcurrentHashMap, anyway.

Add broadcast receive

Currently the receiving network interface have to be specified. It would be nice if also broadcast packages could be received.

Artnet Packets not picked up by Madrix

I have tried Broadcast and Unicast to both 127.0.0.1 and the IP of the network card of my machine. I am running Madrix on it but it will not receive Artnet data from the program I created using this Lib.

Things I have tried.

  1. Running The ArtNetominator I see the packets coming in and it is correct from my Program. But not picked up by Madrix
  2. Using the same config I installed QLC+ and pointed to the same universe and channel on Madrix and it picks up on Both The ArtNetominator and Madrix

Seems like the packets going to Madrix from this lib is not exactly what expected by Madrix. Since Madrix is a very expensive software I would expect that it uses a strict specification of Artnet to operate.

Any clues , Ideas would be greatly appreciated

Issues with ArtNetClient Sequence IDs

The ArtNetClient class multiple issues related to its sequenceId field.

  1. According to the ArtDmx packet specification (as seen here) the sequence field should either be:
    a) a constant 0 (to indicate that the field is not used) or
    b) an incrementing value between (and including) 1 and 255.

    Currently, the field varies between 0 and 255 (both included), which would seem to be an inappropriate combination of both options.

  2. Perhaps more importantly (but also more complicated to patch), the sequence field should be unique for each Port-Address (which apparently is defined as Net+Sub-Net+Universe).

    Right now, the field is incremented for each DMX package without regards for the receiver of the package. This leads to issues in a setup with multiple receivers and multiple universes, where a single receiver might get packages with sequence IDs such as 1, 2, 52, 53, 103, 105 and so on.

We recently experienced this issue using 5 hardware Art-Net to DMX converters. The converters would choke on the packages since they appeared to be out-of-order and the framerate would drop from the expected 30 FPS to something closer to 5 FPS.
We fixed this by patching the void unicastDmx(String address, int subnet, int universe, byte[] dmxData) method so that it uses a sequence ID which is unique for the given address (by means of a Hashmap<String, Integer>). Our setup now runs a 30 FPS.

While this solved our specific issue, after rereading the specification it seems like my solution is not compliant either so I won't provide a pull-request (and also, I haven't fixed this issue for the other, related, methods of the class).

DmxData Buffer doesn't go beyond 15 universes

Hi,

It seems that the input buffer only support universe 0 thru 15, when we try to read data over the fifteenth universe all channel are at 0.

the concerned method is readDmxData from the ArtNetClient class.

An idea of the cause of this limitation ?

Add module-info for java 9 modules

As a developer
I want to have a module definition
So that I can use artnet4j in a modular application

As this does not changes anything on the code itself, there's no need for any minor or major version update.

Additional information:
With Project Jigsaw, a modularization was introduced that makes a developer decide which parts of it's library can be used outside of the library. Utility classes so can be sealed from usage.
To use this, the libraries need a module definition explaining which packages can be used with the library and which other modules this library is using.
Having a module definition available allows to build a JRE only containing the really required classes for the application, which reduces classpath scanning and also removes unused classes from the classpath.

What needs to be done is to create a module-info.java to the src/main/java directory and define a module. There's also A Guide to Java 9 Modules.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.