User Tools

Site Tools


hydrus-flowmeter

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 
hydrus-flowmeter.txt ยท Last modified: 2024/02/20 14:42 by emozolyak

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki