MODBUS over Ethernet
MODBUS is supported in all DLI power controllers and DIN relays with WiFi.
For more information look at the Help/External APIs inthe power controller.
Unit ID:1
The relays are mapped to coils (1..8)
Standard MODBUS commands can be used to manipulate them, ie:
READ_COILS = 0x01
WRITE_SINGLE_COIL=0x05
WRITE_MULTIPLE_COILS = 0x0F
Use scripting and the REST API to configure custom registers to do whatever you please.
As of firmware 1.10.21.0, discrete inputs, input and holding registers can be created on the External API page to serve as a common interaction medium between Modbus/TCP and scripting.
See the built-in Help for more information.
Here's a sample script snippet: (from default.modbus_demo)
function flip_all_modbus_discrete_inputs() for i=1,#modbus.discrete_inputs do modbus.discrete_inputs[i].value=not modbus.discrete_inputs[i].value end end function allow_custom_meter_to_be_set_from_modbus() -- You're expected to create a custom meter and specify its ID here local custom_value=meter.values["custom_meter_id"] if custom_value then -- You're expected to create the registers manually and assign -- sequential addresses to them and specify them here local rlow,rhigh=modbus.holding_registers[1],modbus.holding_registers[2] assert(rlow and rhigh) custom_value.value=util.float32be.decode(util.uint16be.encode(rhigh.value)..util.uint16be.encode(rhigh.value)) for i,t,data in event.stream(event.change_listener(rhigh)) do if data.key=="value" then custom_value.value=util.float32be.decode(util.uint16be.encode(rhigh.value)..util.uint16be.encode(rhigh.value)) end end else log.info("Please edit the script to specify a custom meter meter") end end --Expose temperature to Modbus using 2 registers -- to handle the floating point function expose_temperature_to_modbus() local temperature=meter.values["sensors.0.temperature"] or meter.values["environment.temperature"] if temperature then -- You're expected to create the registers manually, assign -- sequential addresses to them and specify them here (they may be -- input or holding registers). local rlow,rhigh=modbus.input_registers[1],modbus.input_registers[2] assert(rlow and rhigh) local encoded_value=util.float32be.encode(temperature.value) rlow.value=util.uint16be.decode(encoded_value:sub(3,4)) rhigh.value=util.uint16be.decode(encoded_value:sub(1,2)) for i,t,data in event.stream(event.change_listener(temperature)) do if data.key=="value" then encoded_value=util.float32be.encode(temperature.value) rlow.value=util.uint16be.decode(encoded_value:sub(3,4)) rhigh.value=util.uint16be.decode(encoded_value:sub(1,2)) end end else log.info("Please edit the script to choose an existing meter") end end
Other examples of exposing the ADC to Modbus registers and another temperature example.
-- Expose 10V ADC to Modbus -- This is the actual voltage multiplied by 10 -- Divide by 10 to get the actual voltage to 1 decimal point function expose_ACD_10_to_modbus() local voltage0=meter.values["voltage.0"] local register = 2 -- The input_register to use if voltage0 then local volt= voltage0.value -- enable the monitor -- You're expected to create the registers manually, assign -- sequential addresses to them and specify them here (they may be -- input or holding registers). for i,t,data in event.stream(event.change_listener(voltage0)) do if data.key=="value" then modbus.input_registers[register].value = math.floor(voltage0.value*10 + 0.5) end end else log.info("Please edit the script to choose an existing meter") end end -- Expose 100V ADC to Modbus -- This is the actual voltage multiplied by 10 -- Divide by 10 to get the actual voltage to 1 decimal point function expose_ACD_100_to_modbus() local voltage1=meter.values["voltage.1"] local register = 3 -- The input_register to use if voltage1 then local volt= voltage1.value -- enable the monitor -- You're expected to create the registers manually, assign -- sequential addresses to them and specify them here (they may be -- input or holding registers). for i,t,data in event.stream(event.change_listener(voltage1)) do if data.key=="value" then modbus.input_registers[register].value = math.floor(voltage1.value*10 + 0.5) end end else log.info("Please edit the script to choose an existing meter") end end
-- Convert from Kelvin to Fahrenheit function kelvin_to_fahrenheit(t) if not tonumber(t) then log.err("Invalid temperature") return 0 end return 9 / 5 * (t - 273.16) + 32 end -- Temperature is Fahrenheit multiplied by 10. --Divide by 10 to get the temperature to one decimal place function expose_temperature_fahrenheit_to_modbus() local temperature=meter.values["sensors.0.temperature"] -- or meter.values["environment.temperature"] local register = 1 -- The input_register to use if temperature then -- You're expected to create the registers manually, assign -- sequential addresses to them and specify them here (they may be -- input or holding registers). local temp = temperature.value for i,t,data in event.stream(event.change_listener(temperature)) do if data.key=="value" then modbus.input_registers[1].value = math.floor(kelvin_to_fahrenheit(temperature.value) *10) end end else log.info("Please edit the script to choose an existing meter") end end
Here is an example which adds eight input_registers via SSH (all one line)
uom set modbus/input_registers '[{"allow_read":true,"index":0,"name":"IR1","value":0},{"allow_read":true,"index":1,"name":"IR2","value":0},{"allow_read":true,"index":2,"name":"IR3","value":0},{"allow_read":true,"index":3,"name":"IR4","value":0},{"allow_read":true,"index":4,"name":"IR5","value":0},{"allow_read":true,"index":5,"name":"IR6","value":0},{"allow_read":true,"index":6,"name":"IR7","value":0},{"allow_read":true,"index":7,"name":"IR8","value":0}]'
If we haven't answered your questions here, please call (408) 330-5599 or send us an email. We'll be glad to help.
© Digital Loggers, Inc. 2005-2020.