Example: DMX lighting control with LogicMachine

DMX function

Source code    
  1. local luadmx = require('luadmx')
  2. module('DMX', package.seeall)
  3.  
  4. local DMX = {}
  5.  
  6. -- default params
  7. local defaults = {
  8. -- storage key
  9. skey = 'dmx_line_1',
  10. -- RS-485 port
  11. port = '/dev/RS485',
  12. -- number of calls per second
  13. resolution = 20,
  14. -- total number of channels to use
  15. channels = 3,
  16. -- transition time in seconds, does not include DMX transfer time
  17. transition = 2,
  18. }
  19.  
  20. -- value setter
  21. function set(chan, val, key)
  22. key = key or defaults.skey
  23. chan = tonumber(chan) or 0
  24. val = tonumber(val) or -1
  25.  
  26. -- validate channel number and value
  27. if chan >= 1 and chan <= 512 and val >= 0 and val <= 255 then
  28. storage.exec('lset', key, chan - 1, val)
  29. end
  30. end
  31.  
  32. -- value getter
  33. function get(chan, key)
  34. local res, val
  35. key = key or defaults.skey
  36. chan = tonumber(chan) or 0
  37.  
  38. -- validate channel number and value
  39. if chan >= 1 and chan <= 512 then
  40. res = storage.exec('lrange', key, chan - 1, chan - 1)
  41. if type(res) == 'table' then
  42. val = tonumber(res[ 1 ])
  43. end
  44. end
  45.  
  46. return val
  47. end
  48.  
  49. -- DMX init, returns new DMX object
  50. function init(params)
  51. local n, k, v, _
  52.  
  53. -- create metatable and set user parameters
  54. n = setmetatable({}, { __index = DMX })
  55. n.params = params or {}
  56.  
  57. _, n.conn = pcall(require('redis').connect)
  58.  
  59. -- merge parameters that are set by user
  60. for k, v in pairs(defaults) do
  61. if n.params[ k ] == nil then
  62. n.params[ k ] = v
  63. end
  64. end
  65.  
  66. n:reset()
  67.  
  68. return n
  69. end
  70.  
  71. function DMX:reset()
  72. local err, chan, params
  73.  
  74. params = self.params
  75. self.dm, err = luadmx.open(params.port)
  76.  
  77. -- error while opening
  78. if err then
  79. os.sleep(1)
  80. error(err)
  81. end
  82.  
  83. -- set channel count
  84. self.dm:setcount(params.channels)
  85.  
  86. -- number of transaction ticks
  87. self.ticks = math.max(1, params.transition * params.resolution)
  88.  
  89. -- calculate sleep time
  90. self.sleep = 1 / params.resolution
  91.  
  92. -- reset channel map
  93. self.channels = {}
  94.  
  95. -- empty channel value map
  96. self.conn:ltrim(params.skey, 1, 0)
  97.  
  98. -- fill channel map
  99. for chan = 1, params.channels do
  100. self.channels[ chan ] = { current = 0, target = 0, ticks = 0 }
  101.  
  102. -- turn off by default
  103. self.conn:lpush(params.skey, 0)
  104. self.dm:setchannel(chan, 0)
  105. end
  106. end
  107.  
  108. -- get new values
  109. function DMX:getvalues()
  110. local max, channels, ticks, values, val
  111.  
  112. max = self.params.channels
  113. channels = self.channels
  114. ticks = self.ticks
  115. values = self.conn:lrange(self.params.skey, 0, max - 1) or {}
  116.  
  117. -- check for new values for each channel
  118. for chan = 1, max do
  119. val = tonumber(values[ chan ]) or 0
  120.  
  121. -- target value differs, set transcation
  122. if val ~= channels[ chan ].target then
  123. channels[ chan ].target = val
  124. channels[ chan ].delta = (channels[ chan ].target - channels[ chan ].current) / ticks
  125. channels[ chan ].ticks = ticks
  126. end
  127. end
  128. end
  129.  
  130. -- main loop handler
  131. function DMX:run()
  132. self:getvalues()
  133.  
  134. -- transition loop
  135. for i = 1, self.params.resolution do
  136. self:step()
  137. self.dm:send()
  138. os.sleep(self.sleep)
  139. end
  140. end
  141.  
  142. -- single transition step
  143. function DMX:step()
  144. local chan, channels, t
  145.  
  146. channels = self.channels
  147.  
  148. -- transition for each channel
  149. for chan = 1, self.params.channels do
  150. t = channels[ chan ].ticks
  151.  
  152. -- transition is active
  153. if t > 0 then
  154. t = t - 1
  155.  
  156. channels[ chan ].current = channels[ chan ].target - channels[ chan ].delta * t
  157. channels[ chan ].ticks = t
  158.  
  159. self.dm:setchannel(chan, channels[ chan ].current)
  160. end
  161. end
  162. end

DMX handler script

Add the following resident script with sleep interval = 0, adjust port and channel as needed

Source code    
  1. if not dmxhandler then
  2. require('user.dmx')
  3. dmxhandler = DMX.init({
  4. port = '/dev/RS485', -- RS-485 port name
  5. channels = 8, -- number of DMX channels to use
  6. transition = 2, -- soft transition time in seconds
  7. })
  8. end
  9.  
  10. dmxhandler:run()

Setter (used in other scripts)

Source code    
  1. 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.

Source code    
  1. require('user.dmx')
  2. -- get ID as group address last part (x/y/ID)
  3. id = tonumber(event.dst:split('/')[3])
  4. -- get event value (1 byte scaling)
  5. value = event.getvalue()
  6. -- convert from [0..100] to [0..255]
  7. value = math.floor(value * 2.55)
  8. -- set channel ID value
  9. 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).

Source code    
  1. if not scenes then
  2. -- 3 channel scene
  3. scenes = {
  4. { 255, 0, 0 },
  5. { 0, 255, 0 },
  6. { 0, 0, 255 },
  7. { 255, 255, 0 },
  8. { 0, 255, 255 },
  9. { 255, 0, 255 },
  10. { 255, 255, 255 },
  11. }
  12.  
  13. current = 1
  14. end
  15.  
  16. -- set current scene values
  17. scene = scenes[ current ]
  18. for i, v in ipairs(scene) do
  19. DMX.set(i, v)
  20. end
  21.  
  22. -- switch to next scene
  23. current = current + 1
  24. if current > #scenes then
  25. current = 1
  26. end

Random scene example:
The following example should be placed inside a resident script. Sleep time defines scene keep time (at least 1 second).

Source code    
  1. -- number of steps to use, e.g. 3 steps = { 0, 127, 255 }
  2. steps = 5
  3. -- number of channels to set
  4. channels = 3
  5. -- first channel number
  6. offset = 1
  7.  
  8. for i = offset, channels do
  9. v = math.random(0, (steps - 1)) * 255 / (steps - 1)
  10. DMX.set(i, math.floor(v))
  11. end