This is an old revision of the document!
DIEHL INTE heat meter
The meter has integrated Mbus port.
<code lua> – MAIN CODE STARTS AT LINE 118 function onScanStart ()
now = os.time()
end
POLL_DELAY = 60 * 15 – poll rate constant in sec. HEX_NUMBERING = 16 – numbering system conversion
—— convenient print function ———– function tprint(t, indent)
if not indent then indent = 0 end for k, v in pairs(t) do local formatting = string.rep(' ', indent) .. k .. ': ' if type(v) == "table" then ERROR(formatting) tprint(v, indent + 1) -- recursive call else if type(v) == "boolean" then v = v and "TRUE" or "FALSE" end ERROR(formatting .. v) end end
end
– prints any parameters function DBG(…)
for i = 1, #arg do local curArgument = arg[i] if type(curArgument) == 'table' then tprint(curArgument) else INFO(tostring(curArgument)) end end
end ; DEBUG = DBG
—— Table helpers —————————– function table.findPattern(t, pttrn)
for tabIndex, _ in ipairs(t) do
local matchFlag = true
for seqIndex, sequenceByte in ipairs(pttrn) do – checking pttrn inside a t
matchFlag = matchFlag and (sequenceByte == t[tabIndex + seqIndex - 1]) if (not matchFlag) then matchFlag = false break end end if matchFlag then return (tabIndex + #pttrn) end end return false
end
function table.sub(t, startIndex, endIndex)
local tmpTable = {} for k = startIndex, endIndex do table.insert(tmpTable, t[k]) end return tmpTable
end
function table.hexStr(hextab, spacer)
local hex = {} for _, hexbyte in ipairs(hextab) do table.insert(hex, getHexByteAsStr(hexbyte)) end return table.concat(hex, spacer)
end – converts tab to bcd number table.bcd = function(t)
local hex = table.hexStr(t) local out = tonumber(hex) ; DBG('entered bdc with', t, 'hex view: ' .. hex, 'out: ' .. tostring(out)) return out
end – integer convertions table.int = function(t)
return tonumber(table.hexStr(t), HEX_NUMBERING)
end
table.float = function(t)
return t
end
– gets 2 - char hex string of a byte function getHexByteAsStr(inputByte)
local strByte = string.format("%X", inputByte) return (#strByte == 1 and '0' .. strByte) or strByte
end
table.reverse = function (tab)
local outTable = {} for i = #tab, 1, -1 do table.insert(outTable, tab[i]) end return outTable
end
– delimiters & control bytes SHORT_FRAME_STX = 0x10 SEND_NKE = 0x40 ACK = 0xE5 ETX = 0x16 CTRL_LONG_FRAME_STX = 0x68
REQ_UD2 = 0x7B – Request for Class 2 Data FCB REQ_UD2_ = 0x5B – Request for Class 2 Data Frame count bit
BROADCAST_ADDR = 0xFE – 254 CRC_ = 0x59
– prboe packet – PROBE_PACKET = {SHORT_FRAME_STX, REQ_UD2_, BROADCAST_ADDR, CRC_, ETX} – TxD:10 5B FE 59 16 PROBE_PACKET = {SHORT_FRAME_STX, REQ_UD2, BROADCAST_ADDR, CRC_, ETX} – TxD:10 5B FE 59 16
- - its indexes
REQ_UD2_POS = 2 PROBE_PCKT_ADDR_POS = 3 PROBE_PACKET_CRC_POS = 4 PROBE_PACKET_PAYLOAD_LEN = 2
data = {empty = {}}
dataHandlers = {
- - energy = {pattern = {0x4, 0x07}, length = 4, func = table.float},
energy = {pattern = {0x4, 0x07}, length = 4, func = table.float},
volume = {pattern = {0x4, 0x15}, length = 4, func = table.int},
power = {pattern = {0x5, 0x2E}, length = 4, func = table.float}, flow = {pattern = {0x5, 0x3E}, length = 4, func = table.float}, tint = {pattern = {0x2, 0x59}, length = 2, func = table.int}, -- fwd. temp. text = {pattern = {0x2, 0x5D}, length = 2, func = table.int}, -- return temp. err = {pattern = {0x2, 0xFD, 0x17}, length = 2, func = table.int}, upt = {pattern = {0x4, 0x20}, length = 4, func = table.int} -- uptime , sec.
}
function createDevices () – xtraFields are used for comments
addDevice{name = "E", shift = 0, base = 10, xtraFields = {'energy'} } addDevice{name = "V", shift = 0, base = 10, xtraFields = {'volume'} } addDevice{name = "P", shift = 0, base = 10, xtraFields = {'power'} } addDevice{name = "Q", shift = 0, base = 10, xtraFields = {'flow'} } addDevice{name = "FWD", shift = 0, base = 10, xtraFields = {'temp. internal'} } addDevice{name = "RET", shift = 0, base = 10, xtraFields = {'temp. external'} } addDevice{name = "ERR", shift = 0, base = 10, xtraFields = {'error flags'} } addDevice{name = "UPT", shift = 0, base = 10, xtraFields = {'error flags'} }
end
function readRegister (reg, device, unitId)
local function initMeter() DBG("Etnered init packet for inte") local INTE_INIT_FRAME = {SHORT_FRAME_STX, SEND_NKE, BROADCAST_ADDR, 0x00 -- CRC #4 , ETX} INTE_INIT_FRAME[3] = unitId INTE_INIT_FRAME[4] = getCRC(INTE_INIT_FRAME, 2, 2) if (not sendBytes(INTE_INIT_FRAME) ) then ERROR("Could not sent bytes!") return false end if (readOneByte() ~= ACK) then ERROR("No ack after init packet!") ; return false else DBG("Ack received for INIT_PACKETS") end local CURRENT_PARAMS_SELECTION = { CTRL_LONG_FRAME_STX, 0x04, 0x04, CTRL_LONG_FRAME_STX , 0x53, BROADCAST_ADDR, 0x50, 0x0, 0x0 -- CRC #9 , ETX} CURRENT_PARAMS_SELECTION[6] = unitId CURRENT_PARAMS_SELECTION[9] = getCRC(CURRENT_PARAMS_SELECTION, 5, 4) if (not sendBytes(CURRENT_PARAMS_SELECTION) ) then ERROR("Could not send bytes for params selection!") ; return false end if (readOneByte() ~= ACK) then ERROR("No ack after cur params selection packet!") ; return false else DEBUG("Ack received for params selection") ; return true end end
if (not lastReadTimeStamp or (now - lastReadTimeStamp) >= POLL_DELAY ) then DBG("Starting inte read by lastReadTimeStamp") if (not init and not initMeter() ) then ERROR("Could not init INTE meter!") init = false ; return false else init = true -- set global for init meter end PROBE_PACKET[PROBE_PCKT_ADDR_POS] = unitId PROBE_PACKET[PROBE_PACKET_CRC_POS] = getCRC(PROBE_PACKET , REQ_UD2_POS , PROBE_PACKET_PAYLOAD_LEN)
if (not sendBytes(PROBE_PACKET) ) then
ERROR("Could not sent bytes for DATA_QUERY!") return false end
local meterDataFrame = readUntil(ETX)
if (meterDataFrame and (#meterDataFrame > 10) ) then DBG(table.hexStr(meterDataFrame, ' ')) -- parsing for param, struc in pairs(dataHandlers) do DBG('trying to read ' .. tostring(param)) local foundPos = table.findPattern(meterDataFrame, struc.pattern) if foundPos then DBG(' found data at: '.. foundPos) local foundBytes = table.sub(meterDataFrame , foundPos , foundPos + struc.length - 1) foundBytes = table.reverse(foundBytes) local actualData = struc.func(foundBytes) ; DBG("reversed actualData", '|', table.hexStr(foundBytes, ' ')) -- DBG('cur data structure: ', data) data[param] = actualData
lastReadTimeStamp = now
else ERROR("failed to find data for " .. param) data[param] = false end end else ERROR("failed to read long reply, meterDataFrame: " .. tostring(meterDataFrame)) return false end end
DBG("going to return cached data: ", data ) if data then if (device.name == 'E') then DBG(device.xtraFields[1]) return data.energy end if (device.name == 'V') then return data.volume end if (device.name == 'P') then return data.power end if (device.name == 'Q') then return data.flow end if (device.name == 'FWD') then return data.tint end if (device.name == 'RET') then return data.text end if (device.name == 'ERR') then return data.err end if (device.name == 'UPT') then return data.upt end else ERROR("the data is empty ... ") return false end
end
function writeRegister (reg, device, unitId, newValue) end
————————– Other helpers ————————–
function readUntil(endByte)
local buf = {} repeat local b = readOneByte() if b then table.insert(buf, b) end until (not b or (b == endByte))
return ((#buf >= 1) and buf) or false end
function getCRC(a, pos, len)
local sum, mask = 0, 0xFF
for i = pos, pos + len - 1 do
sum = sum + a[i] sum = bit.band(sum, mask) end return sum
end
function readOneByte()
local b = readBytes(1) return b and b[1] or false
end
</lua>