Logic Machine Forum
Modbus TCP slave example - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: Modbus TCP slave example (/showthread.php?tid=181)

Pages: 1 2


Modbus TCP slave example - admin - 08.01.2016

⚠️ 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)



RE: Modbus TCP slave example - Deniss - 15.01.2016

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!


RE: Modbus TCP slave example - admin - 15.01.2016

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.


RE: Modbus TCP slave example - admin - 18.01.2016

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)



RE: Modbus TCP slave example - automatikas - 02.02.2017

Is it posible to have LM5 as modbus slave on RS485 or 232 ports?


RE: Modbus TCP slave example - admin - 02.02.2017

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.


RE: Modbus TCP slave example - automatikas - 02.02.2017

(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


RE: Modbus TCP slave example - automatikas - 29.03.2017

(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?


RE: Modbus TCP slave example - admin - 29.03.2017

Yes, ASCII is not supported, but you can use RTU over RS232.


RE: Modbus TCP slave example - leondias - 29.03.2017

(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


RE: Modbus TCP slave example - automatikas - 29.03.2017

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)



RE: Modbus TCP slave example - admin - 29.03.2017

Replace 'H' with 'F', flush is not needed


RE: Modbus TCP slave example - automatikas - 29.03.2017

(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]


RE: Modbus TCP slave example - Deniss - 20.04.2017

Good day!
Is it possible to make this script running only on variable changes on both sides?


RE: Modbus TCP slave example - admin - 20.04.2017

Modbus reading is purely polling-based. Writing from LM side is event-based though.


RE: Modbus TCP slave example - duvelken - 02.12.2017

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 ?


RE: Modbus TCP slave example - admin - 03.12.2017

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.


RE: Modbus TCP slave example - FatMax - 13.12.2018

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.


RE: Modbus TCP slave example - admin - 13.12.2018

Can you explain your task in some more detail?


RE: Modbus TCP slave example - FatMax - 13.12.2018

I just realised there is software out there that can perform this type of task. Nevermind!