Modbux is a library for network and serial Modbus communications.
This library currently supports behaviors for TCP (Client & Server) and RTU (Master & Slave) protocols.
The following list is the current supported protocols/behaviors and helpers:
-
Modbus RTU:
- Master
- Slave
- Framer
-
Modbus TCP:
- Client
- Server
-
Helpers:
- IEEE754 Float support
- Endianess
The package can be installed by adding modbux
to your list of dependencies in mix.exs
:
def deps do
[
{:modbux, "~> 0.1.0"}
]
end
Modbus RTU is an open serial protocol derived from the Master/Slave architecture originally developed by Modicon. This protocol primarily uses an RS-232 or RS-485 serial interfaces for communications.
To start a Modbus RTU Slave process use start_link/1
.
The following options are available:
tty
- defines the serial port to spawn the Slave.gen_opts
- defines extra options for the Genserver OTP configuration.uart_opts
- defines extra options for the UART configuration.model
- defines the DB initial state.active
- (true
orfalse
) enable/disable DB updates notifications (mailbox).
The messages (when active mode is true
) have the following form:
{:modbus_rtu, {:slave_request, payload}}
or
{:modbus_rtu, {:slave_error, payload, reason}}
The following are some reasons:
:ecrc
- corrupted message (invalid crc).:einval
- invalid function.:eaddr
- invalid memory address requested.
The model or data base (DB) defines the slave/server memory map, the DB is defined by the following syntax:
%{slave_id => %{{memory_type, address_number} => value}}
where:
slave_id
- specifies a unique unit address from 1 to 247.memory_type
- specifies the memory between::c
- Discrete Output Coils.:i
- Discrete Input Contacts.:ir
- Analog Input Registers.:hr
- Analog Output Registers.
address_number
- specifies the memory address.value
- the current value from that memory.
# DB inital state
model = %{
80 => %{
{:c, 1} => 1,
{:c, 2} => 0,
{:i, 1} => 1,
{:i, 2} => 1,
{:ir, 1} => 0,
{:ir, 2} => 1,
{:hr, 1} => 102,
{:hr, 2} => 103
}
}
# Starts the Slave at "ttyUSB0"
{:ok, s_pid} = Modbux.Rtu.Slave.start_link(tty: "ttyUSB0", model: model, active: true)
if needed, the Slave DB can be modified in runtime with elixir code by using request/2
,
a cmd
must be used to update the DB, the cmd
is a 4 elements tuple, as follows:
{:rc, slave, address, count}
readcount
coils.{:ri, slave, address, count}
readcount
inputs.{:rhr, slave, address, count}
readcount
holding registers.{:rir, slave, address, count}
readcount
input registers.{:fc, slave, address, value}
force single coil.{:phr, slave, address, value}
preset single holding register.{:fc, slave, address, values}
force multiple coils.{:phr, slave, address, values}
preset multiple holding registers.
To start a Modbus RTU Master process use start_link/1
.
The following options are available:
tty
- defines the serial port to spawn the Master.timeout
- defines slave timeout.active
- (true
orfalse
) specifies whether data is received as messages (mailbox) or by callingrequest/2
.gen_opts
- defines extra options for the Genserver OTP configuration.uart_opts
- defines extra options for the UART configuration (defaults: [speed: 115200, rx_framing_timeout: 1000]).
The messages (when active mode is true) have the following form:
{:modbus_rtu, {:slave_response, cmd, values}}
or
{:modbus_rtu, {:slave_error, payload, reason}}
The following are some reasons:
:ecrc
- corrupted message (invalid crc).:einval
- invalid function.:eaddr
- invalid memory address requested.
use request/2
to send a cmd
(command) to a Modbus RTU Slave.
# Starts the Master at "ttyUSB1" (in the example is connected to ttyUSB1)
{:ok, m_pid} = Modbux.Rtu.Master.start_link(tty: "ttyUSB1")
# Read 2 holding registers at 1 (memory address) from the slave 80
resp = Modbux.Rtu.Master.request(m_pid, {:rhr, 80, 1, 2})
# resp == {:ok, [102, 103]}
Modbus TCP (also Modbus TCP/IP) is simply the Modbus RTU protocol with a TCP interface that runs on a network.
To start a Modbus TCP Server process use start_link/1
.
The following options are available:
port
- is the Modbux TCP Server tcp port number.timeout
- is the connection timeout.model
- defines the DB initial state.sup_otps
- server supervisor OTP options.active
- (true
orfalse
) enable/disable DB updates notifications (mailbox).
The messages (when active mode is true) have the following form:
{:modbus_tcp, {:slave_request, payload}}
# DB initial state
model = %{80 => %{{:c, 20818} => 0, {:hr, 20818} => 0}}
# Starts the Server at tcp port: 2000
Modbux.Tcp.Server.start_link(model: model, port: 2000)
To start a Modbus TCP Client process use start_link/1
.
The following options are available:
ip
- is the internet address of the desired Modbux TCP Server.tcp_port
- is the desired Modbux TCP Server tcp port number.timeout
- is the connection timeout.active
- (true
orfalse
) specifies whether data is received as messages (mailbox) or by callingconfirmation/1
each timerequest/2
is called.
The messages (when active mode is true) have the following form:
{:modbus_tcp, cmd, values}
to connect to a Modbus TCP Server connect/1
.
Use request/2
to send a cmd
(command) to a Modbus TCP Server and confirmation/1
to parse the server response.
# Starts the Client that will connect to a Server with tcp port: 2000
{:ok, cpid} = Modbux.Tcp.Client.start_link(ip: {127,0,0,1}, tcp_port: 2000, timeout: 2000)
# Connect to the Server
Modbux.Tcp.Client.connect(cpid)
# Read 1 coil at 20818 from the device 80
Modbux.Tcp.Client.request(cpid, {:rc, 0x50, 20818, 1})
# Parse the Server response
resp = Modbux.Tcp.Client.confirmation(cpid)
# resp == {:ok, [0]}
Several modbus register use IEEE754 float format, therefore this library also provides functions to encode and decode data.
# Encode
5.0 = Modbux.IEEE754.from_2_regs(0x40a0, 0x0000, :be)
[-5.0, 5.0] = Modbux.IEEE754.from_2n_regs([0xc0a0, 0x0000, 0x40a0, 0x0000], :be)
# Decode
[0xc0a0, 0x0000] = Modbux.IEEE754.to_2_regs(-5.0)
[0xc0a0, 0x0000, 0x40a0, 0x0000] = Modbux.IEEE754.to_2n_regs([-5.0, 5.0])
Based on https://www.h-schmidt.net/FloatConverter/IEEE754.html.
Depending on the device / server the data can be encoded with different types of endianess, therefore this library also provides functions to encode data.
# Encode
2.3183081793789774e-41 = Modbux.IEEE754.from_2_regs(0x40a0, 0x0000, :le)
[6.910082987278538e-41, 2.3183081793789774e-41] = Modbux.IEEE754.from_2n_regs([0xc0a0, 0x0000, 0x40a0, 0x0000], :le)
Good to know:
- Erlang default endianess is BIG
- MODBUS default endianess is BIG (p.34)
- MODBUS CRC endianess is LITTLE (p.16)
The docs can be found at https://hexdocs.pm/modbux.
Based on:
- http://modbus.org/docs/PI_MBUS_300.pdf
- http://modbus.org/docs/Modbux_Messaging_Implementation_Guide_V1_0b.pdf
- http://modbus.org/docs/Modbux_over_serial_line_V1_02.pdf
- http://www.simplymodbus.ca/index.html
- Fork our repository on github.
- Fix or add what is needed.
- Commit to your repository.
- Issue a github pull request (fill the PR template).
See LICENSE.
- Add Modbux ASCII.
- Add Modbux UDP.
- Add more examples.
- Improve error handling.