I finally found some time to build te functionality I wanted (based on your great code Westcott)!
Firstly, I created the following (virtual) devices in Domoticz:
- a temperature sensor for each room
- a thermostat setpoint for every wall mounted thermostat
- a percentage sensor for each valve (optional, but I need this to control my heater)
I named the devices exactly the same as in the MAX! Cube configuration, this is very important otherwise my script will not work. The temperature sensors have to be named afer the MAX! room names.
I am currently using this script (I called it "script_time_max.lua" and put it in ~/domoticx/scripts/lua):
Code: Select all
package.loadlib("core.so", "*")
local Socket = require "socket"
local Basexx = require "basexx"
local MaxIP='192.168.178.46'
local MaxPort = 62910
local Rooms = {}
local Devices = {}
local Room_nums = {}
function age(timestring)
t = {}
t.year = string.sub(timestring,1,4)
t.month = string.sub(timestring,6,7)
t.day = string.sub(timestring,9,10)
t.hour = string.sub(timestring,12,13)
t.min = string.sub(timestring,15,16)
t.sec = string.sub(timestring,18,19)
return os.difftime(os.time(),os.time(t))
end
function maxCmd_H(data)
-- print('H='..data)
end
function maxCmd_M(data)
i = 0
j = 0
while true do -- find next comma
i = string.find(data, ",", i+1)
if not i then break end
j = i
end
s = data:sub(j+1)
dec = Basexx.from_base64(s)
num_rooms = string.byte(dec,3)
pos=4
for i=1, num_rooms do
room_num = string.byte(dec, pos)
name_len = string.byte(dec, pos+1)
pos = pos+2
name = dec:sub(pos, pos+name_len-1)
pos = pos+name_len
adr = Basexx.to_hex(dec:sub(pos, pos+2))
Rooms[room_num] = name
pos = pos+3
end
num_devs = string.byte(dec, pos)
for i=1, num_devs do
dtype = string.byte(dec, pos+1)
adr = Basexx.to_hex(dec:sub(pos+2, pos+4))
snum = dec:sub(pos+5, pos+14)
name_len = string.byte(dec, pos+15)
pos = pos+16
name = dec:sub(pos, pos+name_len-1)
pos = pos+name_len
room_num = string.byte(dec, pos)
Room_nums[adr] = room_num
Devices[adr] = name
end
end
function maxCmd_C(data)
-- print('C='..data)
end
function maxCmd_L(data)
pos = 1
dec = Basexx.from_base64(data)
L_hex = Basexx.to_hex(dec)
L_len = string.len(L_hex)
while (pos < L_len) do
s = L_hex:sub(pos,(pos+1))
data_len = tonumber(s,16) + 1
hex = L_hex:sub(pos,pos+(data_len*2))
adr = hex:sub(3,8)
room_num = string.format("%02X", Room_nums[adr])
room = Rooms[Room_nums[adr]]
name = Devices[adr]
if not name then name=adr end
valve_info = tonumber(hex:sub(13,14),16)
batt = bit32.extract(valve_info,7,1)
bst = bit32.extract(valve_info,3,1)
mode = bit32.extract(valve_info,0,2)
if (batt==0) then sbat="OK" else sbat="Low" end
if (mode==0) then smode="Auto" elseif (mode==1) then smode="Manual"
elseif (mode==2) then smode="Holiday" elseif (mode==3) then smode="Boost" end
if (data_len == 13) then -- WallMountedThermostat (dev_type 3)
valve_pos = -1
s = hex:sub(17,18)
setpoint = tonumber(s,16) / 2
s = hex:sub(23,26)
temp = tonumber(s,16) / 10
dtype = "Thermostat"
elseif (data_len == 12) then -- HeatingThermostat (dev_type 1 or 2)
s = hex:sub(15,16)
valve_pos = tonumber(s,16)
s = hex:sub(17,18)
setpoint = tonumber(s,16) / 2
if (mode ~= 2) then
s = hex:sub(19,22)
temp = tonumber(s,16) / 10
else
temp = 0
end
dtype = "Valve"
end
if temp < 5 then temp = temp + 25.5 end --necessary since first two digits seem to be unused?
-- Update virtual devices in Domoticz and update MAX! setpoints if necessary
--print(dtype.." "..name.." Setpoint="..setpoint.." Temp="..temp.." Valve pos="..valve_pos)
if dtype == "Valve" then
if tonumber(otherdevices_svalues[name]) ~= valve_pos then
table.insert(commandArray, { ['UpdateDevice'] = otherdevices_idx[name]..'|0|'..valve_pos})
end
elseif dtype == "Thermostat" then
table.insert(commandArray, { ['UpdateDevice'] = otherdevices_idx[room]..'|0|'..temp})
setpoint_Domoticz = tonumber(otherdevices_svalues[name])
if setpoint_Domoticz ~= setpoint then
if age(otherdevices_lastupdate[name]) > 60 then --Domoticz thermostat value must be updated
table.insert(commandArray, { ['UpdateDevice'] = otherdevices_idx[name]..'|0|'..setpoint})
else --Max! setpoint must be updated
MaxCmdSend(adr, room_num, "manual", setpoint_Domoticz)
end
end
end
pos = pos + (data_len*2)
end
end
function MaxCmdSend(id, room, mode, setpoint)
bits = setpoint * 2
smode = string.upper(mode)
if smode == 'MANUAL' then
bits = 64 + bits
elseif smode == 'BOOST' then
bits = 192 + bits
elseif smode == 'VACATION' then
bits = 128 + bits
end
hex = "000440000000"..id..room..string.format("%x",bits)
sendStr = Basexx.to_base64(Basexx.from_hex(hex))
i, status = tcp:send("s:"..sendStr.."\r\n")
if not i then
print("MAX TCP send failed - "..status)
return
end
end
commandArray = {}
tcp = Socket.connect(MaxIP, MaxPort)
if not tcp then
print("Socket connect failed for "..MaxIP..':'..MaxPort)
return
end
tcp:settimeout(2)
local time = os.date("*t")
while (time.min ~= 0) do
s, status, partial = tcp:receive()
if (status) then
print("TCP receive - "..status)
break
end
local line = (s or partial)
local cmd = line:sub(1,1)
local data = line:sub(3)
if (cmd == 'H') then
maxCmd_H(data)
elseif (cmd == 'M') then
maxCmd_M(data)
elseif (cmd == 'C') then
maxCmd_C(data)
elseif (cmd == 'L') then
maxCmd_L(data)
break
end
end
tcp:close()
return commandArray
Now every minute all Domoticz thermostats are read, and if the value is different from the MAX! setpoint, a new setpoint is sent to the corresponding MAX! thermostat. If the setpoint has changed externally, e.g. on the wall-mounted thermostat itself of via the MAX! app, it is updated in Domoticz. Next to this, the actual room temperatures are logged in Domoticz (virtual) temperature sensors.
I'm very happy with the way it works now. There is still one minor problem/annoyance: When the script changes a thermostat setpoint in Domoticz, it takes more than 10 seconds for some strange reason. Everything works though, you only get an error message in the log. I have posted a question about this in another forum thread, seems more people are experiencing this problem.
Please note that this is all using manual setpoints. You can still use automatic temperature programs on your MAX! Cube, but these will be overridden every time you change the setpoint in Domoticz (and vice-versa). It's probably better (and in my opinion more user friendly too) to leave alone the MAX! programming functionality altogether, and create all your setpoint timers in Domoticz.
A nice addition to make it almost completely novice-proof could be automatic device creation at first time use. I've seen this being done using JSON commands somewhere, need to dive into that sometime. From that point on, it's probably not extremely difficult to convert it into an actual Domoticz plugin?! But at the moment that's still a few bridges too far for me...
Anyone interested in testing this code? After manually adding (and correctly naming!) all the required sensors, the code should work out-of-the-box, you only need to change the IP address of your Cube in the top section of the script.