Skip to main content

livebook/artnet_sample.livemd

<!-- livebook:{"file_entries":[{"name":"screenshot01.PNG","type":"attachment"}]} -->

# Art-Net Sample

```elixir
Mix.install([
  {:art_net, "~> 0.1.0"}
])
```

## Create an Art-Net Dmx Packet

Art-Net Dmx packet is created using the `ArtDmx` struct.

```elixir
dmx_packet = %ArtNet.Packet.ArtDmx{sequence: 1, physical: 0, sub_universe: 0, net: 0, length: 1, data: [255]}
```

<!-- livebook:{"output":true} -->

```
%ArtNet.Packet.ArtDmx{sequence: 1, physical: 0, sub_universe: 0, net: 0, length: 1, data: [255]}
```

## Encode the message

The `ArtNet.encode!/1` function is used to encode the `ArtDmx` struct into a binary message.
If the encoding is successful, the function returns the encoded message. Otherwise, it raises an error.

`ArtNet.encode/1` is also available, which returns `{:ok, encoded_message}` or `{:error, reason}`.

```elixir
dmx_message = ArtNet.encode!(dmx_packet)
```

<!-- livebook:{"output":true} -->

```
<<65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 1, 0, 0, 0, 0, 1, 255>>
```

The encoded message is a binary that can be sent over the network via UDP.

## Send an encoded message

To send the encoded message, you need to open a UDP port and send the message to the target IP address and port number.

To open a UDP port, use the `:gen_udp.open/2` function.
`:gen_udp` is a built-in erlang library that provides UDP socket functionality.

Send a UDP packet to the specified IP address and port number.
Art-Net uses port 6454 for communication.

```elixir
ip_address = ~c"127.0.0.1"
port_number = 6454

# Open a port
{:ok, port} = :gen_udp.open(0, [:binary, {:active, true}])
```

<!-- livebook:{"output":true} -->

```
{:ok, #Port<0.8>}
```

Use `:gen_udp.send/4` to send the encoded message.

```elixir
:gen_udp.send(port, ip_address, port_number, dmx_message)
```

<!-- livebook:{"output":true} -->

```
:ok
```

If the message is successfully sent, the function returns `:ok`.

WireShark can be used to verify that the message is sent correctly.
If the message is sent successfully, it will be displayed in WireShark as shown below.

![](files/screenshot01.PNG)

### Send an ArtPoll message

ArtPoll is used to discover Art-Net nodes(devices) on the network.
When an ArtPoll message is sent, Art-Net nodes respond with an ArtPollReply message.
The ArtPollReply message contains information about the node, such as its IP address, port number, and other details.

### Create an ArtPoll message

Create an ArtPoll message using the `ArtPoll` struct.
The `ArtPoll` struct contains `talk_to_me` field, which is a `TalkToMe` struct.

```elixir
alias ArtNet.Packet.ArtPoll
alias ArtNet.Packet.BitField.TalkToMe

talk_to_me = %TalkToMe{reply_on_change: false, diagnostics: false, diag_unicast: false, vlc: false}
poll_packet = %ArtPoll{talk_to_me: talk_to_me, priority: :dp_all}
```

<!-- livebook:{"output":true} -->

```
%ArtNet.Packet.ArtPoll{
  talk_to_me: %ArtNet.Packet.BitField.TalkToMe{
    reply_on_change: false,
    diagnostics: false,
    diag_unicast: false,
    vlc: false
  },
  priority: :dp_all
}
```

### Encode and send the ArtPoll message

Encode the ArtPoll message using the `ArtNet.encode!/1` function.
`ArtNet.encode!1` encodes the packet appropriately based on the packet type.

```elixir
poll_message = ArtNet.encode!(poll_packet)
```

<!-- livebook:{"output":true} -->

```
<<65, 114, 116, 45, 78, 101, 116, 0, 0, 32, 0, 14, 0, 0>>
```

Send the ArtPoll message using the `:gen_udp.send/4` function.

```elixir
:gen_udp.send(port, ip_address, port_number, poll_message)
```

<!-- livebook:{"output":true} -->

```
:ok
```

If the message is successfully sent, the function returns `:ok`.
And, WireShark will display the sent message as shown below.

![](files/screenshot02.PNG)

If an Art-Net node receives the ArtPoll message, it will respond with an ArtPollReply message.
The ArtPollReply message contains information about the node, such as its IP address, port number, and other details.

Below is a screenshot from WireShark showing QLC+ enabled for Art-Net input, receiving an ArtPoll message and sending an ArtPollReply.

![](files/screenshot03.PNG)

## Receive Art-Net messages

To receive Art-Net messages, you need to open a UDP port and listen for incoming messages.

Create a GenServer module to receive Art-Net messages.
The `handle_info/2` callback is used to handle incoming messages.

```elixir
defmodule ReceiveSample do
  use GenServer

  @impl true
  def init(port) do
    {:ok, socket} = :gen_udp.open(port, [:binary])
    {:ok, port} = :inet.port(socket)
    IO.puts("Listening on port: #{port}")
    {:ok, socket}
  end

  @impl true
  def handle_info(msg, state) do
    case msg do
      {:udp, _process_port, _ip_addr, _port_num, res} ->
        IO.inspect(res, label: "Binary message received")
        artnet_packet = ArtNet.decode!(res)
        IO.inspect(artnet_packet, label: "ArtNet packet received")

        state

      _ ->
        state
    end

    {:noreply, state}
  end

  def start_link(port) do
    GenServer.start_link(ReceiveSample, port)
  end
end

```

<!-- livebook:{"output":true} -->

```
{:module, ReceiveSample, <<70, 79, 82, 49, 0, 0, 21, ...>>, {:start_link, 1}}
```

Start the GenServer and prepare to receive messages.

```elixir
{:ok, receiver_pid} = ReceiveSample.start_link(port_number)
```

<!-- livebook:{"output":true} -->

```
Listening on port: 6454
```

<!-- livebook:{"output":true} -->

```
{:ok, #PID<0.206.0>}
```

GenServer is now listening messages.

Send an ArtDmx message to the GenServer listening on the port.

```elixir
:gen_udp.send(port, ip_address, port_number, dmx_message)
```

<!-- livebook:{"output":true} -->

```
:ok
```

<!-- livebook:{"output":true} -->

```
Binary message received: <<65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 1, 0, 0, 0, 0, 1, 255>>
ArtNet packet received: %ArtNet.Packet.ArtDmx{
  sequence: 1,
  physical: 0,
  sub_universe: 0,
  net: 0,
  length: 1,
  data: [255]
}
```

The GenServer receives the ArtDmx message and decodes it into an `ArtDmx` struct.

### Stop the GenServer

if you want to stop the GenServer, you can use the `GenServer.stop/1` function.

```elixir
GenServer.stop(receiver_pid)
```

<!-- livebook:{"output":true} -->

```
:ok
```