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.

Create Modbus client via script
#1
Is there any method of creating a modbus client with a script? 

Scenario:

I have 100 preloaded profiles on a SL. Via VISU, I can input a IP adress scope. With a "number" object, I can determine how many clients that should be created based on IP address.

Is it possible?
Reply
#2
Hi,

Yes this is possible, this script simulates the form you normal fill in manually, basicly any user form can be simulated and automated (;

Try this script:

Code:
-- Batch adding modbus devices - Created by Erwin van der Zwart - Schneider Electric Netherlands -----------
-- For spaceLYnk FW 1.2.1 or higher and homeLYnk FW 1.5.1 or higher ----------------------------------------
--------------------------------------------- Start Parameters ---------------------------------------------

-- Set username and password for access to SL/HL
username = 'admin'
password = 'admin'

---------------------------------------------- End Parameters ----------------------------------------------
---------------------------------- DON'T CHANGE ANYTHING UNDER THIS LINE -----------------------------------
-- Load modules
require('json')
require('socket.url')
require('socket.http')

-- Get current HL/SL ip address
data = io.readproc('if-json')
data = json.decode(data)
ip = data.eth0.inetaddr

-- Create url for modbus device creation
url = 'http://' .. username .. ':' .. password .. '@' .. ip .. '/scada-main/general/plugin?plugin=modbus&request=device-save'

-- Set counters for creation log
number_of_devices = 0
number_failed = 0

-- Function to send request to create modbus device
function url_send(device_proto, device_name, device_profile, device_ip, device_port, device_slave, device_pollinterval, device_id)
 local device = {
     proto = device_proto,
     name = device_name,
   profile = device_profile,
     ip = device_ip,
     port = device_port,
   slave = device_slave,
   pollinterval = device_pollinterval,
   id = device_id,
    }
 data = json.encode(device)
    form_data = 'data=' .. socket.url.escape(data)
    socket.http.TIMEOUT = 15  
    local res, code, response_header = socket.http.request(url, form_data)
 return res, code, response_header
end

-- Create devices
for i = 1, 10, 1 do
 -- used fields to send: protocol,name,profile,ip,port,slave,pollinterval,id
 result, code, response_header = url_send("tcp", "Device " .. i, "iEM-iEM3155", "192.168.10." .. i, "502", i, "5", "")
 -- Calculate results for creation devices log
 if result == '{"success":true}' then
   number_of_devices = number_of_devices + 1
 else
   number_failed = number_failed + 1
 end
end

-- Create result log
if number_failed == 0 then
 log ("Created " .. number_of_devices .. " devices succesfully")
else
 log ("Created " .. number_of_devices .. " devices succesfully and creation of " .. number_failed .. " devices failed")
end

-- Disable script when done automaticly
script.disable(_SCRIPTNAME)

I assume your next question will be: How do i automate the mappings (:

Here is a sample to automaticly create new objects and map them to your modbus device. Objects are created with correct type by info of modbus mapping data points.

Only un-mapped objects are processed, if a object is mapped already it's skipped. 

If you don't want to map all points you need to add some conditions or create a new modbus profile with only points to be auto mapped.

Code:
query = 'SELECT * FROM modbus_mapping'
counter = 0
for _, mapping in ipairs(db:getall(query)) do
  if mapping.bus_address == "" or  mapping.bus_address == nil then
    grp_address = '1/1/' .. counter
    if counter < 256 then
      dec_grp_address = knxlib.encodega(grp_address)
      address = grp.create({
      datatype = mapping.datatype,
      address = grp_address,
      name = mapping.name,
      comment = 'Auto mapped by script',
      units = mapping.units,
      tags = {},
      })
      counter = counter + 1
      db:update('modbus_mapping', { bus_address = dec_grp_address, bus_write = 1, value_delta = 0.1, value_custom = 'Auto mapped by script' }, { id = mapping.id })
    end
  end
end

Please make a back-up before running both of these scripts to be sure the result is what you expect as this script will and can add a lot of objects and create a lot of links automaticly.

This auto mapping script is limited to 256 objects, when you need more auto mappings there needs to be a extra step to move to next address scope. That is not included now.

You can now goto next step to add plans automaticly, add these objects automatic to the plan including images and have a full visu page with your metering device in just 1 mouse click (;

That step is up to you (;

BR,

Erwin
Reply
#3
(18.02.2017, 00:03)Erwin van der Zwart Wrote:
Code:
query = 'SELECT * FROM modbus_mapping'
counter = 0
for _, mapping in ipairs(db:getall(query)) do
  if mapping.bus_address == "" or  mapping.bus_address == nil then
    grp_address = '1/1/' .. counter
    if counter < 256 then
      dec_grp_address = knxlib.encodega(grp_address)
      address = grp.create({
      datatype = mapping.datatype,
      address = grp_address,
      name = mapping.name,
      comment = 'Auto mapped by script',
      units = mapping.units,
      tags = {},
      })
      counter = counter + 1
      db:update('modbus_mapping', { bus_address = dec_grp_address, bus_write = 1, value_delta = 0.1, value_custom = 'Auto mapped by script' }, { id = mapping.id })
    end
  end
end
Hi, Erwin!
Could you modify this script to create more then 256 objects at once?
I have profile with around 900 registers and it's not full
Reply
#4
Hi,

Yes i could, but i don't see the point (:

You can change grp.address = '1/1/' .. counter to grp.address = '1/2/' .. counter and run it again, the script checks already if a object is already mapped and skips it then, so it will resume on the unmapped objects.

If you have 900 objects just run it 4 times and change the grp.address range between each run.

also add script.disable(_SCRIPTNAME) at the end of the above script to avoid it from running multiple times when using it from a resident script

BR,

Erwin
Reply
#5
I see i forgot to thank you for this script Erwin. Modified to my needs and it works flawlessly!
Reply
#6
Hi to Everybody,

I like your scripts !!  Wink

I have a problem with only one object in modbus mapping profile, please see the image in the attachment.

I want change the COIL 192 to COIL 191 ( I forgot it in the profile     Angry  )

Can I use a script to change only this point ? I have more then 500 point in this modbus device.

Thanks in Advance !

Alberto

Attached Files Thumbnail(s)
   
Reply
#7
(28.09.2017, 20:22)Erwin van der Zwart Wrote: Hi,

Yes i could, but i don't see the point (:

You can change grp.address = '1/1/' .. counter to grp.address = '1/2/' .. counter and run it again, the script checks already if a object is already mapped and skips it then, so it will resume on the unmapped objects.

If you have 900 objects just run it 4 times and change the grp.address range between each run.

also add script.disable(_SCRIPTNAME) at the end of the above script to avoid it from running multiple times when using it from a resident script

BR,

Erwin
I've tried to modify and seems it works

Code:
-- Select all entrys from DB inside table 'modbus_mapping'
-- "id", "internal_id", "device", "name", "active", "bus_write", "bus_address", "bus_datatype", "internal", "type", "value_delta","value_base","value_multiplier","value_bitmask","value_nan","value_conv","value_custom","units","address","address_scale","read_count","read_offset","read_swap","datatype","writable","write_only","write_multiple"
query = 'SELECT * FROM modbus_mapping'
counter = 0
for _, mapping in ipairs(db:getall(query)) do
 if mapping.bus_address == "" or  mapping.bus_address == nil then
   mid_group = math.floor(counter/256)
   grp_address = '32/'..mid_group..'/' .. (counter - 256*mid_group)
   log(grp_address)
   if counter < 2047 then
     dec_grp_address = knxlib.encodega(grp_address)
     address = grp.create({
     datatype = mapping.datatype,
     address = grp_address,
     name = mapping.name,
     comment = 'Auto mapped by script',
     units = mapping.units,
     tags = {},
     })
     counter = counter + 1
     db:update('modbus_mapping', { bus_address = dec_grp_address, bus_write = 1, value_delta = 0.1, value_custom = 'Auto mapped by script' }, { id = mapping.id })
   end
 end
end
log('done')
script.disable(_SCRIPTNAME)
but few times this script did not fill full modbus table. it stops on 32/0/225, but all GA was created (the last was 32/3/***)
this script worked about 1 minute

(29.09.2017, 07:37)toujour Wrote: Hi to Everybody,

I like your scripts !!  Wink

I have a problem with only one object in modbus mapping profile, please see the image in the attachment.

I want change the COIL 192 to COIL 191 ( I forgot it in the profile     Angry  )

Can I use a script to change only this point ? I have more then 500 point in this modbus device.

Thanks in Advance !

Alberto
I think there is another simple way
You can create additional profile for your modbus device only with coil 191.
Reply
#8
New thread for my question --> Change Modbus Client via Script
Reply
#9
(18.02.2017, 00:03)Erwin van der Zwart Wrote: Hi,

Yes this is possible, this script simulates the form you normal fill in manually, basicly any user form can be simulated and automated (;

Try this script:

Code:
-- Batch adding modbus devices - Created by Erwin van der Zwart - Schneider Electric Netherlands -----------
-- For spaceLYnk FW 1.2.1 or higher and homeLYnk FW 1.5.1 or higher ----------------------------------------
--------------------------------------------- Start Parameters ---------------------------------------------

-- Set username and password for access to SL/HL
username = 'admin'
password = 'admin'

---------------------------------------------- End Parameters ----------------------------------------------
---------------------------------- DON'T CHANGE ANYTHING UNDER THIS LINE -----------------------------------
-- Load modules
require('json')
require('socket.url')
require('socket.http')

-- Get current HL/SL ip address
data = io.readproc('if-json')
data = json.decode(data)
ip = data.eth0.inetaddr

-- Create url for modbus device creation
url = 'http://' .. username .. ':' .. password .. '@' .. ip .. '/scada-main/general/plugin?plugin=modbus&request=device-save'

-- Set counters for creation log
number_of_devices = 0
number_failed = 0

-- Function to send request to create modbus device
function url_send(device_proto, device_name, device_profile, device_ip, device_port, device_slave, device_pollinterval, device_id)
 local device = {
     proto = device_proto,
     name = device_name,
   profile = device_profile,
     ip = device_ip,
     port = device_port,
   slave = device_slave,
   pollinterval = device_pollinterval,
   id = device_id,
    }
 data = json.encode(device)
    form_data = 'data=' .. socket.url.escape(data)
    socket.http.TIMEOUT = 15  
    local res, code, response_header = socket.http.request(url, form_data)
 return res, code, response_header
end

-- Create devices
for i = 1, 10, 1 do
 -- used fields to send: protocol,name,profile,ip,port,slave,pollinterval,id
 result, code, response_header = url_send("tcp", "Device " .. i, "iEM-iEM3155", "192.168.10." .. i, "502", i, "5", "")
 -- Calculate results for creation devices log
 if result == '{"success":true}' then
   number_of_devices = number_of_devices + 1
 else
   number_failed = number_failed + 1
 end
end

-- Create result log
if number_failed == 0 then
 log ("Created " .. number_of_devices .. " devices succesfully")
else
 log ("Created " .. number_of_devices .. " devices succesfully and creation of " .. number_failed .. " devices failed")
end

-- Disable script when done automaticly
script.disable(_SCRIPTNAME)

Erwin;

This functionality (which is utterly brilliant by the way) seems to be broken i FW 2.1.1. I´ve tried on all FW up to 2.1.0 and it works as a charm. I suspect there's some parameters missing. When I log the response i get a 404. Do you have any idea what might have changed (I don't like undocumented changes!!!) in FW 2.1.1?
Reply
#10
Hi,

The new FW requires a extra parameter 'timeout' that old FW didn't use, and also new browsers are blocking credentials in URL so i changed the script in a new methode for web requests.

Thanks to Admin for that (: 

Here is the new script:
Code:
-- Batch adding modbus devices v1.1 - Created by Erwin van der Zwart - Schneider Electric Netherlands -----------
-- For spaceLYnk FW 2.1.1 or higher and Wiser for KNX FW 2.1.1 or higher ----------------------------------------
----------------------------------------------- Start Parameters ------------------------------------------------

Number_of_devices = 10
Device_name_prefix = "Device"
Device_protocol = "tcp"
Device_port = 502
Device_ip_range = "192.168.0"
Device_ip_start = 1
Device_profile = "iEM-iEM3155"
Device_timeout = 3
Device_pollinterval = 5
Device_slave_start = 1

------------------------------------------------ End Parameters -------------------------------------------------
------------------------------------ DON'T CHANGE ANYTHING UNDER THIS LINE --------------------------------------

-- Set counters for creation log
number_of_devices = 0
number_failed = 0

-- Function to send request (in this case to create a modbus device)
function webrequest(mod, act, vars, data)
  require('json')
  require('dbenv')
  local path
  vars = vars or {}
  function getvar(v)
    return vars[ v ]
  end
  json.data = function()
    return data or {}
  end
  if mod == 'plugin' then
    path = 'plugins/' .. act .. '/web.lua'
  else
    path = 'web/' .. mod .. '/' .. act .. '.lua'
  end
  return dofile('/lib/genohm-scada/' .. path)
end

-- Create devices
for i = 0, Number_of_devices - 1, 1 do
 requestdata = {
   id = "",
   timeout = "" .. Device_timeout,
   pollinterval = "" .. Device_pollinterval,
   slave = "" .. Device_slave_start + i,
   port = "" .. Device_port,
   ip = Device_ip_range .. "." .. Device_ip_start + i,
   profile = Device_profile,
   name = Device_name_prefix .. " " .. i + 1,
   proto = Device_protocol,
 }
 result = webrequest('plugin', 'modbus', {request = 'device-save'}, requestdata)
 -- Calculate results for creation devices log
 if result == '{"success":true}' then
   number_of_devices = number_of_devices + 1
 else
   number_failed = number_failed + 1
 end
end

-- Create result log
if number_failed == 0 then
 log ("Created " .. number_of_devices .. " devices succesfully")
else
 log ("Created " .. number_of_devices .. " devices succesfully and creation of " .. number_failed .. " devices failed")
end

-- Disable script when done automaticly
script.disable(_SCRIPTNAME)
BR,

Erwin
Reply
#11
Fantastic Erwin!

I did notice the timeout parameter was missing, but could not get it to work anyway.

Thank you so much!
Reply
#12
(18.02.2017, 00:03)Erwin van der Zwart Wrote:
Code:
query = 'SELECT * FROM modbus_mapping'
counter = 0
for _, mapping in ipairs(db:getall(query)) do
  if mapping.bus_address == "" or  mapping.bus_address == nil then
    grp_address = '1/1/' .. counter
    if counter < 256 then
      dec_grp_address = knxlib.encodega(grp_address)
      address = grp.create({
      datatype = mapping.datatype,
      address = grp_address,
      name = mapping.name,
      comment = 'Auto mapped by script',
      units = mapping.units,
      tags = {},
      })
      counter = counter + 1
      db:update('modbus_mapping', { bus_address = dec_grp_address, bus_write = 1, value_delta = 0.1, value_custom = 'Auto mapped by script' }, { id = mapping.id })
    end
  end
end

Hi, Erwin. I think I found small Error in this script.
datatype = mapping.datatype - with this we are creating object with modbus datatype

i think it should be
datatype = mapping.bus_datatype
Reply
#13
(28.05.2018, 08:16)Erwin van der Zwart Wrote: Hi,

The new FW requires a extra parameter 'timeout' that old FW didn't use, and also new browsers are blocking credentials in URL so i changed the script in a new methode for web requests.

Thanks to Admin for that (: 

Here is the new script:

 -- SNIP --


BR,

Erwin

I´m trying to rewrite the script you posted Erwin, in order to enable RTU and change the settings. However, I'm getting a error when calling the web request function:

Code:
/lib/genohm-scada/plugins/modbus/web:0: attempt to index global 'uci' (a nil value)
stack traceback:
/lib/genohm-scada/plugins/modbus/web: in function </lib/genohm-scada/plugins/modbus/web:0>
/lib/genohm-scada/plugins/modbus/web: in function </lib/genohm-scada/plugins/modbus/web:0>
[C]: in function 'webrequest'
User script:46: in main chunk

I´m guessing this has something to do with the libraries we are calling at the top, so I'm not sure what it is trying to index  Huh 

Any ideas?
Reply
#14
Add require('uci') to the beginning of webrequest function script.
Reply
#15
(14.09.2018, 09:15)admin Wrote: Add require('uci') to the beginning of webrequest function script.

Wow, worked right away!
Reply
#16
Hello. I have some strange thing with mapping script. All mappings do successfull, but no one mapping read data from ModBus until one of them is opened and save manually.
HomeLynk 2.3.0
Reply
#17
Database changes via scripts are not loaded automatically by the Modbus daemon. Reload happens when you make any change via UI.
Reply
#18
Good Morning,

I have been implementing this code in a project with modbus RTU.
I have it running well but I cant seem to find a way to use this to set the Modbus Device Address.
I assume this is the Mapping.ID? inspecting the SQL it appears to me a locked entity? does this mean it is not possible?

Kind regards,
Brent
Reply
#19
Hi Erwin,
Do you know if there is an equivalent library/function that does the same job for the C-bus SHAC/NAC variant made by Schneider Electric?  I'm trying to automate the mappings process but not for a KNX device.
Reply
#20
Not that i know of, but I assume the modbus part is exactly the same in a SHAC/NAC and that the difference is only in the group address format.

We don’t need this script anymore for our KNX models as it’s now embedded in the FW/GUI of the latest FW 2.5.1...
Reply


Forum Jump: