DMX lighting control with LogicMachine #
DMX user library #
Go to Scripting > User libraries and create a library named dmx
.
Copy/paste the following code into the library.
local luadmx = require('luadmx')
module('DMX', package.seeall)
local DMX = {}
-- default params
local defaults = {
-- storage key
skey = 'dmx_line_1',
-- RS-485 port
port = '/dev/RS485-1',
-- number of calls per second
resolution = 20,
-- total number of channels to use
channels = 3,
-- transition time in seconds, does not include DMX transfer time
transition = 2,
}
-- value setter
function set(chan, val, key)
key = key or defaults.skey
chan = tonumber(chan) or 0
val = tonumber(val) or -1
-- validate channel number and value
if chan >= 1 and chan <= 512 and val >= 0 and val <= 255 then
storage.exec('lset', key, chan - 1, val)
end
end
-- value getter
function get(chan, key)
local res, val
key = key or defaults.skey
chan = tonumber(chan) or 0
-- validate channel number and value
if chan >= 1 and chan <= 512 then
res = storage.exec('lrange', key, chan - 1, chan - 1)
if type(res) == 'table' then
val = tonumber(res[ 1 ])
end
end
return val
end
-- DMX init, returns new DMX object
function init(params)
local n, k, v, _
-- create metatable and set user parameters
n = setmetatable({}, { __index = DMX })
n.params = params or {}
_, n.conn = pcall(require('redis').connect)
-- merge parameters that are set by user
for k, v in pairs(defaults) do
if n.params[ k ] == nil then
n.params[ k ] = v
end
end
n:reset()
return n
end
function DMX:reset()
local err, chan, params
params = self.params
self.dm, err = luadmx.open(params.port)
-- error while opening
if err then
os.sleep(1)
error(err)
end
-- set channel count
self.dm:setcount(params.channels)
-- number of transaction ticks
self.ticks = math.max(1, params.transition * params.resolution)
-- calculate sleep time
self.sleep = 1 / params.resolution
-- reset channel map
self.channels = {}
-- empty channel value map
self.conn:ltrim(params.skey, 1, 0)
-- fill channel map
for chan = 1, params.channels do
self.channels[ chan ] = { current = 0, target = 0, ticks = 0 }
-- turn off by default
self.conn:lpush(params.skey, 0)
self.dm:setchannel(chan, 0)
end
end
-- get new values
function DMX:getvalues()
local max, channels, ticks, values, val
max = self.params.channels
channels = self.channels
ticks = self.ticks
values = self.conn:lrange(self.params.skey, 0, max - 1) or {}
-- check for new values for each channel
for chan = 1, max do
val = tonumber(values[ chan ]) or 0
-- target value differs, set transcation
if val ~= channels[ chan ].target then
channels[ chan ].target = val
channels[ chan ].delta = (channels[ chan ].target - channels[ chan ].current) / ticks
channels[ chan ].ticks = ticks
end
end
end
-- main loop handler
function DMX:run()
self:getvalues()
-- transition loop
for i = 1, self.params.resolution do
self:step()
self.dm:send()
os.sleep(self.sleep)
end
end
-- single transition step
function DMX:step()
local chan, channels, t
channels = self.channels
-- transition for each channel
for chan = 1, self.params.channels do
t = channels[ chan ].ticks
-- transition is active
if t > 0 then
t = t - 1
channels[ chan ].current = channels[ chan ].target - channels[ chan ].delta * t
channels[ chan ].ticks = t
self.dm:setchannel(chan, channels[ chan ].current)
end
end
end
DMX handler script #
Add the following resident script with sleep interval = 0, adjust port and channel count as needed.
if not dmxhandler then
require('user.dmx')
dmxhandler = DMX.init({
port = '/dev/RS485-1', -- RS-485 port name
channels = 8, -- number of DMX channels to use
transition = 2, -- soft transition time in seconds
})
end
dmxhandler:run()
Single object example #
The following event script example sets DMX channel 1 to a supplied 1 byte scaling value (0..100).
channel = 1
value = event.getvalue()
value = math.round(value * 2.55)
require('user.dmx')
DMX.set(channel, value)
Multiple object example #
Create objects with a DMX
tag, where the last part of the
group address is the DMX address (starting from 1).
Create event script mapped to DMX
tag. All objects must have
1 byte scaling data type set.
require('user.dmx')
-- get ID as group address last part (x/y/ID)
id = tonumber(event.dst:split('/')[3])
-- get event value (1 byte scaling)
value = event.getvalue()
-- convert from [0..100] to [0..255]
value = math.round(value * 2.55)
-- set channel ID value
DMX.set(id, value)
Predefined scene example #
The following example should be placed inside a resident script. Sleep time defines scene keep time (at least 1 second).
scenes
table contains values for 3 channels in 0..255 range.
if not scenes then
require('user.dmx')
-- 3 channel scene
scenes = {
{ 255, 0, 0 },
{ 0, 255, 0 },
{ 0, 0, 255 },
{ 255, 255, 0 },
{ 0, 255, 255 },
{ 255, 0, 255 },
{ 255, 255, 255 },
}
current = 1
end
-- set current scene values
scene = scenes[ current ]
for i, v in ipairs(scene) do
DMX.set(i, v)
end
-- switch to next scene
current = current + 1
if current > #scenes then
current = 1
end
Random scene example #
The following example should be placed inside a resident script. Sleep time defines scene keep time (at least 1 second).
require('user.dmx')
-- number of steps to use, e.g. 3 steps = { 0, 127, 255 }
steps = 5
-- number of channels to set
channels = 3
-- first channel number
offset = 1
for i = offset, channels do
v = math.random(0, (steps - 1)) * 255 / (steps - 1)
DMX.set(i, math.floor(v))
end