This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Art-Net DMX
#1
Hi Just asking if anyone has any information using Art-Net DMX  IP.
We are looking to use the C-Bus NAC, (Similar to LM) 
Art-Net is a protocol to control DMX lighting over IP Networks. 

Any example on how to implement would be grateful, say like send channel 1 to 255 or channel 2 to 125 on Module IP 192.168.2.43
Reply
#2
here is the Artnet integration script:

Code:
require("socket")

artnet_id = "Art-net" .. string.char(0)  --8 byte
op_codes = {
 ARTNET_POLL = 0x2000,
 ARTNET_REPLY = 0x2100,
 ARTNET_DMX = 0x5000,
 ARTNET_ADDRESS = 0x6000,
 ARTNET_INPUT = 0x7000,
 ARTNET_TODREQUEST = 0x8000,
 ARTNET_TODDATA = 0x8100,
 ARTNET_TODCONTROL = 0x8200,
 ARTNET_RDM = 0x8300,
 ARTNET_VIDEOSTEUP = 0xa010,
 ARTNET_VIDEOPALETTE = 0xa020,
 ARTNET_VIDEODATA = 0xa040,
 ARTNET_MACMASTER = 0xf000,
 ARTNET_MACSLAVE = 0xf100,
 ARTNET_FIRMWAREMASTER = 0xf200,
 ARTNET_FIRMWAREREPLY = 0xf300,
 ARTNET_IPPROG = 0xf800,
 ARTNET_IPREPLY = 0xf900,
 ARTNET_MEDIA = 0x9000,
 ARTNET_MEDIAPATCH = 0x9200,
 ARTNET_MEDIACONTROLREPLY = 0x9300
}
protocol_version = {
 high = 0,
 low = 14
}
priority_codes = {
 DpLow = 0x10,
 DpMed = 0x40,
 DpHigh = 0x80,
 DpCritical = 0xE0,
 DpVolatile = 0xF0
}


function op_code(name)
 local code = op_codes[name]
 local low = bit.band(code, 0x00FF)
 local high = bit.rshift(code, 8)
 return low, high
end

function artnet_poll(address, port, talk_to_me, priority)
 local command = {}

 local opCodeLow, opCodeHigh = op_code("ARTNET_POLL")
 table.insert(command, opCodeLow)
 table.insert(command, opCodeHigh)

 table.insert(command, protocol_version.low)
 table.insert(command, protocol_version.high)

 table.insert(command, talk_to_me)

 table.insert(command, priority_codes[priority])

 command = artnet_id .. string.char(unpack(command))


 local s = socket.udp()
 s:sendto(command, address, port)
 s:close()
end


function artnet_dmx(address, port, sequence, in_port_physical, in_port_address, data)
 local command = {}

 local opCodeLow, opCodeHigh = op_code("ARTNET_DMX")
 table.insert(command, opCodeLow)
 table.insert(command, opCodeHigh)

 table.insert(command, protocol_version.low)
 table.insert(command, protocol_version.high)

--[[
The sequence number is used to ensure that ArtDmx packets are used in the correct order.
When Art-Net is carried over a medium such as the Internet, it is possible that ArtDmx packets will reach the receiver out of order.
This field is incremented in the range 0x01 to 0xff to allow the receiving node to resequence packets.
The Sequence field is set to 0x00 to disable this feature
]]
 sequence = bit.band(sequence, 0xFF)
 table.insert(command, sequence)

--[[
The physical input port from which DMX512 data was input.
This field is for information only. Use Universe for data routing.
]]
 table.insert(command, in_port_physical)

--15 bit Port-Address (universe)
 local address_hi = bit.rshift(in_port_address, 8)
 local address_low = bit.band(in_port_address, 0x00FF)
 table.insert(command, address_hi)
 table.insert(command, address_low)

 --Length
 local l = #data --2 byte
 local LengthHi = bit.rshift(l, 8)
 local LengthLow = bit.band(l, 0x00FF)
 table.insert(command, LengthHi)
 table.insert(command, LengthLow)

 command = artnet_id .. string.char(unpack(command)) .. string.char(unpack(data))

 local s = socket.udp()
 s:sendto(command, address, port)
 s:close()
end

artnet_dmx(ip, port, 0, 0x12, 0x93, {0xA, 0x12, 0xFF})
Reply
#3
WOW Smile
Thank you, will give it go now.

Sorry for my ignorance, new to LUA, could you explain how to deploy this.
I have a group 0/56/0 to control channel 1 on DMX at 192.168.2.43
Where do I put the script, I assume it goes in the User Library and is then called from an event based script.
Where do I put the DMX Channel No, IP Address, and Port Number.

Again, Sorry if I am being completely stupid.
Reply
#4
Here's a simplified function. Change universe number as needed, sequence and physical parameters can probably be zero. Value is the output channel value ranging from 0 to 255.

Code:
function artnet_dmx(ip, sequence, physical, universe, data)
 local cmd = string.char(
   -- null byte
   0,
   -- ARTNET_DMX opcope
   0, 0x50,
   -- protocol version 14
   0, 14,
   -- sequence number is used to ensure that ArtDmx packets are used in the correct order
   bit.band(sequence, 0xFF),
   -- physical input port from which DMX512 data was input.
   bit.band(physical, 0xFF),
   -- 15 bit Port-Address (universe)
   bit.band(universe, 0xFF), bit.rshift(universe, 8),
   -- Length
   bit.rshift(#data, 8), bit.band(#data, 0xFF)
 )

 local req = "Art-Net" .. cmd .. string.char(unpack(data))

 local sck = require("socket").udp()

 sck:sendto(req, ip, 0x1936)
 sck:close()
end

ip = '192.168.2.43'
universe = 123
value = 255
artnet_dmx(ip, 0, 0, universe, { value })
Reply
#5
Hi,

Nice (: 

Is there also a handler script so we can use it as a server?

BR,

Erwin
Reply
#6
(14.05.2018, 06:28)admin Wrote: Here's a simplified function. Change universe number as needed, sequence and physical parameters can probably be zero. Value is the output channel value ranging from 0 to 255.

Code:
function artnet_dmx(ip, sequence, physical, universe, data)
 local cmd = string.char(
   -- null byte
   0,
   -- ARTNET_DMX opcope
   0, 0x50,
   -- protocol version 14
   0, 14,
   -- sequence number is used to ensure that ArtDmx packets are used in the correct order
   bit.band(sequence, 0xFF),
   -- physical input port from which DMX512 data was input.
   bit.band(physical, 0xFF),
   -- 15 bit Port-Address (universe)
   bit.band(universe, 0xFF), bit.rshift(universe, 8),
   -- Length
   bit.rshift(#data, 8), bit.band(#data, 0xFF)
 )

 local req = "Art-Net" .. cmd .. string.char(unpack(data))

 local sck = require("socket").udp()

 sck:sendto(req, ip, 0x1936)
 sck:close()
end

ip = '192.168.2.43'
universe = 123
value = 255
artnet_dmx(ip, 0, 0, universe, { value })
Many thanks for the details. Works great. What do I need to change to control each DMX 512 Channels

(14.05.2018, 10:26)sjfp Wrote:
(14.05.2018, 06:28)admin Wrote: Here's a simplified function. Change universe number as needed, sequence and physical parameters can probably be zero. Value is the output channel value ranging from 0 to 255.

Code:
function artnet_dmx(ip, sequence, physical, universe, data)
 local cmd = string.char(
   -- null byte
   0,
   -- ARTNET_DMX opcope
   0, 0x50,
   -- protocol version 14
   0, 14,
   -- sequence number is used to ensure that ArtDmx packets are used in the correct order
   bit.band(sequence, 0xFF),
   -- physical input port from which DMX512 data was input.
   bit.band(physical, 0xFF),
   -- 15 bit Port-Address (universe)
   bit.band(universe, 0xFF), bit.rshift(universe, 8),
   -- Length
   bit.rshift(#data, 8), bit.band(#data, 0xFF)
 )

 local req = "Art-Net" .. cmd .. string.char(unpack(data))

 local sck = require("socket").udp()

 sck:sendto(req, ip, 0x1936)
 sck:close()
end

ip = '192.168.2.43'
universe = 123
value = 255
artnet_dmx(ip, 0, 0, universe, { value })
Many thanks for the details. Works great. What do I need to change to control each DMX 512 Channels
Found how. {value1,value2,value3) 1= Ch1 2=Ch2 3=Ch3 etc etc.
But how can I can change channels independently without affecting other channels that may of been set
Reply
#7
You can only write full dmx-512 value array starting with the first address. So for several channels you need a script attached to all controlling groups which will write all required values.

@Erwin, it's possible to implement a simple server which only handles DMX commands. Do you have real use cases for that?
Reply
#8
Hi Admin,

We get the request for Art-Net a few times a year, often for theather and cinema projects.

In most cases there is a main DMX control in the area where they want to control the KNX lights from there main system. In theater it’s mostly the stage cleaning and blue lights and backstage area ligths and often screens for windows between public area and stage. They want to control it often from the control room’s dmx panels.

At this moment we refer to dedicated bidirectional DMX IP gateways to do this task but if we can do it by scripting i would like to run some tests to see how it performs.

PS: All new Dolby Atmos theathers in the NL are controlled by Wiser for KNX at this moment and they control quite some DMX loops for user experience in coridors (:

BR,

Erwin
Reply
#9
Here's a rudimentary server example (resident script, sleep time = 0). Only DMX command is supported, sequences are not supported, universe number is ignored.

Code:
if not server then
 mapping = { '32/1/1', '32/1/2', '32/1/3' }
 cache = {}
 header = 'Art-Net' .. string.char(0, 0, 0x50, 0, 14)

 function parse(data)
   if #data < 19 then
     return
   end

   if data:sub(1, #header) ~= header then
     return
   end

   local values = data:sub(19)
   for i = 1, #values do
     local value = values:byte(i)

     if mapping[ i ] and cache[ i ] ~= value then
       cache[ i ] = value
       grp.write(mapping[ i ], value, dt.uint8)
     end
   end
 end

 server = require('socket').udp()
 server:settimeout(3)
 server:setsockname('*', 0x1936)
end

data, err = server:receive()
if data then
 parse(data)
end
Reply
#10
(14.05.2018, 11:02)admin Wrote: You can only write full dmx-512 value array starting with the first address. So for several channels you need a script attached to all controlling groups which will write all required values.

@Erwin, it's possible to implement a simple server which only handles DMX commands. Do you have real use cases for that?

Thanks for the advise.
I have been looking on how to write that script you mentioned, I have it working in a fashion, but would appreciate to some advise on the best way to achieve logging all the groups.
I have started off by creating a table to store each groups current val, but wondering how to extract all the data that's contained in the table.

I have it working by bring back individual stored values, but cant find the correct way to bring back all stored in a format like
{200,50,0,255,127,100,255,0,0,0,255,0} etc. etc.

any help would be appreciated.
Reply
#11
If you have many groups to control you should use a resident script which monitors all bus traffic and sends update once any group changes. I'll provide an example later on.
Reply
#12
(22.05.2018, 06:00)admin Wrote: If you have many groups to control you should use a resident script which monitors all bus traffic and sends update once any group changes. I'll provide an example later on.

Thank you. Looking forward to seeing your example. Really appreciate your help.
Reply
#13
Resident script, sleep time = 0. Might require some change to work with C-BUS objects.

Code:
if not client then
  ip = '192.168.2.43'
  mapping = { '1/1/1', '1/1/2', '1/1/3', '2/1/1', '2/1/2', '2/1/3' }
  universe = 1
  physical = 0

  udpsock = require('socket').udp()

  addrtoid = {}
  values = {}

  for id, addr in ipairs(mapping) do
    obj = grp.find(addr)
    addrtoid[ addr ] = id

    if obj then
      value = obj.value or 0

      if obj.datatype == dt.scale then
        value = math.floor(value * 2.55)
      end
    else
      value = 0
    end

    values[ id ] = value
  end

  function send()
    local cmd = string.char(
      -- null byte; ARTNET_DMX opcope; protocol version 14, sequence not defined
      0, 0, 0x50, 0, 14, 0,
      -- physical input port from which DMX512 data was input
      bit.band(physical, 0xFF),
      -- 15 bit Port-Address (universe)
      bit.band(universe, 0xFF), bit.rshift(universe, 8),
      -- Length
      bit.rshift(#values, 8), bit.band(#values, 0xFF)
    )

    local req = 'Art-Net' .. cmd .. string.char(unpack(values))
    udpsock:sendto(req, ip, 0x1936)
  end

  function eventhandler(event)
    local id = addrtoid[ event.dst ]
    if id then
      values[ id ] = busdatatype.decode(event.datahex, dt.uint8, event.dstraw)
      send()
    end
  end

  client = require('localbus').new()
  client:sethandler('groupwrite', eventhandler)

  send()
end

client:step()
Reply
#14
(23.05.2018, 08:35)admin Wrote: Resident script, sleep time = 0. Might require some change to work with C-BUS objects.

Code:
if not client then
 ip = '192.168.2.43'
 mapping = { '1/1/1', '1/1/2', '1/1/3', '2/1/1', '2/1/2', '2/1/3' }
 universe = 1
 physical = 0

 udpsock = require('socket').udp()

 addrtoid = {}
 values = {}

 for id, addr in ipairs(mapping) do
   obj = grp.find(addr)
   addrtoid[ addr ] = id

   if obj then
     value = obj.value or 0

     if obj.datatype == dt.scale then
       value = math.floor(value * 2.55)
     end
   else
     value = 0
   end

   values[ id ] = value
 end

 function send()
   local cmd = string.char(
     -- null byte; ARTNET_DMX opcope; protocol version 14, sequence not defined
     0, 0, 0x50, 0, 14, 0,
     -- physical input port from which DMX512 data was input
     bit.band(physical, 0xFF),
     -- 15 bit Port-Address (universe)
     bit.band(universe, 0xFF), bit.rshift(universe, 8),
     -- Length
     bit.rshift(#values, 8), bit.band(#values, 0xFF)
   )

   local req = 'Art-Net' .. cmd .. string.char(unpack(values))
   udpsock:sendto(req, ip, 0x1936)
 end

 function eventhandler(event)
   local id = addrtoid[ event.dst ]
   if id then
     values[ id ] = busdatatype.decode(event.datahex, dt.uint8, event.dstraw)
     send()
   end
 end

 client = require('localbus').new()
 client:sethandler('groupwrite', eventhandler)

 send()
end

client:step()
Thank you for your assistance. Will have a go when i get back to the office tomorrow evening.
Reply
#15
(23.05.2018, 09:49)sjfp Wrote:
(23.05.2018, 08:35)admin Wrote: Resident script, sleep time = 0. Might require some change to work with C-BUS objects.

Code:
if not client then
 ip = '192.168.2.43'
 mapping = { '1/1/1', '1/1/2', '1/1/3', '2/1/1', '2/1/2', '2/1/3' }
 universe = 1
 physical = 0

 udpsock = require('socket').udp()

 addrtoid = {}
 values = {}

 for id, addr in ipairs(mapping) do
   obj = grp.find(addr)
   addrtoid[ addr ] = id

   if obj then
     value = obj.value or 0

     if obj.datatype == dt.scale then
       value = math.floor(value * 2.55)
     end
   else
     value = 0
   end

   values[ id ] = value
 end

 function send()
   local cmd = string.char(
     -- null byte; ARTNET_DMX opcope; protocol version 14, sequence not defined
     0, 0, 0x50, 0, 14, 0,
     -- physical input port from which DMX512 data was input
     bit.band(physical, 0xFF),
     -- 15 bit Port-Address (universe)
     bit.band(universe, 0xFF), bit.rshift(universe, 8),
     -- Length
     bit.rshift(#values, 8), bit.band(#values, 0xFF)
   )

   local req = 'Art-Net' .. cmd .. string.char(unpack(values))
   udpsock:sendto(req, ip, 0x1936)
 end

 function eventhandler(event)
   local id = addrtoid[ event.dst ]
   if id then
     values[ id ] = busdatatype.decode(event.datahex, dt.uint8, event.dstraw)
     send()
   end
 end

 client = require('localbus').new()
 client:sethandler('groupwrite', eventhandler)

 send()
end

client:step()
Thank you for your assistance. Will have a go when i get back to the office tomorrow evening.

Did you get this solution to work. I have a similar requirement. I have a client with a DMX slider controller that they want to control Cbus controlled lights with. Where did you purchase a art-net interface from?

thanks
Reply
#16
This script sends Art-Net commands but you need a different script which receives Art-Net commands. This is doable but first you need a DMX > Art-Net converter. I think something like this should work: https://dmxking.com/artnetsacn/edmx1-pro
Reply
#17
(03.02.2020, 11:41)admin Wrote: This script sends Art-Net commands but you need a different script which receives Art-Net commands. This is doable but first you need a DMX > Art-Net converter. I think something like this should work: https://dmxking.com/artnetsacn/edmx1-pro

thanks for the info I will get intouch with them, how hard is it to write an art-net receiver ?
Reply
#18
An example of Art-Net receiver. It ignores sequence and universe numbers which are not needed for general use. Edit mapping table to select which groups addresses are mapped to DMX channel numbers starting from 1. Output value for each channel is 1 byte (0..255).

Code:
if not server then
  mapping = {
    [1] = '32/1/1',
    [2] = '32/1/2',
    [3] = '32/1/3',
  }

  server = require('socket').udp()
  server:settimeout(1)
  server:setsockname('*', 0x1936)

  header = 'Art-Net' .. string.char(0, 0, 0x50)
  values = {}

  function parse(data)
    local count, value

    if data:sub(1, #header) ~= header then
      return
    end

    count = #data - 18

    for i = 1, count do
      value = data:byte(i + 18)

      if value ~= values[ i ] then
        values[ i ] = value

        if mapping[ i ] then
          grp.update(mapping[ i ], value)
        end
      end
    end
  end
end

data = server:receive()
if data then
  parse(data)
end
Reply
#19
(03.02.2020, 12:51)admin Wrote: An example of Art-Net receiver. It ignores sequence and universe numbers which are not needed for general use. Edit mapping table to select which groups addresses are mapped to DMX channel numbers starting from 1. Output value for each channel is 1 byte (0..255).

Code:
if not server then
  mapping = {
    [1] = '32/1/1',
    [2] = '32/1/2',
    [3] = '32/1/3',
  }

  server = require('socket').udp()
  server:settimeout(1)
  server:setsockname('*', 0x1936)

  header = 'Art-Net' .. string.char(0, 0, 0x50)
  values = {}

  function parse(data)
    local count, value

    if data:sub(1, #header) ~= header then
      return
    end

    count = #data - 18

    for i = 1, count do
      value = data:byte(i + 18)

      if value ~= values[ i ] then
        values[ i ] = value

        if mapping[ i ] then
          grp.update(mapping[ i ], value)
        end
      end
    end
  end
end

data = server:receive()
if data then
  parse(data)
end
Thanks for the info, I’ve tried testing it with a couple of software controller, as I’m not on site, but the wiser only shows a connection to the controller (eg when I log server it shows art-net) but the data is alway nil. 

can I put the art-net transmit on the wiser and test that way?
Reply
#20
You can run the sending script on the same device. Just set IP to 127.0.0.1
Reply


Forum Jump: