{{ :custom_protocols:mbus:hydrus-meter-for-mbus.jpg?direct&150|}}
====== Connecting to DIEHL HYDRUS flowmeter ======
Highly accurate, lead-free brass ultrasonic smart water meter for residential, commercial and industrial installations. It has Mbus onboard.
----
-- 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(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
table.hexView = function(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.hexView(t)
return tonumber(hex)
end
-- integer convertions
table.int = function(t)
return tonumber(table.hexView(t), HEX_NUMBERING)
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
BRDCAST_NET_LAYER_ADDR = 0xFD -- 253
BROADCAST_ADDR = 0xFE -- 254
-- prboe packet
PROBE_PACKET = {SHORT_FRAME_STX, REQ_UD2_, BROADCAST_ADDR, 0x59, ETX}
-- its indexes
REQ_UD2_POS = 2
PROBE_PACKET_PAYLOAD_LEN = 2
PROBE_PACKET_CRC_POS = 4
data = {}
dataHandlers = {
volume = {pattern = {0xC, 0x14}, length = 4, func = table.bcd},
flow = {pattern = {0xB, 0x3C}, length = 3, func = table.bcd},
tint = {pattern = {0xA, 0x5A}, length = 2, func = table.bcd},
text = {pattern = {0xA, 0x66}, length = 2, func = table.bcd},
err = {pattern = {0x4, 0xFD, 0x17}, length = 4, func = table.int}
}
function createDevices ()
addDevice{name = "V", shift = 0, base = 10, xtraFields = {'volume'} }
addDevice{name = "Q", shift = 0, base = 10, xtraFields = {'flow'} }
addDevice{name = "TI", shift = 0, base = 10, xtraFields = {'temp. internal'} }
addDevice{name = "TE", shift = 0, base = 10, xtraFields = {'temp. external'} }
addDevice{name = "ERR", shift = 0, base = 10, xtraFields = {'error flags'} }
end
function readRegister (reg, device, unitId)
if (not lastReadTimeStamp or (now - lastReadTimeStamp) >= POLL_DELAY ) then
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 > 10) then
-- parsing
for param, struc in pairs(dataHandlers) do
local foundPos = table.findPattern(meterDataFrame, struc.pattern)
if foundPos then
DBG('found data for ' .. param ..' at: ', foundPos)
local foundBytes = table.sub(meterDataFrame, foundPos
, foundPos + struc.length - 1)
DBG('found bytes: ', foundBytes)
foundBytes = table.reverse(foundBytes)
DBG("After reverse: ", foundBytes)
local actualData = struc.func(foundBytes)
DBG("actualData: ", actualData)
data[param] = actualData
lastReadTimeStamp = now
else
ERROR("failed to find data !")
data[param] = false
end
end
else
ERROR("failed to read reply!")
data = {}
end
end
if (device.name == 'V') then return data.volume end
if (device.name == 'Q') then return data.flow end
if (device.name == 'TI') then return data.tint end
if (device.name == 'TE') then return data.text end
if (device.name == 'ERR') then return data.err end
end
function writeRegister (reg, device, unitId, newValue)
end
-------------------------- Other helpers --------------------------
function readUntil(endByte)
local ONE_BYTE, buf = 1, {}
repeat
local rx = readBytes(ONE_BYTE)
if rx then
rx = rx[1] ; table.insert(buf, rx)
end
until (not rx or (rx == endByte))
return ((#buf >= ONE_BYTE) 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