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:
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:
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:
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:
-- 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:
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:
-- 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:
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:
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:
 -- 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: