modbus_tcp_server v1.0.2 ModbusServer

This module implements the Modbus Server and is called from the Modbus TCP Server.

The purpose of this module is to handle the requests from the TCP server and execute the reads and writes. It returns the correct response or exception.

This module expects the ModbusDatabase to be running which will be read from or written to. See the examples.

The following Modbus Function Codes are supported:

  • 01->Read Coils
  • 02->Read Discrete Inputs
  • 03->Read Holding Registers
  • 04->Read Input Registers
  • 05->Write Single Coil
  • 06->Write Single Register
  • 15->Write Multiple Coils
  • 16->Write Multiple Registers
  • 22->Mask Write Register
  • 23->Read/Write Multiple Registers

The following Modbus Function Codes are NOT supported:

  • 24->Read FIFO Queue
  • 20->Read File Record
  • 21->Write File Record
  • 07->Read Exception Status
  • 08->Diagnostic
  • 11->Get Com Event Counter
  • 12->Get Com Event Log
  • 17->Report Server ID
  • 43->Read Device Information
  • 43->Encapsulated Interface Transport
  • 43->CANopen General Reference

The following Modbus Exception Codes can be expected to be returned from this server:

  • 01->Illegal Function
  • 02->Illegal Data Address
  • 03->Illegal Data Value

This server will never return any of the following Modbus Exception Codes:

  • 04->Server Device Failure
  • 05->Acknowledge
  • 06->Server Device Busy
  • 08->Memory Parity Error
  • 0A->Gateway Path Unavailable
  • 0B->Gateway Target Device Failed to Respond

Refer to the technical documents section of the http://modbus.org website for more information about the Modbus protocol.

Examples

The follwing examples show how to start the Modbus Server and send requests to it.

Example 1: Read Coils

iex> ModbusServer.start_link()
iex> database = %{0 => true, 1 => false, 2 => true, 3 => true, 4 => false}
iex> ModbusDatabase.start_link(database, :modbus_coil_database)
iex> function_code = 1
iex> starting_coil = 0
iex> read_quantity = 1
iex> message = {function_code, [starting_coil, read_quantity]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, [true]}
iex> GenServer.stop(:modbus_coil_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 2: Read Discrete Inputs

iex> ModbusServer.start_link()
iex> database = %{0 => true, 1 => false, 2 => true, 3 => true, 4 => false}
iex> ModbusDatabase.start_link(database, :modbus_discrete_input_database)
iex> function_code = 2
iex> starting_coil = 0
iex> read_quantity = 1
iex> message = {function_code, [starting_coil, read_quantity]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, [true]}
iex> GenServer.stop(:modbus_discrete_input_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 3: Read Holding Registers

iex> ModbusServer.start_link()
iex> database = %{0 => 100, 1 => 65535, 2 => 32768, 3 => -100, 4 => 95}
iex> ModbusDatabase.start_link(database, :modbus_holding_register_database)
iex> function_code = 3
iex> starting_register = 0
iex> read_quantity = 1
iex> message = {function_code, [starting_register, read_quantity]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, [100]}
iex> GenServer.stop(:modbus_holding_register_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 4: Read Input Registers

iex> ModbusServer.start_link()
iex> database = %{0 => 100, 1 => 65535, 2 => 32768, 3 => -100, 4 => 95}
iex> ModbusDatabase.start_link(database, :modbus_input_register_database)
iex> function_code = 4
iex> starting_register = 0
iex> read_quantity = 1
iex> message = {function_code, [starting_register, read_quantity]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, [100]}
iex> GenServer.stop(:modbus_input_register_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 5: Single Coil Write

iex> ModbusServer.start_link()
iex> database = %{0 => true, 1 => false, 2 => true, 3 => true, 4 => false}
iex> ModbusDatabase.start_link(database, :modbus_coil_database)
iex> function_code = 5
iex> starting_register = 0
iex> write_value = 0x0000
iex> message = {function_code, [starting_register, write_value]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, {0, false}}
iex> GenServer.stop(:modbus_coil_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 6: Single Register Write

iex> ModbusServer.start_link()
iex> database = %{0 => 100, 1 => 65535, 2 => 32768, 3 => -100, 4 => 95}
iex> ModbusDatabase.start_link(database, :modbus_holding_register_database)
iex> function_code = 6
iex> starting_register = 0
iex> write_value = 976
iex> message = {function_code, [starting_register, write_value]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, {0, 976}}
iex> GenServer.stop(:modbus_holding_register_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 6: Multiple Coil Write

iex> ModbusServer.start_link()
iex> database = %{0 => true, 1 => false, 2 => true, 3 => true, 4 => false}
iex> ModbusDatabase.start_link(database, :modbus_coil_database)
iex> function_code = 15
iex> starting_register = 0
iex> quantity = 2
iex> byte_count = 1
iex> write_value1 = false
iex> write_value2 = true
iex> message = {function_code, [starting_register, quantity, byte_count, write_value1, write_value2]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, {0, 2}}
iex> GenServer.stop(:modbus_coil_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 8: Multiple Register Write

iex> ModbusServer.start_link()
iex> database = %{0 => 100, 1 => 65535, 2 => 32768, 3 => -100, 4 => 95}
iex> ModbusDatabase.start_link(database, :modbus_holding_register_database)
iex> function_code = 16
iex> starting_register = 1
iex> quantity = 2
iex> byte_count = 4
iex> write_value1 = 100
iex> write_value2 = -100
iex> message = {function_code, [starting_register, quantity, byte_count, write_value1, write_value2]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, {1, 2}}
iex> GenServer.stop(:modbus_holding_register_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 9: Mask Write Register

iex> ModbusServer.start_link()
iex> database = %{0 => 18, 1 => 65535, 2 => 32768, 3 => -100, 4 => 95}
iex> ModbusDatabase.start_link(database, :modbus_holding_register_database)
iex> function_code = 22
iex> starting_register = 0
iex> and_mask = 0xF2
iex> or_mask = 0x25
iex> message = {function_code, [starting_register, and_mask, or_mask]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, {0, 0xF2, 0x25}}
iex> GenServer.stop(:modbus_holding_register_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Example 10: Read/Write Holding Registers - Read Registers 0-1, Write 50 to Register 1

iex> ModbusServer.start_link()
iex> database = %{0 => 18, 1 => 65535, 2 => 32768, 3 => -100, 4 => 95}
iex> ModbusDatabase.start_link(database, :modbus_holding_register_database)
iex> function_code = 23
iex> starting_register = 0
iex> read_count = 2
iex> write_register = 0
iex> write_count = 1
iex> write_value = 50
iex> byte_count = 2
iex> message = {function_code, [starting_register, read_count, write_register, write_count, byte_count, write_value]}
iex> {:response, values} = ModbusServer.request(ModbusServer, message)
{:response, [50, 65535]}
iex> GenServer.stop(:modbus_holding_register_database)
:ok
iex> GenServer.stop(ModbusServer)
:ok

Summary

Functions

This function calls the GenServer identified by the passed pid, passing it the message

This function starts the ModbusServer process

Functions

request(pid, message)

This function calls the GenServer identified by the passed pid, passing it the message.

The pid parameter is the GenServer pid to send the message to.

The message parameter is the message to pass to the ModbusServer.

start_link()

This function starts the ModbusServer process.

There is no parameters when settings this up. The process is registered using the MODULE, which is ModbusServer.