Example: Control Philips Hue lamps with LogicMachine

Task

How to control Philips Hue lamps from KNX bus?
Below see examples how to control HUE lamps from LogicMachine making it Bacnet to HUE, Modbus to HUE, KNX to HUE, EnOcean to HUE and other protocol to HUE gateway.
There are two version of integration scripts – V1 for old type HUEs without integration into HomeKit, V2 – for new HUE with changed API for HomeKit support.

V1 HUE scripts

Event based script for On/Off a lamp or send predefined HUE value

Add Event-based script for the KNX group address which you want to control the Philips Hue from. In this example it is 12/0/4

Source code    
  1. require('socket.http')
  2. body_on = '{"on":true,"sat":255,"bri":255,"hue":6144}'
  3. body_off = '{"on":false}'
  4. response = {}
  5.  
  6. start = grp.getvalue('12/4/0')
  7.  
  8. -- Turn Philips hue lamp 4 and 5 on
  9. if start==true
  10. then
  11.  
  12. socket.http.request({
  13. url = "http://192.168.178.21/api/richardmeiland/lights/4/state",
  14. method = 'PUT',
  15. sink = ltn12.sink.table(response),
  16. headers = {
  17. ['content-length'] = #body_on,
  18. ['content-type'] = 'application/json',
  19. },
  20. source = ltn12.source.string(body_on),
  21. })
  22. log(response)
  23.  
  24. socket.http.request({
  25. url = "http://192.168.178.21/api/richardmeiland/lights/5/state",
  26. method = 'PUT',
  27. sink = ltn12.sink.table(response),
  28. headers = {
  29. ['content-length'] = #body_on,
  30. ['content-type'] = 'application/json',
  31. },
  32. source = ltn12.source.string(body_on),
  33. })
  34. log(response)
  35. end
  36.  
  37. -- Turn Philips hue lamp 4 and 5 off
  38.  
  39. if start==false
  40. then
  41.  
  42. socket.http.request({
  43. url = "http://192.168.178.21/api/richardmeiland/lights/4/state",
  44. method = 'PUT',
  45. sink = ltn12.sink.table(response),
  46. headers = {
  47. ['content-length'] = #body_off,
  48. ['content-type'] = 'application/json',
  49. },
  50. source = ltn12.source.string(body_off),
  51. })
  52. log(response)
  53.  
  54. socket.http.request({
  55. url = "http://192.168.178.21/api/richardmeiland/lights/5/state",
  56. method = 'PUT',
  57. sink = ltn12.sink.table(response),
  58. headers = {
  59. ['content-length'] = #body_off,
  60. ['content-type'] = 'application/json',
  61. },
  62. source = ltn12.source.string(body_off),
  63. })
  64. log(response)
  65. end

Created by Richard Meiland

HUE control with RGB wheel

In case of more advanced control scenarios, like control from RGB wheel, you can use the following script. The script calculates the HUE values on the fly from RGB color wheel and in such way you have dynamic HUE control instead of predefined values (like in the example above, where we use bit objects). This and the following examples requires user.rgb user library to be present.

user.rgb library:

Source code    
  1. function value_to_rgb (value)
  2. value = lmcore.inttohex(value, 3)
  3. redandgreen = string.sub(value, 1, 4)
  4. red = string.sub(redandgreen, 1, 2)
  5. green = string.sub(redandgreen, -2)
  6. blue = string.sub(value, -2)
  7. valuered = lmcore.hextoint (red)
  8. valuegreen = lmcore.hextoint (green)
  9. valueblue = lmcore.hextoint (blue)
  10. low = math.min(unpack({valuered, valuegreen, valueblue}))
  11. high = math.max(unpack({valuered, valuegreen, valueblue}))
  12. saturation = math.floor((100 * ((high - low) / high)) + 0.5)
  13. if valuered == 0 and valuegreen == 0 and valueblue == 0 then
  14. white = 0
  15. else
  16. white = math.floor(((255 - saturation) / 255 * (valuered + valuegreen + valueblue) / 3)+ 0.5)
  17. end
  18. return valuered,valuegreen,valueblue,white
  19. end
  20.  
  21. function rgb_to_value (r, g, b)
  22. red = lmcore.inttohex(r, 1)
  23. green = lmcore.inttohex(g, 1)
  24. blue = lmcore.inttohex(b, 1)
  25. rgb = red .. green .. blue
  26. value = lmcore.hextoint(rgb,3)
  27. return value
  28. end
  29.  
  30. function hsb_to_rgb(h, s, b)
  31. local hi = math.floor( h / 60 ) % 6
  32. local f = h/60 - math.floor(h/60)
  33. local p = b*(1-s)
  34. local q = b*(1-f*s)
  35. local t = b*(1-(1-f)*s)
  36. if hi == 0 then
  37. return b, t, p
  38. elseif hi == 1 then
  39. return q, b, p
  40. elseif hi == 2 then
  41. return p, b, t
  42. elseif hi == 3 then
  43. return p, q, b
  44. elseif hi == 4 then
  45. return t, p, b
  46. else
  47. return b, p, q
  48. end
  49. end
  50.  
  51. function rgb_to_hsb(r, g, b)
  52. local max, min = math.max(r, g, b), math.min(r, g, b)
  53. local h, s, v
  54. if max == min then
  55. h = 0
  56. elseif max == r and g >= b then
  57. h = 60 * (g-b)/(max-min)
  58. elseif max == r and g < b then
  59. h = 60 * (g-b)/(max-min) + 360
  60. elseif max == g then
  61. h = 60 * (b-r)/(max-min) + 120
  62. else
  63. h = 60 * (r-g)/(max-min) + 240
  64. end
  65. if max == 0 then
  66. s = 0
  67. else
  68. s = 1 - min/max
  69. end
  70. v = max
  71. return h, s, v
  72. end

Event-based script:

Source code    
  1. require('user.rgb')
  2.  
  3. value = event.getvalue()
  4. Red, Green, Blue = value_to_rgb(value)
  5.  
  6. H,S,B = rgb_to_hsb(Red,Green,Blue)
  7.  
  8. Value_Hue = math.floor(H + 0.5)
  9. Value_Saturation = math.floor((S * 100) + 0.5)
  10. Value_Brightness = math.floor(((B / 256) * 100) + 0.5)
  11.  
  12. -- log ("Hue = " .. Value_Hue .. "°")
  13. -- log ("Sat = " .. Value_Saturation .. "%")
  14. -- log ("Bri = " .. Value_Brightness .. "%")
  15.  
  16. Phillips_Hue = math.floor((Value_Hue * 182.044) + 0.5) -- Convert degrees to 2 byte value
  17. Phillips_Saturation = math.floor((Value_Saturation * 2.55) + 0.5) -- Convert % to 1 byte value
  18. Phillips_Brightness = math.floor((Value_Brightness * 2.55) + 0.5) -- Convert % to 1 byte value
  19.  
  20. if Phillips_Brightness > 0 or Phillips_Saturation > 0 or Phillips_Hue > 0 then
  21. body_value = '{"on":true,"sat":' .. Phillips_Saturation .. ',"bri":' .. Phillips_Brightness .. ',"hue":' .. Phillips_Hue .. '}'
  22. log(body_value)
  23. else
  24. body_value = '{"on":false}'
  25. log(body_value)
  26. end
  27.  
  28. require('socket.http')
  29. response = {}
  30.  
  31. socket.http.request({
  32. url = "http://192.168.1.1/api/username/lights/1/state",
  33. method = 'PUT',
  34. sink = ltn12.sink.table(response),
  35. headers = {
  36. ['content-length'] = #body_value,
  37. ['content-type'] = 'application/json',
  38. },
  39. source = ltn12.source.string(body_value),
  40. })
  41. --log(response)

Dim RGB over the brightness value and keep same (color scope) during dimming

This example requires user.rgb user library to be present (see above).

Source code    
  1. require('user.rgb')
  2. valuergb = grp.getvalue('1/1/13') -- RGB Colorwheel object
  3. Red, Green, Blue = value_to_rgb(valuergb)
  4. H,S,B = rgb_to_hsb(Red,Green,Blue)
  5.  
  6. Value_Hue = math.floor(H + 0.5)
  7. Value_Saturation = math.floor((S * 100) + 0.5)
  8. Value_Brightness = math.floor(((B / 256) * 100) + 0.5)
  9.  
  10. B = math.floor((event.getvalue() * 2.55 ) + 0.5) -- Slider to set new RGB brightness (byte object 05.001 (0-100%))
  11. Red, Green, Blue = hsb_to_rgb(H, S, B)
  12. value = rgb_to_value (Red, Green, Blue)
  13. grp.update('1/1/13', value) -- RGB Colorwheel object

 

V2 HUE scripts with adjusted API for HomeKit

Add HUE user library

Create a user lib called hue (disable automatic load checkbox). In the script change only the IP to your HUE bridge IP and save.

See below scripts how to search for HUE bridges in your network, create user, find new lights, get all light information, control HUE from bit/byte/RGB objects.

Source code    
  1. require('json')
  2. require('socket.http')
  3. require("ltn12")
  4.  
  5. --************************************************
  6. ip_add = 'xxx.xxx.xxx.xxx' -- result from bridgeSearch()
  7. user = 'xxxxxxxxxxxxx' -- result from bridgeSetup()
  8. --************************************************
  9.  
  10. function bridgeSearch()
  11. require("socket")
  12. require('socket.http')
  13. local udp = socket.udp()
  14. if udp == nil then
  15. return
  16. else
  17. local result, message
  18. local datagram = "M-SEARCH * HTTP/1.1\r\n"
  19. .. "HOST: 239.255.255.250:1900\r\n"
  20. .. "ST: MAN: ssdp:discover MX: 10 ST: ssdp:all\r\n"
  21. .. "\r\n"
  22. result, message = udp:sendto(datagram, "239.255.255.250", 1900)
  23. datagram, message = udp:receivefrom()
  24. ip = datagram:match("http://(.-):80")
  25. end
  26. udp:close()
  27. return ip
  28. end
  29.  
  30. function bridgeSetup()
  31. local response = {}
  32. body_bridgeSetup= '{"devicetype":"homelynk#homelynk"}'
  33. -- Set username for homeLYnk
  34. socket.http.request({
  35. url = "http://"..ip_add.."/api",
  36. method = 'POST',
  37. sink = ltn12.sink.table(response),
  38. headers = {
  39. ['content-length'] = #body_bridgeSetup,
  40. ['content-type'] = 'application/json',
  41. },
  42. source = ltn12.source.string(body_bridgeSetup),
  43. })
  44. return response
  45. end
  46.  
  47. function checkUser()
  48. local response = {}
  49. body_bridgeSetup= '{}'
  50. -- Set username for homeLYnk
  51. socket.http.request({
  52. url = "http://"..ip_add.."/api/"..user,
  53. method = 'GET',
  54. sink = ltn12.sink.table(response),
  55. headers = {
  56. ['content-length'] = #body_bridgeSetup,
  57. ['content-type'] = 'application/json',
  58. },
  59. source = ltn12.source.string(body_bridgeSetup),
  60. })
  61. ret = table.concat(response)
  62. data = json.pdecode(ret)
  63. if (data[1].error.type)==1 then
  64. return false
  65. else
  66. return true
  67. end
  68. end
  69.  
  70. function searchHueLights()
  71. local response = {}
  72. body_searchHueLights= ''
  73. -- Set username for homeLYnk
  74. socket.http.request({
  75. url = "http://"..ip_add.."/api/"..user.."/lights",
  76. method = 'POST',
  77. sink = ltn12.sink.table(response),
  78. headers = {
  79. ['content-length'] = #body_searchHueLights,
  80. ['content-type'] = 'application/json',
  81. },
  82. source = ltn12.source.string(body_searchHueLights),
  83. })
  84. return response
  85. end
  86.  
  87. function getHueLights()
  88. local response = {}
  89. body_searchHueLights= ''
  90. -- Set username for homeLYnk
  91. socket.http.request({
  92. url = "http://"..ip_add.."/api/"..user.."/lights",
  93. method = 'GET',
  94. sink = ltn12.sink.table(response),
  95. headers = {
  96. ['content-length'] = #body_searchHueLights,
  97. ['content-type'] = 'application/json',
  98. },
  99. source = ltn12.source.string(body_searchHueLights),
  100. })
  101. return response
  102. end
  103.  
  104. function sendToLight(Light_num,body_request)
  105. local response = {}
  106. socket.http.request({
  107. url = "http://"..ip_add.."/api/"..user.."/lights/"..tostring(Light_num).."/state",
  108. method = 'PUT',
  109. sink = ltn12.sink.table(response),
  110. headers = {
  111. ['content-length'] = #body_request,
  112. ['content-type'] = 'application/json',
  113. },
  114. source = ltn12.source.string(body_request),
  115. })
  116. return response
  117. end
  118.  
  119. function sendToGroup(Group_num,body_request)
  120. local response = {}
  121. socket.http.request({
  122. url = "http://"..ip_add.."/api/"..user.."/groups/"..tostring(Light_num).."/action",
  123. method = 'PUT',
  124. sink = ltn12.sink.table(response),
  125. headers = {
  126. ['content-length'] = #body_request,
  127. ['content-type'] = 'application/json',
  128. },
  129. source = ltn12.source.string(body_request),
  130. })
  131. return response
  132. end
  133.  
  134. function setBrightness(Light_num,brightness)
  135. if brightness == 0 then
  136. body_msg = '{"on":false}'
  137. response = sendToLight(lamp_id,body_msg)
  138. return response
  139. else
  140. brightness = math.floor((brightness * 2.54) + 0.5)
  141. --HTTP request send
  142. body_msg = '{"on":true,"bri":'..brightness..'}'
  143. response = sendToLight(Light_num,body_msg)
  144. return response
  145. end
  146. end
  147.  
  148. function setRGB(Light_num,RGB_variable)
  149. --RGB color set in homeLYnk
  150. Red=bit.rshift(bit.band(RGB_variable,0xff0000),16)
  151. Green=bit.rshift(bit.band(RGB_variable,0x00ff00),8)
  152. Blue=bit.band(RGB_variable,0x0000ff)
  153. -- Norm of RGB values
  154. Red_f= Red/255
  155. Green_f=Green/255
  156. Blue_f= Blue/255
  157. --Gamma correction
  158. if Red_f>0.04045 then
  159. Red_f=((Red_f+0.055)/(1+0.055))^2.4
  160. else
  161. Red_f=Red_f/12.92
  162. end
  163. if Green_f>0.04045 then
  164. Green_f=((Green_f+0.055)/(1+0.055))^2.4
  165. else
  166. Green_f=Green_f/12.92
  167. end
  168. if Blue_f>0.04045 then
  169. Blue_f=((Blue_f+0.055)/(1+0.055))^2.4
  170. else
  171. Blue_f=Red_f/12.92
  172. end
  173. --Coversion RGB->xy
  174. X=Red_f*0.649926+Green_f*0.103455+Blue_f*0.197109
  175. Y=Red_f*0.234327+Green_f*0.743075+Blue_f*0.022598
  176. Z=Green_f*0.053077+Blue_f*1.035763
  177. x=X/(X+Y+Z)
  178. y=Y/(X+Y+Z)
  179. --HTTP request send
  180. body_msg = '{"on":true,"xy":['..x..','..y..']}'
  181. response = sendToLight(Light_num,body_msg)
  182. return response
  183. end

Search for HUE bridges in your network and enter the IP result into user.hue where now ‘xxx.xxx.xxx.xxx’ is entered

Source code    
  1. require('user.hue')
  2. -- get bridge IP
  3. result = bridgeSearch()
  4. log(result) -- paste this result into user.hue as IP address

Create a user and enter the user result into user.hue where now ‘xxxxxxxxxxxx’ is entered

Source code    
  1. require('user.hue')
  2. -- get bridge user IMPORTANT! make sure to press link button on HUE bridge before running bridgeSetup()
  3. result = bridgeSetup()
  4. log(result) -- paste this result into user.hue as user

Make bridge search for new lights

Source code    
  1. require('user.hue')
  2. -- get bridge to search for new lights
  3. result = searchHueLights()
  4. log(result)

Get all light information

Source code    
  1. require('user.hue')
  2. -- get all lights from bridge
  3. result = getHueLights()
  4. log(result)

Control HUE from BIT object (on/off) (use as event based script)

Source code    
  1. require('user.hue')
  2. value = event.getvalue()
  3. lamp_id = 1 -- change this ID to your actual light, see getHueLights() result for lamps or use your app to resolve them
  4.  
  5. if value == true then
  6. body_msg = '{"on":true}'
  7. sendToLight(lamp_id,body_msg)
  8. else
  9. body_msg = '{"on":false}'
  10. sendToLight(lamp_id,body_msg)
  11. end

Control HUE from byte object (brightness) (use as event based script)

Source code    
  1. require('user.hue')
  2. value = event.getvalue() -- 1 byte unsigned integer scale 0 - 100
  3. lamp_id = 1 -- change this ID to your actual light, see getHueLights() result for lamps or use your app to resolve them
  4. setBrightness(lamp_id,value)

Control HUE from RGB object (color) (use as event based script)

Source code    
  1. require('user.hue')
  2. value = event.getvalue()
  3. lamp_id = 1 -- change this ID to your actual light, see getHueLights() result for lamps or use your app to resolve them
  4.  
  5. if value == 0 then
  6. body_msg = '{"on":false}'
  7. sendToLight(lamp_id,body_msg)
  8. else
  9. setRGB(lamp_id,value)
  10. end

Get feedback values from HUE when you change state with the HUE app (Resident script, 1 to 5 seconds)

Source code    
  1. address_state_lamp_1 = '1/1/1'
  2. address_state_lamp_2 = '1/1/2'
  3. address_state_lamp_3 = '1/1/3'
  4. address_state_lamp_4 = '1/1/4'
  5.  
  6. name_lamp_1 = 'Hue color lamp 1'
  7. name_lamp_2 = 'Hue color lamp 2'
  8. name_lamp_3 = 'Hue color lamp 3'
  9. name_lamp_4 = 'Hue color lamp 4'
  10.  
  11. require('user.hue')
  12. require('json')
  13. reply = getHueLights()
  14. mylamps = json.decode(reply[1])
  15. for _, item in pairs(mylamps) do
  16. --log(item.manufacturername)
  17. --log(item.swversion)
  18. --log(item.type)
  19. --log(item.state.ct)
  20. --log(item.state.reachable)
  21. --log(item.state.alert)
  22. --log(item.state.on)
  23. --log(item.state.bri)
  24. --log(item.state.colormode)
  25. --log(item.state.hue)
  26. --log(item.state.sat)
  27. --log(item.state.effect)
  28. --log(item.state.xy[1])
  29. --log(item.state.xy[2])
  30. --log(item.uniqueid)
  31. --log(item.modelid)
  32. --log(item.name)
  33. if item.name == name_lamp_1 then
  34. if item.state.reachable == true then
  35. currentvalue = grp.getvalue(address_state_lamp_1)
  36. if currentvalue ~= item.state.on then
  37. grp.update(address_state_lamp_1, item.state.on)
  38. log('lamp ' .. item.name .. ' state is: ' .. tostring(item.state.on))
  39. end
  40. end
  41. end
  42. if item.name == name_lamp_2 then
  43. if item.state.reachable == true then
  44. currentvalue = grp.getvalue(address_state_lamp_2)
  45. if currentvalue ~= item.state.on then
  46. grp.update(address_state_lamp_2, item.state.on)
  47. log('lamp ' .. item.name .. ' state is: ' .. tostring(item.state.on))
  48. end
  49. end
  50. end
  51. if item.name == name_lamp_3 then
  52. if item.state.reachable == true then
  53. currentvalue = grp.getvalue(address_state_lamp_3)
  54. if currentvalue ~= item.state.on then
  55. grp.update(address_state_lamp_3, item.state.on)
  56. log('lamp ' .. item.name .. ' state is: ' .. tostring(item.state.on))
  57. end
  58. end
  59. end
  60. if item.name == name_lamp_4 then
  61. if item.state.reachable == true then
  62. currentvalue = grp.getvalue(address_state_lamp_4)
  63. if currentvalue ~= item.state.on then
  64. grp.update(address_state_lamp_4, item.state.on)
  65. log('lamp ' .. item.name .. ' state is: ' .. tostring(item.state.on))
  66. end
  67. end
  68. end
  69. end

Created by Erwin van der Zwart