===== Heat meters SKS-3 by Axis Industries ===== ---- These meters support the M-Bus exchange protocol. In WebHMI, you can work with them using custom protocols. This protocol can be used with appropriate adjustments for other M-Bus devices as well. The text of the protocol: -- app_reset packet addr and CRC should be changed local app_reset = {0x68, 0x04, 0x04, 0x68, 0x53, 0x5D, 0x50, 0x00, 0x01, 0x16} -- get data packet local REQ_UD2 = {0x10, 0x7B, 0x5D, 0xD8, 0x16} local err_cnt = 0 -- error counter local dif_err_mask = 0x30 -- parameter error mask local resp3 = {} -- table for response bytes local alreadyRead = false -- flag of full read local timeStmp = 0 -- timestamp of full read AppRST = false -- flag of app reset done -- function createDevices () -- prefix dif dife vif vife len bytes addDevice({name = "VOL", shift = 0, base = 16, xtraFields = {0x4, 0, 0x14, 0, 0x4}}) -- current volume addDevice({name = "VOLB", shift = 0, base = 16, xtraFields = {0x84, 0x40, 0x14, 0, 0x4}}) -- current volume 2 addDevice({name = "HPWR", shift = 0, base = 16, xtraFields = {0x05, 0x00, 0x2E, 0, 0x4}}) -- power 1 addDevice({name = "PWRB", shift = 0, base = 16, xtraFields = {0x85, 0x40, 0x2E, 0, 0x4}}) -- power 2 addDevice({name = "TA", shift = 0, base = 16, xtraFields = {0x02, 0x00, 0x59, 0, 0x2}}) -- Temperature 1 addDevice({name = "TB", shift = 0, base = 16, xtraFields = {0x02, 0x00, 0x5D, 0, 0x2}}) -- Temperature 2 addDevice({name = "TC", shift = 0, base = 16, xtraFields = {0x02, 0x00, 0x65, 0, 0x2}}) -- Temperature 3 addDevice({name = "TCC", shift = 0, base = 16, xtraFields = {0x82, 0x40, 0x59, 0, 0x2}}) -- Temperature 3 addDevice({name = "TD", shift = 0, base = 16, xtraFields = {0x82, 0x40, 0x5D, 0, 0x2}}) -- Temperature 4 addDevice({name = "ENA", shift = 0, base = 16, xtraFields = {0x04, 0x00, 0x06, 0, 0x4}}) -- energy addDevice({name = "ENC", shift = 0, base = 16, xtraFields = {0x04, 0x00, 0x07, 0, 0x4}}) -- energy addDevice({name = "END", shift = 0, base = 16, xtraFields = {0x84, 0x40, 0x05, 0, 0x4}}) -- energy addDevice({name = "EAA", shift = 0, base = 16, xtraFields = {0x04, 0x00, 0x05, 0, 0x4}}) -- energy addDevice({name = "Q", shift = 0, base = 16, xtraFields = {0x05, 0x00, 0x3E, 0, 0x4}}) -- flow 1 addDevice({name = "QB", shift = 0, base = 16, xtraFields = {0x85, 0x40, 0x3E, 0, 0x4}}) -- flow 2 end -- of createDevices function onScanStart () AppRST = false end function readRegister (reg, device, unitId) -- put addr into packet app_reset[6] = unitId REQ_UD2[3] = unitId -- now put CRC app_reset[9] = getCRC(app_reset,5,4) REQ_UD2[4] = getCRC(REQ_UD2,2,2) -- Application reset if not AppRST then -- not yet ? local res = sendBytes(app_reset) -- send app_reset DEBUG("Request was sent!") if (res == false) then DEBUG("app reset was sent send bytes") return false end local response = readBytes(1) -- get reply if (response == false) then ERROR("Can't read response") return false else DEBUG("App reset was successfull") AppRST = true err_cnt = 0 end end -- after 10 errors reset again if err_cnt > 10 then AppRST = false TRACE("Repeated AppRST after 10 errors") return false end -- err check -- after 20 seconds getdata again local now = os.time() if (not alreadyRead) then timeStmp = now -- store time -- REQ_UD2 -- local resp2 = sendBytes(REQ_UD2) -- send packet if (resp2 == false) then DEBUG("Can't send bytes") return false end -- get first 4 bytes local response2 = readBytes(4) if (response2 == false) then ERROR("Can't read response") err_cnt = err_cnt + 1 return false elseif not ((response2[1] == 0x68) and (response2[4] == 0x68)) then ERROR("Wrong response format!") err_cnt = err_cnt + 1 return false else -- Ok reading full packet local length = response2[2] -- get len from packet -- get ALL resp3 = readBytes(length+2) if (resp3 == false) then ERROR("Can't read response") err_cnt = err_cnt + 1 return false else -- debug print DEBUG("C field = "..tostring(resp3[1])) DEBUG("Addr field = "..tostring(resp3[2])) DEBUG("CI field hex= "..string.format("%X",resp3[3])) -- ID 4 bytes -- local ID_str = "" for i = 4, 7 do if resp3[i] < 10 then ID_str = "0"..string.format("%X",resp3[i])..ID_str else ID_str = string.format("%X",resp3[i])..ID_str end end DEBUG("ID hex = "..ID_str) -- Manufacrutre 2 bytes -- local m_str = "" for i = 8, 9 do if resp3[i] < 10 then m_str = "0"..string.format("%X",resp3[i])..m_str else m_str = string.format("%X",resp3[i])..m_str end end DEBUG("Manufacture 2 bytes hex = "..m_str) -- Version -------------------- DEBUG("Version = "..string.format("%X",resp3[10])) DEBUG("Medium = "..string.format("%X",resp3[11])) DEBUG("Access No. = "..string.format("%X",resp3[12])) DEBUG("Status = "..string.format("%X",resp3[13])) ---------------------------------------- -- signature 2 bytes -- local m_str = "" for i = 14, 15 do if resp3[i] < 10 then m_str = "0"..string.format("%X",resp3[i])..m_str else m_str = string.format("%X",resp3[i])..m_str end end DEBUG("Signature 2 bytes hex = "..m_str) alreadyRead = true end end end -- adjust real polling interval here if (now - timeStmp) >= 15 then alreadyRead = false end -- local position = 0 local resp_num = 0 local err_flag = false -- find and check value using aux function position, err_flag = efindpos(resp3, device.xtraFields[1], device.xtraFields[2], device.xtraFields[3], device.xtraFields[4]) -- convert to number resp_num = getBCD(resp3, position, device.xtraFields[5]) err_cnt = 0 if not err_flag then return resp_num else return 0xffff end end -- of function function writeRegister (reg, device, unitId, newValue) -- Add your code here end -- AUXILARY FUNCTIONS -- function getBCD(a,pos,len) -- local str = "" local i,max_i = pos, pos+len-1 TRACE("getDCD pos is "..tostring(pos).." till "..tostring(max_i)) while i <= max_i do if (a[i] < 0x0f) then str = "0"..string.format("%X",a[i])..str else str = string.format("%X",a[i])..str end i = i + 1 end TRACE("getDCD Going to return .."..str.." and number is - ") local result = tonumber(str,16) TRACE(result) return result end -- getBCD -- calc CRC function getCRC(a,pos,len) local sum = 0 for i = pos, pos+len-1 do sum = sum + a[i] end TRACE("CRC sum = "..tostring(sum)) return sum end -- getCRC -- find data in packet using DIF VIF function efindpos(a, dif, dife, vif, vife) -- local dife_exists = false local vife_exists = false local err_flag = false -- check for error if (bit.band(0x30, dif) == 0x30) then err_flag = true end -- check dif if (bit.band(0x80, dif) == 0x80) then dife_exists = true end -- if vife exist if (bit.band(0x80, vif) == 0x80) then vife_exists = true end -- if (not dife_exists) then if (not vife_exists) then -- dif vif for i,v in ipairs (a) do if dif == bit.band(v,0x0f) then -- compare nibble w/o error if a[i+1] == vif then return i+2,err_flag end end end -- loop else -- vife_exists for i,v in ipairs (a) do if dif == bit.band(v,0x0f) then -- compare low nibbel w/o error if (a[i+1]==vif) and (a[i+2]==vife) then return i+3,err_flag end end end -- loop end else -- dife_exists -- INFO("efindpos dife exists"..string.format("%X",dife)) if (not vife_exists) then -- dif vif for i,v in ipairs (a) do if (dif == bit.band(v,0x8f) and a[i+1] == dife ) then -- if a[i+2] == vif then return i+3,err_flag end end end -- loop else -- dife vife_exists -- DEBUG("efindpos Vife exists"..string.format("%X",vife)) for i,v in ipairs (a) do local condition = ( (dif == bit.band(v,0x8f)) and (dife == a[i+1])) if condition then -- сравниваем мл тетраду без ошибки if (a[i+2]==vif) and (a[i+3]==vife) then return i+4,err_flag end end end -- loop end end -- if return nil -- found nothing end -- of efindpos