The implemented LUA version is 5.1.5. The LUA API changes will be reflected within the firmware changelog.
The following LUA functions are implemented in the firmware:
"byte"
"char"
"dump"
"find"
"format"
"gfind"
"gmatch"
"gsub"
"len"
"lower"
"match"
"rep"
"reverse"
"sub"
"upper"
Turns ON or OFF the on board LED. It can be also used for debugging purposes.
--turns on blue LED
api.ledControl(1)
Pause the execution for ms milliseconds.
api.delayms(1000) --delay one second
api.ledControl(1) -- turn on blue LED
api.delayms(1000) -- wait 1 second
api.ledControl(0) -- turn off blue LED
Generate random number within given range.
Available only for LoRaWAN devices (function uses it's module to generate random number)
-- generates random number within interval from 5 to 20
ran=api.randInt(5, 20)
Returns an unique integer number in range <0; 2^32>.
--get an unique number
num = api.getUniqueNumber()
Get curent battery voltage in mV.
--get battery voltage value in mV
mv = api.getBatteryVoltage()
Returns current number of milliseconds since startup. Counts up to 2^32 and then restarts from 0. By default it's using RTC derived tick which is incremented during sleep, but it's possible to explicitly select systick, which isn't.
--get a timestamp, can be used for timing
timestamp = api.getTick()
-- to show the amount of milliseconds you can use print() function
print(timestamp)
Prints contents of variable as hexadecimal string (dumps array into console)
--print string "123ef" as hexadecimal
api.dumpArray("123ef")
--OUTPUT 00 : 31 32 33 65 66
Performs operation with given floating point data.
Only if it's allowed by the firmware.
Convert function available from FW version v2.5.1
-- Multiply IEE 754 float with a floating point constant and return coerced value
-- S - string float representation, e.g. “2.234”
-- B - binary little endian IEEE 754 representation as 4 characters/bytes in a string
ret = api.float("mul", "SSN", "12.8", "1000.0")
print(ret)
-- output = 12800
ret = api.float("mul", "SSS", "12.8", "1000.0")
print(ret)
-- output = 12800.0000
-- hexadecimal interpretation of a number 12.8 is 0x414CCCCD (IEE 754)
x=string.char(0xCD,0xCC,0x4C,0x41)
ret = api.float("mul", "BSS", x, "1000.0")
print(ret)
-- output = 12800.0000
-- Convert integer to IEE 754 float
-- N - Input in little endian integer(for convert only)/Returns ouput as little endian integer
-- I - Binary little endian integer representation as 4 characters/bytes in a string(for convert only).
-- B - binary little endian IEEE 754 representation as 4 characters/bytes in a string
-- > - returns result as big endian, if used as output IEEE 754 representation as 4 characters/bytes in a string
-- < - returns result as little endian, if used as output IEEE 754 representation as 4 characters/bytes in a string (default value)
y=string.char(0xCD,0xCC,0x4C,0x42)
ret = api.float("convert", "IB", y)
api.dumpArray(ret)
-- output = 9A 99 84 4E
y=125
ret = api.float("convert", "NB>", y)
api.dumpArray(ret)
-- output = 42 FA 00 00
Saves a persistent variable value, can be used between different wake up iterations.
--set persistent variable value from index 1000 to value of 3424
--can be used to send different data between wake-ups
--for variables persistent between device reset,
--use indexes 48 to 1071
api.setVar(1000, 3424)
Returns persistent variable value, can be used between different wake up iterations.
--get persistent variable value from index 1000
--can be used to send different data between wake-ups
--for variables persistent between device reset,
--use indexes 48 to 1071
slotNumber = api.getVar(1000)
Schedules the next wake up event of the device to provided day of month (day), hour, minute and second. The provided wake up date is therefore absolute and not relative as in wakeUpIn().
--schedules next wake up to the 25th, 2:22:58
status = api.wakeUpAt(25, 2, 22, 58)
Schedules the next wake up event of the device after specified time interval. The provided wake up date is therefore relative and not absolute as in wakeUpAt(). Note: The input arguments are not limited, but the total period specified must not exceed 31 days. (e.g. hour = 40, days = 2 gives a period of 3 days and 16 hours).
--schedules next wake up in 1 day and 122 minutes
status = api.wakeUpIn(1, 0, 122, 0)
Returns current date and time set on this device. The time can be synchronized over LoRa, or when uploading LUA script using LUA scripting interface.
--read current date and time
y,M,d,h,m,s = api.getTimeDate()
Set date and time on this device.
--set date and time
api.getTimeDate(2022, 1, 31, 24, 59, 0)
Sends buffer msg to LoRa. Acknowledged or non-acknowledged transport can be used using ack parameter. Maximum execution time is limited by timeout in miliseconds.
--sends 0xCCBBAA35 to LoRa with 20s timeout and acknowledged mode
msg = pack.pack('<b4', 0xCC, 0xBB, 0xAA, 0x35)
status, port, answer = api.loraSend(1,20000, msg)
if status >= 1 then --checks if device received more than one 1 byte
api.dumpArray(answer) --if true, print the data
end
Sends join request.
Rejoin is made automaticaly once per 7 days by default, but it can be changed using following "api.loraSetup" function.
-- check if joined, if not join ...
api.loraJoin()
-- join!
api.loraJoin(1)
Sets parameters defined by strings s1 and s2.
This function resets previous activation settings. Meaning, if you call repeatedly api.loraSetup("ACTIVATION", "OTAA") it'll send join request each time this function is called.
“ACTIVATION”
- Type of activation“ADR”
- Adaptive Data Rate“DR”
- Data Rate“DC”
- Duty Cycle“OTAA-REJOIN-CONFIRMED-FAILED-COUNTER"
- Sets number of confirmed messages after join reuest (cannot be lower than 5, which is also default number)"OTAA-MINIMAL-CONFIRMED-PERIOD"
- Sets period of time (in milliseconds) to send join reuest (by default 7 days)"OTAA"
, "ABP"
"ON"
, "OFF"
, "DCTIME"
, "GET"
--set activation type as ABP
api.loraSetup("ACTIVATION", "ABP")
--set data rate to 0
api.loraSetup("DR", 0)
Listens for message until received or timeout in class C.
--Listen on LoRa for a message with default timout of 10 seconds
status, port, buffer = api.loraListenClassC()
Sets only one LoRa credential.
--Set one LoRa credential (in this case devEUI)
api.loraSetCredential("devEUI", "3333333333333333")
Sets LoRa credentials.
--Set LoRa credentials
api.loraSetCredentials("22011221","3333333333333333","44444444444444444444444444444444","55555555555555555555555555555555","70B344440013333")
Sends buffer msg to NB on specified IP, port and protocol type. Maximum length of Rx and Tx messages is 512 Bytes. Maximum execution time is limited by timeout miliseconds.
-- sends "test message" string to IP 185.8.239.192 on port 5566 with 6s timeout
status,answer = api.nbSend("185.8.239.192", 5566, "test message", 6000, "UDP")
-- sends buffer to IP and port specified in GUI with 6s timeout
buf, err, proto, wake, ip, port, ctx = api.getGUIContext()
status,answer = api.nbSend(ip, port, "test message", 6000, proto)
Sends an AT command to NB module with specified timeout.
-- Get IMSI number of SIM card with timeout of 4 seconds
res = api.nbAT("AT+CIMI", 4000)
print(res)
-- IMEI --
x=api.nbAT("AT+CGSN")
start = string.find(x, "\n")
finish = string.find(x, "\r", start+1)
imei = string.sub(x,(start+1),(finish-1))
print("IMEI: " .. imei)
-- Get signal strength
res,b = api.nbAT("AT+CSQ")
print(res)
Transmits Dali command to specified address.
-- initialize Dali device
ans = api.daliTransaction(0xA500)
Read analog value on defined pin.
Disabled by defaul, FW & HW changes required.
AnalogChannel=1
mV = api.AnalogReadPin(AnalogChannel)
print("Raw value: " .. tostring(mV) .. " mV")
Waits for specified event on defined pin. If event occurs, device wakes up and calls onWake() function.
Read DIO pin state.
Writes DIO pin state.
-- set logical 1 to pins 2 and 3, and logical 0 to pins 1 and 4
api.DIOwritePin(1, 0)
api.DIOwritePin(2, 1)
api.DIOwritePin(3, 1)
api.DIOwritePin(4, 0)
-- read state of pins 1 to 4
print("pin 1 set to:", api.DIOreadPin(1))
print("pin 2 set to:", api.DIOreadPin(2))
print("pin 3 set to:", api.DIOreadPin(3))
print("pin 4 set to:", api.DIOreadPin(4))
Returns temperature in milli degrees of Celsius (m°C) from DS18B20. In case of using multiple DS18B20 connected to one data pin, it's possible to address one DS18B20 with ID.
DS18B20 is functional after hardware changes. Also has to be allowed by the firmware.
-- Search all devices on the 1-Wire bus
found=api.ds18b20Search(1,2)
-- `found` contains a table of addresses of found devices
-- or `nil` when no device was found
-- Print count of devices found
if (found)
then
print("Found " .. #found .. " devices")
-- Loop over all addresses
for i = 1,#found,1
do
temp=api.ds18b20GetTemp(1,2,found[i])
temp = temp/1000
print("Teplomer #" .. i .. " " .. temp .. "C")
end
else
print("No devices found!")
end
--Without addressing (one sensor=one data pin)
res1 = api.ds18b20GetTemp(1, 2) -- get temperature from sensor connected to data pin 2
print("Temperature: "..tostring(res).." mDeg.C")
Prints into console address of all the devices.
--Find addresses of 3 sensors
api.ds18b20Search(1, 2, 3)
Reports current value of S0 channel counter (specified in channel input argument).
By calling this function, an internal shadow variable for the channel counter is updated, so that the counter for onThreshold() event is reset.
--Read the value of S0 channel 3 and stores to given variable (val)
val = api.S0readCounter(3)
Used to set the value of S0 counter in a non-volatile memory. Typicaly used on startup to restore current value.
--Set counter value for channel 0 to the value of 100
api.S0initializeCounter(1, 100)
Defines a threshold between current value of S0 channel counter and last reported value. When difference of these last two reaches the value, the onThreshold() event is called.
--Set threshold for channel 2 to the value of 10000
api.S0setThreshold(2, 10000)
Transmit "msg" and wait given "timeout" milliseconds for the answer. The transmission is retried "retry" times.
Turn on MBus using mbusState first.
--Send MBus frame [0x10, 0x50, 0x30, 0x16] and wait 5s for the answer twice
msg = pack.pack('<b4', 0x10, 0x50, 0x30, 0x16)
status,c,a,ci,ans = api.mbusTransaction(msg, 5000, 2)
Configures the MBus communication interface.
Configuration from GUI is used by default, but it can be overriden using this function.
Turn on M-Bus using mbusState after setting up M-Bus parameters using this function.
--setup M-Bus interface to 9600 Baud, 8E2
api.mbusSetup(9600, 2, 2, 8)
Turns on the M-Bus circuitry.
Use api.mbusState(1) before api.mbusTransaction() and api.mbusState(0) after to reduce consumption. The consumption significantly raises if the circuitry is turned on for too long.
Do not use the api.mbusState(1) during LoRaWAN or NB-IoT message transmition.
api.mbusState(1) --turn on M-Bus
Scan for Mbus devices.
api.mbusSetup(2400,2,1,8)
api.mbusState(1) --turn on M-Bus
e,cnt = api.mbusScan("read") -- scan for everything(ID, man, ...) & use default timeout 3000 ms
for i = 1, cnt do
print(string.format("%08X", (e[i].identification)))
end
api.mbusState(0) --turn off M-Bus
This function is used for secondary addressing using 0xFD address internally. For more details check the example.
Example how to use api.mbusFilter for one secondary address
x=pack.pack("<I",0x22003287) -- store little endian unsigned integer value of 0x22003287 to "x" variable
api.mbusFilter("populate",x) -- insert the meter ID to mbusFilter
status,_,_,_,_,raw = api.mbusTransaction(0,3000,1) -- "mbusFilter" is a table to manage secondary addresses starting with index 0. Since ID "0x22003287" is first inserted value, the index of this ID is "0"
api.dumpArray(raw) -- print the content of variable raw
Example using while function to get complete mbus frame (if it's available)
--"i"=index in the table created by api.mbusFilter() (starting with 0)
status,_,_,_,_,raw = api.mbusTransaction(i,3000,1)
if status > 1 then
while status > 1 do
status,_,_,_,_,rawNext = api.mbusTransaction("",200,1)
if status > 1 then
raw = raw .. rawNext
else
break
end
end
end
api.dumpArray(raw)
Function to create and manipulate internal table conatining secondary addresses.
baudrate = 2400 -- baudrate: up to 921600 baud
parity = 2 -- communication parity: 0 for none, 1 for odd and 2 for even parity
stopBits = 1 -- number of stop bits: 1 or 2
dataBits = 8 -- number of data bits: 7 or 8
-- b18 = bytes to pack in total
-- 0x02 = number of physical quantities/filters in this first group
-- 0x03 = number of following bytes
-- 0xFD, 0xDC, 0xFF = first applied filter in first group
-- 0xFD, 0xC9, 0xFF = second applied filter in first group
api.mbusVifDifFilter("populate",pack.pack("b18", 0x02, -- first group (index 0)
0x03, 0xFD, 0xDC, 0xFF, -- current
0x03, 0xFD, 0xC9, 0xFF, -- voltage
0x02, -- second group (index 1)
0x03, 0xFD, 0xDA, 0xFF, -- current
0x03, 0xFD, 0xC8, 0xFF)) -- voltage
api.mbusSetup(baudrate,parity,stopBits,dataBits)
api.mbusState(1)
api.delayms(2000)
-- choose only one index
api.mbusVifDifFilter("activate", 0) -- index 0
-- api.mbusVifDifFilter("activate", 1) -- index 1
b=pack.pack('<b5', 0x10, 0x7B, 0xFE, (0x7B+0xFE)%256, 0x16)
status,_,_,_,_,raw = api.mbusTransaction(b,3000,1)
api.mbusState(0)
print("From MBus Calorimeter: ")
api.dumpArray(raw)
Function filtering received M-Bus frame by given group of bytes (VIF/DIF)
Changes the configuration of W M-bus.
--setup W M-bus interface to power of 10 dBm, role master/concentrator and T2 mode
api.wmbusSetup(10, "master", "T2")
Sets W M-bus C field.
-- Set W M-bus C field as 128
api.wmbusSetCField(128)
Sets W M-bus header.
-- Set W M-bus header for specific device
print("--- W M-bus T1 Digital Thermometer ---")
api.wmbusSetup(10, "meter", "T1")
api.wmbusSetCField(17)
api.wmbusSetHeader(123, 123456, 2, 33)
Sends frame through W M-bus.
-- Send W M-bus frame
api.wmbusSendFrame(212, "foobar")
Waits timeout milliseconds for data reception from W M-bus.
-- Receive W M-bus data with 2000 ms timeout
status, cfield, manid, id, ver, devtype, ci, payload = api.wmbusReceiveFrame(2000)
print(status)
print(id)
print(payload)
Waits timeout milliseconds for data reception from W M-bus.
Only available for W M-bus in concentrator role.
x=string.char(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
status, cfield, manid, id, ver, devtype, ci, payload = api.wmbusReceiveSendFrame(200, x, 2000)
print(status)
print(id)
print(payload)
Send data and receive from W M-bus device
This function is only available for W M-bus in meter role.
x=string.char(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
status, cfield, manid, id, ver, devtype, ci, payload = api.wmbusReceiveSendFrame(200, x)
print(status)
print(id)
print(payload)
Fubction and it's operations to set and filter received Wireless M-Bus frame.
This function is only available for W M-bus in meter role.
Sends msg to RS485 bus.
Turn on RS485 using rs485State first.
--sends 'test' string to RS485
api.rs485Send('test')
Change the configuration of RS485 interface.
baudrate (integer) - Baudrate to use for communication (up to 921600 baud)
parity (integer) - Parity, 0 for none, 1 for odd and 2 for even parity
stopBits (integer) - Number of stop bits, 1 or 2 allowed
dataBits (integer) - Number of data bits, 7 or 8 allowed
--setup RS485 interface to 9600 Baud, 8E1
api.rs485Setup(9600, 2, 1, 8)
Turns on the RS485 circuitry.
Must be used before rs485Send or rs485Receive.
api.rs485State(0) --turn off RS485
Waits timeout milliseconds for data reception from RS485 bus.
Turn on RS485 using rs485State() function first.
After the first character is received the inter-character delay is 10ms. If the inter-character delay needs to be longer the function should be called again.
--waits 1s for answer from RS485 bus
ans,len = api.rs485Receive(1000)
Calculates Modbus request checksum.
--calculate checksum for Modbus request 110100010002
req = pack.pack('<b6', 0x11,0x01,0x00,0x01,0x00,0x02)
crc = api.modbusCrc(req) --crc = "EE9B"
Processes NVT message and either sets baudrate, datasize, parity or stop size for MBUS or MODBUS.
-- If massage received (buf) send it to RS485
ret,port,buf = api.loraSend(0,1000,data)
if buf ~= nil then
buf, nvtans = api.nvtProcess(buf)
api.rs485Send(buf)
end
Encodes message to NVT format
ans,len = api.rs485Receive(50)
ans = api.nvtEncode(ans)
api.loraSend(0,1,ans)
Please also check our public GitLab repository HERE
Here you can find default LoRaWAN LUA scripts for IoT Converter
----- CONFIGURATION ----
------- LoRaWAN --------
receiveTimeout = 50
rxTimeout = 10000
ack = 1
------ Timing ---------
--- wakeup interval ---
periodMinutes = 60
-----------------------
-- get and format S0 inputs
function getS0Data()
s01 = api.S0readCounter(1)
print("S0-1: "..tostring(s01))
s02 = api.S0readCounter(2)
print("S0-2: "..tostring(s02))
s03 = api.S0readCounter(3)
print("S0-3: "..tostring(s03))
s04 = api.S0readCounter(4)
print("S0-4: "..tostring(s04))
-- read old values
s01_l = api.getVar(16)
s02_l = api.getVar(17)
s03_l = api.getVar(18)
s04_l = api.getVar(19)
s01_ll = api.getVar(20)
s02_ll = api.getVar(21)
s03_ll = api.getVar(22)
s04_ll = api.getVar(23)
-- update old values
api.setVar(16, s01)
api.setVar(17, s02)
api.setVar(18, s03)
api.setVar(19, s04)
api.setVar(20, s01_l)
api.setVar(21, s02_l)
api.setVar(22, s03_l)
api.setVar(23, s04_l)
-- get battery voltage
v = api.getBatteryVoltage()
-- assemble the frame
buf = pack.pack('<b15', 5, ((v/256)%256), (v%256), s01, s01_l, s01_ll, s02, s02_l, s02_ll, s03, s03_l, s03_ll, s04, s04_l, s04_ll)
-- print the frame
print("Frame in hex: <devClass, voltage, S0_1, S0_1_last, ...>")
api.dumpArray(buf)
return buf
end
function onWake ()
print("onWake(), periodic wake up")
buf = getS0Data()
print("Sending to LORA")
api.loraSend(ack,rxTimeout,buf)
print("Done sending")
print("No error, sent to lora")
api.wakeUpIn(0,0,periodMinutes,0)
end
function onThreshold ()
print("onThreshold(), reason S0: " .. tostring(src))
buf = getS0Data()
print("Sending to LORA")
api.loraSend(ack,rxTimeout,buf)
print("Done sending")
end
function onStartup()
print("onStartup(), S0 persistent emulation - 4 channels ...")
--set to threshold
api.S0setThreshold(1, 0)
api.S0setThreshold(2, 0)
api.S0setThreshold(3, 0)
api.S0setThreshold(4, 0)
api.setVar(16, 0)
api.setVar(17, 0)
api.setVar(18, 0)
api.setVar(19, 0)
api.setVar(20, 0)
api.setVar(21, 0)
api.setVar(22, 0)
api.setVar(23, 0)
s01 = api.getVar(20)
s02 = api.getVar(21)
s03 = api.getVar(22)
s04 = api.getVar(23)
api.S0initializeCounter(1, s01)
api.S0initializeCounter(2, s02)
api.S0initializeCounter(3, s03)
api.S0initializeCounter(4, s04)
end
Payload example
Payload | Variable | Value |
---|---|---|
05 | 05 | 05 |
0cf2 | batery | 3.3V |
00021b29 | s01 (current) | 138025 |
00021b15 | s01 (old) | 138005 |
00021aff | s01 (older) | 137983 |
0000b547 | s02 (current) | 46407 |
0000b541 | s02 (old) | 46401 |
0000b53b | s02 (older) | 46395 |
00000000 | s03 (current) | 0 |
00000000 | s03 (old) | 0 |
00000000 | s03 (older) | 0 |
00000015 | s04 (current) | 21 |
00000015 | s04 (old) | 21 |
00000015 | s04 (older) | 21 |
-- Copyright 2021 ACRIOS Systems s.r.o.
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------
--- CONFIGURATION -----
-----------------------
----- LoRaWAN ----------
ack = 0 -- 1 for acknowledged, 0 for non-acknowledged
port = 100 -- transmit port
receiveTimeout = 10000 -- the maximum execution time in milliseconds
----- Modbus -----------
baudrate = 9600 -- baudrate: up to 921600 baud
parity = 0 -- communication parity: 0 for none, 1 for odd and 2 for even parity
stopBits = 1 -- number of stop bits: 1 or 2
dataBits = 8 -- number of data bits: 7 or 8
rxTimeout = 500 -- slave device receive timeout in ms
------ Timing ---------
-- device wakeup interval
periodHours = 2
periodMinutes = 30
-----------------------
-----------------------
-- CONFIGURATION END --
-----------------------
function onWake ()
-- set link parameters - e.g. 9600 baud, 8N1
api.rs485Setup(baudrate,parity,stopBits,dataBits)
api.rs485State(1)
api.rs485Receive(0) -- flush input buffer
-- EXAMPLE Modbus request:
-- check our applicaton notes or https://simplymodbus.ca/FC03.htm
-- req = ""
-- SLAVE ADDRESS (0xAA)| FUNCTION CODE (0x04)|
-- MSB REG ADDR (0x00) | LSB REG ADDR (0x08) |
-- MSB REG COUNT (0x00) | LSB REG COUNT (0x01)
-- req = pack.pack('<b6', 0xAA, 0x04, 0x00, 0x08, 0x00, 0x01)
-- crc = api.modbusCrc(req)
-- req = req .. crc -- add CRC, request is complete and ready for sending
if req == nil then
print("Please provide configuration!")
ans = "NO CONFIGURATION PROVIDED!"
else
print("To RS485: ")
api.dumpArray(req)
api.rs485Send(req)
ans,length=api.rs485Receive(rxTimeout)
print("From RS485: ")
api.dumpArray(ans)
api.delayms(500)
api.rs485State(0)
end
if #ans < 1 then
buf = "NO DATA RECEIVED"
else
buf = ans
end
print("To LoRaWAN: ")
api.dumpArray(buf)
print("Sending to LoRaWAN")
res, rxport, rcvd = api.loraSend(ack, receiveTimeout, buf, port)
print("Done sending")
print("Sleep now, wake in " .. tostring(periodHours) .. "hrs:" .. tostring(periodMinutes) .. "mins.....")
api.wakeUpIn(0,periodHours,periodMinutes,0)
end
function onStartup()
print("Starting up LoRaWAN default Modbus script")
end
-- Copyright 2021 ACRIOS Systems s.r.o.
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------
--- CONFIGURATION -----
-----------------------
----- LoRaWAN ----------
ack = 0 -- 1 for acknowledged, 0 for non-acknowledged
port = 100 -- transmit port
receiveTimeout = 10000 -- the maximum execution time in milliseconds
----- M-BUS -----------
baudrate = 2400 -- baudrate: up to 921600 baud
parity = 2 -- communication parity: 0 for none, 1 for odd and 2 for even parity
stopBits = 1 -- number of stop bits: 1 or 2
dataBits = 8 -- number of data bits: 7 or 8
------ Timing ---------
-- device wakeup interval
periodHours = 2
periodMinutes = 30
-----------------------
-----------------------
-- CONFIGURATION END --
-----------------------
function onWake ()
-- set link parameters - 2400 baud, 8E1
api.mbusSetup(baudrate,parity,stopBits,dataBits)
api.mbusState(1)
api.delayms(2000)
-- CREATE MBUS QUERY --
-- UD2
--b=pack.pack('<b5', 0x10, 0x5B, 0xFE, 0x59, 0x16)
b=pack.pack('<b5', 0x10, 0x7B, 0xFE, (0x7B+0xFE)%256, 0x16)
status,c,a,ci,ans = api.mbusTransaction(b,3000,1)
api.mbusState(0)
print("From MBus Calorimeter: ")
api.dumpArray(ans)
if #ans < 1 then
buf = "NO DATA RECEIVED"
else
buf = ans
end
print("To LoRaWAN: ")
api.dumpArray(buf)
print("Sending to LoRaWAN")
res, rxport, rcvd = api.loraSend(ack, receiveTimeout, buf, port)
print("Done sending")
print("Sleep now, wake in " .. tostring(periodHours) .. "hrs:" .. tostring(periodMinutes) .. "mins.....")
api.wakeUpIn(0,periodHours,periodMinutes,0)
end
function onStartup()
print("Starting up LoRaWAN MBUS default script")
end
-- Copyright 2021 ACRIOS Systems s.r.o.
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---- CONFIGURATION ----
periodDays = 1
periodHours = 0
periodMinutes = 0
ack = 1 -- 1 for acknowledged, 0 for non-acknowledged delivery
---------------------
function loraSendBig(raw, filterId)
ret, port, cfg = api.loraSend(ack, 5000, string.char(1)..string.sub(raw,1,50), filterId)
processConfig(ret, port, cfg)
if #raw > 50 then
ret, port, cfg = api.loraSend(ack, 5000, string.char(2)..string.sub(raw,51,100), filterId)
processConfig(ret, port, cfg)
if #raw > 100 then
ret, port, cfg = api.loraSend(ack, 5000, string.char(3)..string.sub(raw,101,150), filterId)
processConfig(ret, port, cfg)
if #raw > 150 then
ret, port, cfg = api.loraSend(ack, 5000, string.char(4)..string.sub(raw,151,200), filterId)
processConfig(ret, port, cfg)
if #raw > 200 then
ret, port, cfg = api.loraSend(ack, 5000, string.char(5)..string.sub(raw,201,250), filterId)
processConfig(ret, port, cfg)
end
end
end
end
end
function processConfig(ret, port, cfg)
-- port 230 is selected for new filter configuration
if port == 230 then
print("New filter config:")
if cfg == nil or #cfg < 3 then
print("Purge filter, receive all")
api.wmbusFilter("purge")
filterLength = 0
api.setVar(1, filterLength)
else
api.dumpArray(cfg)
api.wmbusFilter("populate", cfg)
filterLength = #cfg/4
filterIdHasReceived = {}
for i=1, filterLength do
filterIdHasReceived[i] = 0
end
maxTimeout = 32000*filterLength
if maxTimeout < 120000 then
maxTimeout = 120000
end
api.setVar(1, filterLength)
end
end
-- port 231 is used to switch between WMBUS modes
if port == 231 then
if cfg == nil or cfg == '' then
print("Bad WMBUS mode, Specify C1 or T1 or S1")
else
print("New WMBUS mode: "..cfg)
api.wmbusSetup(10, "master", cfg, 1)
end
end
end
function onWake ()
filterLength = api.getVar(1)
done = 0
print("Filter length: " .. tostring(filterLength))
if filterLength > 0 then
filterIdHasReceived = {}
for i=1, filterLength do
filterIdHasReceived[i] = 0
end
else
print("Error - no filter setup!")
ret, port, cfg = api.loraSend(ack,10000,'NO FILTER, send ID filter to 230, mode to 231', 240)
processConfig(ret, port, cfg)
end
start = api.getTick()
maxTimeout = 32000*filterLength
if maxTimeout < 120000 then
maxTimeout = 120000
end
-- receive WMBUS frames and upload to LoRaWAN in format [[0x chunkCount chunkNumber] raw wmbus frame]
-- to port corresponding to Filter ID
while filterLength > 0 do
while true do
-- if timeout, report not responding units
now = api.getTick()
if now - start > maxTimeout then
failing = ""
for i=1, filterLength do
failing = failing .. string.char(filterIdHasReceived[i])
end
print("List of failing Filter IDs:")
api.dumpArray(failing)
-- send list of failing devices to port 241
ret, port, cfg = api.loraSend(ack,10000, failing, 241)
processConfig(ret, port, cfg)
print("Timeout receiving data from all meters...")
-- wake up later
api.wakeUpIn(periodDays, periodHours, periodMinutes, 0)
return
end
status, CI, manID, id, ver, devType, ctrlInfo, data, raw, filterId = api.wmbusReceiveFrame(60000, 0)
if raw ~= nil then
print("Received valid wMBUS frame...")
break
end
end
if filterId ~= nil then
if filterId > 0 then
if filterIdHasReceived[filterId] == 0 then
done = done + 1
print("Upload data from node "..tostring(filterId).." to LoRaWAN. ("..tostring(done).."/"..tostring(filterLength)..")")
filterIdHasReceived[filterId] = 1
api.dumpArray(string.char(filterId) .. raw)
loraSendBig(raw, filterId)
else
print("Data from meter "..tostring(filterId).." already received.")
end
test = 1
for i=1, filterLength do
if filterIdHasReceived[i] == 0 then
test = 0
break
end
end
if test == 1 then
print("Received data from all meters, sleeping.......")
-- wake up later
api.wakeUpIn(periodDays, periodHours, periodMinutes, 0)
return
end
end
end
end
end
function onStartup()
print("Starting up WMBUS to LoRaWAN script, V1.3")
-- Initial filter ids: 0x01673213, 0x19854733 ... (in little endian)
-- COMMENT OUT START
--filter = ""
--filter = filter .. string.char(0x13, 0x32, 0x67, 0x01) -- 0x01673213
--filter = filter .. string.char(0x33, 0x47, 0x85, 0x19) -- 0x19854733
--api.wmbusFilter("populate", filter)
-- COMMENT OUT END
ret, filterLength = api.wmbusFilter("fetch")
api.setVar(1, filterLength)
-- Start receiving at C1/T1 mode/frequency
api.wmbusSetup(10, "master","T1", 1)
end
Here you can find default NB-IoT LUA scripts for IoT Converter
-- Copyright 2021 ACRIOS Systems s.r.o.
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function wordToBuffer(word)
-- little endian
local buff = ""
buff = buff .. string.char(word%256) .. string.char((word/256)%256) .. string.char(((word/256)/256)%256) .. string.char((((word/256)/256)/256)%256)
return buff
end
-- get and format S0 inputs
function getS0Data()
s01 = api.S0readCounter(1)
print("S0-1: "..tostring(s01))
s02 = api.S0readCounter(2)
print("S0-2: "..tostring(s02))
s03 = api.S0readCounter(3)
print("S0-3: "..tostring(s03))
s04 = api.S0readCounter(4)
print("S0-4: "..tostring(s04))
-- read old values
s01_l = api.getVar(16)
s02_l = api.getVar(17)
s03_l = api.getVar(18)
s04_l = api.getVar(19)
s01_ll = api.getVar(20)
s02_ll = api.getVar(21)
s03_ll = api.getVar(22)
s04_ll = api.getVar(23)
-- update old values
api.setVar(16, s01)
api.setVar(17, s02)
api.setVar(18, s03)
api.setVar(19, s04)
api.setVar(20, s01_l)
api.setVar(21, s02_l)
api.setVar(22, s03_l)
api.setVar(23, s04_l)
-- get battery voltage
v = api.getBatteryVoltage()
-- assemble the frame
buf = string.char(5) -- device class
buf = buf .. string.char(v%256) .. string.char((v/256)%256)
buf = buf .. wordToBuffer(s01) .. wordToBuffer(s01_l) .. wordToBuffer(s01_ll)
buf = buf .. wordToBuffer(s02) .. wordToBuffer(s02_l) .. wordToBuffer(s02_ll)
buf = buf .. wordToBuffer(s03) .. wordToBuffer(s03_l) .. wordToBuffer(s03_ll)
buf = buf .. wordToBuffer(s04) .. wordToBuffer(s04_l) .. wordToBuffer(s04_ll)
-- print the frame
print("Frame in hex: <devClass, voltage, S0_1, S0_1_last, ...>")
api.dumpArray(buf)
return buf
end
-- CONFIGURATION --
protocol = "UDP"
APN = "nb.m2mc"
PLMNID = "23003"
band = "20"
ip = "192.168.0.20"
port = 4242
receiveTimeout = 50
periodMinutes = 60
--
function onWake ()
print("onWake(), periodic wake up")
buf = getS0Data()
print("Sending to NB-IoT: " .. ip .. ":" .. tostring(port) .. " (" .. protocol .. ")")
res = api.nbSend(ip, port, buf, receiveTimeout, protocol)
if res == 0 then
print("Done, sent to NB-IoT")
else
print("Error " .. tostring(res) .. ", failed to send to NB-IoT!")
end
api.wakeUpIn(0,0,periodMinutes,0)
end
function onThreshold ()
print("onThreshold(), reason S0: " .. tostring(src))
buf = getS0Data()
print("Sending to NB-IoT: " .. ip .. ":" .. tostring(port) .. " (" .. protocol .. ")")
res = api.nbSend(ip, port, buf, receiveTimeout, protocol)
if res == 0 then
print("Done, sent to NB-IoT")
else
print("Error " .. tostring(res) .. ", failed to send to NB-IoT!")
end
end
function onStartup()
print("onStartup(), S0 persistent emulation...")
--set to threshold
api.S0setThreshold(1, 0)
api.S0setThreshold(2, 0)
api.S0setThreshold(3, 0)
api.S0setThreshold(4, 0)
s01 = api.getVar(20)
s02 = api.getVar(21)
s03 = api.getVar(22)
s04 = api.getVar(23)
api.S0initializeCounter(1, s01)
api.S0initializeCounter(2, s02)
api.S0initializeCounter(3, s03)
api.S0initializeCounter(4, s04)
api.nbAT("AT*MCGDEFCONT=\"IP\",\"" .. APN .."\"", 5000, 1)
api.nbAT("AT+COPS=1,2,\"".. PLMNID .."\"", 5000, 1)
api.nbAT("AT+CBAND=" .. band, 5000, 1)
end
-- Copyright 2021 ACRIOS Systems s.r.o.
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------
--- CONFIGURATION -----
-----------------------
----- NB-IoT ----------
APN = "nb.m2mc"
PLMNID = "23003"
protocol = "UDP" -- UDP or TCP
ip = "192.168.0.20"
port = 4242
receiveTimeout = 500 -- the maximum execution time in milliseconds
----- Modbus -----------
baudrate = 9600 -- baudrate: up to 921600 baud
parity = 0 -- communication parity: 0 for none, 1 for odd and 2 for even parity
stopBits = 1 -- number of stop bits: 1 or 2
dataBits = 8 -- number of data bits: 7 or 8
rxTimeout = 500 -- slave device receive timeout in ms
------ Timing ---------
-- device wakeup interval
periodHours = 2
periodMinutes = 30
-----------------------
-----------------------
-- CONFIGURATION END --
-----------------------
function onWake ()
-- set link parameters - e.g. 9600 baud, 8N1
api.rs485Setup(baudrate,parity,stopBits,dataBits)
api.rs485State(1)
api.rs485Receive(0) -- flush input buffer
-- EXAMPLE Modbus request:
-- check our applicaton notes or https://simplymodbus.ca/FC03.htm
-- req = ""
-- SLAVE ADDRESS (0xAA)| FUNCTION CODE (0x04)|
-- MSB REG ADDR (0x00) | LSB REG ADDR (0x08) |
-- MSB REG COUNT (0x00) | LSB REG COUNT (0x01)
-- req = pack.pack('<b6', 0xAA, 0x04, 0x00, 0x08, 0x00, 0x01)
-- crc = api.modbusCrc(req)
-- req = req .. crc -- add CRC, request is complete and ready for sending
if req == nil then
print("Please provide configuration!")
ans = "NO CONFIGURATION PROVIDED!"
else
print("To RS485: ")
api.dumpArray(req)
api.rs485Send(req)
ans,length=api.rs485Receive(rxTimeout)
print("From RS485: ")
api.dumpArray(ans)
api.delayms(500)
api.rs485State(0)
end
if #ans < 1 then
buf = "NO DATA RECEIVED"
else
buf = ans
end
print("To NBIOT: ")
api.dumpArray(buf)
print("Sending to NB-IoT")
api.nbSend(ip, port, buf, receiveTimeout, protocol)
print("Done sending")
print("Sleep now, wake in " .. tostring(periodHours) .. "hrs:" .. tostring(periodMinutes) .. "mins.....")
api.wakeUpIn(0,periodHours,periodMinutes,0)
end
function onStartup()
print("Starting up NB-IoT default Modbus script with APN settings")
result = api.nbAT("AT*MCGDEFCONT=\"IP\",\"" .. APN .."\"", 5000, 1)
print("APN result: " .. result)
result = api.nbAT("AT+COPS=1,2,\"" .. PLMNID .."\"", 5000, 1)
print("PLMNID result: " .. result)
end
-- Copyright 2021 ACRIOS Systems s.r.o.
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------
--- CONFIGURATION -----
-----------------------
----- NB-IoT ----------
APN = "nb.m2mc"
PLMNID = "23003"
protocol = "UDP" -- UDP or TCP
ip = "192.168.0.20"
port = 4242
receiveTimeout = 500 -- the maximum execution time in milliseconds
----- M-BUS -----------
baudrate = 2400 -- baudrate: up to 921600 baud
parity = 2 -- communication parity: 0 for none, 1 for odd and 2 for even parity
stopBits = 1 -- number of stop bits: 1 or 2
dataBits = 8 -- number of data bits: 7 or 8
------ Timing ---------
-- device wakeup interval
periodHours = 2
periodMinutes = 30
-----------------------
-----------------------
-- CONFIGURATION END --
-----------------------
function onWake ()
-- set link parameters - 2400 baud, 8E1
api.mbusSetup(baudrate,parity,stopBits,dataBits)
api.mbusState(1)
api.delayms(2000)
-- CREATE MBUS QUERY --
-- UD2
--b=pack.pack('<b5', 0x10, 0x5B, 0xFE, 0x59, 0x16)
b=pack.pack('<b5', 0x10, 0x7B, 0xFE, (0x7B+0xFE)%256, 0x16)
status,c,a,ci,ans = api.mbusTransaction(b,3000,1)
api.mbusState(0)
print("From MBus Calorimeter: ")
api.dumpArray(ans)
if #ans < 1 then
buf = "NO DATA RECEIVED"
else
buf = ans
end
print("To NBIOT: ")
api.dumpArray(buf)
print("Sending to NB-IoT")
api.nbSend(ip, port, buf, receiveTimeout, protocol)
print("Done sending")
print("Sleep now, wake in " .. tostring(periodHours) .. "hrs:" .. tostring(periodMinutes) .. "mins.....")
api.wakeUpIn(0,periodHours,periodMinutes,0)
end
function onStartup()
print("Starting up NB-IoT default script with APN settings")
result = api.nbAT("AT*MCGDEFCONT=\"IP\",\"" .. APN .."\"", 5000, 1)
print("APN result: " .. result)
result = api.nbAT("AT+COPS=1,2,\"" .. PLMNID .."\"", 5000, 1)
print("PLMNID result: " .. result)
end
-- Copyright 2021 ACRIOS Systems s.r.o.
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---- CONFIGURATION ----
APN = "nb.m2mc"
PLMNID = "23003"
periodDays = 1
periodHours = 0
periodMinutes = 0
proto = "UDP"
ip = "192.168.0.20"
port = 4242
---------------------
function processConfig(ret, cmd, cfg)
-- cmd 1 is selected for new filter configuration
if cmd == 1 then
print("New filter config:")
if cfg == nil or #cfg < 3 then
print("Purge filter, receive all")
api.wmbusFilter("purge", cfg)
filterLength = 0
api.setVar(1, filterLength)
else
api.dumpArray(cfg)
api.wmbusFilter("populate", cfg)
filterLength = #cfg/4
filterIdHasReceived = {}
for i=1, filterLength do
filterIdHasReceived[i] = 0
end
maxTimeout = filterLength*60000
api.setVar(1, filterLength)
end
end
-- cmd 2 is used to switch between WMBUS modes
if cmd == 2 then
if cfg == nil or cfg == '' then
print("Bad WMBUS mode, Specify C1 or T1 or S1")
else
print("New WMBUS mode: "..cfg)
api.wmbusSetup(10, "master", cfg, 1)
end
end
end
function onWake ()
filterLength = api.getVar(1)
print("Filter length: " .. tostring(filterLength))
if filterLength > 0 then
filterIdHasReceived = {}
for i=1, filterLength do
filterIdHasReceived[i] = 0
end
else
print("Error - no filter setup!")
ret, recved = api.nbSend(ip, port, string.char(255) .. 'NO FILTER, send ID filter to 1, mode to 2', 5000, proto)
if recved ~= nil then
if #recved > 1 then
processConfig(ret, string.byte(string.sub(recved,1,1)), string.sub(recved, 2))
end
end
end
start = api.getTick()
maxTimeout = filterLength*60000
-- receive WMBUS frames and upload to NBIOT in format [[meter filter ID] [raw wmbus frame]]
while filterLength > 0 do
while true do
-- if timeout, report not responding units
now = api.getTick()
if now - start > maxTimeout then
failing = ""
for i=1, filterLength do
failing = failing .. string.char(filterIdHasReceived[i])
end
print("List of failing Filter IDs:")
api.dumpArray(failing)
ret, recved = api.nbSend(ip, port, failing, 5000, proto)
if recved ~= nil then
if #recved > 1 then
processConfig(ret, string.byte(string.sub(recved,1,1)), string.sub(recved, 2))
end
end
print("Timeout receiving data from all meters...")
-- wake up later
api.wakeUpIn(periodDays, periodHours, periodMinutes, 0)
return
end
status, CI, manID, id, ver, devType, ctrlInfo, data, raw, filterId = api.wmbusReceiveFrame(60000, 1)
if raw ~= nil then
print("Received valid wMBUS frame...")
break
end
end
if filterId ~= nil then
if filterId > 0 then
if filterIdHasReceived[filterId] == 0 then
print("Upload data from node "..tostring(filterId).." to NBIOT.")
filterIdHasReceived[filterId] = 1
api.dumpArray(string.char(filterId) .. raw)
ret, recved = api.nbSend(ip, port, string.char(filterId) .. raw, 5000, proto)
if recved ~= nil then
if #recved > 1 then
processConfig(ret, string.byte(string.sub(recved,1,1)), string.sub(recved, 2))
end
end
else
print("Data from meter "..tostring(filterId).." already received.")
end
test = 1
for i=1, filterLength do
if filterIdHasReceived[i] == 0 then
test = 0
break
end
end
if test == 1 then
print("Received data from all meters, sleeping.......")
-- wake up later
api.wakeUpIn(periodDays, periodHours, periodMinutes, 0)
return
end
end
end
end
end
function onStartup()
print("Starting up WMBUS to NBIOT script, V1.2")
api.nbAT("AT*MCGDEFCONT=\"IP\",\"" .. APN .."\"", 5000, 1)
api.nbAT("AT+COPS=1,2,\"".. PLMNID .."\"", 5000, 1)
api.nbAT("AT+CBAND=20", 5000, 1)
-- Initial filter ids: 0x01673213, 0x19854733 ... (in little endian)
-- COMMENT OUT START
--filter = ""
--filter = filter .. string.char(0x13, 0x32, 0x67, 0x01) -- 0x01673213
--filter = filter .. string.char(0x33, 0x47, 0x85, 0x19) -- 0x19854733
--filterLength = #filter/4
--api.wmbusFilter("populate", filter)
-- COMMENT OUT END
ret, filterLength = api.wmbusFilter("fetch")
api.setVar(1, filterLength)
-- Start receiving at C1/T1 mode/frequency
api.wmbusSetup(10, "master","T1", 1)
end