DMX lighting control with LM
Example: DMX lighting control with LogicMachine
DMX function
- 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',
- -- 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 as needed
- if not dmxhandler then
- require('user.dmx')
- dmxhandler = DMX.init({
- port = '/dev/RS485', -- RS-485 port name
- channels = 8, -- number of DMX channels to use
- transition = 2, -- soft transition time in seconds
- })
- end
-
- dmxhandler:run()
Setter (used in other scripts)
- DMX.set(channel, value)
Mark DMX objects
Create objects with DMX tag, where last part of group address is DMX address (starting from 1). Create event script mapped to DMX tag.
- 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).
- if not scenes then
- -- 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).
- -- 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