Example: DMX lighting control with LogicMachine

DMX function for firmware before 7.2016

Add the following user library in Scripting –> User libraries

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_chan_',
  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.set(key .. chan, val)
  29. end
  30. end
  31.  
  32. -- DMX init, returns new DMX object
  33. function init(params)
  34. local n, k, v
  35.  
  36. -- create metatable and set user parameters
  37. n = setmetatable({}, { __index = DMX })
  38. n.params = params or {}
  39.  
  40. -- merge parameters that are set by user
  41. for k, v in pairs(defaults) do
  42. if n.params[ k ] == nil then
  43. n.params[ k ] = v
  44. end
  45. end
  46.  
  47. n:reset()
  48.  
  49. return n
  50. end
  51.  
  52. function DMX:reset()
  53. local err, chan
  54.  
  55. self.dm, err = luadmx.open(self.params.port)
  56.  
  57. -- error while opening
  58. if err then
  59. os.sleep(1)
  60. error(err)
  61. end
  62.  
  63. -- set channel count
  64. self.dm:setcount(self.params.channels)
  65.  
  66. -- number of transaction ticks
  67. self.ticks = math.max(1, self.params.transition * self.params.resolution)
  68.  
  69. -- calculate sleep time
  70. self.sleep = 1 / self.params.resolution
  71.  
  72. -- reset channel map
  73. self.channels = {}
  74.  
  75. -- fill channel map
  76. for chan = 1, self.params.channels do
  77. self.channels[ chan ] = { current = 0, target = 0, ticks = 0 }
  78.  
  79. -- turn off by default
  80. storage.set(self.params.skey .. chan, 0)
  81. self.dm:setchannel(chan, 0)
  82. end
  83. end
  84.  
  85. -- get new values
  86. function DMX:getvalues()
  87. local chan, val
  88.  
  89. -- check for new values for each channel
  90. for chan = 1, self.params.channels do
  91. val = storage.get(self.params.skey .. chan)
  92.  
  93. -- target value differs, set transcation
  94. if val ~= self.channels[ chan ].target then
  95. self.channels[ chan ].target = val
  96. self.channels[ chan ].delta = (self.channels[ chan ].target - self.channels[ chan ].current) / self.ticks
  97. self.channels[ chan ].ticks = self.ticks
  98. end
  99. end
  100. end
  101.  
  102. -- main loop handler
  103. function DMX:run()
  104. local i, sec, usec
  105.  
  106. if not self.calibrated then
  107. sec, usec = os.microtime()
  108. end
  109.  
  110. self:getvalues()
  111.  
  112. -- transition loop
  113. for i = 1, self.params.resolution do
  114. self:step()
  115. self.dm:send()
  116.  
  117. -- wait until next step
  118. os.sleep(self.sleep)
  119. end
  120.  
  121. -- calibrate delay loop to match ~1 second
  122. if not self.calibrated then
  123. -- lower slep time by a small step
  124. if os.udifftime(sec, usec) > 1.05 then
  125. self.sleep = self.sleep - math.max(10, self.sleep / self.params.resolution)
  126. -- calibration ok
  127. else
  128. self.calibrated = true
  129. end
  130. end
  131. end
  132.  
  133. -- single transition step
  134. function DMX:step()
  135. local chan, t
  136.  
  137. -- transition for each channel
  138. for chan = 1, self.params.channels do
  139. t = self.channels[ chan ].ticks
  140.  
  141. -- transition is active
  142. if t > 0 then
  143. t = t - 1
  144.  
  145. self.channels[ chan ].current = self.channels[ chan ].target - self.channels[ chan ].delta * t
  146. self.channels[ chan ].ticks = t
  147.  
  148. self.dm:setchannel(chan, self.channels[ chan ].current)
  149. end
  150. end
  151. end

DMX function for firmware after 7.2016

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