CoolMaster VRV integration with LogicMachine #
Task #
The following script will show how to integrate CoolMaster VRV controller with LogicMachine over RS-232 serial port or TCP. The following VRV systems are supported: Daikin, Fujitsu, Gree, Hitachi, LG, Mitsubishi, Mitsubishi Heavy, Panasonic, Samsung, Sanyo, Toshiba.
RS-232 integration #
If your LogicMachine does not have a RS-232 port, you can integrate it through USB to RS232 converter based on CP210x, FT232, PL2303, MCT U232 chips.
Objects #
All objects must have a VRV
tag set.
Set object names using the following scheme:
- 101 on/off – binary
- 101 temp – 2 byte floating point
- 101 mode – 1 byte (0 = cool, 1 = dry, 2 = heat)
- 101 fspeed – 1 byte (0 = low, 1 = medium, 2 = high, 3 = auto)
- all on/off – binary object to turn all blocks on and off at once
You can create status objects by adding status
to the object name, for example:
- 101 on/off status
- 101 temp status
Resident script #
Add the following resident script with 0 interval. Replace /dev/RS232
with the respective name of the port on your LM.
if not cm then
require('serial')
require('genohm-scada.eibdgm')
-- knx and coolmaster mapping
knxtocm = {}
cmtoknx = {}
-- value mapping
modetotext = { [0] = 'cool', [1] = 'dry', [2] = 'heat' }
texttomode = { cool = 0, dry = 1, heat = 2 }
speedtotext = { [0] = 'l', [1] = 'm', [2] = 'h', [3] = 'a' }
texttospeed = { low = 0, med = 1, high = 2, auto = 3 }
-- get all tagged objects and set mapping
for _, object in ipairs(grp.tag('VRV')) do
local address, fn, stat = unpack(object.name:split(' '))
if address ~= 'all' then
address = tonumber(address)
end
-- status object
if stat then
if not cmtoknx[ address ] then
cmtoknx[ address ] = {}
end
cmtoknx[ address ][ fn ] = {
id = object.id,
value = grp.getvalue(object.id),
}
-- control object
else
knxtocm[ object.id ] = {
address = address,
fn = fn,
}
end
end
function updateknx(address, fn, value, datatype)
local object = cmtoknx[ address ] and cmtoknx[ address ][ fn ] or nil
-- object not found
if not object then
return
end
-- no value or same value, no update required
if value == nil or object.value == value then
return
end
-- save new value and write to knx
object.value = value
grp.write(object.id, value, datatype)
end
function parseline(line)
local address, status, setpoint, temp, speed, mode
address = line:sub(1, 3)
address = tonumber(address)
-- address is not a number, cannot parse line
if not address then
return
end
-- on/off status
status = line:sub(5, 6):lower() == 'on'
updateknx(address, 'on/off', status, dt.bool)
-- setpoint is integer
setpoint = line:sub(9, 10)
setpoint = tonumber(setpoint)
updateknx(address, 'temp', setpoint, dt.float16)
-- room temp is float, separated by comma
temp = line:sub(13, 17):gsub(',', '.')
temp = tonumber(temp)
updateknx(address, 'temp_room', temp, dt.float16)
-- speed: low, med, high, auto
speed = line:sub(20, 23):lower():trim()
updateknx(address, 'fspeed', texttospeed[ speed ], dt.uint8)
-- mode: cool, heat, fan, dry, auto
mode = line:sub(25, 28):lower():trim()
updateknx(address, 'mode', texttomode[ mode ], dt.uint8)
end
-- read single line from serial
function readline()
local line, timeout, char, err
line = {}
timeout = 10
-- read until timeout or full line
while timeout > 0 do
-- get single char from serial
char, err = cm:read(1, 0.1)
-- read timeout
if not char then
timeout = timeout - 1
-- end-of-line
elseif char == '\n' then
break
-- ignore carriage return
elseif char ~= '\r' then
table.insert(line, char)
end
end
-- read ok
if timeout > 0 then
return table.concat(line)
end
end
-- read current status
function readstat()
local line
cm:flush()
cm:write('stat\r\n')
-- read until error or end of stat
while true do
line = readline()
-- timeout or end occured
if not line or line == 'OK' then
break
end
parseline(line)
end
end
-- send single cmd to coolmaster
function writecmd(cmd)
local result
cm:flush()
cm:write(cmd .. '\r\n')
readline() -- command echo
result = readline() -- command result
readline() -- new line prompt
return result
end
-- handle group writes
function eventhandler(event)
local object, cmd, value, param
object = knxtocm[ event.dstraw ]
-- knx object not mapped, ignore
if not object then
return
end
-- on/off - boolean
if object.fn == 'on/off' then
value = knxdatatype.decode(event.datahex, dt.bool)
cmd = value and 'on' or 'off'
-- setpoint - floating point
elseif object.fn == 'temp' then
value = knxdatatype.decode(event.datahex, dt.float16)
param = string.format('%.2f', value)
cmd = 'temp'
-- mode (fan, dry, auto)
elseif object.fn == 'mode' then
value = knxdatatype.decode(event.datahex, dt.uint8)
cmd = modetotext[ value ]
-- speed (low, medium, high, auto)
elseif object.fn == 'fspeed' then
value = knxdatatype.decode(event.datahex, dt.uint8)
param = speedtotext[ value ]
if param then
cmd = 'fspeed'
end
end
-- got valid command
if cmd then
-- all allows only on/off
if object.address == 'all' then
cmd = 'all' .. cmd
-- append address to command
else
cmd = cmd .. ' ' .. object.address
end
-- append additional parameter if set
if param then
cmd = cmd .. ' ' .. param
end
writecmd(cmd, true)
end
end
-- coolmaster serial connection
cm = serial.open('/dev/RS232', { baudrate = 9600 })
-- knx connection
client = eibdgm:new()
client:sethandler('groupwrite', eventhandler)
-- start-up time
sec, usec = os.microtime()
end
-- handle knx
client:step()
-- read stats every 3 seconds
diff = os.udifftime(sec, usec)
if diff < 0 or diff >= 3 then
readstat()
sec, usec = os.microtime()
end
TCP integration #
Objects #
All objects must have a VRV
tag set.
Set object names using the following scheme (full address and function):
- L1.101 on/off – 1-bit, unit on/off control
- L1.101 setpoint – unit setpoint, 2-byte floating point
- L1.101 mode – unit mode, 1-byte (0 = cool, 1 = heat, 2 = fan, 3 = dry, 4 = auto)
- L1.101 fspeed – fan speed, 1-byte (0 = low, 1 = medium, 2 = high, 3 = top, 4 = auto)
- L1 on/off – 1-bit, on/off control for all units on a given line
- L* on/off – 1-bit, on/off control for all units on all lines
You can create status objects by adding status
to the object name, for example:
- L1.101 on/off status
- L1.101 setpoint status
- L1.101 mode status
- L1.101 fspeed status
- L1.101 temp status – room temperature, 2-byte floating point
Note! L1 corresponds to "Line 1" and 101 corresponds to the address of an A/C unit which are both programmed on the CoolMaster device.
Resident script #
Create a resident script with 0 sleep interval, change 192.168.3.20 to CoolMasterNet device IP. Status information is polled every 3 seconds.
if not client then
require('socket')
require('genohm-scada.eibdgm')
IP = '192.168.3.20'
PORT = 10102
-- knx coolmaster mapping
knxtocm = {}
cmtoknx = {}
function reverse(src)
local res = {}
for k, v in ipairs(src) do
res[ v ] = k - 1
end
return res
end
-- integertext mode/fspeed value mapping
modetotext = { 'cool', 'heat', 'fan', 'dry', 'auto' }
texttomode = reverse(modetotext)
speedtotext = { 'low', 'med', 'high', 'top', 'auto' }
texttospeed = reverse(speedtotext)
-- get all tagged objects and set mapping
for _, object in ipairs(grp.tag('VRV')) do
local address, fn, stat = unpack(object.name:split(' '))
-- status object
if stat then
if not cmtoknx[ address ] then
cmtoknx[ address ] = {}
end
cmtoknx[ address ][ fn ] = {
id = object.id,
value = grp.getvalue(object.id),
}
-- control object
else
knxtocm[ object.id ] = {
address = address,
fn = fn,
}
end
end
function checkerror(res, err)
if not res then
sock:close()
sock = nil
alert('CoolMaster error: %s', tostring(err))
end
return res, err
end
function connect()
sock = socket.tcp()
sock:settimeout(5)
return checkerror(sock:connect(IP, PORT))
end
function send(data)
if not sock then
connect()
end
if sock then
checkerror(sock:send(data))
end
end
-- read single line from socket
function readline()
if not sock then
connect()
end
if sock then
return checkerror(sock:receive())
end
end
function updateknx(address, fn, value, datatype)
local object = cmtoknx[ address ] and cmtoknx[ address ][ fn ] or nil
-- object not found
if not object then
return
end
-- no value or same value, no update required
if value == nil or object.value == value then
return
end
-- save new value and write to knx
object.value = value
grp.write(object.id, value, datatype)
end
function parseline(line)
local address, status, setpoint, temp, speed, mode
line = line:gsub('>', '')
address = line:sub(1, 6)
-- address is invalid, cannot parse line
if address:sub(1, 1) ~= 'L' then
return
end
-- on/off status
status = line:sub(8, 9):lower() == 'on'
updateknx(address, 'on/off', status, dt.bool)
-- setpoint is integer
setpoint = line:sub(12, 13)
setpoint = tonumber(setpoint)
updateknx(address, 'setpoint', setpoint, dt.float16)
-- room temp is float, separated by comma
temp = line:sub(16, 17)
temp = tonumber(temp)
updateknx(address, 'temp', temp, dt.float16)
-- speed: low, med, high, auto
speed = line:sub(20, 23):lower():trim()
updateknx(address, 'fspeed', texttospeed[ speed ], dt.uint8)
-- mode: cool, heat, fan, dry, auto
mode = line:sub(25, 28):lower():trim()
updateknx(address, 'mode', texttomode[ mode ], dt.uint8)
end
-- read current status
function readstat()
send('ls\r\n')
-- read until error or end of stat
while true do
local line = readline()
-- timeout or end occured
if not line or line == 'OK' then
break
end
parseline(line)
end
end
-- handle group writes
function eventhandler(event)
local object, cmd, value, param
object = knxtocm[ event.dstraw ]
-- knx object not mapped, ignore
if not object then
return
end
-- on/off - boolean
if object.fn == 'on/off' then
value = knxdatatype.decode(event.datahex, dt.bool)
cmd = value and 'on' or 'off'
-- setpoint - floating point
elseif object.fn == 'setpoint' then
value = knxdatatype.decode(event.datahex, dt.float16)
param = string.format('%.1f', value)
cmd = 'temp'
-- mode (fan, dry, auto)
elseif object.fn == 'mode' then
value = knxdatatype.decode(event.datahex, dt.uint8)
cmd = modetotext[ value + 1 ]
-- speed (low, medium, high, auto)
elseif object.fn == 'fspeed' then
value = knxdatatype.decode(event.datahex, dt.uint8)
param = speedtotext[ value + 1 ]
if param then
cmd = 'fspeed'
end
end
-- got valid command
if cmd then
-- append address to command
cmd = cmd .. ' ' .. object.address
-- append additional parameter if set
if param then
cmd = cmd .. ' ' .. param
end
send(cmd .. '\r\n')
readline() -- command echo
result = readline() -- command result
readline() -- new line prompt
end
end
-- knx connection
client = eibdgm:new({ timeout = 1 })
client:sethandler('groupwrite', eventhandler)
-- start-up time
sec, usec = os.microtime()
end
-- handle knx
client:step()
-- read stats every 3 seconds
diff = os.udifftime(sec, usec)
if diff < 0 or diff >= 3 then
readstat()
sec, usec = os.microtime()
end