Developer documentation

The details printed in debug mode (requests and responses) are very useful for using the included dummy_serial port for unit testing purposes. For examples, see the file test/

Design considerations

My take on the design is that is should be as simple as possible, hence the name MinimalModbus, but it should implement the smallest number of functions needed for it to be useful. The target audience for this driver simply wants to talk to Modbus clients using a serial interface using some simple driver.

Only a few functions are implemented. It is very easy to implement lots of (seldom used) functions, resulting in buggy code with large fractions of it almost never used. It is instead much better to implement the features when needed/requested. There are many Modbus function codes, but I guess that most are not used.

It is a goal that the same driver should be compatible for both Python2 and Python3 programs. Some suggestions for making this possible are found here:

There should be unittests for all functions, and mock communication data.

Errors should be caught as early as possible, and the error messages should be informative. For this reason there is type checking for for the parameters in most functions. This is rather un-pythonic, but is intended to give more clear error messages (for easier remote support).

Note that the term ‘address’ is ambigous, why it is better to use the terms ‘register address’ or ‘slave address’.

Use only external links in the README.txt, otherwise they will not work on Python Package Index (PyPI). No Sphinx-specific constructs are allowed in that file.

Design priorities:
  • Easy to use
  • Catch errors early
  • Informative error messages
  • Good unittest coverage
  • Same codebase for Python2 and Python3

General driver structure

The general structure of the program is shown here:

Function Description
read_register() One of the facades for _genericCommand().
_genericCommand() Generates payload, then calls _performCommand().
_performCommand() Embeds payload into error-checking codes etc, then calls _communicate().
_communicate() Handles raw strings for communication via pySerial.

Most of the logic is located in separate (easy to test) functions on module level. For a description of them, see Internal documentation for MinimalModbus.

Number conversion to and from bytestrings

The Python module struct is used for conversion. See

Several wrapper functions are defined for easy use of the conversion. These functions also do argument validity checking.

Data type To bytestring From bytestring
(internal usage) _numToOneByteString()  
Bit _createBitpattern() _bitResponseToValue()
Integer (char, short) _numToTwoByteString() _twoByteStringToNum()
Several registers _valuelistToBytestring() _bytestringToValuelist()
Long integer _longToBytestring() _bytestringToLong()
Floating point number _floatToBytestring() _bytestringToFloat()
String _textstringToBytestring() _bytestringToTextstring()

Note that the struct module produces byte buffers for Python3, but bytestrings for Python2. This is compensated for automatically by using the wrapper functions _pack() and _unpack().

For a description of them, see Internal documentation for MinimalModbus.


Unit tests are provided in the tests subfolder. To run them:


Also a dummy/mock/stub for the serial port, dummy_serial, is provided for test purposes. See Documentation for dummy_serial (which is a serial port mock).

The test coverage analysis is found at

Hardware tests are performed using a Delta DTB4824 process controller. See Internal documentation for hardware testing of MinimalModbus using DTB4824 for more information.

A brief introduction to unittesting is found here:

The unittest module is documented here:

The unittests uses previosly recorded communication data for the testing. Inside the unpacked folder go to test and run the unit tests with:



To automatically run the tests for the different Python versions:


It is also possible to run the individual test files:


MinimalModbus is also tested with hardware. A Delta temperature controller DTB4824 is used together with a USB-to-RS485 converter.

Run it with:


The baudrate and portname can optionally be set from command line:

python 19200 /dev/ttyUSB0

For more details on testing with this hardware, see Internal documentation for hardware testing of MinimalModbus using DTB4824.

Making sure that error messages are informative for the user

To have a look on the error messages raised during unit testing of minimalmodbus, monkey-patch test_minimalmodbus.SHOW_ERROR_MESSAGES_FOR_ASSERTRAISES as seen here:

>>> import unittest
>>> import test_minimalmodbus
>>> test_minimalmodbus.SHOW_ERROR_MESSAGES_FOR_ASSERTRAISES = True
>>> suite = unittest.TestLoader().loadTestsFromModule(test_minimalmodbus)
>>> unittest.TextTestRunner(verbosity=2).run(suite)

This is part of the output:

testFunctioncodeNotInteger (test_minimalmodbus.TestEmbedPayload) ...
    TypeError('The functioncode must be an integer. Given: 1.0',)

    TypeError("The functioncode must be an integer. Given: '1'",)

    TypeError('The functioncode must be an integer. Given: [1]',)

    TypeError('The functioncode must be an integer. Given: None',)
testKnownValues (test_minimalmodbus.TestEmbedPayload) ... ok
testPayloadNotString (test_minimalmodbus.TestEmbedPayload) ...
    TypeError('The payload should be a string. Given: 1',)

    TypeError('The payload should be a string. Given: 1.0',)

    TypeError("The payload should be a string. Given: ['ABC']",)

    TypeError('The payload should be a string. Given: None',)
testSlaveaddressNotInteger (test_minimalmodbus.TestEmbedPayload) ...
    TypeError('The slaveaddress must be an integer. Given: 1.0',)

    TypeError("The slaveaddress must be an integer. Given: 'DEF'",)
testWrongFunctioncodeValue (test_minimalmodbus.TestEmbedPayload) ...
    ValueError('The functioncode is too large: 222, but maximum value is 127.',)

    ValueError('The functioncode is too small: -1, but minimum value is 1.',)
testWrongSlaveaddressValue (test_minimalmodbus.TestEmbedPayload) ...
    ValueError('The slaveaddress is too large: 248, but maximum value is 247.',)

    ValueError('The slaveaddress is too small: -1, but minimum value is 0.',)

See test_minimalmodbus for details on how this is implemented.

It is possible to run just a few tests. To load a single class of test cases:

suite = unittest.TestLoader().loadTestsFromTestCase(test_minimalmodbus.TestSetBitOn)

If necessary:


Recording communication data for unittesting

With the known data output from an instrument, we can finetune the inner details of the driver (code refactoring) without worrying that we change the output from the code. This data will be the ‘golden standard’ to which we test the code. Use as many as possible of the commands, and paste all the output in a text document. From this it is pretty easy to reshuffle it into unittest code.

Here is an example how to record communication data, which then is pasted into the test code (for use with a mock/dummy serial port). See for example Internal documentation for unit testing of MinimalModbus (click ‘[source]’ on right side, see RESPONSES at end of the page). Do like this:

>>> import minimalmodbus
>>> minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True # Seems mandatory for Windows
>>> instrument_1 = minimalmodbus.Instrument('/dev/ttyUSB0',10)
>>> instrument_1.debug = True
>>> instrument_1.read_register(4097,1)
MinimalModbus debug mode. Writing to instrument: '\n\x03\x10\x01\x00\x01\xd0q'
MinimalModbus debug mode. Response from instrument: '\n\x03\x02\x07\xd0\x1e)'
>>> instrument_1.write_register(4097,325.8,1)
MinimalModbus debug mode. Writing to instrument: '\n\x10\x10\x01\x00\x01\x02\x0c\xbaA\xc3'
MinimalModbus debug mode. Response from instrument: '\n\x10\x10\x01\x00\x01U\xb2'
>>> instrument_1.read_register(4097,1)
MinimalModbus debug mode. Writing to instrument: '\n\x03\x10\x01\x00\x01\xd0q'
MinimalModbus debug mode. Response from instrument: '\n\x03\x02\x0c\xba\x996'
>>> instrument_1.read_bit(2068)
MinimalModbus debug mode. Writing to instrument: '\n\x02\x08\x14\x00\x01\xfa\xd5'
MinimalModbus debug mode. Response from instrument: '\n\x02\x01\x00\xa3\xac'
>>> instrument_1.write_bit(2068,1)
MinimalModbus debug mode. Writing to instrument: '\n\x05\x08\x14\xff\x00\xcf%'
MinimalModbus debug mode. Response from instrument: '\n\x05\x08\x14\xff\x00\xcf%'

This is also very useful for debugging drivers built on top of MinimalModbus. See for example the test code for omegacn7500 Internal documentation for unit testing of omegacn7500 (click ‘[source]’, see RESPONSES at end of the page).

Using the dummy serial port

A dummy serial port is included for testing purposes, see dummy_serial. Use it like this:

>>> import dummy_serial
>>> import test_minimalmodbus
>>> dummy_serial.RESPONSES = test_minimalmodbus.RESPONSES # Load previously recorded responses
>>> import minimalmodbus
>>> minimalmodbus.serial.Serial = dummy_serial.Serial # Monkey-patch a dummy serial port
>>> instrument = minimalmodbus.Instrument('DUMMYPORTNAME', 1) # port name, slave address (in decimal)
>>> instrument.read_register(4097, 1)

In the example above there is recorded data available for read_register(4097, 1). If no recorded data is available, an error message is displayed:

>>> instrument.read_register(4098, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jonas/pythonprogrammering/minimalmodbus/trunk/", line 174, in read_register
    return self._genericCommand(functioncode, registeraddress, numberOfDecimals=numberOfDecimals)
  File "/home/jonas/pythonprogrammering/minimalmodbus/trunk/", line 261, in _genericCommand
    payloadFromSlave = self._performCommand(functioncode, payloadToSlave)
  File "/home/jonas/pythonprogrammering/minimalmodbus/trunk/", line 317, in _performCommand
    response            = self._communicate(message)
  File "/home/jonas/pythonprogrammering/minimalmodbus/trunk/", line 395, in _communicate
    raise IOError('No communication with the instrument (no answer)')
IOError: No communication with the instrument (no answer)

The dummy serial port can be used also with instrument drivers built on top of MinimalModbus:

>>> import dummy_serial
>>> import test_omegacn7500
>>> dummy_serial.RESPONSES = test_omegacn7500.RESPONSES # Load previously recorded responses
>>> import omegacn7500
>>> omegacn7500.minimalmodbus.serial.Serial = dummy_serial.Serial # Monkey-patch a dummy serial port
>>> instrument = omegacn7500.OmegaCN7500('DUMMYPORTNAME', 1) # port name, slave address
>>> instrument.get_pv()

To see the generated request data (without bothering about the response):

>>> import dummy_serial
>>> import minimalmodbus
>>> minimalmodbus.serial.Serial = dummy_serial.Serial # Monkey-patch a dummy serial port
>>> instrument = minimalmodbus.Instrument('DUMMYPORTNAME', 1)
>>> instrument.debug = True
>>> instrument.read_bit(2068)
MinimalModbus debug mode. Writing to instrument: '\x01\x02\x08\x14\x00\x01\xfb\xae'
MinimalModbus debug mode. Response from instrument: ''

(Then an error message appears)

Data encoding in Python2 and Python3

The string type has changed in Python3 compared to Python2. In Python3 the type bytes is used when communicating via pySerial.

Dependent on the Python version number, the data sent from MinimalModbus to pySerial has different types.

String constants

This is a string constant both in Python2 and Python3:

st = 'abc\x69\xe6\x03'

This is a bytes constant in Python3, but a string constant in Python2 (allowed for 2.6 and higher):

by = b'abc\x69\xe6\x03'

Type conversion in Python3

To convert a string to bytes, use one of these:

bytes(st, 'latin1') # Note that 'ascii' encoding gives error for some values.

To convert bytes to string, use one of these:

str(by, encoding='latin1')
Encoding Allowed range
ascii 0-127
latin-1 0-255

Corresponding in Python2

Ideally, we would like to use the same source code for Python2 and Python3. In Python 2.6 and higher there is the bytes() function for forward compatibility, but it is merely a synonym for str().

To convert from ‘bytes‘(string) to string:

str(by) # not possible to give encoding
by.decode('latin1') # Gives unicode

To convert from string to ‘bytes‘(string):

bytes(st) # not possible to give encoding
st.encode('latin1') # Can not be used for values larger than 127

It is thus not possible to use exactly the same code for both Python2 and Python3. Where it is unavoidable, use:

if sys.version_info[0] > 2:

Extending MinimalModbus

It is straight-forward to extend MinimalModbus to handle more Modbus function codes. Use the method _performCommand() to send data to the slave, and to receive the response. Note that the API might change, as this is outside the official API.

This is easily tested in interactive mode. For example the method read_register() generates payload, which internally is sent to the instrument using _performCommand():

>>> instr.debug = True
>>> instr.read_register(5,1)
MinimalModbus debug mode. Writing to instrument: '\x01\x03\x00\x05\x00\x01\x94\x0b'
MinimalModbus debug mode. Response from instrument: '\x01\x03\x02\x00º9÷'

It is possible to use _performCommand() directly. You can use any Modbus function code (1-127), but you need to generate the payload yourself. Note that the same data is sent:

>>> instr._performCommand(3, '\x00\x05\x00\x01')
MinimalModbus debug mode. Writing to instrument: '\x01\x03\x00\x05\x00\x01\x94\x0b'
MinimalModbus debug mode. Response from instrument: '\x01\x03\x02\x00º9÷'

Use this if you are to implement other Modbus function codes, as it takes care of CRC generation etc.

Other useful internal functions

There are several useful (module level) helper functions available in the minimalmodbus module. The module level helper functions can be used without any hardware connected. See Internal documentation for MinimalModbus. These can be handy when developing your own Modbus instrument hardware.

For example:

>>> minimalmodbus._calculateCrcString('\x01\x03\x00\x05\x00\x01')

And to embed the payload '\x10\x11\x12' to slave address 1, with functioncode 16:

>>> minimalmodbus._embedPayload(1, 16, '\x10\x11\x12')

Playing with two’s complement:

>>> minimalmodbus._twosComplement(-1, bits=8)

Calculating the minimum silent interval (seconds) at a baudrate of 19200 bits/s:

>>> minimalmodbus._calculate_minimum_silent_period(19200)

Note that the API might change, as this is outside the official API.

Found a bug?

Try to isolate the bug by running in interactive mode (Python interpreter) with debug mode activated. Send a mail to the mailing list with the output, and also the output from _getDiagnosticString().

Of course it is appreciated if you can spend a few moments trying to locate the problem, as it might possibly be related to your particular instrument (and thus difficult to reproduce without it). The source code is very readable, so is should be straight-forward to work with. Then please send your findings to the mailing list.

Generate documentation

Use the top-level Make to generate HTML and PDF documentation:

make docs
make pdf

Do linkchecking and test coverage measurements:

make linkcheck make coverage

Alternatively, build the HTML and PDF documentation (in directory doc after making sure that PYTHONPATH is correct):

make html
make latexpdf

Verify all external links:

make linkcheck


The HTML theme on is the Sphinx ‘sphinx_rtd_theme’ theme.

Note that Sphinx version 1.3 or later is required to build the documentation.

Notes on distribution

Installing the module from local svn files

In the trunk directory:

sudo python install

If there are conditional __name__ == '__main__' clauses in the module, these can be tested using (adapt path to your system):

python /usr/local/lib/python2.6/dist-packages/
python /usr/local/lib/python2.6/dist-packages/

How to generate a source distribution from the present development code

This will create a subfolder dist with zipped or gztared source folders:

python sdist
python sdist --formats=gztar,zip

Notes on generating binary distributions

This will create the subfolders build and dist:

python bdist

This will create a subfolder dist with a Windows installer:

python bdist --formats=wininst

Build a distribution before installing it

This will create a subfolder build:

python build

Development installation

This will create a link to the project, instead of properly installing it:

sudo python develop

It will add the current path to the file: /usr/local/lib/python2.7/dist-packages/easy-install.pth.

To uninstall it:

sudo python develop --uninstall

Preparation for release

Change version number etc

  • Manually change the __version__ field in the source file.
  • Manually change the release date in CHANGES.txt

(Note that the version number in the Sphinx configuration file doc/ and in the file are changed automatically. Also the copyright year in doc/ is changed automatically).

How to number releases are described in PEP 440.

Code style checking etc

Check the code:


(The 2to3 tool is not necessary, as we run the unittests under both Python2 and Python3).


Run unit tests for all supported Python versions:

make test-all

Alternatively, run unit tests (in the trunk/test directory):



Also make tests using Delta DTB4824 hardware. See Internal documentation for hardware testing of MinimalModbus using DTB4824.

Test the source distribution generation (look in the PKG-INFO file):

python sdist
Also make sure that these are functional (see sections below):
  • Documentation generation
  • Test coverage report generation

(Prepare subversion)

Make sure the Subversion is updated:

svn update
svn status -v --no-ignore

Make a tag in Subversion (adapt to version number):

svn copy -m "Release 0.5"

Upload to PyPI

Build the source distribution (as .gzip.tar and .zip) , and upload it to PYPI (will use the README.txt etc):

python register
python sdist --formats=gztar,zip upload

(Upload to Sourceforge)

Upload the .gzip.tar and .zip files to Sourceforge by logging in and manually using the web form.

Upload the generated documentation to Sourceforge. In directory trunk/doc/build/html:

scp -r * pyhys,

Upload the documentation PDF. In directory trunk/doc/build/latex:

scp minimalmodbus.pdf pyhys,

Upload the test coverage report. In directory trunk:

scp -r htmlcov pyhys,

Test documentation

Test links on the Sourceforge and PyPI pages. If adjustments are required on the PyPI page, log in and manually adjust the text. This might be for example parsing problems with the ReST text (allows no Sphinx-specific constructs).

(Generate Windows installer)

On a Windows machine, build the windows installer:

python bdist_wininst

Upload the Windows installer to PYPI by logging in, and uploading it manually.

Upload the Windows installer to Sourceforge by manually using the web form.

Test installer

Make sure that the installer works, and the dependencies are handled correctly. Try at least Linux and Windows.


Burn a CD/DVD with these items:

  • Source tree
  • Source distributions
  • Windows installer
  • Generated HTML files
  • PDF documentation
  • svn repository in archive format


  • Mailing list
  • Sourceforge project news

(Applying patches)

Apply the patch like:

/minimalmodbus$ patch -Np0 -d trunk < concurrency_latency_tests_09-21.diff

(Downloading backups from the Sourceforge server)

To download the svn repository in archive format, type this in the destination directory on your computer:

rsync -av* .

Useful development tools

Each of these have some additional information below on this page.

Version control software. See
Version control software. See
For generating HTML documentation. See
Unittest coverage tool. See
This is a tool for finding bugs in python source code. See
Code style checker. See

Subversion (svn) usage

Subversion provides an easy way to share code with each other. You can find all MinimalModbus files on the subversion repository on Look in the trunk subfolder.

Install SVN on some Linux machines

Install it with:

sudo apt-get install subversion

Download the files

The usage is:

svn checkout URL NewSubfolder

where NewSubfolder is the name of a subfolder that will be created in present directory. You can also write svn co instead of svn checkout.

In a proper directory on your computer, download the files (not only the trunk subfolder) using:

svn checkout svn:// minimalmodbus

Submit contributions

First run the svn update command to download the latest changes from the repository. Then make the changes in the files. Use the svn status command to see which files you have changed. Then upload your changes with the svn commit -m 'comment' command. Note that it easy to revert any changes in SVN, so feel free to test.

Shortlist of frequently used SVN commands

These are the most used commands:

svn update
svn status
svn status -v
svn status -v --no-ignore
svn diff
svn log
svn commit -m 'Write your log message here'

In the ‘trunk’ directory:

svn propset svn:ignore html .
svn proplist
svn propget svn:ignore

or if ignoring multiple items, edit the list using:

svn propedit svn:ignore .

Automatic keyword substitution:

svn propset svn:keywords "Date Revision"
svn propset svn:keywords "Date Revision"
svn propset svn:keywords "Date Revision" README.txt
svn propget svn:keywords

SVN settings

SVN uses the computer locale settings for selecting the language (including keyword substitution).

Language settings:

locale      # Shows present locale settings
locale -a   # Shows available locales
export LC_ALL="en_US.utf8"

(Installing MinimalModbus from SVN repository)

Update your local copy by:

svn update

Go to the minimalmodbus/trunk directory:

sudo python install

Test it using (adapt path to your system):

python /usr/local/lib/python2.6/dist-packages/

Git usage

Clone the repository from Github (it will create a directory):

git clone

Show details:

git remote -v
git status
git branch

Stage changes:

git add testb.txt

Commit locally:

git commit -m "test1"

Commit remotely (will ask for Github username and password):

git push origin

Sphinx usage

This documentation is generated with the Sphinx tool:

It is used to automatically generate HTML documentation from docstrings in the source code. See for example Internal documentation for MinimalModbus. To see the source code of the Python file, click [source] on the right part of that page. To see the source of the Sphinx page definition file, click ‘View page Source’ (or possibly ‘Edit on Github’) in the upper right corner.

To install, use:

easy_install sphinx

or possibly:

sudo easy_install sphinx

Check installed version by typing:


Spinx formatting conventions

What Usage Result
Inline web link `Link text <>`_ Link text
Internal link :ref:`testminimalmodbus` Internal documentation for unit testing of MinimalModbus
Inline code ``code text`` code text
String ‘A’ ‘A’
String w escape ch. (string within inline code) 'ABC\x00'
(less good) (string within inline code, double backslash) 'ABC\\x00' For use in Python docstrings.
(less good) (string with double backslash) ‘ABC\x00’ Avoid
Environment var :envvar:`PYTHONPATH` PYTHONPATH
OS-level command :command:`make` make
File :file:``
Path :file:`path/to/myfile.txt` path/to/myfile.txt
Type **bytes** bytes
Module :mod:`minimalmodbus` minimalmodbus
Data (full) :data:`minimalmodbus.BAUDRATE` minimalmodbus.BAUDRATE
Constant :const:`False` False
Function :func:`._checkInt` _checkInt()
Function (full) :func:`minimalmodbus._checkInt` minimalmodbus._checkInt()
Argument *payload* payload
Class :class:`.Instrument` Instrument
Class (full) :class:`minimalmodbus.Instrument` minimalmodbus.Instrument
Method :meth:`.read_bit` read_bit()
Method (full) :meth:`minimalmodbus.Instrument.read_bit` minimalmodbus.Instrument.read_bit()

Note that only the functions and methods that are listed in the index will show as links.

  • Top level heading underlining symbol: = (equals)
  • Next lower level: - (minus)
  • A third level if necessary (avoid this): ` (backquote)
Internal links
  • Add an internal marker .. _my-reference-label: before a heading.
  • Then make an internal link to it using :ref:`my-reference-label`.
Strings with backslash
  • In Python docstrings, use raw strings (a r before the tripplequote), to have the backslashes reach Sphinx.
Informative boxes
  • .. seealso:: Example of a **seealso** box.
  • .. note:: Example of a **note** box.
  • .. warning:: Example of a **warning** box.

See also

Example of a seealso box.


Example of a note box.


Example of a warning box.

Sphinx build commands

To build the documentation, go to the directory trunk/doc and then run:

make html

That should generate HTML files to the directory trunk/doc/build/html.

To generate PDF:

make latexpdf

Note that the PYTHONPATH must be set properly, so that Sphinx can import the modules to document. See below.

It is also possible to run without the make command. In the trunk/doc directory:

sphinx-build -b html -d build/doctrees  -a . build/html

If the python source files not are updated in the HTML output, then remove the contents of trunk/doc/build/doctrees and rebuild the documentation. (This has now been included in the Makefile).

Remember that the Makefile uses tabs for indentation, not spaces.

Sometimes there are warnings and errors when generating the HTML pages. They can appear different, but are most often related to problems importing files. In that case start the Python interpreter and try to import the module, for example:

>>> import test_minimalmodbus

From there you can most often solve the problem.

In order to generate PDF documentation, you need to install pdflatex (approx 1 GByte!):

sudo apt-get install texlive texlive-latex-extra

Unittest coverage measurement using

Install the script

sudo pip install coverage

Collect test data:

coverage run


coverage run

Generate html report (ends up in trunk/test/htmlcov):

coverage html

Or to exclude some third party modules (adapt to your file structure):

coverage html --omit=/usr/*

Alternatively, adjust the settings in the .coverage file.

Using the flake8 style checker tool

This tool checks the coding style, using pep8 and flake. Install it:

sudo apt-get install python-flake8

Run it:


Configurations are made in a [flake8] section of the tox.ini file.

Using the pep8 style checker tool

This tool checks the coding style. See

Install the pep8 checker tool:

sudo pip install pep8

Run it:



pep8 --statistics

pep8 -r --select=E261 --show-source



  • Improved documentation (especially the sections with TODO).
  • Tool for interpretation of Modbus messages
  • Increase test coverage for
  • PEP8 fine tuning of source.
  • Improve the dummy_serial behavior, to better mimic Windows behavior.
  • Unittests for measuring the sleep time in _communicate.
  • Serial port flushing
  • Floats with other byte order
  • Logging instead of _print_out()
  • Timing based on time.clock() for Windows
  • string templating compatible with python2.6 (use {2} in format).