Modbus TCP Server

Introduction

This package implements a Modbus TCP Server as an OTP application. Modbus is an industrial automation communications protocol. Detailed protocol specifications can be found at modbus.org.

Refer to the documentation in ModbusServer to see what functionality is available in this implementation.

It should be noted that Reading and Writing Files will not be in scope of this project, unless someone else contributes the code, tests, and documentation. I have found that Reading and Writing Files is used very rarely, if ever, in industry. There are always alternative ways to transfer files. Protocols such as FTP and SFTP are good examples.

Also, none of the Function codes marked as Serial Line Only in the Application Protocol document are implemented in this server due to this server being specifically for TCP. I would not be opposed to implementing a serial version but it is not an immediate goal.

Disclaimer

This work is completely my own and is not endorsed nor supported by my employer.

Status

This server has a test suite with tests created directly from the Modbus specification. Additionally, it was tested using the Kepware Modbus TCP OPC Server.

Installation

The package can be installed as:

  1. Add modbus_tcp_server to your list of dependencies in mix.exs:

    def deps do
      [{:modbus_tcp_server, "~> 1.1.0"}]
    end
  2. Ensure modbus is started before your application:

    def application do
      [applications: [:modbus_tcp_server]]
    end

Usage

Configuration

The Modbus TCP Server database is defined through configuration files. The Mix.Config format of configuration is used. There are 3 tiers of configuration files, each one overrides the values from the previous. When the server is deployed on Windows, the following directories are used.

  1. %PROGRAMDATA%/Elixir-Modbus/modbus_map.exs
  2. %APPDATA%/Elixir-Modbus/modbus_map.exs
  3. modbus_map.exs

If the server is deployed on a POSIX (Linux/Unix) system, the following directories are used.

  1. /etc/elixir-modbus/modbus_map.exs
  2. ~/.elixir-modbus/modbus_map.exs
  3. modbus_map.exs

Item #3 is a modbus_map.exs file in the directory where the server is started.

If item #1 or item #2 don’t exist, an empty configuration is inserted and the merge is correctly handled.

modbus_map.exs File Format

The modbus_map.exs files follow the Mix.Config format and must return a Keyword List. The application name used in the Modbus TCP Server is :modbus_tcp_server.

The modbus_map.exs File Format is shown below.

[
  {:modbus_tcp_server, # this line states that this configuration applies to the :modbus_tcp_server application
    [
      {:modbus_coil_database, [0, 1]}, # this line defines modbus_coil_database will contain 2 coils: 0 and 1
      {:modbus_discrete_input_database, [0, 1, 2]}, # this line defines modbus_discrete_input_database will contain 3 discrete inputs: 0, 1 and 2
      {:modbus_holding_register_database, [5]}, # this line defines modbus_holding_register_database will contain 1 discrete inputs: 5
      {:modbus_input_register_database, [5, 15]}, # this line defines modbus_input_register_database will contain 2 discrete inputs: 5 and 15
    ]
  }
]

Configuration Example

Multiple files are used for configuration in the following example. You will see how values from more specific configuration files override those from less specific ones.

The follow block shows the contents of the %PROGRAMDATA%/Elixir-Modbus/modbus_map.exs (or /etc/elixir-modbus/modbus_map.exs) file.

[
  {:modbus_tcp_server, 
    [
      {:modbus_discrete_input_database, [0, 1]},
    ]
  }
]

Next up are the contents from %APPDATA%/Elixir-Modbus/modbus_map.exs (or ~/.elixir-modbus/modbus_map.exs).

[
  {:modbus_tcp_server, 
    [
      {:modbus_input_register_database, [0, 1, 2]}
      {:modbus_discrete_input_database, [5, 6]},
    ]
  }
]

Finally, the modbus_map.exs from the current working directory is shown.

[
  {:modbus_tcp_server, 
    [
      {:modbus_coil_database, 0 .. 10 |> Enum.to_list},
      {:modbus_discrete_input_database, [5, 6, 7]},
      {:modbus_holding_register_database, 0 .. 20 |> Enum.to_list},
    ]
  }
]

The final configuration loaded into the application will merge all 3 of these files, with the modbus_map.exs having the highest precedence, followed by %APPDATA/Elixir-Modbus/modbus_map.exs (~/.elixir-modbus/modbus_map.exs), and finally by %PROGRAMDATA%/Elixir-Modbus/modbus_map.exs (/etc/elixir-modbus/modbus_map.exs).

[
  {:modbus_tcp_server, 
    [
      {:modbus_coil_database, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]},
      {:modbus_discrete_input_database, [5, 6, 7]},
      {:modbus_holding_register_database, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]},
      {:modbus_input_register_database, [0, 1, 2]}
    ]
  }
]

This example also shows that you can create the list of registers for any of the databases using any valid elixir code. The :modbus_coil_database and :modbus_holding_register_database use ranges and the Enum.to_list function to create the list of registers.

Contributing

Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.

You can contribute in many ways:

Types of Contributions

Report Bugs

Report bugs at bitbucket.

If you are reporting a bug, please include:

  • Your operating system name and version.
  • Any details about your local setup that might be helpful in troubleshooting.
  • Detailed steps to reproduce the bug.

Fix Bugs

Look through the bitbucket issues for bugs. Anything tagged with “bug” and “help wanted” is open to whoever wants to implement a fix for it.

Implement Features

Look through the GitHub issues for features. Anything tagged with “enhancement” and “help wanted” is open to whoever wants to implement it.

Write Documentation

Modbus TCP Server could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such.

Submit Feedback

The best way to send feedback is to file an issue at bitbucket.

If you are proposing a new feature:

  • Explain in detail how it would work.
  • Keep the scope as narrow as possible, to make it easier to implement.
  • Remember that this is a volunteer-driven project, and that contributions are welcome :)

Get Started!

Ready to contribute? Here’s how to set up Modbus TCP Server for local development.

  1. Fork the Modbus TCP repo on bitbucket.

  2. Clone your fork locally::

    $ cd path_for_the_repo
    $ hg clone hg@bitbucket.com/idahogray/modbus-elixir
  3. Create a branch for local development::

    $ hg branch name-of-your-bugfix-or-feature

    Now you can make your changes locally.

  4. The next step would be to run the test cases. Before you run the tests, you should ensure all dependancies are installed::

    $ mix deps.get
    $ mix test --no-start
  5. If your contribution is a bug fix or new feature, you must add a test to the existing test suite.

  6. Commit your changes and push your branch to GitHub::

    $ hg commit -m "Your detailed description of your changes."
    $ hg push
  7. Submit a pull request through the bitbucket website.

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. The pull request must include tests.

  2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst.

History

  • 1.0.0 (2017/01/01)

    • First release on hex
  • 1.0.1 (2017/01/04)

  • 1.0.2 (2017/01/08)

    • Fixed some tests that I didn’t realize were failing when I released v1.0.0
    • Fixed an error related to string handling in the Configuration of the modbus database
  • 1.1.0 (2017/01/08)

    • Added the ability to have the multi-tier configuration files on POSIX systems (Linux/Unix)

Credits

Development Lead

Contributors

Copyright (c) 2016, Keith Gray.

Modbus TCP Server is licensed under the MIT License.