====== Connecting to Scylar INT 8 heat meters ====== These heatmeters use Mbus protocol and can be added with the help of [[custom_protocols|custom protocols]]. Create custom protocol and copy the lua code below into the code block of that custom protocol. If the meter has RS-485 board, it can be connected directly to WebHMI's RS-485 port. Wiring diagram: {{ network:scylar_scheme_2.png?direct&700 |}} Setup the connection for the meter as follows: {{ network:scylar_conn_setup_1.png?direct |}} The bus address (96) can be found in the meters keypad screen #3, Paramter name 'Pri_Adr1'. {{ network:primary_addr.png?direct&200 |}} {{ network:scylar_conn_setup_2.png?direct |}} This protocol can read the following heatmeter's data: ^ Parameter ^ Register address in project ^ Units ^ |Energy | CE0 | 0.001 Gcal | |Energy tariff1 | CEA0 | 0.001 Gcal | |Energy tariff2 | CEB0 | 0.001 Gcal | |Volume | CV0 | 0.001 m3 | |Power | PWR0 | 0.001 kW | |Flow | CFL0 | 0.001 m3/h | |Forward temperature | FWT0 | 0.1 °C | |Return temperature | RET0 | 0.1 °C | |Operational days | OPD0 | days | |Error run hours| ERH0 | hours | The driver returns integer values. You should scale them appropriately to get exact values using Multiplier field in register properties. == Driver test procedure == Before testing the protocol in the field, it's preferrable to test it in a lab (if you have a meter on-hand). To simulate temperatures you can use resistors or potentiometers, note that return temperature have to be less than forward for 3 or more degrees (otherwise energy won't work). Flowmeter can be simulated by shorting 10-11 pins of i/o board. {{ network:scylar_conn2.png?direct&300 |}} == Custom protocol (driver) code block == -- app reset template, modified then with addr and crc local app_reset = {0x68, 0x04, 0x04, 0x68, 0x53, 0x5D, 0x50, 0x50, 0x01, 0x16}; -- get data packet local REQ_UD2 = {0x10, 0x7B, 0x5D, 0xD8, 0x16}; local first_scan = true; local alreadyRead = false; local timeStmp = 0; local POLLTIME = 20; local err_cnt = 0; local dif_err_mask = 0x30; -- errorneous data read mask local resp3 = {}; -- response storage table AppRST = false; -- function createDevices () -- prefix dif dife vif vife len bytes addDevice({name = "FWT", shift = 0, base = 16, xtraFields = {0xA, 0x5A, 0, 0x2}}); -- forward temperature addDevice({name = "RET", shift = 0, base = 16, xtraFields = {0xA, 0x5E, 0, 0x2}}); -- rev. temp. -- addDevice({name = "ERH", shift = 0, base = 16, xtraFields = {0xA, 0xA6, 0x18, 0x2}}); -- error hours addDevice({name = "CFL", shift = 0, base = 16, xtraFields = {0xB, 0x3B, 0, 0x3}}); -- current flow rate addDevice({name = "CV", shift = 0, base = 16, xtraFields = {0xC, 0x13, 0, 0x4}}); -- current volume addDevice({name = "CE", shift = 0, base = 16, xtraFields = {0xC, 0xFB, 0x0D, 0x4}}); -- current volume addDevice({name = "CEA", shift = 0, base = 16, xtraFields = {0x8C, 0x10, 0xFB, 0x0D, 0x4}}); -- current energy T1 mask addDevice({name = "CEB", shift = 0, base = 16, xtraFields = {0x8C, 0x20, 0x13, 0x00, 0x4}}); -- current energy T1 mask addDevice({name = "PWR", shift = 0, base = 16, xtraFields = {0x0C, 0x2B, 0x00, 0x4}}); -- power -- addDevice({name = "OPD", shift = 0, base = 16, xtraFields = {0xA, 0x27, 0, 0x2}}); -- op. days end -- of createDevices function readRegister (reg, device, unitId) -- substitution with correct flag app_reset[6] = unitId; REQ_UD2[3] = unitId; -- and now CRC app_reset[9] = getCRC(app_reset,5,4); REQ_UD2[4] = getCRC(REQ_UD2,2,2); -- Application reset if not AppRST then -- was app reset made ? --------- App reset ----------- res = sendBytes(app_reset); -- send it TRACE("Request was sent!"); if (res == false) then TRACE("app reset could not send bytes"); return false; end local response = readBytes(1); -- read one byte if (response == false) then ERROR("Can't read response") ; return false; else TRACE("App reset was successfull"); AppRST = true; err_cnt = 0; end end local CRCvar, CRCinPacket = 0,0; -- reset flag and do app reset againg if err_cnt > 100 then AppRST = false; TRACE("Repeated AppRST after 100 errors"); return false; end -- err check -- after polltime ask againg local now = os.time(); if (not alreadyRead) then timeStmp = now; -- REQ_UD2 -- local resp2 = sendBytes(REQ_UD2); if (resp2 == false) then TRACE("Can't send bytes"); return false; end -- get first 4 bytes local response2 = readBytes(4); if (response2 == false) then ERROR("Can't read response, false returned") ; err_cnt = err_cnt + 1; return false; elseif not ((response2[1] == 0x68) and (response2[4] == 0x68)) then ERROR("Wrong response format!68 ~= 68 ") ; err_cnt = err_cnt + 1; return false; elseif (#response2 ~= 4 ) then ERROR("Wrong response2 len , length = "..tostring(#response2)) ; return false; else -- normal read local length = response2[2]; -- get length -- get everything resp3 = readBytes(length+2); if (resp3 == false) then ERROR("Can't read response, false returned ") ; err_cnt = err_cnt + 1; return false; elseif (#resp3 ~= (length+2)) then ERROR("Wrong resp3 len , length = "..tostring(#resp3)) ; return false; else -- glue packets for i,v in pairs(resp3) do table.insert(response2,v); end -- crc check CRCinPacket = tonumber(tostring(response2[#response2-1])); TRACE("CRCinPacket is "..CRCinPacket); CRCvar = getCRC(response2,5,length); TRACE("Calculated CRCvar is "..CRCvar); -- TRACE("And CRCvar "..string.format("%X",CRCvar)); if CRCinPacket == CRCvar then TRACE("They are equal"); else TRACE("They are NOT equal"); ERROR("Can't read response, false returned because of CRC !!! ") ; err_cnt = err_cnt + 1; return false; end -- debug print TRACE("C field = "..tostring(resp3[1])); TRACE("Addr field = "..tostring(resp3[2])); TRACE("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 TRACE("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 TRACE("Manufacture 2 bytes hex = "..m_str); -- Version -------------------- TRACE("Version = "..string.format("%X",resp3[10])); TRACE("Medium = "..string.format("%X",resp3[11])); TRACE("Access No. = "..string.format("%X",resp3[12])); TRACE("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",resp[i])..m_str; end end TRACE("Signature 2 bytes hex = "..m_str); alreadyRead = true; end end end -- if not alreadyRead -- set polling time if (now - timeStmp) >= POLLTIME then alreadyRead = false; end -- local position = 0; local resp_num = 0; local resp_tab = {}; local err_flag = false; local DataLength = 0; local FloatFlag = 0; TRACE("Parsing register "..device.name); position, DataLength, FloatFlag, err_flag = ExtFind(resp3,device.xtraFields); if (not err_flag) and (position ~= nil) then TRACE("parsed .. the following "); TRACE(reg.addr); TRACE("And device name = "..device.name); resp_tab, resp_num = getBCD(resp3, position, DataLength); return resp_num; else TRACE("Error while parsing.. "); return false; end end -- of function -- extra -- function getBCD(a, pos, len) -- reverse BCD table and make a number if (pos == nil) then TRACE("Pos = nil at entering to getBCD"); return 0xffff; end local str = ""; local result_table = {}; local bcd_sign = 1; local i, max_i = pos, pos+len-1; TRACE("getBCD pos is "..tostring(pos).." till "..tostring(max_i)); while i <= max_i do -- make string if (a[i] <= 0x0F) then str = "0"..string.format("%X",a[i])..str; else str = string.format("%X",a[i])..str; end -- make table table.insert(result_table,1,a[i]); i = i + 1; end -- check sign if string.find(str,"(F)") ~= nil then bcd_sign = -1; str = string.gsub(str,"F","0",1); end TRACE("getBCD Going to return .."..str.." and number is - "); local result = tonumber(str,10)*bcd_sign; TRACE(result); TRACE("table concat "..table.concat(result_table)); return result_table, result; end -- getBCD function getCRC(a,pos,len) local sum = 0; local mask = 0xFF; for i = pos, pos+len-1 do sum = sum + a[i]; sum = bit.band(sum,mask); end TRACE("CRC sum in the end of function = "..tostring(sum)); return sum; end -- getCRC function ExtFind(table, difvif_Table) TRACE("Entered ExtFind function "); local dib_len,vib_len = 1,1; local float_flag = false ; local data_LenTab = {[12] = 4, [10] = 2, [11] = 3}; local data_len = data_LenTab[bit.band(0x0F, difvif_Table[1])]; TRACE("data length is "..tostring(data_len)); if data_len == 0x05 then data_len = (data_len - 1) ; float_flag = true; TRACE("Float data found"); end local i = 1; while bit.band(0x80, difvif_Table[i]) == 0x80 do i = i + 1; end -- for dib_len = i; TRACE("DIB length = "..tostring(dib_len)); local j = i + 1; while bit.band(0x80, difvif_Table[j]) == 0x80 do j = j + 1; end -- for vib_len = j-i; TRACE("VIB length = "..tostring(vib_len)); for i,v in pairs(table) do local dif = 0; local err_flag = false; local dib_match = true; local vib_match = true; ------------------------------ -- TRACE("v in Dib search is"..string.format("%X",v)); if (bit.band(0x30, v) == 0x30) then -- TRACE("After bit.band v is with error mask "..string.format("%X",v)); err_flag = true ; dif = bit.band((0xFF - 0x30), v); else -- TRACE("No error , now v is "..string.format("%X",v)); dif = v; err_flag = false; end -- TRACE("dif found as "..string.format("%X",v)); local k=0; repeat k = k + 1; dib_match = dib_match and (table[i+k-1] == difvif_Table[k]); -- TRACE("table element ="..string.format("%X",table[i+k-1]).." difvif elmnt = ".. -- string.format("%X",difvif_Table[k])); -- TRACE("K ="..tostring(k).." dib_match = "..tostring(dib_match)); until (k==dib_len); -- TRACE("dib_match "..tostring(dib_match)); if dib_match then local g=0; repeat g = g + 1; vib_match = vib_match and (table[i+dib_len+g-1] == difvif_Table[dib_len+g]); -- TRACE("g = "..tostring(g).." Vib_match = "..tostring(vib_match)); until (g==vib_len); -- TRACE("vib_match "..tostring(vib_match)); end --if if dib_match and vib_match then TRACE("Going to return pos. len, float err flags "..tostring(i+dib_len+vib_len).." ".. tostring(data_len).." "..tostring(float_flag).." "..tostring(err_flag)); return i+dib_len+vib_len, data_len, float_flag, err_flag; end end -- for return nil; end -- of EXT Find -- function onScanStart () -- AppRST = false; end function writeRegister (reg, device, unitId, newValue) -- Add your code here end