Example: VRV system control from KNX bus with CoolMaster

Task

If there is a necessity to control VRV systems from your fieldbus system you can use/integrate one of the following control methods:

Using the above methods you can do quite cost-effective integration of VRV systems in your KNX, BACnet, EnOcean etc. field-buses and common visualization.

The following script will show how to interconnect CoolMaster VRV controller to LogicMachine3 over RS-232 serial port. The following VRV systems can be integrated: Daikin, Fujitsu, Gree, Hitachi, LG, Mitsubishi, Mitsubishi Heavy, Panasonic, Samsung, Sanyo, Toshiba.

RS-232 integration

If your LogicMachine does not have RS-232, you can integrate it through USB to RS232 converter based on CP210x, FT232, PL2303, MCT U232 chips. For example, this.

Objects

For correct work objects should be named correctly:

For status objects we add ‘status’ after the name, example:

For all objects you should set tag = VRV

Resident script

Add the following resident script with 0 interval. Replace ‘/dev/RS232’ with the respective name of the port of your LogicMachine.

Source code    
  1. if not cm then
  2. require('serial')
  3. require('genohm-scada.eibdgm')
  4.  
  5. -- knx and coolmaster mapping
  6. knxtocm = {}
  7. cmtoknx = {}
  8.  
  9. -- value mapping
  10. modetotext = { [0] = 'cool', [1] = 'dry', [2] = 'heat' }
  11. texttomode = { cool = 0, dry = 1, heat = 2 }
  12. speedtotext = { [0] = 'l', [1] = 'm', [2] = 'h', [3] = 'a' }
  13. texttospeed = { low = 0, med = 1, high = 2, auto = 3 }
  14.  
  15. -- get all tagged objects and set mapping
  16. for _, object in ipairs(grp.tag('VRV')) do
  17. local address, fn, stat = unpack(object.name:split(' '))
  18.  
  19. if address ~= 'all' then
  20. address = tonumber(address)
  21. end
  22.  
  23. -- status object
  24. if stat then
  25. if not cmtoknx[ address ] then
  26. cmtoknx[ address ] = {}
  27. end
  28.  
  29. cmtoknx[ address ][ fn ] = {
  30. id = object.id,
  31. value = grp.getvalue(object.id),
  32. }
  33. -- control object
  34. else
  35. knxtocm[ object.id ] = {
  36. address = address,
  37. fn = fn,
  38. }
  39. end
  40. end
  41.  
  42. function updateknx(address, fn, value, datatype)
  43. local object = cmtoknx[ address ] and cmtoknx[ address ][ fn ] or nil
  44.  
  45. -- object not found
  46. if not object then
  47. return
  48. end
  49.  
  50. -- no value or same value, no update required
  51. if value == nil or object.value == value then
  52. return
  53. end
  54.  
  55. -- save new value and write to knx
  56. object.value = value
  57. grp.write(object.id, value, datatype)
  58. end
  59.  
  60. function parseline(line)
  61. local address, status, setpoint, temp, speed, mode
  62.  
  63. address = line:sub(1, 3)
  64. address = tonumber(address)
  65.  
  66. -- address is not a number, cannot parse line
  67. if not address then
  68. return
  69. end
  70.  
  71. -- on/off status
  72. status = line:sub(5, 6):lower() == 'on'
  73. updateknx(address, 'on/off', status, dt.bool)
  74.  
  75. -- setpoint is integer
  76. setpoint = line:sub(9, 10)
  77. setpoint = tonumber(setpoint)
  78. updateknx(address, 'temp', setpoint, dt.float16)
  79.  
  80. -- room temp is float, separated by comma
  81. temp = line:sub(13, 17):gsub(',', '.')
  82. temp = tonumber(temp)
  83. updateknx(address, 'temp_room', temp, dt.float16)
  84.  
  85. -- speed: low, med, high, auto
  86. speed = line:sub(20, 23):lower():trim()
  87. updateknx(address, 'fspeed', texttospeed[ speed ], dt.uint8)
  88.  
  89. -- mode: cool, heat, fan, dry, auto
  90. mode = line:sub(25, 28):lower():trim()
  91. updateknx(address, 'mode', texttomode[ mode ], dt.uint8)
  92. end
  93.  
  94. -- read single line from serial
  95. function readline()
  96. local line, timeout, char, err
  97.  
  98. line = {}
  99. timeout = 10
  100.  
  101. -- read until timeout or full line
  102. while timeout > 0 do
  103. -- get single char from serial
  104. char, err = cm:read(1, 0.1)
  105.  
  106. -- read timeout
  107. if not char then
  108. timeout = timeout - 1
  109. -- end-of-line
  110. elseif char == '\n' then
  111. break
  112. -- ignore carriage return
  113. elseif char ~= '\r' then
  114. table.insert(line, char)
  115. end
  116. end
  117.  
  118. -- read ok
  119. if timeout > 0 then
  120. return table.concat(line)
  121. end
  122. end
  123.  
  124. -- read current status
  125. function readstat()
  126. local line
  127. cm:flush()
  128. cm:write('stat\r\n')
  129.  
  130. -- read until error or end of stat
  131. while true do
  132. line = readline()
  133.  
  134. -- timeout or end occured
  135. if not line or line == 'OK' then
  136. break
  137. end
  138.  
  139. parseline(line)
  140. end
  141. end
  142.  
  143. -- send single cmd to coolmaster
  144. function writecmd(cmd)
  145. local result
  146.  
  147. cm:flush()
  148. cm:write(cmd .. '\r\n')
  149.  
  150. readline() -- command echo
  151. result = readline() -- command result
  152. readline() -- new line prompt
  153.  
  154. return result
  155. end
  156.  
  157. -- handle group writes
  158. function eventhandler(event)
  159. local object, cmd, value, param
  160.  
  161. object = knxtocm[ event.dstraw ]
  162. -- knx object not mapped, ignore
  163. if not object then
  164. return
  165. end
  166.  
  167. -- on/off - boolean
  168. if object.fn == 'on/off' then
  169. value = knxdatatype.decode(event.datahex, dt.bool)
  170. cmd = value and 'on' or 'off'
  171. -- setpoint - floating point
  172. elseif object.fn == 'temp' then
  173. value = knxdatatype.decode(event.datahex, dt.float16)
  174. param = string.format('%.2f', value)
  175. cmd = 'temp'
  176. -- mode (fan, dry, auto)
  177. elseif object.fn == 'mode' then
  178. value = knxdatatype.decode(event.datahex, dt.uint8)
  179. cmd = modetotext[ value ]
  180. -- speed (low, medium, high, auto)
  181. elseif object.fn == 'fspeed' then
  182. value = knxdatatype.decode(event.datahex, dt.uint8)
  183. param = speedtotext[ value ]
  184.  
  185. if param then
  186. cmd = 'fspeed'
  187. end
  188. end
  189.  
  190. -- got valid command
  191. if cmd then
  192. -- all allows only on/off
  193. if object.address == 'all' then
  194. cmd = 'all' .. cmd
  195. -- append address to command
  196. else
  197. cmd = cmd .. ' ' .. object.address
  198. end
  199.  
  200. -- append additional parameter if set
  201. if param then
  202. cmd = cmd .. ' ' .. param
  203. end
  204.  
  205. writecmd(cmd, true)
  206. end
  207. end
  208.  
  209. -- coolmaster serial connection
  210. cm = serial.open('/dev/RS232', { baudrate = 9600 })
  211.  
  212. -- knx connection
  213. client = eibdgm:new()
  214. client:sethandler('groupwrite', eventhandler)
  215.  
  216. -- start-up time
  217. sec, usec = os.microtime()
  218. end
  219.  
  220. -- handle knx
  221. client:step()
  222.  
  223. -- read stats every 3 seconds
  224. diff = os.udifftime(sec, usec)
  225. if diff < 0 or diff >= 3 then
  226. readstat()
  227. sec, usec = os.microtime()
  228. end