This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Modbus TCP slave example
#1
This script is outdated use this script instead: https://forum.logicmachine.net/showthread.php?tid=4288



Simple example of how to implement Modbus TCP slave via a resident script (sleep time = 0). It only supports binary objects as coils and 1-byte / 2-byte integer objects as registers. Number of coils and registers is not limited, object mapping can be set by filling coils, registers and regdt tables.

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
if not mb then   require('genohm-scada.eibdgm')   require('luamodbus')   -- list of coil mapping, starting from 0   coils = { '1/1/1', '1/1/2' }   -- list of register mapping, starting from 0   registers = { '2/2/2', '3/3/3' }   -- list of register data types, element count must match registers table   regdt = { dt.int8, dt.uint16 }   -- knx group write callback   function knxgroupwrite(event)     local value     -- try to find matching coil     for id, addr in ipairs(coils) do       if event.dst == addr then         value = knxdatatype.decode(event.datahex, dt.bool)         mb:setcoils(id - 1, value)       end     end     -- try to find matching register     for id, addr in ipairs(registers) do       if event.dst == addr then         value = knxdatatype.decode(event.datahex, regdt[ id ])         mb:setregisters(id - 1, value)       end     end   end   -- coil write callback   function mbwritecoils(coil, value)     local addr = coils[ coil + 1 ]     if addr then       grp.write(addr, value, dt.bool)     end   end   -- register write callback   function mbwriteregisters(register, value)     local addr = registers[ register + 1 ]     if addr then       grp.write(addr, value, regdt[ register + 1])     end   end   -- knx group monitor, handles group writes   knxclient = eibdgm:new({ timeout = 0.1 })   knxclient:sethandler('groupwrite', knxgroupwrite)   -- modbus slave, listen on all interfaces and default port 502   mb = luamodbus.tcp()   mb:open('0.0.0.0', 502)   -- setting slave id is optional   -- mb:setslave(1)   mb:setreceivetimeout(0.1)   mb:setmapping(#coils, 0, #registers, 0)   -- init coils   for id, addr in ipairs(coils) do     value = grp.getvalue(addr)     mb:setcoils(id - 1, value)   end   -- init registers   for id, addr in ipairs(registers) do     value = grp.getvalue(addr)     mb:setregisters(id - 1, value)   end   -- set callbacks for coil and register write   mb:setwritecoilcb(mbwritecoils)   mb:setwriteregistercb(mbwriteregisters) end -- handle modbus and knx mb:handleslave() knxclient:loop(0.1)
#2
Thank you for this script!
Situation is like this -
My client is PLC, and it supports only 4byte float. I get data from KNX, where object is 2 byte float.
Is in LM4 possible conversation from 2byte float to 4 byte float (KNX->PLC) and 4byte float to 2 byte float (PLC->LM4)? When value changes from KNX device, I can see it in my PLC as 4 byte float, and change same object 4 byte float on PLC, then on KNX device changes data 2 byte float?

Thanks in advance!
#3
Lua uses 64-bit floating point numbers, so internally there's not difference between 2-byte or 4-byte float. Adding 4-byte float to Modbus is tricky because it only supports 2-byte registers, so there are additional encoding/decoding steps required. I'll try to provide an updated example later.
#4
Due to 2-byte <> 4-byte conversion, you need to use two uint16 objects: 2/1/2 and 2/1/3 in this example. Float16 object address is 2/1/1.

1. Add a script to float16 object, it will split the value and write it to 2/1/2 and 2/1/3. Change 15.15.255 to your LM physical address so this script is only triggered from KNX bus but not from LM internally.
Code:
12345678910
if event.src ~= '15.15.255' and event.src ~= 'local' then value = event.getvalue() raw = knxdatatype.encode(value, dt.float32).dataraw r1 = raw:byte(1) * 0x100 + raw:byte(2) r2 = raw:byte(3) * 0x100 + raw:byte(4) grp.update('2/1/2', r1, dt.uint16) grp.update('2/1/3', r2, dt.uint16) end

2. Attach a script to 2/1/3 (second uint16 object). It converts two uint16 regs to float32 internally and then writes the value to float16 object.
Code:
1234567
require('luamodbus') r1 = grp.getvalue('2/1/2') r2 = grp.getvalue('2/1/3') value = luamodbus.convert('float32', r1, r2) grp.write('2/1/1', value, dt.float16)
#5
Is it posible to have LM5 as modbus slave on RS485 or 232 ports?
#6
Replace this:
Code:
123456
-- modbus slave, listen on all interfaces and default port 502 mb = luamodbus.tcp() mb:open('0.0.0.0', 502) -- setting slave id is optional -- mb:setslave(1)

With this (adjust serial settings and slave id as needed):
Code:
1234
mb = luamodbus.rtu() mb:open('/dev/RS485', 9600, 'E', 8, 1, 'H') mb:connect() mb:setslave(1)

RS-232 is mostly used for ModBus ASCII which is not supported.
#7
(02.02.2017, 12:09)admin Wrote: Replace this:
Code:
123456
-- modbus slave, listen on all interfaces and default port 502 mb = luamodbus.tcp() mb:open('0.0.0.0', 502) -- setting slave id is optional -- mb:setslave(1)

With this (adjust serial settings and slave id as needed):
Code:
1234
mb = luamodbus.rtu() mb:open('/dev/RS485', 9600, 'E', 8, 1, 'H') mb:connect() mb:setslave(1)

RS-232 is mostly used for ModBus ASCII which is not supported.

works like a charm

but needed to change port to /dev/RS485-1 to make it working
#8
(02.02.2017, 13:35)automatikas Wrote:
(02.02.2017, 12:09)admin Wrote: RS-232 is mostly used for ModBus ASCII which is not supported.

Just to make clear for all ASCII is not supported, but can i use RTU on RS232 port?
#9
Yes, ASCII is not supported, but you can use RTU over RS232.
#10
(29.03.2017, 10:37)admin Wrote: Yes, ASCII is not supported, but you can use RTU over RS232.

Didn't work for me, but I suppose it could be our Modbus device problem if so
#11
is it the same setup as rs485? i have seen some flush() command else were.

Have tried this below but no respons. RS485 ports are working great.

Code:
1234
mb = luamodbus.rtu() mb:open('/dev/RS232', 9600, 'E', 8, 1, 'H') mb:connect() mb:setslave(1)
#12
Replace 'H' with 'F', flush is not needed
#13
(29.03.2017, 10:55)admin Wrote: Replace 'H' with 'F', flush is not needed

Strange... 

Have tried half or full duplex no response. I use usb-COM port adapter. from DB9 i use 2,3 for rxtx and 5 for GND. LM started responding when i disconnect GND from RS port of LM just rx tx pins. 
LM starts sending response back but not correct data for modbus just echos same query back. Modbus master fires illegal response exception then. 

TX [01][03][00][00][00][0A][C5][CD]
RX [01][03][00][00][00][0A][C5][CD]
#14
Good day!
Is it possible to make this script running only on variable changes on both sides?
#15
Modbus reading is purely polling-based. Writing from LM side is event-based though.
#16
Hi,

i tried the modbus example to read out some LM data to my PLC.
connection on TCP is fine and i don't get scanning errors when i use qmodmaster
but all values remain 0

only things i changed are this:
Code:
12345678
 -- list of coil mapping, starting from 0  coils = { '1/1/2' }  -- list of register mapping, starting from 0  registers = { '1/1/1' }  -- list of register data types, element count must match registers table  regdt = { dt.uint16 }


1/1/2 is just a 1 bit boolean set to 1

i also changed the ip because i got an error "illegal data adress" on my qmodmaster when scanning.

do i need to change other things to get this to work ?

thx

for some reason i tried to inactivate and activate the script and the value came up.

is this normal ? do i have to reactivate the script everytime i make changes ?
#17
Code is reloaded when script is saved but all previously defined variables remain in memory. Since mb is already defined, init code (if not mb then...) is not reached after changes are saved. The only way to do a full reload is to disable then enable the script.
#18
Is it functionally possible to loop through a series of registers and output to log if it finds a specified value? With series I mean thousands.
#19
Can you explain your task in some more detail?
#20
I just realised there is software out there that can perform this type of task. Nevermind!


Forum Jump: