L-M-T Long Cable Measurement
Documentation for the script purposed for long range cable measurement with use of LMT-CV-N-PEC2-D.
Introduction
The Converter is a battery-powered NB-IoT device that performs autonomous measurements at configurable intervals and reports status via NB-IoT connectivity. It features intelligent alarm burst reporting to extend battery life while ensuring timely fault notification.
The device is designed for monitoring pipe insulation and continuity of underground infrastructure. It operates completely autonomously with intelligent state-change detection and adaptive reporting modes to maximize battery longevity during both normal operation and fault conditions.
This article contains:
- Requirements
- Script Specification
- Downlink Configuration
- Installation Specifications
- Battery Lifetime Estimates
- Payload Showcase
Requirements
The following is required:
- Device LMT-CV-N-PEC2-D
- Following firmware allowing for further specified use of the device
- Following Lua script handling the device control can be downloaded HERE or seen/copied below
You may use Acrios GUI to upload or the Lua script or firmware, but the devices come with all the things pre-installed.
For the script configuration, you may use downlink commands or edit the script via use of .
Script Specification
The device uses a Lua script for its functionality and configuration.
Full Lua Script
ver = "1.0"
---- CONFIGURATION ----
APN = "auto"
PLMNID = "0"
protocol = "UDP"
ip = "192.168.0.20"
port = 4242
withImsi = true
receiveTimeout = 15000
-- Payload structure description
-- 1 - MessageType [IMSI - 16B][Framecounter unsigned int - 4B][0000 - 4x1B] ( 0 = OK 1 = Error state first 2 int for P1 second for P2 )
-- 2 - MessageType [IMSI - 16B][Framecounter unsigned int - 4B][0000 - 4x1B] [[unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ]]
-- 3 - MessageType [IMSI - 16B][Framecounter unsigned int - 4B][0000 - 4x1B] [[unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ]] [VCC in mV unsigned short- 2B]
MessageType = 3 -- IMSI (withImsi) 1 = 4B 0 as ok 1 as error [eq 0000] | 2 = for 1 type and (4B + 4B)for one channel which contains Resistance of LOOP and LEAK for both channels
------- DEVICE WAKEUP PERIOD --------
SendingPeriod = 60 * 60 -- send message in minutes
CheckPeriod = 55 -- check period in seconds
-- EEPROM IDX --
SendingPeriod_eeprom_idx = 40
CheckPeriod_eeprom_idx = 42
-- SHORTS
at = api.nbAT
b = string.char
pp = pack.pack
pu = pack.unpack
p = print
ex = api.exec
Insulation = "Insulation"
Continuity = "Continuity"
state = -2
ohm = 0
comment = ""
ErrStateFlag = 0
IMSI = ""
message = {
group = {
frameCounter = 0,
errorFlag = 1,
okFlag = 0,
maxErrMessInRow = 10,
ts = 0,
ts_a = 0,
errMessCounter = 0,
prevAlarm = false,
currentMode = "normal",
burstCount = 0,
lastSendTs = 0,
prevPattern = {},
prevAlarmPattern = {},
[1] = {
Insulation = { Insulation = Insulation, state = state, comment = comment, ohm = ohm },
Continuity = { Continuity = Continuity, state = state, comment = comment, ohm = ohm }
},
[2] = {
Insulation = { Insulation = Insulation, state = state, comment = comment, ohm = ohm },
Continuity = { Continuity = Continuity, state = state, comment = comment, ohm = ohm }
}
}
}
function comparePatterns(old, new)
local changes = 0
for i = 1, #old do
if old[i] ~= new[i] then
changes = changes + 1
p("Pattern changed at pos:" .. i .. ": " .. old[i] .. " -> " .. new[i])
end
end
return changes
end
function gt(state) return api.getTick(state) end
function wSc(mV) --waiting for the SC to be charged
if pow_source ~= "ac" then -- only if device has battery power
local vchkTs = gt(1)
while true do
local volt = api.getBatteryVoltage()
p("V =", volt, "mV")
if volt > mV or ((gt(1) - vchkTs) > 120000) then
break
end
ex("_sleep", 10)
end
end
end
function createStatePattern()
local pattern = {}
local tests = { Insulation, Continuity }
local index = 1
for i = 1, 2 do
for _, test_name in ipairs(tests) do
pattern[index] = message.group[i][test_name].state
index = index + 1
end
end
return pattern
end
function copyPattern(src)
local dst = {}
for i, v in ipairs(src) do
dst[i] = v
end
return dst
end
function getIMSI() return (string.gmatch(api.nbAT("IMSI?", 5000), "([0-9]+)")() or "000000000000000") end
function getIMEI() return (string.gmatch(api.nbAT("IMEI?", 5000), "([0-9]+)")() or "000000000000000") end
function getICCID() return (string.gmatch(api.nbAT("ICCID?", 5000), "([0-9]+)")() or "00000000000000000000") end
function compensateVoltage(measured_mv, batt_mv)
local V_ref = 3600
if batt_mv <= 0 then
return measured_mv
end
local num = measured_mv * V_ref
local half = math.floor(batt_mv / 2)
local compensated_mv = math.floor((num + half) / batt_mv)
return compensated_mv
end
function calc_pipeR(Vin_mV, VoutCh1_mV, VoutCh2_mV)
local Ch1_R = 18000
local Ch2_R = 27
local ch2R1 = -1
local ch1R1 = -1
if VoutCh1_mV == 0 or VoutCh1_mV < 0 then
ch1R1 = 1000000
else
local num1 = Ch1_R * Vin_mV
local half1 = math.floor(VoutCh1_mV / 2)
local rounded1 = math.floor((num1 + half1) / VoutCh1_mV)
ch1R1 = rounded1 - Ch1_R
end
if VoutCh2_mV == 0 or VoutCh2_mV < 0 then
ch2R1 = 1000000
else
local num2 = Ch2_R * Vin_mV
local half2 = math.floor(VoutCh2_mV / 2)
local rounded2 = math.floor((num2 + half2) / VoutCh2_mV)
ch2R1 = rounded2 - Ch2_R
end
return ch1R1, ch2R1, nil
end
-- Checks for insulation and continuity alarms based on measured resistances and cable length, using integer thresholds and tolerances.
function checkAlarms(insulation_ohm, continuity_ohm, length_m, group_id)
local INSULATION_THRESHOLD = 10000
local INSULATION_TOL_PERCENT = 20
local CONTINUITY_PER_100M = 30
local CONTINUITY_OPEN_THRESHOLD = 1000000
local CONTINUITY_TOL_PERCENT = 20
if insulation_ohm < 0 or continuity_ohm < 0 or length_m <= 0 then
message.group[group_id].Insulation.state = 1
message.group[group_id].Continuity.state = 1
return nil, nil, "ERROR: Invalid inputs (negative ohm or zero length)!"
end
local ins_temp = INSULATION_THRESHOLD * (100 - INSULATION_TOL_PERCENT)
local insulation_alarm_threshold = math.floor((ins_temp + 50) / 100)
local cont_temp_max = CONTINUITY_PER_100M * length_m
local max_continuity_ohm = math.floor((cont_temp_max + 50) / 100)
local cont_temp_threshold = max_continuity_ohm * (100 + CONTINUITY_TOL_PERCENT)
local continuity_alarm_threshold = math.floor((cont_temp_threshold + 50) / 100)
local insulation_alarm = (insulation_ohm < insulation_alarm_threshold)
local continuity_alarm = (continuity_ohm > continuity_alarm_threshold) or
(continuity_ohm >= CONTINUITY_OPEN_THRESHOLD)
local mess_t = nil
if insulation_alarm then
mess_t = "ALARM: Insulation (P" ..
tostring(group_id) ..
"-LOOP+ & P" ..
tostring(group_id) .. "-LEAK pins) under limit (< " .. insulation_alarm_threshold .. " ohm, tolerance -20%)"
message.group[group_id].Insulation.state = 1
elseif continuity_alarm then
mess_t = "ALARM: Continuity (P" ..
tostring(group_id) .. "-LOOP+ & P" .. tostring(group_id) .. "-LOOP-) over limit (> " ..
continuity_alarm_threshold .. " R, tolerance +20%) or open (>= 1 M)"
message.group[group_id].Continuity.state = 1
else
mess_t = "OK: No alarms (insulation=" ..
insulation_ohm .. "R, continuity=" .. continuity_ohm .. "R, length=" .. length_m .. "m)"
message.group[group_id].Insulation.state = 0
message.group[group_id].Continuity.state = 0
end
return insulation_alarm, continuity_alarm, mess_t
end
-- Reads measurements from a specified group, compensates voltages, calculates resistances, and checks for alarms.
function readGroup(group_id, averageCount)
local res, ch1, ch2, mv = api.loopMeasurement(group_id, 30, averageCount)
local item = message.group[group_id]
item.Insulation.state = message.group.okFlag
item.Continuity.state = message.group.okFlag
item.Insulation.comment = "ok"
item.Continuity.comment = "ok"
if res == 0 then
p("res:" ..
res ..
"\t ch1(mV):" .. ch1 .. "\t ch2(mV):" .. ch2 .. "\t batt(mV):" .. mv .. "\t avg:" .. averageCount)
p("Compensated Values")
local ch1_m = compensateVoltage(ch1, mv)
local ch2_m = compensateVoltage(ch2, mv)
p("res:" ..
res ..
"\t ch1(mV):" ..
ch1_m .. "\t ch2(mV):" .. ch2_m .. "\t batt(mV):" .. mv .. "\t avg:" .. averageCount)
local ch1_ohm, ch2_ohm, err = calc_pipeR(3600, ch1_m, ch2_m)
item.Insulation.ohm = ch1_ohm
item.Continuity.ohm = ch2_ohm
if err == nil then
p("Calculated Resistance of pipe")
p("ch1_ohm:" .. ch1_ohm .. "\t ch2_ohm:" .. ch2_ohm)
local insulation_alarm, continuity_alarm, alarm_msg = checkAlarms(ch1_ohm, ch2_ohm, 100, group_id)
p("Alarm check: " .. (alarm_msg or "No message"))
if insulation_alarm then
p("INSULATION ALARM!")
item.Insulation.comment = "INSULATION ALARM!"
item.Insulation.state = message.group.errorFlag
end
if continuity_alarm then
p("CONTINUITY ALARM!")
item.Continuity.comment = "CONTINUITY ALARM!"
item.Continuity.state = message.group.errorFlag
end
else
p(err)
end
else
p("res:" .. res .. "\t error")
end
end
function processResponse(rcvd)
p("Processing response...")
if rcvd == nil or #rcvd <= 0 then
p("No response...")
return
end
p("Received Data: ")
api.dumpArray(rcvd, "raw")
local cmd = rcvd:byte(1)
rcvd = rcvd:sub(2) -- drop command byte
local tmp
if cmd == 20 then -- period in minutes report
if #rcvd == 2 then
p("Sending Period in minutes")
_, tmp = pu(rcvd, "<H") -- 2 bytes, lil endian (unsigned short)
api.setVar(SendingPeriod_eeprom_idx, tmp * 60, true) -- config periodic message in minutes
SendingPeriod = api.getVar(SendingPeriod_eeprom_idx, SendingPeriod)
end
elseif cmd == 21 then -- period in seconds to check
if #rcvd == 2 then
p("Check Period in seconds")
_, tmp = pu(rcvd, "<H") -- 2 bytes, lil endian (unsigned short)
api.setVar(CheckPeriod_eeprom_idx, tmp, true) -- config check interval in seconds
CheckPeriod = api.getVar(CheckPeriod_eeprom_idx, CheckPeriod)
end
elseif cmd == 7 then -- compatibility command for reset
p("Resetting...")
ex("_reset")
elseif cmd == 25 then -- execute custom lua request command 19
local ok, func, err = pcall(loadstring, rcvd)
if ok then
pcall(func)
elseif err then
p("Error executing: ", err)
end
func = nil
end
end
-- UPLINK MESSAGES ------------------------------------------
function sendStandardMessage(with_rec)
-- cmdByte 10
p("SENDING MESSAGE:")
local sendImsi = withImsi and IMSI or getIMSI()
local messBuf = ""
local messOhm = 0
local batVolt = 0
for i = 1, #message.group do
local item = message.group[i]
if MessageType == 1 then
messBuf = messBuf ..item.Insulation.state .. item.Continuity.state
elseif MessageType >= 2 then
messBuf = messBuf .. item.Insulation.state .. item.Continuity.state
messOhm = messOhm .. pp("I2", item.Insulation.ohm, item.Continuity.ohm)
end
if MessageType == 3 then
batVolt = pp("<H", api.getBatteryVoltage())
end
end
message.group.frameCounter = message.group.frameCounter + 1
if MessageType == 1 then
msg = sendImsi ..pp("I",message.group.frameCounter).. messBuf
elseif MessageType >= 2 then
msg = sendImsi ..pp("I",message.group.frameCounter).. messBuf .. messOhm
end
if MessageType == 3 then
msg = sendImsi ..pp("I",message.group.frameCounter).. messBuf .. messOhm .. batVolt
end
p("message FCNT :"..tostring(message.group.frameCounter) )
api.dumpArray(msg, "raw")
wSc(2600) -- wait for SC charged
_, rcvd = api.nbSend(ip, port, msg, receiveTimeout, protocol)
if rcvd ~= nil and #rcvd > 0 and with_rec == 1 then
processResponse(rcvd)
end
end
function onWake()
ex("_sysclk", "48000000") -- speed up CPU
ex("_print_mode", "CONNECTED")
local errState = 0
local sent = false
p("OnWake")
p("Reading P1 ports")
readGroup(1, 1)
p("Reading P2 ports")
readGroup(2, 1)
local currentTs = gt(2)
local currentPattern = createStatePattern()
for i = 1, #message.group do
local item = message.group[i]
errState = errState + item.Insulation.state + item.Continuity.state
p("Error state value Insulation & Continuity:" .. tostring(item.Insulation.state),
tostring(item.Continuity.state))
end
p("Error state value:" .. tostring(errState))
local isAlarmNow = (errState > 0)
p("isAlarmNow: " .. tostring(isAlarmNow) .. " errState: " .. tostring(errState))
if isAlarmNow then
if not message.group.prevAlarm then
-- First alarm detection
sendStandardMessage(0)
sent = true
message.group.burstCount = 1
message.group.currentMode = "alarm_burst"
message.group.prevAlarmPattern = copyPattern(currentPattern)
p("First alarm burst send #1")
else
-- Continuing alarm
local patternChanged = false
if #message.group.prevAlarmPattern > 0 then
local diffs = comparePatterns(message.group.prevAlarmPattern, currentPattern)
if diffs > 0 then
patternChanged = true
p("Alarm pattern changed: " .. diffs)
end
end
if patternChanged then
message.group.burstCount = 0
message.group.currentMode = "alarm_burst"
end
if message.group.currentMode == "alarm_burst" then
if message.group.burstCount < message.group.maxErrMessInRow then
sendStandardMessage(0)
sent = true
message.group.burstCount = message.group.burstCount + 1
message.group.prevAlarmPattern = copyPattern(currentPattern)
p("Burst send #" .. message.group.burstCount)
if message.group.burstCount == message.group.maxErrMessInRow then
message.group.currentMode = "alarm_periodic"
p("Switching to alarm periodic")
end
end
elseif message.group.currentMode == "alarm_periodic" then
local timeElapsed = currentTs - message.group.lastSendTs
if timeElapsed >= SendingPeriod then
sendStandardMessage(1)
sent = true
message.group.prevAlarmPattern = copyPattern(currentPattern)
p("Periodic alarm send")
end
end
end
else
-- No alarm now
if message.group.prevAlarm then
-- Return to normal state
sendStandardMessage(0)
sent = true
message.group.currentMode = "normal"
message.group.burstCount = 0
message.group.prevAlarm = false
message.group.prevPattern = copyPattern(currentPattern)
p("Return to normal send")
else
-- Normal mode: check for pattern change
local changes = 0
if #message.group.prevPattern > 0 then
changes = comparePatterns(message.group.prevPattern, currentPattern)
end
if changes > 0 then
sendStandardMessage(1)
sent = true
p("Normal change send, changes: " .. changes)
end
message.group.prevPattern = copyPattern(currentPattern)
end
end
-- Always update state
message.group.prevAlarm = isAlarmNow
if sent then
message.group.lastSendTs = currentTs
end
api.wakeUpIn(0, 0, 0, CheckPeriod)
end
function onStartup()
ex("_sysclk", "48000000") -- speed up CPU
ex("_print_mode", "CONNECTED")
p("OnStart")
at("APN=" .. APN)
at("PLMNID=" .. PLMNID)
IMSI = getIMSI()
CheckPeriod = api.getVar(CheckPeriod_eeprom_idx, CheckPeriod)
message.group.ts = gt(2)
message.group.lastSendTs = message.group.ts
onStartup = nil
end
Connection Configuration
The following variables configure the connection.
Beware, that incorrectly setting up connection detail via downlink will disconnect the device.
APN = "auto" -- Automatic APN selection
PLMNID = "0" -- Auto-detect mobile operator
protocol = "UDP" -- Fixed protocol
ip = "192.168.0.20" -- Server IP address
port = 4242 -- Server UDP port
withImsi = true -- Include IMSI in payload
receiveTimeout = 15000 -- 15-second response timeout
Modifying the Script for Different SIM Card Providers
If you wish to use a SIM card coming from a different provider than Miotiq, modify the script accordingly.
1a. Set these parameters to the values provided by your SIM card provider.
...
APN = "auto" -- <- change this (but keep the commas, e.g.: "cdp.iot.t-mobile.nl")
PLMNID = 0 -- <- change this (e.g.: 20416)
...
1b. Or set the values as following for the automatic selection.
...
APN = "auto"
PLMNID = 0
...
The automatic selection has to be supported by the SIM card provider.
2. If you are using our backend, set IMSI to 1.
...
withIMSI = 1
...
Message Configuration
The following variables configure the messages.
You may configure the following aspects:
Type 1 – Status Only
Sends only alarm states (OK/Alarm) for both P1 and P2 channels. No resistance or battery data. Smallest message size (24 bytes). Best for minimal bandwidth requirements where you only need to know if an alarm occurred.
Type 2 – Status + Resistance
Includes alarm states plus measured resistance values in ohms for insulation and continuity on both channels. Useful for diagnostics and understanding fault severity. Message size: 40 bytes.
Type 3 – Status + Resistance + Battery (Recommended)
Complete monitoring payload with alarm states, resistance values, and battery voltage in millivolts. Enables predictive battery maintenance and full device diagnostics. Recommended for production deployments. Message size: 42 bytes.
| Type | Size | Alarm State | Resistance | Battery |
|---|---|---|---|---|
| 1 | 24B | ✅ | ❌ | ❌ |
| 2 | 40B | ✅ | ✅ | ❌ |
| 3 | 42B | ✅ | ✅ | ✅ |
-- Payload structure description
-- 1 - MessageType [IMSI - 16B][Framecounter unsigned int - 4B][0000 - 4x1B] ( 0 = OK 1 = Error state first 2 int for P1 second for P2 )
-- 2 - MessageType [IMSI - 16B][Framecounter unsigned int - 4B][0000 - 4x1B] [[unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ]]
-- 3 - MessageType [IMSI - 16B][Framecounter unsigned int - 4B][0000 - 4x1B] [[unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ][unsigned int value in ohm - 4B ]] [VCC in mV unsigned short- 2B]
MessageType = 3 -- IMSI (withImsi) 1 = 4B 0 as ok 1 as error [eq 0000] | 2 = for 1 type and (4B + 4B)for one channel which contains Resistance of LOOP and LEAK for both channels
Device Wake-up Period and EEProm IDX
You may further configure the sendig period and check period.
Note that the value you input is in seconds
------- DEVICE WAKEUP PERIOD --------
SendingPeriod = 60 * 60 -- send message in seconds (this would be 60 minutes)
CheckPeriod = 55 -- check period in seconds
-- EEPROM IDX --
SendingPeriod_eeprom_idx = 40
CheckPeriod_eeprom_idx = 42
Downlink Configuration
The device supports over-the-air configuration via NB-IoT downlink messages. Remote commands are processed in the processResponse() function.
HEX Conversion
The downlink payloads have to be sent in HEX downlink payload. Check the following mini guide to see how to do the HEX conversion.
HEX Conversion Mini Guide
Number conversion
If the downlink is in form of a number, following needs to be done.
For number conversion, use HEX converter such as this one
- Enter a number you want to convert, e.g. 4000

-
Now use the little endian version, which would be
A0 0Fin this case. -
This would be the downlink message, if you were to change e.g. initial delay. Make sure that there is a space behind the last byte for the downlink to be valid.
If you were to convert a number, that has one byte only, but the requirement for payload is 2 bytes, simply replace the second byte (or other amount of unused bytes) with 00
For example, 0A is 10, therefore downlink would look like this: 0A 00
Text conversion
For text conversion, use HEX converter such as this one
This would be used mostly for custom Lua commands in case of this script.
- Enter a text you would like to convert, e.g.
print("Hi")

-
And simply copy the converted HEX payload, which would be
70 72 69 6E 74 28 22 48 69 22 29in this case. -
Add a relevant downlink command in front of your message, in this case preface the HEX string with
19as that is a command for a custom Lua command. -
The resulting downlink message would be
19 70 72 69 6E 74 28 22 48 69 22 29
The message structure is as follows:
The first part of the message is the command number:
14 - (0x14)
Then it follows either with a numerical value, or the command is all that is needed, or the hex can be a Lua code snippet.
In this case, we are using command 0x14, which changes the sending period, so we follow it up with and integer value:
1E
And since it is a smaller number, we will follow it with an empty byte:
00
The message would look as follows:
14 1E 00 - changes sending period to every 30 minutes
Command 20 – Change Measurement Sending Period
What it does: Controls how often the device sends status messages when no faults are detected.
Use case:
- Default is 1 hour (3600 seconds) – good for most installations
- Change to 30 minutes (1800 seconds) if you want more frequent updates
- Change to 6 hours if you want to save battery in non-critical areas
How to send:
The downlink should have a following form.
Command Code: 0x14
Parameter: [Period in minutes]
Example 1: Change to 30 minutes
Hex message: 14 1E 00
Breakdown: 14 = Command code (0x14)
1E = 30 minutes (little-endian: 0x001E)
00 = (continuation of 2-byte value)
Example 2: Change to 1 hour (default)
Hex message: 14 3C 00
Breakdown: 14 = Command code
3C = 60 minutes (0x003C)
00 = (continuation)
Example 3: Change to 6 hours
Hex message: 14 68 01
Breakdown: 14 = Command code
68 01 = 360 minutes (0x0168)
Practical impact:
| Sending Period | Message Frequency | Battery Impact |
|---|---|---|
| 30 minutes | 2× hourly | ~10% higher consumption |
| 1 hour (default) | Every hour | Baseline |
| 6 hours | 4× daily | ~60% lower consumption |
Command 21 – Change Measurement Check Period
What it does: Controls how often the device wakes up to perform measurements.
Use case:
- Default is 55 seconds – detects faults within ≤60 seconds
- Change to 120 seconds if you only need fault detection every 2 minutes (saves battery)
- Change to 30 seconds if you need very fast fault detection (uses more battery)
How to send:
The downlink should have a following form.
Command Code: 0x15
Parameter: [Period in seconds]
Example 1: Change to 55 seconds (default)
Hex message: 15 37 00
Breakdown: 15 = Command code (0x15)
37 = 55 seconds (0x0037)
00 = (continuation)
Example 2: Change to 120 seconds (2 minutes)
Hex message: 15 78 00
Breakdown: 15 = Command code
78 = 120 seconds (0x0078)
00 = (continuation)
Example 3: Change to 30 seconds
Hex message: 15 1E 00
Breakdown: 15 = Command code
1E = 30 seconds (0x001E)
00 = (continuation)
** Important:** CheckPeriod affects fault detection latency. If you set it to 120 seconds, faults may take up to 2 minutes to be detected.
Practical impact:
| Check Period | Fault Detection Time | Battery Impact |
|---|---|---|
| 30 seconds | ≤ 30 seconds | ~30% higher consumption |
| 55 seconds (default) | ≤ 60 seconds | Baseline |
| 120 seconds | ≤ 120 seconds | ~25% lower consumption |
Command 7 – Reset Device
What it does: Performs a complete device restart. Configuration (from EEPROM) is preserved.
Use case:
- Device is stuck or unresponsive
- After updating firmware
- Troubleshooting connectivity issues
How to send:
The downlink should have a following form.
Command Code: 0x07
(No parameters required)
Example:
Hex message: 07
Effect:
- Device reboots immediately
- All EEPROM settings retained
- Device reconnects to NB-IoT network
- Measurement cycle restarts fresh
Time to recovery: ~30–60 seconds after reset command is sent
Command 25 – Execute Custom Lua Code
What it does: Runs custom Lua scripts directly on the device.
Use case:
- Advanced debugging
- Querying current device state
- Custom automation
- Technical support requests
How to send:
The downlink should have a following form.
Command Code: 0x19
Parameter: [Lua script code]
Example 1: Reset frame counter
Hex message: 19 6D 65 73 73 61 67 65 2E 67 72 6F 75 70 2E 66 72 61 6D 65 43 6F 75 6E 74 65 72 20 3D 20 30
Lua code: message.group.frameCounter = 0
Example 2: Query battery voltage
Hex message: 19 70 28 22 42 61 74 74 65 72 79 3A 20 22 20 2E 2E 20 61 70 69 2E 67 65 74 42 61 74 74 65 72 79 56 6F 6C 74 61 67 65 28 29 20 2E 2E 20 22 20 6D 56 22 29
Lua code: p("Battery: " .. api.getBatteryVoltage() .. " mV")
Installation Specifications
Electrical Specifications
| Parameter | Value |
|---|---|
| Excitation Voltage | 3.6 VDC |
| Measurement Interval (default) | 55 seconds |
| Reporting Interval (normal) | 3600 seconds (1 hour) |
| Protocol | NB-IoT (UDP) |
| Default Server Port | 4242 |
Cable and Measurement Specifications
| Parameter | Value | Notes |
|---|---|---|
| Maximum Cable Length | 300 m | Supported measurement range |
| Wire Resistivity | 0.012–0.015 Ω/m | Test wire specification |
| Pipe Length Configuration | 0–300 m | User-configurable |
Measurement Principle
The device uses a voltage divider principle for pipe measurement:
- Insulation Test (R1-2 / LOOP+ to LEAK): Measures resistance between positive excitation and insulation monitoring line
- Continuity Test (R1-3 / LOOP+ to LOOP-): Measures resistance between positive and negative conductors of the pipe
Both measurements are performed simultaneously at regular CheckPeriod intervals.
Alarm Thresholds
Insulation Alarm (R1-2):
- Threshold: < 10 kΩ
- Tolerance: ±20%
- Effective alarm level: < 8 kΩ
- Action: Triggers insulation fault report
Continuity Alarm (R1-3):
- Base threshold: 30 Ω per 100 m of configured pipe length
- Open/over-range threshold: ≥ 1 MΩ
- Tolerance: ±20% (applied to calculated threshold)
- Action: Triggers continuity fault report
Example Continuity Calculation (100 m cable):
Base threshold: 30 Ω/100m × 100m = 30 Ω
With tolerance: 30 Ω × (1 + 0.20) = 36 Ω alarm threshold
Example Continuity Calculation (300 m cable):
Base threshold: 30 Ω/100m × 300m = 90 Ω
With tolerance: 90 Ω × (1 + 0.20) = 108 Ω alarm threshold
Physical Connector and Wiring
Terminal Mapping:
| Terminal | Group | Function |
|---|---|---|
| P1-LOOP+ | P1 (Pair 1) | High potential excitation (positive) |
| P1-LEAK | P1 (Pair 1) | Insulation measurement point |
| P1-LOOP- | P1 (Pair 1) | Continuity measurement return |
| P2-LOOP+ | P2 (Pair 2) | High potential excitation (positive) |
| P2-LEAK | P2 (Pair 2) | Insulation measurement point |
| P2-LOOP- | P2 (Pair 2) | Continuity measurement return |
Cable Arrangement:
Device Terminal Cable Under Test
─────────────────────────────────────────
P1-LOOP+ ─────────────────── [Conductor A]
│
[Device]
│
P1-LOOP- ─────────────────── [Conductor B]
P1-LEAK ────────────────────── [Separate insulation line]
Constraints:
- Maximum cable length: 300 meters
- Wire resistivity specification: 0.012–0.015 Ω/m
- Both P1 and P2 groups can measure independent cable pairs simultaneously
Battery Lifetime Estimates
Normal Operation (No Faults)
Measurement profile: Check every 55 seconds; send status every 1 hour
| Battery Capacity | Battery Voltage | Energy per Measurement | Expected Battery Life |
|---|---|---|---|
| 1D 19Ah (no margin) | 3.4V | 1.13 mWh | 6.6 years |
| 1D 19Ah (30% safety margin) | 3.4V | 1.13 mWh | 4.6 years |
The 30% safety margin is recommended for production deployments to account for:
- Temperature fluctuations
- Measurement variability
- Network latency spikes
- Unexpected alarm conditions
Alarm State Impact
Battery consumption increases significantly during fault conditions due to burst messaging:
Alarm Burst Phase:
- Up to 10 messages at ~55-second intervals
- Total duration: ~550 seconds (~9 minutes)
- Increased power draw due to frequent NB-IoT transmissions
Pattern Change Reset:
- If alarm pattern changes, burst count resets
- Additional 10-message cycle begins
- Worst-case scenario: Alternating faults can accelerate battery drain
Periodic Alarm Phase:
- After 10 burst messages, falls back to 1-hour intervals
- Consistent with normal operation reporting rate
Recommendation: For installations with frequent or intermittent faults, plan battery replacement more conservatively (3–4 year intervals).
Voltage Compensation
The firmware automatically compensates for battery voltage variations to maintain measurement accuracy across the discharge curve:
V_ref = 3.6V
compensated_mv = (measured_mv × V_ref) / batt_mv
This ensures reliable alarm detection even as battery voltage decays from 3.6V to 2.8V.
Reporting Logic and Messaging
Measurement and State Detection
Measurement Cycle:
- Device wakes at CheckPeriod interval (default: 55 seconds)
- Performs voltage measurements on both P1 and P2 groups
- Compensates voltages for current battery level
- Calculates resistance values via voltage divider formula
- Evaluates alarms against configured thresholds
- Creates current state pattern: [P1_Ins, P1_Cont, P2_Ins, P2_Cont]
State Pattern Definition:
0 = OK (no alarm)
1 = Alarm (threshold exceeded)
Example: [0, 0, 1, 0] = P2 insulation alarm, all others OK
Payload Showcase
Message Type 3
Raw Payload (hex):
39 30 31 32 38 30 30 37 37 32 35 35 38 37 34 01 00 00 00 00 00 00 00 6B 26 00 00 1B 00 00 00 58 1F 00 00 64 00 00 00 A0 0D
Breakdown
| Field | Bytes | Value | Interpretation |
|---|---|---|---|
| IMSI | 16B | 39 30 31 32 38 30 30 37 37 32 35 35 38 37 34 | 901280077255874 |
| FrameCounter | 4B | 01 00 00 00 | 1 (first message) |
| P1 Insulation State | 1B | 00 | OK |
| P1 Continuity State | 1B | 00 | OK |
| P2 Insulation State | 1B | 00 | OK |
| P2 Continuity State | 1B | 00 | OK |
| P1 Insulation Resistance | 4B | 6B 26 00 00 | 9835 Ω (9.8 kΩ) |
| P1 Continuity Resistance | 4B | 1B 00 00 00 | 27 Ω |
| P2 Insulation Resistance | 4B | 58 1F 00 00 | 8024 Ω (8.0 kΩ) |
| P2 Continuity Resistance | 4B | 64 00 00 00 | 100 Ω |
| Battery Voltage | 2B | A0 0D | 3488 mV (3.488V) |
Summary
Device: 901280077255874
Status: All measurements healthy, no alarms detected
P1 Group:
- Insulation: 9.8 kΩ (within limits)
- Continuity: 27 Ω (reference resistor baseline)
P2 Group:
- Insulation: 8.0 kΩ (within limits)
- Continuity: 100 Ω (good condition)
Battery: 3.488V (good condition, plan replacement within 1 year)
Message Type 2
Raw Payload (hex):
39 30 31 32 38 30 30 37 37 32 35 35 38 37 34 01 00 00 00 00 00 00 00 77 26 00 00 1B 00 00 00 58 1F 00 00 64 00 00 00
Breakdown
| Field | Bytes | Value | Interpretation |
|---|---|---|---|
| IMSI | 16B | 39 30 31 32 38 30 30 37 37 32 35 35 38 37 34 | 901280077255874 |
| FrameCounter | 4B | 01 00 00 00 | 1 (first message) |
| P1 Insulation State | 1B | 00 | OK |
| P1 Continuity State | 1B | 00 | OK |
| P2 Insulation State | 1B | 00 | OK |
| P2 Continuity State | 1B | 00 | OK |
| P1 Insulation Resistance | 4B | 77 26 00 00 | 9847 Ω (9.8 kΩ) |
| P1 Continuity Resistance | 4B | 1B 00 00 00 | 27 Ω |
| P2 Insulation Resistance | 4B | 58 1F 00 00 | 8024 Ω (8.0 kΩ) |
| P2 Continuity Resistance | 4B | 64 00 00 00 | 100 Ω |
Summary
Device: 901280077255874
Status: All measurements healthy, no alarms detected
P1 Group:
- Insulation: 9.8 kΩ (within limits)
- Continuity: 27 Ω (reference resistor baseline)
P2 Group:
- Insulation: 8.0 kΩ (within limits)
- Continuity: 100 Ω (good condition)
Note: This is Message Type 2 (status + resistance). Battery voltage is not included.
Message Type 1
Raw Payload (hex):
39 30 31 32 38 30 30 37 37 32 35 35 38 37 34 01 00 00 00 00 00 00 00
Breakdown
| Field | Bytes | Value | Interpretation |
|---|---|---|---|
| IMSI | 16B | 39 30 31 32 38 30 30 37 37 32 35 35 38 37 34 | 901280077255874 |
| FrameCounter | 4B | 01 00 00 00 | 1 |
| P1 Insulation State | 1B | 00 | OK |
| P1 Continuity State | 1B | 00 | OK |
| P2 Insulation State | 1B | 00 | OK |
| P2 Continuity State | 1B | 00 | OK |
Summary
Type 1 contains only the first 24 bytes of Type 2.
All channels report healthy (no alarms). Resistance values are omitted.
- Device: 901280077255874
- P1: Both OK
- P2: Both OK
- Size: 24 bytes (vs 40 bytes for Type 2)