[
  {
    "path": ".github/workflows/tests.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: Tests\n\non:\n  push:\n    branches: [ master ]\n  workflow_dispatch:\n\njobs:\n  build:\n\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.8\", \"3.9\", \"3.10\", \"3.11\", \"3.12\"]\n        os: [ubuntu-latest, macOS-latest, windows-latest]\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install flake8 pytest setuptools\n        python setup.py install\n    - name: Lint with flake8\n      run: |\n        # stop the build if there are Python syntax errors or undefined names\n        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics\n        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide\n        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --extend-exclude=build/,docs/\n    - name: Test with pytest\n      run: |\n        pytest\n"
  },
  {
    "path": ".gitignore",
    "content": "/MANIFEST\n/build/\n/dist/\n/lab/\n*.pyc\n/*.egg-info/\n/.idea/\n/.vscode/\n/venv/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# We recommend specifying your dependencies to enable reproducible builds:\n# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\npython:\n  install:\n  - requirements: docs/requirements.txt\n\n# Additional formats to be build (in addition to default HTML)\nformats:\n  - epub\n  - htmlzip\n  - pdf\n"
  },
  {
    "path": "CHANGES",
    "content": "Revision history for pyModbusTCP\n\n0.3.1.dev0 xxxx-xx-xx\n\n    - fix ModbusServer: debug messages now include OSError exceptions produced by getpeername (thanks to Pär Åhlund).\n\n0.3.0 2024-09-04\n\n    - pyModbusTCP.client: now use the standard logging method as in the server part.\n    - ModbusClient: debug flag is removed (see examples/client_debug.py).\n\n0.2.2 2024-07-31\n\n    - fix ModbusServer: wrong check of discrete inputs length in DataBank (thanks to OTnetproj).\n    - updated compatibility test (python versions): remove 3.7, add 3.12.\n\n0.2.1 2023-11-21\n\n    - fix ModbusServer: wrong check of input registers length in DataBank (thanks to monsieurvor).\n    - improve hostname validation in utils (thanks to MCXIV and schmocker).\n    - experimental add of read_device_identification() (over modbus encapsulated interface) to client and server.\n    - experimental add of write_read_multiple_registers() function (code 0x17) to client and server (thanks to tanj).\n    - updated compatibility test (python versions): remove 3.5/3.6, add 3.11.\n\n0.2.0 2022-06-05\n\n    - ModbusClient: parameters are now properties instead of methods (more intuitive).\n    - ModbusClient: now TCP auto open mode is active by default (auto_open=True, auto_close=False).\n    - ModbusClient: add custom_request method for send user define PDU to modbus server.\n    - ModbusClient: remove RTU mode.\n    - ModbusClient: clarify some things (private methods and internal vars rename).\n    - ModbusServer: big redesign to improve readability and maintainability.\n    - ModbusServer: no longer use hard linked class DataBank (it now return a deprecation warn).\n    - ModbusServer: add of ModbusServerDataHandler and DefaultDataBank class for easy customize.\n    - add or update server examples for new modbus server design.\n    - some updates on tests, fix random arrays generation on client test part.\n    - clarify usage of root privilege for open tcp/502 (avoid Errno 13) in server example.\n    - python 2 end of support.\n\n0.1.10 2021-03-02\n\n    - word_list_to_long() and long_list_to_word(), now support 64 bits long long with opt long_long.\n    - encode_ieee() and decode_ieee(), now support double-precision format with opt double.\n    - add shortcut alias for functions with long names in utils.\n    - rewrite of some functions in utils.\n    - improve test_utils readability.\n    - server DataBank enforce type check on set data methods to avoid server crash (thanks to xuantw).\n    - now get_2comp can deal with negative python int.\n    - remove reference to devel github branch.\n    - improve last_error_txt() and last_except_txt().\n\n0.1.9 2021-02-26\n\n    - add module error MB_SOCK_CLOSE_ERR (occur if frame send on close socket).\n    - add modbus exception EXP_NEGATIVE_ACKNOWLEDGE (code 0x07) to constants.\n    - add last_error_txt() and last_except_txt() for produce human readable status.\n    - add EXP_TXT, EXP_DETAILS and MB_ERR_TXT dicts to constants (text representation of codes).\n    - update of the compatibility test for python version: remove 2.6/3.2/3.3, add 3.7/3.8/3.9.\n    - conform to PEP 396 (add pyModbusTCP.__version__ field).\n\n0.1.8 2018-10-15\n\n    - fix ModbusServer: avoid hard coded TCP port (thanks to akobyl).\n    - add stop() and is_run property to ModbusServer (thanks to Rugiewitz).\n\n0.1.7 2018-08-20\n\n    - fix ModbusServer issue on Windows (thanks to andreascian).\n\n0.1.6 2018-05-14\n\n    - fix multiple TCP packets recv issue in ModbusClient and ModbusServer (thanks Farin94).\n\n0.1.5 2017-11-23\n\n    - add long_list_to_word to utils.\n    - add float support as example.\n\n0.1.4 2017-11-13\n\n    - fix port and host accessors, change check now use \"==\" and not \"is\".\n\n0.1.3 2017-09-29\n\n    - setup now use setuptools.\n\n0.1.2 2017-09-28\n\n    - fix 'Rx' label on error.\n    - change file mode for server.py example.\n    - fix compatibility with modbus unit_id = 0 (thanks to mfkenney).\n    - fix compatibility for modbus frame with garbage.\n\n0.1.1 2016-05-30\n\n    - add toggle_bit() to utils.\n    - add server.py example.\n    - add HOWTO for safe PyPI upload.\n\n0.1.0 2016-05-30\n\n    - fix some issues in PEP 8 conformance and inline doc.\n    - client modbus RTU: move crc16 compute to utils.\n    - add write_multiple_coils() function (code 0x0f).\n    - add test_bit(), set_bit(), reset_bit() to utils.\n    - add a modbus/TCP multithreaded server through ModbusServer class (for test only).\n\n0.0.13 2015-12-24\n\n    - add auto_open and auto_close to README.rst and quickstart doc.\n    - add example min_read_bit.py for show minimal code approach.\n\n0.0.12 2015-12-11\n\n    - add auto_open and auto_close mode to ModbusClient.\n    - add accessor function for socket timeout.\n    - close TCP socket if hostname change\n    - close TCP socket if port change\n\n0.0.11 2015-03-27\n\n    - fix sock.settimeout missing (thanks to bonaime).\n    - fix PEP 8 style (thanks to bonaime).\n\n0.0.10 2015-01-22\n\n    - Add timeout parameter to ModbusClient constructor (thanks to bonaime).\n\n0.0.9 2014-10-10\n\n    - Fix rx_byte_count check in ModbusClient class.\n\n0.0.8 2014-09-23\n\n    - Catch excepts on socket send/recv.\n    - Sphinx documentation include a quickstart and examples.\n\n0.0.7 2014-08-31\n\n    - Add unit test (see test/).\n    - Add params host/port/unit_id/debug on ModbusClient constructor.\n    - Add utils module for modbus data mangling.\n\n0.0.6 2014-08-25\n\n    - Fix \"socket error\" message when call open() on dual stack IPv6/4 host.\n    - Check rx byte count field in functions 1 to 4.\n    - Fix max bit number problem in functions 1 and 2 (from 125 to 2000).\n    - Add debug message, if _send() call on close socket.\n    - Rename module name from const to constants.\n    - Update MANIFEST.in to remove docs and examples from sdist archive.\n    - Update README.rst sample code for Python3.\n\n0.0.5 2014-08-08\n\n    - Now deal with IPv6 host.\n    - Fix Python3 issue in _crc().\n    - Improve modbus RTU receive code.\n    - Secure frame size before struct.unpack invocation.\n\n0.0.4 2014-08-07\n\n    - Add class documentation (add doc/ and sphinx/ directory).\n    - Add sphinx docstring in client.py.\n\n0.0.3 2014-08-05\n\n    - Fix padding problem in write_single_coil().\n    - Add new examples.\n\n0.0.2 2014-08-05\n\n    - Compatibility with Python 3 and 2.7.\n    - Use RST format for README instead of markdown, now set long_description.\n    - Add a MANIFEST.in file and include examples/ on sdist.\n\n0.0.1 2014-08-04\n\n    - First release of pyModbusTCP.\n"
  },
  {
    "path": "HOWTO-PyPi.md",
    "content": "## How to upload on PyPI\n\nHere we use the twine tool to do the job, see [Twine setup](#twine-setup) to add and configure it.\n\n\n### build archive and wheel\n\n```bash\npython setup.py sdist bdist_wheel\n```\n\n### upload archive and wheel to PyPi test server\n\n```bash\ntwine upload dist/pyModbusTCP-x.x.x* -r pypitest\n```\n\nCheck result at https://test.pypi.org/project/pyModbusTCP/.\n\n### upload archive and wheel to PyPi server\n\n```bash\ntwine upload dist/pyModbusTCP-x.x.x* -r pypi\n```\n\nCheck result at https://pypi.python.org/project/pyModbusTCP/.\n\n\n## Twine setup\n\n### install twine\n\n```bash\nsudo pip install twine\n```\n\n### create it's conf file\n\nCreate ~/.pypirc with credentials for pypi and pypitest.\n\n```bash\ncat <<EOT >> ~/.pypirc\n[distutils]\nindex-servers =\n  pypi\n  pypitest\n\n[pypi]\nrepository: https://upload.pypi.org/legacy/\nusername: __token__\npassword: mytoken\n\n[pypitest]\nrepository: https://test.pypi.org/legacy/\nusername: __token__\npassword: mytoken\nEOT\n```\nUpdate it with valid credentials.\n\n```bash\nnano ~/.pypirc\n```\n"
  },
  {
    "path": "HOWTO-pkg-devel.md",
    "content": "## How to set package developer mode (also call editable mode on pip)\n\n*After set this, we can directly test effect of editing a package files\nwithout need to fully reinstall it.*\n\nTurn on develop mode (add current package files to python path) in a virtual env:\n\n```bash\npython -m venv venv && source venv/bin/activate\npip install --editable .\n```\nTurn off:\n\n```bash\npip uninstall pyModbusTCP\n```\nView the current python path:\n\n```bash\npython -c 'import sys; print(sys.path)'\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 l.lefebvre\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.rst\ninclude setup.py\ninclude CHANGES\ninclude LICENSE\n\nrecursive-include pyModbusTCP *.py\n"
  },
  {
    "path": "README.rst",
    "content": ".. |badge_tests| image:: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml/badge.svg?branch=master\n                :target: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml\n\n.. |badge_docs| image:: https://readthedocs.org/projects/pymodbustcp/badge/?version=latest\n               :target: http://pymodbustcp.readthedocs.io/\n\npyModbusTCP |badge_tests| |badge_docs|\n======================================\n\nA simple Modbus/TCP client library for Python.\npyModbusTCP is pure Python code without any extension or external module dependency.\n\nSince version 0.1.0, a server is also available.\n\nTests\n-----\n\nThe module is currently test on Python 3.8, 3.9, 3.10, 3.11 and 3.12.\n\nFor Linux, Mac OS and Windows.\n\nDocumentation\n-------------\n\nDocumentation of the last release is available online at https://pymodbustcp.readthedocs.io/.\n\nSetup\n-----\n\nYou can install this package from:\n\nPyPI, the easy way:\n\n.. code-block:: bash\n\n    # install the last available release (stable)\n    sudo pip install pyModbusTCP\n\n.. code-block:: bash\n\n    # install a specific version (here release v0.1.10)\n    sudo pip install pyModbusTCP==v0.1.10\n\nFrom GitHub:\n\n.. code-block:: bash\n\n    # install a specific version (here release v0.1.10) directly from github servers\n    sudo pip install git+https://github.com/sourceperl/pyModbusTCP.git@v0.1.10\n\nNote on the use of versions:\n\nOver time, some things can change. So, it's a good practice that you always use a specific version of a package for\nyour project, instead of just relying on the default behavior. Without precision, the installation tools will always\ninstall the latest version available for a package, this may have some drawbacks. For example, in pyModbusTCP, the TCP\nautomatic open mode will be active by default from version 0.2.0. It is not the case with previous versions and it just\ndoesn't exist before the 0.0.12. This can lead to some strange behaviour of your application if you are not aware of\nthe change. Look at `CHANGES <https://github.com/sourceperl/pyModbusTCP/blob/master/CHANGES>`_ for details on versions\navailable.\n\nUsage example\n-------------\n\nSee examples/ for full scripts.\n\ninclude (for all samples)\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    from pyModbusTCP.client import ModbusClient\n\nmodule init (TCP always open)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    # TCP auto connect on first modbus request\n    c = ModbusClient(host=\"localhost\", port=502, unit_id=1, auto_open=True)\n\nmodule init (TCP open/close for each request)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    # TCP auto connect on modbus request, close after it\n    c = ModbusClient(host=\"127.0.0.1\", auto_open=True, auto_close=True)\n\nRead 2x 16 bits registers at modbus address 0 :\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    regs = c.read_holding_registers(0, 2)\n\n    if regs:\n        print(regs)\n    else:\n        print(\"read error\")\n\nWrite value 44 and 55 to registers at modbus address 10 :\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    if c.write_multiple_registers(10, [44,55]):\n        print(\"write ok\")\n    else:\n        print(\"write error\")\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# pyModbusTCP documentation build configuration file, created by\n# sphinx-quickstart on Thu Sep 28 18:36:32 2017.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\nimport os\nimport sys\n\n# Insert pyModbusTCP path to read current version in it\nsys.path.insert(0, os.path.abspath('..'))\n\nimport pyModbusTCP\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx_rtd_theme']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\n# source_suffix = ['.rst', '.md']\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'pyModbusTCP'\ncopyright = u'2023, Loïc Lefebvre'\nauthor = u'Loïc Lefebvre'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = pyModbusTCP.__version__\n\n# The full version, including alpha/beta/rc tags.\nrelease = pyModbusTCP.__version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = 'en'\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#\n# today = ''\n#\n# Else, today_fmt is used as the format for a strftime call.\n#\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This patterns also effect to html_static_path and html_extra_path\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n# keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = 'sphinx_rtd_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\n# html_theme_options = {\n#     'sidebar_width': '240px',\n# }\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.\n# \"<project> v<release> documentation\" by default.\n#\n# html_title = u'pyModbusTCP v0.1.2'\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#\n# html_logo = None\n\n# The name of an image file (relative to this directory) to use as a favicon of\n# the docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\n# html_static_path = ['_static']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#\n# html_extra_path = []\n\n# If not None, a 'Last updated on:' timestamp is inserted at every page\n# bottom, using the given strftime format.\n# The empty string is equivalent to '%b %d, %Y'.\n#\n# html_last_updated_fmt = None\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n#\n# html_domain_indices = True\n\n# If false, no index is generated.\n#\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'\n#\n# html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# 'ja' uses this config value.\n# 'zh' user can custom change `jieba` dictionary path.\n#\n# html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#\n# html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'pyModbusTCPdoc'\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n     # The paper size ('letterpaper' or 'a4paper').\n     #\n     # 'papersize': 'letterpaper',\n\n     # The font size ('10pt', '11pt' or '12pt').\n     #\n     # 'pointsize': '10pt',\n\n     # Additional stuff for the LaTeX preamble.\n     #\n     # 'preamble': '',\n\n     # Latex figure (float) alignment\n     #\n     # 'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (master_doc, 'pyModbusTCP.tex', u'pyModbusTCP Documentation',\n     u'Loïc Lefebvre', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n#\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#\n# latex_appendices = []\n\n# It false, will not define \\strong, \\code, \titleref, \\crossref ... but only\n# \\sphinxstrong, ..., \\sphinxtitleref, ... To help avoid clash with user added\n# packages.\n#\n# latex_keep_old_macro_names = True\n\n# If false, no module index is generated.\n#\n# latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, 'pymodbustcp', u'pyModbusTCP Documentation',\n     [author], 1)\n]\n\n# If true, show URL addresses after external links.\n#\n# man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (master_doc, 'pyModbusTCP', u'pyModbusTCP Documentation',\n     author, 'pyModbusTCP', 'One line description of project.',\n     'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n#\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#\n# texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#\n# texinfo_no_detailmenu = False\n"
  },
  {
    "path": "docs/examples/client_float.rst",
    "content": "===============================\nClient: add float (inheritance)\n===============================\n\n\n.. literalinclude:: ../../examples/client_float.py\n"
  },
  {
    "path": "docs/examples/client_minimal.rst",
    "content": "====================\nClient: minimal code\n====================\n\n\n.. literalinclude:: ../../examples/client_minimal.py\n"
  },
  {
    "path": "docs/examples/client_read_coils.rst",
    "content": "==================\nClient: read coils\n==================\n\n\n.. literalinclude:: ../../examples/client_read_coils.py\n"
  },
  {
    "path": "docs/examples/client_read_h_registers.rst",
    "content": "==============================\nClient: read holding registers\n==============================\n\n\n.. literalinclude:: ../../examples/client_read_h_registers.py\n"
  },
  {
    "path": "docs/examples/client_thread.rst",
    "content": "======================\nClient: polling thread\n======================\n\n\n.. literalinclude:: ../../examples/client_thread.py\n"
  },
  {
    "path": "docs/examples/client_write_coils.rst",
    "content": "===================\nClient: write coils\n===================\n\n\n.. literalinclude:: ../../examples/client_write_coils.py\n"
  },
  {
    "path": "docs/examples/index.rst",
    "content": "pyModbusTCP examples\n====================\n\n*Here some examples to see pyModbusTCP in some use cases*\n\n.. toctree::\n   :maxdepth: 2\n\n   client_minimal\n   client_read_coils\n   client_read_h_registers\n   client_write_coils\n   client_float\n   client_thread\n   server\n   server_allow\n   server_change_log\n   server_serial_gw\n   server_schedule\n   server_virtual_data\n"
  },
  {
    "path": "docs/examples/server.rst",
    "content": "===================\nServer: basic usage\n===================\n\n\n.. literalinclude:: ../../examples/server.py\n"
  },
  {
    "path": "docs/examples/server_allow.rst",
    "content": "==========================\nServer: with an allow list\n==========================\n\n\n.. literalinclude:: ../../examples/server_allow.py\n"
  },
  {
    "path": "docs/examples/server_change_log.rst",
    "content": "==========================\nServer: with change logger\n==========================\n\n\n.. literalinclude:: ../../examples/server_change_log.py\n"
  },
  {
    "path": "docs/examples/server_schedule.rst",
    "content": "===============================\nServer: schedule and alive word\n===============================\n\n\n.. literalinclude:: ../../examples/server_schedule.py\n"
  },
  {
    "path": "docs/examples/server_serial_gw.rst",
    "content": "=================================\nServer: Modbus/TCP serial gateway\n=================================\n\n\n.. literalinclude:: ../../examples/server_serial_gw.py\n"
  },
  {
    "path": "docs/examples/server_virtual_data.rst",
    "content": "====================\nServer: virtual data\n====================\n\n\n.. literalinclude:: ../../examples/server_virtual_data.py\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Welcome to pyModbusTCP's documentation\n======================================\n\n.. toctree::\n   :maxdepth: 2\n\n   quickstart/index.rst\n   package/index.rst\n   examples/index.rst\n\n"
  },
  {
    "path": "docs/package/class_ModbusClient.rst",
    "content": "Module pyModbusTCP.client\n=========================\n\n.. automodule:: pyModbusTCP.client\n\n*This module provide the ModbusClient class used to deal with modbus server.*\n\nclass ModbusClient\n------------------\n\n.. autoclass:: ModbusClient\n   :members:\n   :special-members: __init__\n\nclass DeviceIdentificationResponse\n----------------------------------\n\n.. autoclass:: DeviceIdentificationResponse\n   :members:\n   :special-members: __init__"
  },
  {
    "path": "docs/package/class_ModbusServer.rst",
    "content": "Module pyModbusTCP.server\n=========================\n\n.. automodule:: pyModbusTCP.server\n\n*This module provide the class for the modbus server, it's data handler interface and finally the data bank.*\n\nclass ModbusServer\n------------------\n\n.. autoclass:: ModbusServer\n   :members:\n\n   .. automethod:: __init__\n\nclass DataHandler\n-----------------\n\n.. autoclass:: DataHandler\n   :members:\n   :special-members: __init__\n\nclass DataBank\n--------------\n\n.. autoclass:: DataBank\n   :members:\n   :special-members: __init__\n\nclass DeviceIdentification\n--------------------------\n\n.. autoclass:: DeviceIdentification\n   :members:\n   :special-members: __init__\n"
  },
  {
    "path": "docs/package/index.rst",
    "content": "pyModbusTCP modules documentation\n=================================\n\nContents:\n\n.. toctree::\n   :maxdepth: 2\n\n   class_ModbusClient\n   class_ModbusServer\n   module_utils\n\n"
  },
  {
    "path": "docs/package/module_utils.rst",
    "content": "Module pyModbusTCP.utils\n========================\n\n*This module provide a set of functions for modbus data mangling.*\n\nBit functions\n-------------\n\n.. automodule:: pyModbusTCP.utils\n   :members: byte_length, get_bits_from_int, reset_bit, set_bit, test_bit, toggle_bit\n\nWord functions\n--------------\n\n.. automodule:: pyModbusTCP.utils\n   :members: long_list_to_word, word_list_to_long\n\nTwo's complement functions\n--------------------------\n\n.. automodule:: pyModbusTCP.utils\n   :members: get_2comp, get_list_2comp\n\nIEEE floating-point functions\n-----------------------------\n\n.. automodule:: pyModbusTCP.utils\n   :members: decode_ieee, encode_ieee\n\nMisc functions\n--------------\n\n.. automodule:: pyModbusTCP.utils\n   :members: crc16, valid_host\n"
  },
  {
    "path": "docs/quickstart/index.rst",
    "content": "Quick start guide\n=================\n\nOverview of the package\n-----------------------\n\npyModbusTCP give access to modbus/TCP server through the ModbusClient object.\nThis class is define in the client module.\n\nSince version 0.1.0, a server is available as ModbusServer class. This server\nis currently in test (API can change at any time).\n\nTo deal with frequent need of modbus data mangling (for example convert 32 bits\nIEEE float to 2x16 bits words) a special module named utils provide some helpful\nfunctions.\n\n**Package map:**\n\n.. image:: map.png\n   :scale: 75 %\n\nPackage setup\n-------------\n\nfrom PyPi::\n\n    # install the last available version (stable)\n    sudo pip3 install pyModbusTCP\n    # or upgrade from an older version\n    sudo pip3 install pyModbusTCP --upgrade\n\n    # you can also install a specific version (here v0.1.10)\n    sudo pip3 install pyModbusTCP==v0.1.10\n\nfrom GitHub::\n\n    git clone https://github.com/sourceperl/pyModbusTCP.git\n    cd pyModbusTCP\n    # here change \"python\" by your python target(s) version(s) (like python3.9)\n    sudo python setup.py install\n\nModbusClient: init\n------------------\n\nInit module from constructor (raise ValueError if host/port error)::\n\n    from pyModbusTCP.client import ModbusClient\n\n    try:\n        c = ModbusClient(host='localhost', port=502)\n    except ValueError:\n        print(\"Error with host or port params\")\n\nOr with properties::\n\n    from pyModbusTCP.client import ModbusClient\n\n    c = ModbusClient()\n    c.host = 'localhost'\n    c.port = 502\n\nModbusClient: TCP link management\n---------------------------------\n\nSince version 0.2.0, \"auto open\" mode is the default behaviour to deal with TCP open/close.\n\nThe \"auto open\" mode keep the TCP connection always open, so the default constructor is::\n\n        c = ModbusClient(host=\"localhost\", auto_open=True, auto_close=False)\n\nIt's also possible to open/close TCP socket before and after each request::\n\n        c = ModbusClient(host=\"localhost\", auto_open=True, auto_close=True)\n\nAnother way to deal with connection is to manually set it. Like this::\n\n        c = ModbusClient(host=\"localhost\", auto_open=False, auto_close=False)\n\n        # open the socket for 2 reads then close it.\n        if c.open():\n            regs_list_1 = c.read_holding_registers(0, 10)\n            regs_list_2 = c.read_holding_registers(55, 10)\n            c.close()\n\nModbusClient: available modbus requests functions\n-------------------------------------------------\n\nSee http://en.wikipedia.org/wiki/Modbus for full table.\n\n+------------+------------------------------+---------------+--------------------------------------------------------------------------+\n| Domain     | Function name                | Function code | ModbusClient function                                                    |\n+============+==============================+===============+==========================================================================+\n| Bit        | Read Discrete Inputs         | 2             | :py:meth:`~pyModbusTCP.client.ModbusClient.read_discrete_inputs`         |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Read Coils                   | 1             | :py:meth:`~pyModbusTCP.client.ModbusClient.read_coils`                   |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Write Single Coil            | 5             | :py:meth:`~pyModbusTCP.client.ModbusClient.write_single_coil`            |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Write Multiple Coils         | 15            | :py:meth:`~pyModbusTCP.client.ModbusClient.write_multiple_coils`         |\n+------------+------------------------------+---------------+--------------------------------------------------------------------------+\n| Register   | Read Input Registers         | 4             | :py:meth:`~pyModbusTCP.client.ModbusClient.read_input_registers`         |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Read Holding Registers       | 3             | :py:meth:`~pyModbusTCP.client.ModbusClient.read_holding_registers`       |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Write Single Register        | 6             | :py:meth:`~pyModbusTCP.client.ModbusClient.write_single_register`        |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Write Multiple Registers     | 16            | :py:meth:`~pyModbusTCP.client.ModbusClient.write_multiple_registers`     |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Read/Write Multiple Registers| 23            | :py:meth:`~pyModbusTCP.client.ModbusClient.write_read_multiple_registers`|\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Mask Write Register          | 22            | n/a                                                                      |\n+------------+------------------------------+---------------+--------------------------------------------------------------------------+\n| File       | Read FIFO Queue              | 24            | n/a                                                                      |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Read File Record             | 20            | n/a                                                                      |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Write File Record            | 21            | n/a                                                                      |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Read Exception Status        | 7             | n/a                                                                      |\n+------------+------------------------------+---------------+--------------------------------------------------------------------------+\n| Diagnostic | Diagnostic                   | 8             | n/a                                                                      |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Get Com Event Counter        | 11            | n/a                                                                      |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Get Com Event Log            | 12            | n/a                                                                      |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Report Slave ID              | 17            | n/a                                                                      |\n|            +------------------------------+---------------+--------------------------------------------------------------------------+\n|            | Read Device Identification   | 43            | :py:meth:`~pyModbusTCP.client.ModbusClient.read_device_identification`   |\n+------------+------------------------------+---------------+--------------------------------------------------------------------------+\n\nModbusClient: how-to debug\n--------------------------\n\nIf need, you can enable debug log for ModbusClient like this::\n\n    import logging\n\n    from pyModbusTCP.client import ModbusClient\n\n    # set debug level for pyModbusTCP.client to see frame exchanges\n    logging.basicConfig()\n    logging.getLogger('pyModbusTCP.client').setLevel(logging.DEBUG)\n\n    c = ModbusClient(host=\"localhost\", port=502)\n\nwhen debug level is set, all debug messages are displayed on the console::\n\n    c.read_coils(0)\n\nwill give us the following result::\n\n    DEBUG:pyModbusTCP.client:(localhost:502:1) Tx [8F 8A 00 00 00 06 01] 01 00 00 00 01\n    DEBUG:pyModbusTCP.client:(localhost:502:1) Rx [8F 8A 00 00 00 04 01] 01 01 00\n\n\nutils module: Modbus data mangling\n----------------------------------\n\nWhen we have to deal with the variety types of registers of PLC device, we often\nneed some data mangling. Utils part of pyModbusTCP can help you in this task.\nNow, let's see some use cases.\n\n- deal with negative numbers (two's complement)::\n\n    from pyModbusTCP import utils\n\n    list_16_bits = [0x0000, 0xFFFF, 0x00FF, 0x8001]\n\n    # show \"[0, -1, 255, -32767]\"\n    print(utils.get_list_2comp(list_16_bits, 16))\n\n    # show \"-1\"\n    print(utils.get_2comp(list_16_bits[1], 16))\n\nMore at http://en.wikipedia.org/wiki/Two%27s_complement\n\n- convert integer of val_size bits (default is 16) to an array of boolean::\n\n    from pyModbusTCP import utils\n\n    # show \"[True, False, True, False, False, False, False, False]\"\n    print(utils.get_bits_from_int(0x05, val_size=8))\n\n- read of 32 bits registers (also know as long format)::\n\n    from pyModbusTCP import utils\n\n    list_16_bits = [0x0123, 0x4567, 0xdead, 0xbeef]\n\n    # big endian sample (default)\n    list_32_bits = utils.word_list_to_long(list_16_bits)\n    # show \"['0x1234567', '0xdeadbeef']\"\n    print([hex(i) for i in list_32_bits])\n\n    # little endian sample\n    list_32_bits = utils.word_list_to_long(list_16_bits, big_endian=False)\n    # show \"['0x45670123', '0xbeefdead']\"\n    print([hex(i) for i in list_32_bits])\n\n- IEEE single/double precision floating-point::\n\n    from pyModbusTCP import utils\n\n    # 32 bits IEEE single precision\n    # encode : python float 0.3 -> int 0x3e99999a\n    # display \"0x3e99999a\"\n    print(hex(utils.encode_ieee(0.3)))\n    # decode: python int 0x3e99999a -> float 0.3\n    # show \"0.300000011921\" (it's not 0.3, precision leak with float...)\n    print(utils.decode_ieee(0x3e99999a))\n\n    # 64 bits IEEE double precision\n    # encode: python float 6.62606957e-34 -> int 0x390b860bb596a559\n    # display \"0x390b860bb596a559\"\n    print(hex(utils.encode_ieee(6.62606957e-34, double=True)))\n    # decode: python int 0x390b860bb596a559 -> float 6.62606957e-34\n    # display \"6.62606957e-34\"\n    print(utils.decode_ieee(0x390b860bb596a559, double=True))\n\n"
  },
  {
    "path": "docs/quickstart/map.dot",
    "content": "/* \ngenerate pyModbusTCP map as PNG\n(need sudo apt-get install graphviz)\n\ncommand:\ndot -Tpng map.dot > map.png\n*/\n\ndigraph pyModbusTCP_map {\n    \"pyModbusTCP\" -> \"client\";\n    \"pyModbusTCP\" -> \"server\";\n    \"pyModbusTCP\" -> \"utils\";\n    \"pyModbusTCP\" -> \"constants\";\n    \"client\"      -> \"class ModbusClient\";\n    \"server\"      -> \"class ModbusServer\";\n    \"utils\"       -> \"data mangling functions\";\n    \"constants\"   -> \"all package constants\";\n}\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "sphinx-rtd-theme==1.3.0\n"
  },
  {
    "path": "examples/README.md",
    "content": "## Important notice\n\n**The examples in this directory are designed to work with the version of pyModbusTCP currently in this repository. They\nmay or may not work with the PyPi version which is always the latest stable version and not the development one.**\n\n"
  },
  {
    "path": "examples/client_debug.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\" An example of basic logging for ModbusClient debugging purposes. \"\"\"\n\nimport logging\nimport time\n\nfrom pyModbusTCP.server import ModbusServer\nfrom pyModbusTCP.client import ModbusClient\n\n# a logger for this script\nlogger = logging.getLogger(__name__)\n\n# global log conf: sets a default format and level for all loggers in this application (include pyModbusTCP)\nlogging.basicConfig(format='%(asctime)s - %(name)-20s - %(levelname)-8s - %(message)s', level=logging.INFO)\n# set debug level for pyModbusTCP.client to see frame exchanges\nlogging.getLogger('pyModbusTCP.client').setLevel(logging.DEBUG)\n\n# run a modbus server at localhost:5020\nModbusServer(host='localhost', port=5020, no_block=True).start()\n\n# this message is show\nlogger.info(f'app startup')\n\n# init modbus client to connect to localhost:5020\nc = ModbusClient(port=5020)\n\n# main loop\nfor i in range(100):\n    # this message is not not show (global log level is set to INFO)\n    logger.debug(f'run loop #{i}')\n    # modbus i/o\n    c.read_coils(0)\n    time.sleep(2)\n"
  },
  {
    "path": "examples/client_float.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\" How-to add float support to ModbusClient. \"\"\"\n\nfrom pyModbusTCP.client import ModbusClient\nfrom pyModbusTCP.utils import (decode_ieee, encode_ieee, long_list_to_word,\n                               word_list_to_long)\n\n\nclass FloatModbusClient(ModbusClient):\n    \"\"\"A ModbusClient class with float support.\"\"\"\n\n    def read_float(self, address, number=1):\n        \"\"\"Read float(s) with read holding registers.\"\"\"\n        reg_l = self.read_holding_registers(address, number * 2)\n        if reg_l:\n            return [decode_ieee(f) for f in word_list_to_long(reg_l)]\n        else:\n            return None\n\n    def write_float(self, address, floats_list):\n        \"\"\"Write float(s) with write multiple registers.\"\"\"\n        b32_l = [encode_ieee(f) for f in floats_list]\n        b16_l = long_list_to_word(b32_l)\n        return self.write_multiple_registers(address, b16_l)\n\n\nif __name__ == '__main__':\n    # init modbus client\n    c = FloatModbusClient(host='localhost', port=502, auto_open=True)\n\n    # write 10.0 at @0\n    c.write_float(0, [10.0])\n\n    # read @0 to 9\n    float_l = c.read_float(0, 10)\n    print(float_l)\n\n    c.close()\n"
  },
  {
    "path": "examples/client_minimal.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\" Minimal code example. \"\"\"\n\nfrom pyModbusTCP.client import ModbusClient\n\n# read 3 coils at @0 on localhost server\nprint('coils=%s' % ModbusClient().read_coils(0, 3))\n"
  },
  {
    "path": "examples/client_read_coils.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\" Read 10 coils and print result on stdout. \"\"\"\n\nimport time\n\nfrom pyModbusTCP.client import ModbusClient\n\n# init modbus client\nc = ModbusClient(host='localhost', port=502, auto_open=True)\n\n# main read loop\nwhile True:\n    # read 10 bits (= coils) at address 0, store result in coils list\n    coils_l = c.read_coils(0, 10)\n\n    # if success display registers\n    if coils_l:\n        print('coil ad #0 to 9: %s' % coils_l)\n    else:\n        print('unable to read coils')\n\n    # sleep 2s before next polling\n    time.sleep(2)\n"
  },
  {
    "path": "examples/client_read_h_registers.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\" Read 10 holding registers and print result on stdout. \"\"\"\n\nimport time\n\nfrom pyModbusTCP.client import ModbusClient\n\n# init modbus client\nc = ModbusClient(auto_open=True)\n\n# main read loop\nwhile True:\n    # read 10 registers at address 0, store result in regs list\n    regs_l = c.read_holding_registers(0, 10)\n\n    # if success display registers\n    if regs_l:\n        print('reg ad #0 to 9: %s' % regs_l)\n    else:\n        print('unable to read registers')\n\n    # sleep 2s before next polling\n    time.sleep(2)\n"
  },
  {
    "path": "examples/client_serial_gw.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nModbus RTU to TCP basic gateway (master attached)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nModbus master device -> [Modbus RTU] -> client_serial_gw -> [Modbus/TCP] -> Modbus server\n\nOpen /dev/ttyUSB0 at 115200 bauds and relay RTU messages to modbus server for slave address 30.\n$ ./client_serial_gw.py /dev/ttyUSB0 --baudrate 115200 --address 30\n\"\"\"\n\nimport argparse\nimport logging\nimport struct\n\n# need sudo pip3 install pyserial==3.4\nfrom serial import Serial, serialutil\n\nfrom pyModbusTCP.client import ModbusClient\nfrom pyModbusTCP.constants import EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND\nfrom pyModbusTCP.utils import crc16\n\n\n# some class\nclass ModbusRTUFrame:\n    \"\"\" Modbus RTU frame container class. \"\"\"\n\n    def __init__(self, raw=b''):\n        # public\n        self.raw = raw\n\n    def __repr__(self) -> str:\n        return self.as_hex\n\n    @property\n    def as_hex(self) -> str:\n        \"\"\"Return RAW frame as a hex string.\"\"\"\n        return '-'.join(['%02X' % x for x in self.raw])\n\n    @property\n    def pdu(self):\n        \"\"\"Return PDU part of frame.\"\"\"\n        return self.raw[1:-2]\n\n    @property\n    def slave_addr(self):\n        \"\"\"Return slave address part of frame.\"\"\"\n        return self.raw[0]\n\n    @property\n    def function_code(self):\n        \"\"\"Return function code part of frame.\"\"\"\n        return self.raw[1]\n\n    @property\n    def is_set(self):\n        \"\"\"Check if frame is set\n\n        :return: True if frame is set\n        :rtype: bool\n        \"\"\"\n        return bool(self.raw)\n\n    @property\n    def is_valid(self):\n        \"\"\"Check if frame is valid.\n\n        :return: True if frame is valid\n        :rtype: bool\n        \"\"\"\n        return len(self.raw) > 4 and crc16(self.raw) == 0\n\n    def build(self, raw_pdu, slave_addr):\n        \"\"\"Build a full modbus RTU message from PDU and slave address.\n\n        :param raw_pdu: modbus as raw value\n        :type raw_pdu: bytes\n        :param slave_addr: address of the slave\n        :type slave_addr: int\n        \"\"\"\n        # [address] + PDU\n        tmp_raw = struct.pack('B', slave_addr) + raw_pdu\n        # [address] + PDU + [CRC 16]\n        tmp_raw += struct.pack('<H', crc16(tmp_raw))\n        self.raw = tmp_raw\n\n\nclass SlaveSerialWorker:\n    \"\"\" A serial worker to manage I/O with RTU master device. \"\"\"\n\n    def __init__(self, port, end_of_frame=0.05):\n        # public\n        self.serial_port = port\n        self.end_of_frame = end_of_frame\n        self.request = ModbusRTUFrame()\n        self.response = ModbusRTUFrame()\n\n    def handle_request(self):\n        \"\"\"Default PDU request processing here, you must implement it in your app.\"\"\"\n        raise RuntimeError('implement this')\n\n    def run(self):\n        \"\"\"Serial worker process.\"\"\"\n        # flush serial buffer\n        self.serial_port.reset_input_buffer()\n        # request loop\n        while True:\n            # init a new transaction\n            self.request = ModbusRTUFrame()\n            self.response = ModbusRTUFrame()\n            # receive from serial\n            # wait for first byte of data\n            self.serial_port.timeout = None\n            rx_raw = self.serial_port.read(1)\n            # if ok, wait for the remaining\n            if rx_raw:\n                self.serial_port.timeout = self.end_of_frame\n                # wait for next bytes of data until end of frame delay\n                while True:\n                    rx_chunk = self.serial_port.read(256)\n                    if not rx_chunk:\n                        break\n                    else:\n                        rx_raw += rx_chunk\n            # store the receipt frame\n            self.request.raw = rx_raw\n            crc_ok = self.request.is_valid\n            # log of received items for debugging purposes\n            logger.debug('Receive: %s (CRC %s)' % (self.request, \"OK\" if crc_ok else \"ERROR\"))\n            # just ignore current frame on CRC error\n            if not crc_ok:\n                continue\n            # relay PDU of request to modbus server\n            self.handle_request()\n            # if a response frame is set sent it\n            if self.response.is_set:\n                # log sent items for debugging purposes\n                logger.debug('Send: %s' % self.response)\n                self.serial_port.write(self.response.raw)\n\n\nclass Serial2ModbusClient:\n    \"\"\" Customize a slave serial worker for map a modbus TCP client. \"\"\"\n\n    def __init__(self, serial_w, mbus_cli, slave_addr=1, allow_bcast=False):\n        \"\"\"Serial2ModbusClient constructor.\n\n        :param serial_w: a SlaveSerialWorker instance\n        :type serial_w: SlaveSerialWorker\n        :param mbus_cli: a ModbusClient instance\n        :type mbus_cli: ModbusClient\n        :param slave_addr: modbus slave address\n        :type slave_addr: int\n        :param allow_bcast: allow processing broadcast frames (slave @0)\n        :type allow_bcast: bool\n        \"\"\"\n        # public\n        self.serial_w = serial_w\n        self.mbus_cli = mbus_cli\n        self.slave_addr = slave_addr\n        self.allow_bcast = allow_bcast\n        # replace serial worker default request handler by Serial2ModbusClient one\n        self.serial_w.handle_request = self._handle_request\n\n    def _handle_request(self):\n        \"\"\"Request handler for SlaveSerialWorker\"\"\"\n        # broadcast/unicast frame ?\n        if self.serial_w.request.slave_addr == 0 and self.allow_bcast:\n            # if config allow it, process a broadcast request (=> process it, but don't respond)\n            self.mbus_cli.custom_request(self.serial_w.request.pdu)\n        elif self.serial_w.request.slave_addr == self.slave_addr:\n            # process unicast request\n            resp_pdu = self.mbus_cli.custom_request(self.serial_w.request.pdu)\n            # if no error, format a response frame\n            if resp_pdu:\n                # regular response from Modbus/TCP client\n                self.serial_w.response.build(raw_pdu=resp_pdu, slave_addr=self.serial_w.request.slave_addr)\n            else:\n                # exception response\n                exp_pdu = struct.pack('BB', self.serial_w.request.function_code + 0x80,\n                                      EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND)\n                self.serial_w.response.build(raw_pdu=exp_pdu, slave_addr=self.serial_w.request.slave_addr)\n\n    def run(self):\n        \"\"\"Start serial processing.\"\"\"\n        self.serial_w.run()\n\n\nif __name__ == '__main__':\n    # parse args\n    parser = argparse.ArgumentParser()\n    parser.add_argument('serial_device', type=str, help='serial device (like /dev/ttyUSB0)')\n    parser.add_argument('-d', '--debug', action='store_true', help='debug mode')\n    parser.add_argument('-a', '--address', type=int, default=1, help='slave address (default is 1)')\n    parser.add_argument('--allow-broadcast', action='store_true', help='serial allow broadcast frame (to address 0)')\n    parser.add_argument('-b', '--baudrate', type=int, default=9600, help='serial rate (default is 9600)')\n    parser.add_argument('-e', '--eof', type=float, default=0.05, help='serial end of frame delay in s (default: 0.05)')\n    parser.add_argument('-H', '--host', type=str, default='localhost', help='server host (default: localhost)')\n    parser.add_argument('-p', '--port', type=int, default=502, help='server TCP port (default: 502)')\n    parser.add_argument('-t', '--timeout', type=float, default=1.0, help='server timeout delay in s (default: 1.0)')\n    args = parser.parse_args()\n    # init logging\n    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)\n    logger = logging.getLogger(__name__)\n    try:\n        # init serial port\n        logger.info('Open serial port %s at %d bauds (eof = %.2fs)', args.serial_device, args.baudrate, args.eof)\n        serial_port = Serial(port=args.serial_device, baudrate=args.baudrate)\n        # start modbus client as request relay\n        logger.info('Connect to modbus server at %s:%d (timeout = %.2fs)', args.host, args.port, args.timeout)\n        mbus_cli = ModbusClient(host=args.host, port=args.port, unit_id=1, timeout=args.timeout)\n        # init serial worker\n        serial_worker = SlaveSerialWorker(serial_port, end_of_frame=args.eof)\n        # start Serial2ModbusClient\n        logger.info('Start serial worker (slave address = %d)' % args.address)\n        Serial2ModbusClient(mbus_cli=mbus_cli, serial_w=serial_worker,\n                            slave_addr=args.address, allow_bcast=args.allow_broadcast).run()\n    except serialutil.SerialException as e:\n        logger.critical('Serial device error: %r', e)\n        exit(1)\n"
  },
  {
    "path": "examples/client_thread.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nmodbus polling thread\n~~~~~~~~~~~~~~~~~~~~~\n\nStart a thread for polling a set of registers, display result on console.\nExit with ctrl+c.\n\"\"\"\n\nimport time\nfrom threading import Lock, Thread\n\nfrom pyModbusTCP.client import ModbusClient\n\nSERVER_HOST = \"localhost\"\nSERVER_PORT = 502\n\n# set global\nregs = []\n\n# init a thread lock\nregs_lock = Lock()\n\n\ndef polling_thread():\n    \"\"\"Modbus polling thread.\"\"\"\n    global regs, regs_lock\n    c = ModbusClient(host=SERVER_HOST, port=SERVER_PORT, auto_open=True)\n    # polling loop\n    while True:\n        # do modbus reading on socket\n        reg_list = c.read_holding_registers(0, 10)\n        # if read is ok, store result in regs (with thread lock)\n        if reg_list:\n            with regs_lock:\n                regs = list(reg_list)\n        # 1s before next polling\n        time.sleep(1)\n\n\n# start polling thread\ntp = Thread(target=polling_thread)\n# set daemon: polling thread will exit if main thread exit\ntp.daemon = True\ntp.start()\n\n# display loop (in main thread)\nwhile True:\n    # print regs list (with thread lock synchronization)\n    with regs_lock:\n        print(regs)\n    # 1s before next print\n    time.sleep(1)\n"
  },
  {
    "path": "examples/client_write_coils.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Write 4 coils to True, wait 2s, write False and redo it.\"\"\"\n\nimport time\n\nfrom pyModbusTCP.client import ModbusClient\n\n# init\nc = ModbusClient(host='localhost', port=502, auto_open=True)\nbit = True\n\n# main loop\nwhile True:\n    # write 4 bits in modbus address 0 to 3\n    print('write bits')\n    print('----------\\n')\n    for ad in range(4):\n        is_ok = c.write_single_coil(ad, bit)\n        if is_ok:\n            print('coil #%s: write to %s' % (ad, bit))\n        else:\n            print('coil #%s: unable to write %s' % (ad, bit))\n        time.sleep(0.5)\n\n    print('')\n    time.sleep(1)\n\n    # read 4 bits in modbus address 0 to 3\n    print('read bits')\n    print('---------\\n')\n    bits = c.read_coils(0, 4)\n    if bits:\n        print('coils #0 to 3: %s' % bits)\n    else:\n        print('coils #0 to 3: unable to read')\n\n    # toggle\n    bit = not bit\n    # sleep 2s before next polling\n    print('')\n    time.sleep(2)\n"
  },
  {
    "path": "examples/server.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nModbus/TCP server\n~~~~~~~~~~~~~~~~~\n\nRun this as root to listen on TCP privileged ports (<= 1024).\n\nAdd \"--host 0.0.0.0\" to listen on all available IPv4 addresses of the host.\n$ sudo ./server.py --host 0.0.0.0\n\"\"\"\n\nimport argparse\nimport logging\n\nfrom pyModbusTCP.server import ModbusServer\n\n# init logging\nlogging.basicConfig()\n# parse args\nparser = argparse.ArgumentParser()\nparser.add_argument('-H', '--host', type=str, default='localhost', help='Host (default: localhost)')\nparser.add_argument('-p', '--port', type=int, default=502, help='TCP port (default: 502)')\nparser.add_argument('-d', '--debug', action='store_true', help='set debug mode')\nargs = parser.parse_args()\n# logging setup\nif args.debug:\n    logging.getLogger('pyModbusTCP.server').setLevel(logging.DEBUG)\n# start modbus server\nserver = ModbusServer(host=args.host, port=args.port)\nserver.start()\n"
  },
  {
    "path": "examples/server_allow.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nAn example of Modbus/TCP server which allow modbus read and/or write only from\nspecific IPs.\n\nRun this as root to listen on TCP privileged ports (<= 1024).\n\"\"\"\n\nimport argparse\n\nfrom pyModbusTCP.constants import EXP_ILLEGAL_FUNCTION\nfrom pyModbusTCP.server import DataHandler, ModbusServer\n\n# some const\nALLOW_R_L = ['127.0.0.1', '192.168.0.10']\nALLOW_W_L = ['127.0.0.1']\n\n\n# a custom data handler with IPs filter\nclass MyDataHandler(DataHandler):\n    def read_coils(self, address, count, srv_info):\n        if srv_info.client.address in ALLOW_R_L:\n            return super().read_coils(address, count, srv_info)\n        else:\n            return DataHandler.Return(exp_code=EXP_ILLEGAL_FUNCTION)\n\n    def read_d_inputs(self, address, count, srv_info):\n        if srv_info.client.address in ALLOW_R_L:\n            return super().read_d_inputs(address, count, srv_info)\n        else:\n            return DataHandler.Return(exp_code=EXP_ILLEGAL_FUNCTION)\n\n    def read_h_regs(self, address, count, srv_info):\n        if srv_info.client.address in ALLOW_R_L:\n            return super().read_h_regs(address, count, srv_info)\n        else:\n            return DataHandler.Return(exp_code=EXP_ILLEGAL_FUNCTION)\n\n    def read_i_regs(self, address, count, srv_info):\n        if srv_info.client.address in ALLOW_R_L:\n            return super().read_i_regs(address, count, srv_info)\n        else:\n            return DataHandler.Return(exp_code=EXP_ILLEGAL_FUNCTION)\n\n    def write_coils(self, address, bits_l, srv_info):\n        if srv_info.client.address in ALLOW_W_L:\n            return super().write_coils(address, bits_l, srv_info)\n        else:\n            return DataHandler.Return(exp_code=EXP_ILLEGAL_FUNCTION)\n\n    def write_h_regs(self, address, words_l, srv_info):\n        if srv_info.client.address in ALLOW_W_L:\n            return super().write_h_regs(address, words_l, srv_info)\n        else:\n            return DataHandler.Return(exp_code=EXP_ILLEGAL_FUNCTION)\n\n\nif __name__ == '__main__':\n    # parse args\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-H', '--host', type=str, default='localhost', help='Host (default: localhost)')\n    parser.add_argument('-p', '--port', type=int, default=502, help='TCP port (default: 502)')\n    args = parser.parse_args()\n    # init modbus server and start it\n    server = ModbusServer(host=args.host, port=args.port, data_hdl=MyDataHandler())\n    server.start()\n"
  },
  {
    "path": "examples/server_change_log.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nAn example of Modbus/TCP server with a change logger.\n\nRun this as root to listen on TCP privileged ports (<= 1024).\n\"\"\"\n\nimport argparse\nimport logging\n\nfrom pyModbusTCP.server import DataBank, ModbusServer\n\n\nclass MyDataBank(DataBank):\n    \"\"\"A custom ModbusServerDataBank for override on_xxx_change methods.\"\"\"\n\n    def on_coils_change(self, address, from_value, to_value, srv_info):\n        \"\"\"Call by server when change occur on coils space.\"\"\"\n        msg = 'change in coil space [{0!r:^5} > {1!r:^5}] at @ 0x{2:04X} from ip: {3:<15}'\n        msg = msg.format(from_value, to_value, address, srv_info.client.address)\n        logging.info(msg)\n\n    def on_holding_registers_change(self, address, from_value, to_value, srv_info):\n        \"\"\"Call by server when change occur on holding registers space.\"\"\"\n        msg = 'change in hreg space [{0!r:^5} > {1!r:^5}] at @ 0x{2:04X} from ip: {3:<15}'\n        msg = msg.format(from_value, to_value, address, srv_info.client.address)\n        logging.info(msg)\n\n\nif __name__ == '__main__':\n    # parse args\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-H', '--host', type=str, default='localhost', help='Host (default: localhost)')\n    parser.add_argument('-p', '--port', type=int, default=502, help='TCP port (default: 502)')\n    args = parser.parse_args()\n    # logging setup\n    logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)\n    # init modbus server and start it\n    server = ModbusServer(host=args.host, port=args.port, data_bank=MyDataBank())\n    server.start()\n"
  },
  {
    "path": "examples/server_schedule.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nModbus/TCP server with start/stop schedule\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nRun this as root to listen on TCP privileged ports (<= 1024).\n\nDefault Modbus/TCP port is 502, so we prefix call with sudo. With argument\n\"--host 0.0.0.0\", server listen on all IPv4 of the host. Instead of just\nopen tcp/502 on local interface.\n$ sudo ./server_schedule.py --host 0.0.0.0\n\"\"\"\n\nimport argparse\nimport time\n\n# need https://github.com/dbader/schedule\nimport schedule\n\nfrom pyModbusTCP.server import ModbusServer\n\n\ndef alive_word_job():\n    \"\"\"Update holding register @0 with day second (since 00:00).\n\n    Job called every 10s by scheduler.\n    \"\"\"\n    server.data_bank.set_holding_registers(0, [int(time.time()) % (24*3600) // 10])\n\n\n# parse args\nparser = argparse.ArgumentParser()\nparser.add_argument('-H', '--host', type=str, default='localhost', help='Host (default: localhost)')\nparser.add_argument('-p', '--port', type=int, default=502, help='TCP port (default: 502)')\nargs = parser.parse_args()\n# init modbus server and start it\nserver = ModbusServer(host=args.host, port=args.port, no_block=True)\nserver.start()\n# init scheduler\n# schedule a daily downtime (from 18:00 to 06:00)\nschedule.every().day.at('18:00').do(server.stop)\nschedule.every().day.at('06:00').do(server.start)\n# update life word at @0\nschedule.every(10).seconds.do(alive_word_job)\n# main loop\nwhile True:\n    schedule.run_pending()\n    time.sleep(1)\n"
  },
  {
    "path": "examples/server_serial_gw.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nModbus/TCP basic gateway (RTU slave(s) attached)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n[pyModbusTCP server] -> [ModbusSerialWorker] -> [serial RTU devices]\n\nRun this as root to listen on TCP privileged ports (<= 1024).\n\nOpen /dev/ttyUSB0 at 115200 bauds and relay it RTU messages to slave(s).\n$ sudo ./server_serial_gw.py --baudrate 115200 /dev/ttyUSB0\n\"\"\"\n\nimport argparse\nimport logging\nimport queue\nimport struct\nfrom queue import Queue\nfrom threading import Event\n\n# need sudo pip install pyserial==3.4\nfrom serial import Serial, serialutil\n\nfrom pyModbusTCP.constants import (EXP_GATEWAY_PATH_UNAVAILABLE,\n                                   EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND)\nfrom pyModbusTCP.server import ModbusServer\nfrom pyModbusTCP.utils import crc16\n\n\n# some class\nclass ModbusRTUFrame:\n    \"\"\" Modbus RTU frame container class. \"\"\"\n\n    def __init__(self, raw=b''):\n        # public\n        self.raw = raw\n\n    @property\n    def pdu(self):\n        \"\"\"Return PDU part of frame.\"\"\"\n        return self.raw[1:-2]\n\n    @property\n    def slave_address(self):\n        \"\"\"Return slave address part of frame.\"\"\"\n        return self.raw[0]\n\n    @property\n    def function_code(self):\n        \"\"\"Return function code part of frame.\"\"\"\n        return self.raw[1]\n\n    @property\n    def is_valid(self):\n        \"\"\"Check if frame is valid.\n\n        :return: True if frame is valid\n        :rtype: bool\n        \"\"\"\n        return len(self.raw) > 4 and crc16(self.raw) == 0\n\n    def build(self, raw_pdu, slave_ad):\n        \"\"\"Build a full modbus RTU message from PDU and slave address.\n\n        :param raw_pdu: modbus as raw value\n        :type raw_pdu: bytes\n        :param slave_ad: address of the slave\n        :type slave_ad: int\n        \"\"\"\n        # [address] + PDU\n        tmp_raw = struct.pack('B', slave_ad) + raw_pdu\n        # [address] + PDU + [CRC 16]\n        tmp_raw += struct.pack('<H', crc16(tmp_raw))\n        self.raw = tmp_raw\n\n\nclass RtuQuery:\n    \"\"\" Request container to deal with modbus serial worker. \"\"\"\n\n    def __init__(self):\n        self.completed = Event()\n        self.request = ModbusRTUFrame()\n        self.response = ModbusRTUFrame()\n\n\nclass ModbusSerialWorker:\n    \"\"\" A serial worker to manage I/O with RTU devices. \"\"\"\n\n    def __init__(self, port, timeout=1.0, end_of_frame=0.05):\n        # public\n        self.serial_port = port\n        self.timeout = timeout\n        self.end_of_frame = end_of_frame\n        # internal request queue\n        # accept 5 simultaneous requests before overloaded exception is return\n        self.rtu_queries_q = Queue(maxsize=5)\n\n    def loop(self):\n        \"\"\"Serial worker main loop.\"\"\"\n        while True:\n            # get next exchange from queue\n            rtu_query = self.rtu_queries_q.get()\n            # send to serial\n            self.serial_port.reset_input_buffer()\n            self.serial_port.write(rtu_query.request.raw)\n            # receive from serial\n            # wait for first byte of data until timeout delay\n            self.serial_port.timeout = self.timeout\n            rx_raw = self.serial_port.read(1)\n            # if ok, wait for the remaining\n            if rx_raw:\n                self.serial_port.timeout = self.end_of_frame\n                # wait for next bytes of data until end of frame delay\n                while True:\n                    rx_chunk = self.serial_port.read(256)\n                    if not rx_chunk:\n                        break\n                    else:\n                        rx_raw += rx_chunk\n            rtu_query.response.raw = rx_raw\n            # mark all as done\n            rtu_query.completed.set()\n            self.rtu_queries_q.task_done()\n\n    def srv_engine_entry(self, session_data):\n        \"\"\"Server engine entry point (pass request to serial worker queries queue).\n\n        :param session_data: server session data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # init a serial exchange from session data\n        rtu_query = RtuQuery()\n        rtu_query.request.build(raw_pdu=session_data.request.pdu.raw,\n                                slave_ad=session_data.request.mbap.unit_id)\n        try:\n            # add a request in the serial worker queue, can raise queue.Full\n            self.rtu_queries_q.put(rtu_query, block=False)\n            # wait result\n            rtu_query.completed.wait()\n            # check receive frame status\n            if rtu_query.response.is_valid:\n                session_data.response.pdu.raw = rtu_query.response.pdu\n                return\n            # except status for slave failed to respond\n            exp_status = EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND\n        except queue.Full:\n            # except status for overloaded gateway\n            exp_status = EXP_GATEWAY_PATH_UNAVAILABLE\n        # return modbus exception\n        func_code = rtu_query.request.function_code\n        session_data.response.pdu.build_except(func_code=func_code, exp_status=exp_status)\n\n\nif __name__ == '__main__':\n    # parse args\n    parser = argparse.ArgumentParser()\n    parser.add_argument('device', type=str, help='serial device (like /dev/ttyUSB0)')\n    parser.add_argument('-H', '--host', type=str, default='localhost', help='host (default: localhost)')\n    parser.add_argument('-p', '--port', type=int, default=502, help='TCP port (default: 502)')\n    parser.add_argument('-b', '--baudrate', type=int, default=9600, help='serial rate (default is 9600)')\n    parser.add_argument('-t', '--timeout', type=float, default=1.0, help='timeout delay (default is 1.0 s)')\n    parser.add_argument('-e', '--eof', type=float, default=0.05, help='end of frame delay (default is 0.05 s)')\n    parser.add_argument('-d', '--debug', action='store_true', help='set debug mode')\n    args = parser.parse_args()\n    # init logging\n    logging.basicConfig(level=logging.DEBUG if args.debug else None)\n    logger = logging.getLogger(__name__)\n    try:\n        # init serial port\n        logger.debug('Open serial port %s at %d bauds', args.device, args.baudrate)\n        serial_port = Serial(port=args.device, baudrate=args.baudrate)\n        # init serial worker\n        serial_worker = ModbusSerialWorker(serial_port, args.timeout, args.eof)\n        # start modbus server with custom engine\n        logger.debug('Start modbus server (%s, %d)', args.host, args.port)\n        srv = ModbusServer(host=args.host, port=args.port,\n                           no_block=True, ext_engine=serial_worker.srv_engine_entry)\n        srv.start()\n        # start serial worker loop\n        logger.debug('Start serial worker')\n        serial_worker.loop()\n    except serialutil.SerialException as e:\n        logger.critical('Serial device error: %r', e)\n        exit(1)\n    except ModbusServer.Error as e:\n        logger.critical('Modbus server error: %r', e)\n        exit(2)\n"
  },
  {
    "path": "examples/server_virtual_data.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nModbus/TCP server with virtual data\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nMap the system date and time to @ 0 to 5 on the \"holding registers\" space.\nOnly the reading of these registers in this address space is authorized. All\nother requests return an illegal data address except.\n\nRun this as root to listen on TCP priviliged ports (<= 1024).\n\"\"\"\n\nimport argparse\nfrom datetime import datetime\n\nfrom pyModbusTCP.server import DataBank, ModbusServer\n\n\nclass MyDataBank(DataBank):\n    \"\"\"A custom ModbusServerDataBank for override get_holding_registers method.\"\"\"\n\n    def __init__(self):\n        # turn off allocation of memory for standard modbus object types\n        # only \"holding registers\" space will be replaced by dynamic build values.\n        super().__init__(virtual_mode=True)\n\n    def get_holding_registers(self, address, number=1, srv_info=None):\n        \"\"\"Get virtual holding registers.\"\"\"\n        # populate virtual registers dict with current datetime values\n        now = datetime.now()\n        v_regs_d = {0: now.day, 1: now.month, 2: now.year,\n                    3: now.hour, 4: now.minute, 5: now.second}\n        # build a list of virtual regs to return to server data handler\n        # return None if any of virtual registers is missing\n        try:\n            return [v_regs_d[a] for a in range(address, address+number)]\n        except KeyError:\n            return\n\n\nif __name__ == '__main__':\n    # parse args\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-H', '--host', type=str, default='localhost', help='Host (default: localhost)')\n    parser.add_argument('-p', '--port', type=int, default=502, help='TCP port (default: 502)')\n    args = parser.parse_args()\n    # init modbus server and start it\n    server = ModbusServer(host=args.host, port=args.port, data_bank=MyDataBank())\n    server.start()\n"
  },
  {
    "path": "pyModbusTCP/__init__.py",
    "content": "# Python package: Client and Server for ModBus/TCP\n#        Website: https://github.com/sourceperl/pyModbusTCP\n#        License: MIT (http://http://opensource.org/licenses/mit-license.php)\n#    Description: Client/Server ModBus/TCP\n#                 Support functions 3 and 16 (class 0)\n#                 1,2,4,5,6 (Class 1)\n#                 15,23,43\n\nimport logging\n\nfrom .constants import VERSION\n\n__title__ = 'pyModbusTCP'\n__description__ = 'A simple Modbus/TCP library for Python.'\n__url__ = 'https://github.com/sourceperl/pyModbusTCP'\n__version__ = VERSION\n__license__ = 'MIT'\n\nlogger = logging.getLogger(__name__)\n"
  },
  {
    "path": "pyModbusTCP/client.py",
    "content": "\"\"\" pyModbusTCP Client \"\"\"\n\nimport logging\nimport random\nimport socket\nimport struct\nfrom binascii import hexlify\nfrom dataclasses import dataclass, field\nfrom socket import AF_UNSPEC, SOCK_STREAM\nfrom typing import Dict\n\nfrom .constants import (ENCAPSULATED_INTERFACE_TRANSPORT, EXP_DETAILS,\n                        EXP_NONE, EXP_TXT, MB_CONNECT_ERR, MB_ERR_TXT,\n                        MB_EXCEPT_ERR, MB_NO_ERR, MB_RECV_ERR, MB_SEND_ERR,\n                        MB_SOCK_CLOSE_ERR, MB_TIMEOUT_ERR,\n                        MEI_TYPE_READ_DEVICE_ID, READ_COILS,\n                        READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS,\n                        READ_INPUT_REGISTERS, VERSION, WRITE_MULTIPLE_COILS,\n                        WRITE_MULTIPLE_REGISTERS,\n                        WRITE_READ_MULTIPLE_REGISTERS, WRITE_SINGLE_COIL,\n                        WRITE_SINGLE_REGISTER)\nfrom .utils import byte_length, set_bit, valid_host\n\n# add a logger for pyModbusTCP.client\nlogger = logging.getLogger(__name__)\n\n\n@dataclass\nclass DeviceIdentificationResponse:\n    \"\"\"Modbus TCP client function read_device_identification() response struct.\n\n    :param conformity_level: this represents supported access and object type\n    :type conformity_level: int\n    :param more_follows: for stream request can be set to 0xff if other objects are available (0x00 in other cases)\n    :type more_follows: int\n    :param next_object_id: the next object id to be asked by following transaction\n    :type next_object_id: int\n    :param objects_by_id: a dictionary with requested object (dict keys are object id as int)\n    :type objects_by_id: dict\n    \"\"\"\n    conformity_level: int = 0\n    more_follows: int = 0\n    next_object_id: int = 0\n    objects_by_id: Dict[int, bytes] = field(default_factory=lambda: {})\n\n    @property\n    def vendor_name(self):\n        return self.objects_by_id.get(0x00)\n\n    @property\n    def product_code(self):\n        return self.objects_by_id.get(0x01)\n\n    @property\n    def major_minor_revision(self):\n        return self.objects_by_id.get(0x02)\n\n    @property\n    def vendor_url(self):\n        return self.objects_by_id.get(0x03)\n\n    @property\n    def product_name(self):\n        return self.objects_by_id.get(0x04)\n\n    @property\n    def model_name(self):\n        return self.objects_by_id.get(0x05)\n\n    @property\n    def user_application_name(self):\n        return self.objects_by_id.get(0x06)\n\n\nclass ModbusClient:\n    \"\"\"Modbus TCP client.\"\"\"\n\n    class _InternalError(Exception):\n        pass\n\n    class _NetworkError(_InternalError):\n        def __init__(self, code, message):\n            self.code = code\n            self.message = message\n\n    class _ModbusExcept(_InternalError):\n        def __init__(self, code):\n            self.code = code\n\n    def __init__(self, host='localhost', port=502, unit_id=1, timeout=30.0, auto_open=True, auto_close=False):\n        \"\"\"Constructor.\n\n        :param host: hostname or IPv4/IPv6 address server address\n        :type host: str\n        :param port: TCP port number\n        :type port: int\n        :param unit_id: unit ID\n        :type unit_id: int\n        :param timeout: socket timeout in seconds\n        :type timeout: float\n        :param auto_open: auto TCP connect\n        :type auto_open: bool\n        :param auto_close: auto TCP close)\n        :type auto_close: bool\n        :return: Object ModbusClient\n        :rtype: ModbusClient\n        \"\"\"\n        # private\n        # internal variables\n        self._host = None\n        self._port = None\n        self._unit_id = None\n        self._timeout = None\n        self._auto_open = None\n        self._auto_close = None\n        self._sock = socket.socket()\n        self._transaction_id = 0  # MBAP transaction ID\n        self._version = VERSION  # this package version number\n        self._last_error = MB_NO_ERR  # last error code\n        self._last_except = EXP_NONE  # last except code\n        # public\n        # constructor arguments: validate them with property setters\n        self.host = host\n        self.port = port\n        self.unit_id = unit_id\n        self.timeout = timeout\n        self.auto_open = auto_open\n        self.auto_close = auto_close\n\n    def __repr__(self):\n        r_str = 'ModbusClient(host=\\'%s\\', port=%d, unit_id=%d, timeout=%.2f, auto_open=%s, auto_close=%s)'\n        r_str %= (self.host, self.port, self.unit_id, self.timeout, self.auto_open, self.auto_close)\n        return r_str\n\n    def __del__(self):\n        self.close()\n\n    @property\n    def version(self):\n        \"\"\"Return the current package version as a str.\"\"\"\n        return self._version\n\n    @property\n    def last_error(self):\n        \"\"\"Last error code.\"\"\"\n        return self._last_error\n\n    @property\n    def last_error_as_txt(self):\n        \"\"\"Human-readable text that describe last error.\"\"\"\n        return MB_ERR_TXT.get(self._last_error, 'unknown error')\n\n    @property\n    def last_except(self):\n        \"\"\"Return the last modbus exception code.\"\"\"\n        return self._last_except\n\n    @property\n    def last_except_as_txt(self):\n        \"\"\"Short human-readable text that describe last modbus exception.\"\"\"\n        default_str = 'unreferenced exception 0x%X' % self._last_except\n        return EXP_TXT.get(self._last_except, default_str)\n\n    @property\n    def last_except_as_full_txt(self):\n        \"\"\"Verbose human-readable text that describe last modbus exception.\"\"\"\n        default_str = 'unreferenced exception 0x%X' % self._last_except\n        return EXP_DETAILS.get(self._last_except, default_str)\n\n    @property\n    def host(self):\n        \"\"\"Get or set the server to connect to.\n\n        This can be any string with a valid IPv4 / IPv6 address or hostname.\n        Setting host to a new value will close the current socket.\n        \"\"\"\n        return self._host\n\n    @host.setter\n    def host(self, value):\n        # check type\n        if type(value) is not str:\n            raise TypeError('host must be a str')\n        # check value\n        if valid_host(value):\n            if self._host != value:\n                self.close()\n                self._host = value\n            return\n        # can't be set\n        raise ValueError('host can\\'t be set (not a valid IP address or hostname)')\n\n    @property\n    def port(self):\n        \"\"\"Get or set the current TCP port (default is 502).\n\n        Setting port to a new value will close the current socket.\n        \"\"\"\n        return self._port\n\n    @port.setter\n    def port(self, value):\n        # check type\n        if type(value) is not int:\n            raise TypeError('port must be an int')\n        # check validity\n        if 0 < value < 65536:\n            if self._port != value:\n                self.close()\n                self._port = value\n            return\n        # can't be set\n        raise ValueError('port can\\'t be set (valid if 0 < port < 65536)')\n\n    @property\n    def unit_id(self):\n        \"\"\"Get or set the modbus unit identifier (default is 1).\n\n        Any int from 0 to 255 is valid.\n        \"\"\"\n        return self._unit_id\n\n    @unit_id.setter\n    def unit_id(self, value):\n        # check type\n        if type(value) is not int:\n            raise TypeError('unit_id must be an int')\n        # check validity\n        if 0 <= value <= 255:\n            self._unit_id = value\n            return\n        # can't be set\n        raise ValueError('unit_id can\\'t be set (valid from 0 to 255)')\n\n    @property\n    def timeout(self):\n        \"\"\"Get or set requests timeout (default is 30 seconds).\n\n        The argument may be a floating point number for sub-second precision.\n        Setting timeout to a new value will close the current socket.\n        \"\"\"\n        return self._timeout\n\n    @timeout.setter\n    def timeout(self, value):\n        # enforce type\n        value = float(value)\n        # check validity\n        if 0 < value < 3600:\n            if self._timeout != value:\n                self.close()\n                self._timeout = value\n            return\n        # can't be set\n        raise ValueError('timeout can\\'t be set (valid between 0 and 3600)')\n\n    @property\n    def auto_open(self):\n        \"\"\"Get or set automatic TCP connect mode (True = turn on).\"\"\"\n        return self._auto_open\n\n    @auto_open.setter\n    def auto_open(self, value):\n        # enforce type\n        self._auto_open = bool(value)\n\n    @property\n    def auto_close(self):\n        \"\"\"Get or set automatic TCP close after each request mode (True = turn on).\"\"\"\n        return self._auto_close\n\n    @auto_close.setter\n    def auto_close(self, value):\n        # enforce type\n        self._auto_close = bool(value)\n\n    @property\n    def is_open(self):\n        \"\"\"Get current status of the TCP connection (True = open).\"\"\"\n        return self._sock.fileno() > 0\n\n    def open(self):\n        \"\"\"Connect to modbus server (open TCP connection).\n\n        :returns: connect status (True on success)\n        :rtype: bool\n        \"\"\"\n        try:\n            self._open()\n            return True\n        except ModbusClient._NetworkError as e:\n            self._req_except_handler(e)\n            return False\n\n    def _open(self):\n        \"\"\"Connect to modbus server (open TCP connection).\"\"\"\n        # open an already open socket -> reset it\n        if self.is_open:\n            self.close()\n        # init socket and connect\n        # list available sockets on the target host/port\n        # AF_xxx : AF_INET -> IPv4, AF_INET6 -> IPv6,\n        #          AF_UNSPEC -> IPv6 (priority on some system) or 4\n        # list available socket on target host\n        for res in socket.getaddrinfo(self.host, self.port, AF_UNSPEC, SOCK_STREAM):\n            af, sock_type, proto, canon_name, sa = res\n            try:\n                self._sock = socket.socket(af, sock_type, proto)\n            except socket.error:\n                continue\n            try:\n                self._sock.settimeout(self.timeout)\n                self._sock.connect(sa)\n            except socket.error:\n                self._sock.close()\n                continue\n            break\n        # check connect status\n        if not self.is_open:\n            raise ModbusClient._NetworkError(MB_CONNECT_ERR, 'connection refused')\n\n    def close(self):\n        \"\"\"Close current TCP connection.\"\"\"\n        self._sock.close()\n\n    def custom_request(self, pdu):\n        \"\"\"Send a custom modbus request.\n\n        :param pdu: a modbus PDU (protocol data unit)\n        :type pdu: bytes\n        :returns: modbus frame PDU or None if error\n        :rtype: bytes or None\n        \"\"\"\n        # make request\n        try:\n            return self._req_pdu(pdu)\n        # handle errors during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return None\n\n    def read_coils(self, bit_addr, bit_nb=1):\n        \"\"\"Modbus function READ_COILS (0x01).\n\n        :param bit_addr: bit address (0 to 65535)\n        :type bit_addr: int\n        :param bit_nb: number of bits to read (1 to 2000)\n        :type bit_nb: int\n        :returns: bits list or None if error\n        :rtype: list of bool or None\n        \"\"\"\n        # check params\n        if not 0 <= int(bit_addr) <= 0xffff:\n            raise ValueError('bit_addr out of range (valid from 0 to 65535)')\n        if not 1 <= int(bit_nb) <= 2000:\n            raise ValueError('bit_nb out of range (valid from 1 to 2000)')\n        if int(bit_addr) + int(bit_nb) > 0x10000:\n            raise ValueError('read after end of modbus address space')\n        # make request\n        try:\n            tx_pdu = struct.pack('>BHH', READ_COILS, bit_addr, bit_nb)\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)\n            # field \"byte count\" from PDU\n            byte_count = rx_pdu[1]\n            # coils PDU part\n            rx_pdu_coils = rx_pdu[2:]\n            # check rx_byte_count: match nb of bits request and check buffer size\n            if byte_count < byte_length(bit_nb) or byte_count != len(rx_pdu_coils):\n                raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')\n            # allocate coils list to return\n            ret_coils = [False] * bit_nb\n            # populate it with coils value from the rx PDU\n            for i in range(bit_nb):\n                ret_coils[i] = bool((rx_pdu_coils[i // 8] >> i % 8) & 0x01)\n            # return read coils\n            return ret_coils\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return None\n\n    def read_discrete_inputs(self, bit_addr, bit_nb=1):\n        \"\"\"Modbus function READ_DISCRETE_INPUTS (0x02).\n\n        :param bit_addr: bit address (0 to 65535)\n        :type bit_addr: int\n        :param bit_nb: number of bits to read (1 to 2000)\n        :type bit_nb: int\n        :returns: bits list or None if error\n        :rtype: list of bool or None\n        \"\"\"\n        # check params\n        if not 0 <= int(bit_addr) <= 0xffff:\n            raise ValueError('bit_addr out of range (valid from 0 to 65535)')\n        if not 1 <= int(bit_nb) <= 2000:\n            raise ValueError('bit_nb out of range (valid from 1 to 2000)')\n        if int(bit_addr) + int(bit_nb) > 0x10000:\n            raise ValueError('read after end of modbus address space')\n        # make request\n        try:\n            tx_pdu = struct.pack('>BHH', READ_DISCRETE_INPUTS, bit_addr, bit_nb)\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)\n            # extract field \"byte count\"\n            byte_count = rx_pdu[1]\n            # frame with bits value -> bits[] list\n            rx_pdu_d_inputs = rx_pdu[2:]\n            # check rx_byte_count: match nb of bits request and check buffer size\n            if byte_count < byte_length(bit_nb) or byte_count != len(rx_pdu_d_inputs):\n                raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')\n            # allocate a bit_nb size list\n            bits = [False] * bit_nb\n            # fill bits list with bit items\n            for i in range(bit_nb):\n                bits[i] = bool((rx_pdu_d_inputs[i // 8] >> i % 8) & 0x01)\n            # return bits list\n            return bits\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return None\n\n    def read_holding_registers(self, reg_addr, reg_nb=1):\n        \"\"\"Modbus function READ_HOLDING_REGISTERS (0x03).\n\n        :param reg_addr: register address (0 to 65535)\n        :type reg_addr: int\n        :param reg_nb: number of registers to read (1 to 125)\n        :type reg_nb: int\n        :returns: registers list or None if fail\n        :rtype: list of int or None\n        \"\"\"\n        # check params\n        if not 0 <= int(reg_addr) <= 0xffff:\n            raise ValueError('reg_addr out of range (valid from 0 to 65535)')\n        if not 1 <= int(reg_nb) <= 125:\n            raise ValueError('reg_nb out of range (valid from 1 to 125)')\n        if int(reg_addr) + int(reg_nb) > 0x10000:\n            raise ValueError('read after end of modbus address space')\n        # make request\n        try:\n            tx_pdu = struct.pack('>BHH', READ_HOLDING_REGISTERS, reg_addr, reg_nb)\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)\n            # extract field \"byte count\"\n            byte_count = rx_pdu[1]\n            # frame with regs value\n            f_regs = rx_pdu[2:]\n            # check rx_byte_count: buffer size must be consistent and have at least the requested number of registers\n            if byte_count < 2 * reg_nb or byte_count != len(f_regs):\n                raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')\n            # allocate a reg_nb size list\n            registers = [0] * reg_nb\n            # fill registers list with register items\n            for i in range(reg_nb):\n                registers[i] = struct.unpack('>H', f_regs[i * 2:i * 2 + 2])[0]\n            # return registers list\n            return registers\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return None\n\n    def read_input_registers(self, reg_addr, reg_nb=1):\n        \"\"\"Modbus function READ_INPUT_REGISTERS (0x04).\n\n        :param reg_addr: register address (0 to 65535)\n        :type reg_addr: int\n        :param reg_nb: number of registers to read (1 to 125)\n        :type reg_nb: int\n        :returns: registers list or None if fail\n        :rtype: list of int or None\n        \"\"\"\n        # check params\n        if not 0 <= int(reg_addr) <= 0xffff:\n            raise ValueError('reg_addr out of range (valid from 0 to 65535)')\n        if not 1 <= int(reg_nb) <= 125:\n            raise ValueError('reg_nb out of range (valid from 1 to 125)')\n        if int(reg_addr) + int(reg_nb) > 0x10000:\n            raise ValueError('read after end of modbus address space')\n        # make request\n        try:\n            tx_pdu = struct.pack('>BHH', READ_INPUT_REGISTERS, reg_addr, reg_nb)\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)\n            # extract field \"byte count\"\n            byte_count = rx_pdu[1]\n            # frame with regs value\n            f_regs = rx_pdu[2:]\n            # check rx_byte_count: buffer size must be consistent and have at least the requested number of registers\n            if byte_count < 2 * reg_nb or byte_count != len(f_regs):\n                raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')\n            # allocate a reg_nb size list\n            registers = [0] * reg_nb\n            # fill registers list with register items\n            for i in range(reg_nb):\n                registers[i] = struct.unpack('>H', f_regs[i * 2:i * 2 + 2])[0]\n            # return registers list\n            return registers\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return None\n\n    def read_device_identification(self, read_code=1, object_id=0):\n        \"\"\"Modbus function Read Device Identification (0x2B/0x0E).\n\n        :param read_code: read device id code, 1 to 3 for respectively: basic, regular and extended stream access,\n            4 for one specific identification object individual access (default is 1)\n        :type read_code: int\n        :param object_id: object id of the first object to obtain (default is 0)\n        :type object_id: int\n        :returns: a DeviceIdentificationResponse instance with the data or None if the requests fails\n        :rtype: DeviceIdentificationResponse or None\n        \"\"\"\n        # check params\n        if not 1 <= int(read_code) <= 4:\n            raise ValueError('read_code out of range (valid from 1 to 4)')\n        if not 0 <= int(object_id) <= 0xff:\n            raise ValueError('object_id out of range (valid from 0 to 255)')\n        # make request\n        try:\n            tx_pdu = struct.pack('BBBB', ENCAPSULATED_INTERFACE_TRANSPORT,\n                                 MEI_TYPE_READ_DEVICE_ID, read_code, object_id)\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=7)\n            # init response object for populate it\n            response = DeviceIdentificationResponse()\n            # extract fields\n            # field \"conformity level\"\n            response.conformity_level = rx_pdu[3]\n            # field \"more follows\"\n            response.more_follows = rx_pdu[4]\n            # field \"next object id\"\n            response.next_object_id = rx_pdu[5]\n            # field \"number of objects\"\n            nb_of_objs = rx_pdu[6]\n            # decode [[obj_id, obj_len, obj_value],...]\n            pdu_offset = 7\n            for _ in range(nb_of_objs):\n                # parse object PDU bytes\n                try:\n                    obj_id = rx_pdu[pdu_offset]\n                    obj_len = rx_pdu[pdu_offset+1]\n                    obj_value = rx_pdu[pdu_offset+2:pdu_offset+2+obj_len]\n                except IndexError:\n                    raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')\n                if obj_len != len(obj_value):\n                    raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')\n                # set offset to next object\n                pdu_offset += 2 + obj_len\n                # add result to request list\n                response.objects_by_id[obj_id] = obj_value\n            return response\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return None\n\n    def write_single_coil(self, bit_addr, bit_value):\n        \"\"\"Modbus function WRITE_SINGLE_COIL (0x05).\n\n        :param bit_addr: bit address (0 to 65535)\n        :type bit_addr: int\n        :param bit_value: bit value to write\n        :type bit_value: bool\n        :returns: True if write ok\n        :rtype: bool\n        \"\"\"\n        # check params\n        if not 0 <= int(bit_addr) <= 0xffff:\n            raise ValueError('bit_addr out of range (valid from 0 to 65535)')\n        # make request\n        try:\n            # format \"bit value\" field for PDU\n            bit_value_raw = (0x0000, 0xff00)[bool(bit_value)]\n            # make a request\n            tx_pdu = struct.pack('>BHH', WRITE_SINGLE_COIL, bit_addr, bit_value_raw)\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)\n            # decode reply\n            resp_coil_addr, resp_coil_value = struct.unpack('>HH', rx_pdu[1:5])\n            # check server reply\n            if (resp_coil_addr != bit_addr) or (resp_coil_value != bit_value_raw):\n                raise ModbusClient._NetworkError(MB_RECV_ERR, 'server reply does not match the request')\n            return True\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return False\n\n    def write_single_register(self, reg_addr, reg_value):\n        \"\"\"Modbus function WRITE_SINGLE_REGISTER (0x06).\n\n        :param reg_addr: register address (0 to 65535)\n        :type reg_addr: int\n        :param reg_value: register value to write\n        :type reg_value: int\n        :returns: True if write ok\n        :rtype: bool\n        \"\"\"\n        # check params\n        if not 0 <= int(reg_addr) <= 0xffff:\n            raise ValueError('reg_addr out of range (valid from 0 to 65535)')\n        if not 0 <= int(reg_value) <= 0xffff:\n            raise ValueError('reg_value out of range (valid from 0 to 65535)')\n        # make request\n        try:\n            # make a request\n            tx_pdu = struct.pack('>BHH', WRITE_SINGLE_REGISTER, reg_addr, reg_value)\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)\n            # decode reply\n            resp_reg_addr, resp_reg_value = struct.unpack('>HH', rx_pdu[1:5])\n            # check server reply\n            if (resp_reg_addr != reg_addr) or (resp_reg_value != reg_value):\n                raise ModbusClient._NetworkError(MB_RECV_ERR, 'server reply does not match the request')\n            return True\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return False\n\n    def write_multiple_coils(self, bits_addr, bits_value):\n        \"\"\"Modbus function WRITE_MULTIPLE_COILS (0x0F).\n\n        :param bits_addr: bits address (0 to 65535)\n        :type bits_addr: int\n        :param bits_value: bits values to write\n        :type bits_value: list\n        :returns: True if write ok\n        :rtype: bool\n        \"\"\"\n        # check params\n        if not 0 <= int(bits_addr) <= 0xffff:\n            raise ValueError('bit_addr out of range (valid from 0 to 65535)')\n        if not 1 <= len(bits_value) <= 1968:\n            raise ValueError('number of coils out of range (valid from 1 to 1968)')\n        if int(bits_addr) + len(bits_value) > 0x10000:\n            raise ValueError('write after end of modbus address space')\n        # make request\n        try:\n            # build PDU coils part\n            # allocate a list of bytes\n            byte_l = [0] * byte_length(len(bits_value))\n            # populate byte list with coils values\n            for i, item in enumerate(bits_value):\n                if item:\n                    byte_l[i // 8] = set_bit(byte_l[i // 8], i % 8)\n            # format PDU coils part with byte list\n            pdu_coils_part = struct.pack('%dB' % len(byte_l), *byte_l)\n            # concatenate PDU parts\n            tx_pdu = struct.pack('>BHHB', WRITE_MULTIPLE_COILS, bits_addr, len(bits_value), len(pdu_coils_part))\n            tx_pdu += pdu_coils_part\n            # make a request\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)\n            # response decode\n            resp_write_addr, resp_write_count = struct.unpack('>HH', rx_pdu[1:5])\n            # check response fields\n            write_ok = resp_write_addr == bits_addr and resp_write_count == len(bits_value)\n            return write_ok\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return False\n\n    def write_multiple_registers(self, regs_addr, regs_value):\n        \"\"\"Modbus function WRITE_MULTIPLE_REGISTERS (0x10).\n\n        :param regs_addr: registers address (0 to 65535)\n        :type regs_addr: int\n        :param regs_value: registers values to write\n        :type regs_value: list\n        :returns: True if write ok\n        :rtype: bool\n        \"\"\"\n        # check params\n        if not 0 <= int(regs_addr) <= 0xffff:\n            raise ValueError('regs_addr out of range (valid from 0 to 65535)')\n        if not 1 <= len(regs_value) <= 123:\n            raise ValueError('number of registers out of range (valid from 1 to 123)')\n        if int(regs_addr) + len(regs_value) > 0x10000:\n            raise ValueError('write after end of modbus address space')\n        # make request\n        try:\n            # init PDU registers part\n            pdu_regs_part = b''\n            # populate it with register values\n            for reg in regs_value:\n                # check current register value\n                if not 0 <= int(reg) <= 0xffff:\n                    raise ValueError('regs_value list contains out of range values')\n                # pack register for build frame\n                pdu_regs_part += struct.pack('>H', reg)\n            bytes_nb = len(pdu_regs_part)\n            # concatenate PDU parts\n            tx_pdu = struct.pack('>BHHB', WRITE_MULTIPLE_REGISTERS, regs_addr, len(regs_value), bytes_nb)\n            tx_pdu += pdu_regs_part\n            # make a request\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)\n            # response decode\n            resp_write_addr, resp_write_count = struct.unpack('>HH', rx_pdu[1:5])\n            # check response fields\n            write_ok = resp_write_addr == regs_addr and resp_write_count == len(regs_value)\n            return write_ok\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return False\n\n    def write_read_multiple_registers(self, write_addr, write_values, read_addr, read_nb=1):\n        \"\"\"Modbus function WRITE_READ_MULTIPLE_REGISTERS (0x17).\n\n        :param write_addr: write registers address (0 to 65535)\n        :type write_addr: int\n        :param write_values: registers values to write\n        :type write_values: list\n        :param read_addr: read register address (0 to 65535)\n        :type read_addr: int\n        :param read_nb: number of registers to read (1 to 125)\n        :type read_nb: int\n        :returns: registers list or None if fail\n        :rtype: list of int or None\n        \"\"\"\n        # check params\n        check_l = [(not 0 <= int(write_addr) <= 0xffff, 'write_addr out of range (valid from 0 to 65535)'),\n                   (not 1 <= len(write_values) <= 121, 'number of registers out of range (valid from 1 to 121)'),\n                   (int(write_addr) + len(write_values) > 0x10000, 'write after end of modbus address space'),\n                   (not 0 <= int(read_addr) <= 0xffff, 'read_addr out of range (valid from 0 to 65535)'),\n                   (not 1 <= int(read_nb) <= 125, 'read_nb out of range (valid from 1 to 125)'),\n                   (int(read_addr) + int(read_nb) > 0x10000, 'read after end of modbus address space'), ]\n        for err, msg in check_l:\n            if err:\n                raise ValueError(msg)\n        # make request\n        try:\n            # init PDU registers part\n            pdu_regs_part = b''\n            # populate it with register values\n            for reg in write_values:\n                # check current register value\n                if not 0 <= int(reg) <= 0xffff:\n                    raise ValueError('write_values list contains out of range values')\n                # pack register for build frame\n                pdu_regs_part += struct.pack('>H', reg)\n            bytes_nb = len(pdu_regs_part)\n            # concatenate PDU parts\n            tx_pdu = struct.pack('>BHHHHB', WRITE_READ_MULTIPLE_REGISTERS, read_addr, read_nb,\n                                 write_addr, len(write_values), bytes_nb)\n            tx_pdu += pdu_regs_part\n            # make a request\n            rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=4)\n            # response decode\n            # extract field \"byte count\"\n            byte_count = rx_pdu[1]\n            # frame with regs value\n            f_regs = rx_pdu[2:]\n            # check rx_byte_count: buffer size must be consistent and have at least the requested number of registers\n            if byte_count < 2 * read_nb or byte_count != len(f_regs):\n                raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')\n            # allocate a reg_nb size list\n            registers = [0] * read_nb\n            # fill registers list with register items\n            for i in range(read_nb):\n                registers[i] = struct.unpack('>H', f_regs[i * 2:i * 2 + 2])[0]\n            # return registers list\n            return registers\n        # handle error during request\n        except ModbusClient._InternalError as e:\n            self._req_except_handler(e)\n            return\n\n    def _send(self, frame):\n        \"\"\"Send frame over current socket.\n\n        :param frame: modbus frame to send (MBAP + PDU)\n        :type frame: bytes\n        \"\"\"\n        # check socket\n        if not self.is_open:\n            raise ModbusClient._NetworkError(MB_SOCK_CLOSE_ERR, 'try to send on a close socket')\n        # send\n        try:\n            self._sock.send(frame)\n        except socket.timeout:\n            self._sock.close()\n            raise ModbusClient._NetworkError(MB_TIMEOUT_ERR, 'timeout error')\n        except socket.error:\n            self._sock.close()\n            raise ModbusClient._NetworkError(MB_SEND_ERR, 'send error')\n\n    def _send_pdu(self, pdu):\n        \"\"\"Convert modbus PDU to frame and send it.\n\n        :param pdu: modbus frame PDU\n        :type pdu: bytes\n        \"\"\"\n        # for auto_open mode, check TCP and open on need\n        if self.auto_open and not self.is_open:\n            self._open()\n        # add MBAP header to PDU\n        tx_frame = self._add_mbap(pdu)\n        # send frame with error check\n        self._send(tx_frame)\n        # debug\n        self._on_tx_rx(frame=tx_frame, is_tx=True)\n\n    def _recv(self, size):\n        \"\"\"Receive data over current socket.\n\n        :param size: number of bytes to receive\n        :type size: int\n        :returns: receive data or None if error\n        :rtype: bytes\n        \"\"\"\n        try:\n            r_buffer = self._sock.recv(size)\n        except socket.timeout:\n            self._sock.close()\n            raise ModbusClient._NetworkError(MB_TIMEOUT_ERR, 'timeout error')\n        except socket.error:\n            r_buffer = b''\n        # handle recv error\n        if not r_buffer:\n            self._sock.close()\n            raise ModbusClient._NetworkError(MB_RECV_ERR, 'recv error')\n        return r_buffer\n\n    def _recv_all(self, size):\n        \"\"\"Receive data over current socket, loop until all bytes is received (avoid TCP frag).\n\n        :param size: number of bytes to receive\n        :type size: int\n        :returns: receive data or None if error\n        :rtype: bytes\n        \"\"\"\n        r_buffer = b''\n        while len(r_buffer) < size:\n            r_buffer += self._recv(size - len(r_buffer))\n        return r_buffer\n\n    def _recv_pdu(self, min_len=2):\n        \"\"\"Receive the modbus PDU (Protocol Data Unit).\n\n        :param min_len: minimal length of the PDU\n        :type min_len: int\n        :returns: modbus frame PDU or None if error\n        :rtype: bytes or None\n        \"\"\"\n        # receive 7 bytes header (MBAP)\n        rx_mbap = self._recv_all(7)\n        # decode MBAP\n        (f_transaction_id, f_protocol_id, f_length, f_unit_id) = struct.unpack('>HHHB', rx_mbap)\n        # check MBAP fields\n        f_transaction_err = f_transaction_id != self._transaction_id\n        f_protocol_err = f_protocol_id != 0\n        f_length_err = f_length >= 256\n        f_unit_id_err = f_unit_id != self.unit_id\n        # checking error status of fields\n        if f_transaction_err or f_protocol_err or f_length_err or f_unit_id_err:\n            self.close()\n            self._on_tx_rx(frame=rx_mbap, is_tx=False)\n            raise ModbusClient._NetworkError(MB_RECV_ERR, 'MBAP checking error')\n        # recv PDU\n        rx_pdu = self._recv_all(f_length - 1)\n        # for auto_close mode, close socket after each request\n        if self.auto_close:\n            self.close()\n        # dump frame\n        self._on_tx_rx(frame=rx_mbap + rx_pdu, is_tx=False)\n        # body decode\n        # check PDU length for global minimal frame (an except frame: func code + exp code)\n        if len(rx_pdu) < 2:\n            raise ModbusClient._NetworkError(MB_RECV_ERR, 'PDU length is too short')\n        # extract function code\n        rx_fc = rx_pdu[0]\n        # check except status\n        if rx_fc >= 0x80:\n            exp_code = rx_pdu[1]\n            raise ModbusClient._ModbusExcept(exp_code)\n        # check PDU length for specific request set in min_len (keep this after except checking)\n        if len(rx_pdu) < min_len:\n            raise ModbusClient._NetworkError(MB_RECV_ERR, 'PDU length is too short for current request')\n        # if no error, return PDU\n        return rx_pdu\n\n    def _add_mbap(self, pdu):\n        \"\"\"Return full modbus frame with MBAP (modbus application protocol header) append to PDU.\n\n        :param pdu: modbus PDU (protocol data unit)\n        :type pdu: bytes\n        :returns: full modbus frame\n        :rtype: bytes\n        \"\"\"\n        # build MBAP\n        self._transaction_id = random.randint(0, 65535)\n        protocol_id = 0\n        length = len(pdu) + 1\n        mbap = struct.pack('>HHHB', self._transaction_id, protocol_id, length, self.unit_id)\n        # full modbus/TCP frame = [MBAP]PDU\n        return mbap + pdu\n\n    def _req_pdu(self, tx_pdu, rx_min_len=2):\n        \"\"\"Request processing (send and recv PDU).\n\n        :param tx_pdu: modbus PDU (protocol data unit) to send\n        :type tx_pdu: bytes\n        :param rx_min_len: min length of receive PDU\n        :type rx_min_len: int\n        :returns: the receive PDU or None if error\n        :rtype: bytes\n        \"\"\"\n        # init request engine\n        self._req_init()\n        # send PDU\n        self._send_pdu(tx_pdu)\n        # return receive PDU\n        return self._recv_pdu(min_len=rx_min_len)\n\n    def _req_init(self):\n        \"\"\"Reset request status flags.\"\"\"\n        self._last_error = MB_NO_ERR\n        self._last_except = EXP_NONE\n\n    def _req_except_handler(self, _except):\n        \"\"\"Global handler for internal exceptions.\"\"\"\n        # on request network error\n        if isinstance(_except, ModbusClient._NetworkError):\n            self._last_error = _except.code\n            self._debug_msg(_except.message)\n        # on request modbus except\n        if isinstance(_except, ModbusClient._ModbusExcept):\n            self._last_error = MB_EXCEPT_ERR\n            self._last_except = _except.code\n            self._debug_msg(f'modbus exception (code {self.last_except} \"{self.last_error_as_txt}\")')\n\n    def _debug_msg(self, msg: str):\n        logger.debug(f'({self.host}:{self.port}:{self.unit_id}) {msg}')\n\n    def _on_tx_rx(self, frame: bytes, is_tx: bool):\n        # format a log message\n        if logger.isEnabledFor(logging.DEBUG):\n            type_s = 'Tx' if is_tx else 'Rx'\n            mbap_s = hexlify(frame[0:7], sep=' ').upper().decode()\n            pdu_s = hexlify(frame[7:], sep=' ').upper().decode()\n            self._debug_msg(f'{type_s} [{mbap_s}] {pdu_s}')\n        # notify user\n        self.on_tx_rx(frame=frame, is_tx=is_tx)\n\n    def on_tx_rx(self, frame: bytes, is_tx: bool):\n        \"\"\"Call for each Tx/Rx (for user purposes).\"\"\"\n        pass\n"
  },
  {
    "path": "pyModbusTCP/constants.py",
    "content": "\"\"\" pyModbusTCP package constants definition \"\"\"\n\n# Package version\nVERSION = '0.3.1.dev0'\n# Modbus/TCP\nMODBUS_PORT = 502\n# Modbus function code\nREAD_COILS = 0x01\nREAD_DISCRETE_INPUTS = 0x02\nREAD_HOLDING_REGISTERS = 0x03\nREAD_INPUT_REGISTERS = 0x04\nWRITE_SINGLE_COIL = 0x05\nWRITE_SINGLE_REGISTER = 0x06\nWRITE_MULTIPLE_COILS = 0x0F\nWRITE_MULTIPLE_REGISTERS = 0x10\nWRITE_READ_MULTIPLE_REGISTERS = 0x17\nENCAPSULATED_INTERFACE_TRANSPORT = 0x2B\nSUPPORTED_FUNCTION_CODES = (READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, READ_INPUT_REGISTERS,\n                            WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS,\n                            WRITE_READ_MULTIPLE_REGISTERS, ENCAPSULATED_INTERFACE_TRANSPORT)\n# MEI type\nMEI_TYPE_READ_DEVICE_ID = 0x0E\n# Modbus except code\nEXP_NONE = 0x00\nEXP_ILLEGAL_FUNCTION = 0x01\nEXP_DATA_ADDRESS = 0x02\nEXP_DATA_VALUE = 0x03\nEXP_SLAVE_DEVICE_FAILURE = 0x04\nEXP_ACKNOWLEDGE = 0x05\nEXP_SLAVE_DEVICE_BUSY = 0x06\nEXP_NEGATIVE_ACKNOWLEDGE = 0x07\nEXP_MEMORY_PARITY_ERROR = 0x08\nEXP_GATEWAY_PATH_UNAVAILABLE = 0x0A\nEXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0B\n# Exception as short human-readable\nEXP_TXT = {\n    EXP_NONE: 'no exception',\n    EXP_ILLEGAL_FUNCTION: 'illegal function',\n    EXP_DATA_ADDRESS: 'illegal data address',\n    EXP_DATA_VALUE: 'illegal data value',\n    EXP_SLAVE_DEVICE_FAILURE: 'slave device failure',\n    EXP_ACKNOWLEDGE: 'acknowledge',\n    EXP_SLAVE_DEVICE_BUSY: 'slave device busy',\n    EXP_NEGATIVE_ACKNOWLEDGE: 'negative acknowledge',\n    EXP_MEMORY_PARITY_ERROR: 'memory parity error',\n    EXP_GATEWAY_PATH_UNAVAILABLE: 'gateway path unavailable',\n    EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND: 'gateway target device failed to respond'\n}\n# Exception as details human-readable\nEXP_DETAILS = {\n    EXP_NONE: 'The last request produced no exceptions.',\n    EXP_ILLEGAL_FUNCTION: 'Function code received in the query is not recognized or allowed by slave.',\n    EXP_DATA_ADDRESS: 'Data address of some or all the required entities are not allowed or do not exist in slave.',\n    EXP_DATA_VALUE: 'Value is not accepted by slave.',\n    EXP_SLAVE_DEVICE_FAILURE: 'Unrecoverable error occurred while slave was attempting to perform requested action.',\n    EXP_ACKNOWLEDGE: 'Slave has accepted request and is processing it, but a long duration of time is required. '\n                     'This response is returned to prevent a timeout error from occurring in the master. '\n                     'Master can next issue a Poll Program Complete message to determine whether processing '\n                     'is completed.',\n    EXP_SLAVE_DEVICE_BUSY: 'Slave is engaged in processing a long-duration command. Master should retry later.',\n    EXP_NEGATIVE_ACKNOWLEDGE: 'Slave cannot perform the programming functions. '\n                              'Master should request diagnostic or error information from slave.',\n    EXP_MEMORY_PARITY_ERROR: 'Slave detected a parity error in memory. '\n                             'Master can retry the request, but service may be required on the slave device.',\n    EXP_GATEWAY_PATH_UNAVAILABLE: 'Specialized for Modbus gateways, this indicates a misconfiguration on gateway.',\n    EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND: 'Specialized for Modbus gateways, sent when slave fails to respond.'\n}\n# Module error codes\nMB_NO_ERR = 0\nMB_RESOLVE_ERR = 1\nMB_CONNECT_ERR = 2\nMB_SEND_ERR = 3\nMB_RECV_ERR = 4\nMB_TIMEOUT_ERR = 5\nMB_FRAME_ERR = 6\nMB_EXCEPT_ERR = 7\nMB_CRC_ERR = 8\nMB_SOCK_CLOSE_ERR = 9\n# Module error as short human-readable\nMB_ERR_TXT = {\n    MB_NO_ERR: 'no error',\n    MB_RESOLVE_ERR: 'name resolve error',\n    MB_CONNECT_ERR: 'connect error',\n    MB_SEND_ERR: 'socket send error',\n    MB_RECV_ERR: 'socket recv error',\n    MB_TIMEOUT_ERR: 'recv timeout occur',\n    MB_FRAME_ERR: 'frame format error',\n    MB_EXCEPT_ERR: 'modbus exception',\n    MB_CRC_ERR: 'bad CRC on receive frame',\n    MB_SOCK_CLOSE_ERR: 'socket is closed'\n}\n# Misc\nMAX_PDU_SIZE = 253\n"
  },
  {
    "path": "pyModbusTCP/server.py",
    "content": "\"\"\" pyModbusTCP Server \"\"\"\n\nimport logging\nimport socket\nimport struct\nfrom socketserver import BaseRequestHandler, ThreadingTCPServer\nfrom threading import Event, Lock, Thread\nfrom warnings import warn\n\nfrom .constants import (ENCAPSULATED_INTERFACE_TRANSPORT, EXP_DATA_ADDRESS,\n                        EXP_DATA_VALUE, EXP_ILLEGAL_FUNCTION, EXP_NONE,\n                        MAX_PDU_SIZE, MEI_TYPE_READ_DEVICE_ID, READ_COILS,\n                        READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS,\n                        READ_INPUT_REGISTERS, WRITE_MULTIPLE_COILS,\n                        WRITE_MULTIPLE_REGISTERS,\n                        WRITE_READ_MULTIPLE_REGISTERS, WRITE_SINGLE_COIL,\n                        WRITE_SINGLE_REGISTER)\nfrom .utils import set_bit, test_bit\n\n# add a logger for pyModbusTCP.server\nlogger = logging.getLogger(__name__)\n\n\nclass DataBank:\n    \"\"\" Data space class with thread safe access functions \"\"\"\n\n    _DEPR_MSG = 'This class method is deprecated. Use DataBank instance method instead: '\n\n    @classmethod\n    def get_bits(cls, *_args, **_kwargs):\n        msg = DataBank._DEPR_MSG + 'server.data_bank.get_coils() or get_discrete_inputs()'\n        warn(msg, DeprecationWarning, stacklevel=2)\n\n    @classmethod\n    def set_bits(cls, *_args, **_kwargs):\n        msg = DataBank._DEPR_MSG + 'server.data_bank.set_coils() or set_discrete_inputs()'\n        warn(msg, DeprecationWarning, stacklevel=2)\n\n    @classmethod\n    def get_words(cls, *_args, **_kwargs):\n        msg = DataBank._DEPR_MSG + 'server.data_bank.get_holding_registers() or get_input_registers()'\n        warn(msg, DeprecationWarning, stacklevel=2)\n\n    @classmethod\n    def set_words(cls, *_args, **_kwargs):\n        msg = DataBank._DEPR_MSG + 'server.data_bank.set_holding_registers() or set_input_registers()'\n        warn(msg, DeprecationWarning, stacklevel=2)\n\n    def __init__(self, coils_size=0x10000, coils_default_value=False,\n                 d_inputs_size=0x10000, d_inputs_default_value=False,\n                 h_regs_size=0x10000, h_regs_default_value=0,\n                 i_regs_size=0x10000, i_regs_default_value=0,\n                 virtual_mode=False):\n        \"\"\"Constructor\n\n        Modbus server data bank constructor.\n\n        :param coils_size: Number of coils to allocate (default is 65536)\n        :type coils_size: int\n        :param coils_default_value: Coils default value at startup (default is False)\n        :type coils_default_value: bool\n        :param d_inputs_size: Number of discrete inputs to allocate (default is 65536)\n        :type d_inputs_size: int\n        :param d_inputs_default_value: Discrete inputs default value at startup (default is False)\n        :type d_inputs_default_value: bool\n        :param h_regs_size: Number of holding registers to allocate (default is 65536)\n        :type h_regs_size: int\n        :param h_regs_default_value: Holding registers default value at startup (default is 0)\n        :type h_regs_default_value: int\n        :param i_regs_size: Number of input registers to allocate (default is 65536)\n        :type i_regs_size: int\n        :param i_regs_default_value: Input registers default value at startup (default is 0)\n        :type i_regs_default_value: int\n        :param virtual_mode: Disallow all modbus data space to work with virtual values (default is False)\n        :type virtual_mode: bool\n        \"\"\"\n        # public\n        self.coils_size = int(coils_size)\n        self.coils_default_value = bool(coils_default_value)\n        self.d_inputs_size = int(d_inputs_size)\n        self.d_inputs_default_value = bool(d_inputs_default_value)\n        self.h_regs_size = int(h_regs_size)\n        self.h_regs_default_value = int(h_regs_default_value)\n        self.i_regs_size = int(i_regs_size)\n        self.i_regs_default_value = int(i_regs_default_value)\n        self.virtual_mode = virtual_mode\n        # specific modes (override some values)\n        if self.virtual_mode:\n            self.coils_size = 0\n            self.d_inputs_size = 0\n            self.h_regs_size = 0\n            self.i_regs_size = 0\n        # private\n        self._coils_lock = Lock()\n        self._coils = [self.coils_default_value] * self.coils_size\n        self._d_inputs_lock = Lock()\n        self._d_inputs = [self.d_inputs_default_value] * self.d_inputs_size\n        self._h_regs_lock = Lock()\n        self._h_regs = [self.h_regs_default_value] * self.h_regs_size\n        self._i_regs_lock = Lock()\n        self._i_regs = [self.i_regs_default_value] * self.i_regs_size\n\n    def __repr__(self):\n        attrs_str = ''\n        for attr_name in self.__dict__:\n            if isinstance(attr_name, str) and not attr_name.startswith('_'):\n                if attrs_str:\n                    attrs_str += ', '\n                attrs_str += '%s=%r' % (attr_name, self.__dict__[attr_name])\n        return 'DataBank(%s)' % attrs_str\n\n    def get_coils(self, address, number=1, srv_info=None):\n        \"\"\"Read data on server coils space\n\n        :param address: start address\n        :type address: int\n        :param number: number of bits (optional)\n        :type number: int\n        :param srv_info: some server info (must be set by server only)\n        :type srv_info: ModbusServer.ServerInfo\n        :returns: list of bool or None if error\n        :rtype: list or None\n        \"\"\"\n        # secure extract of data from list used by server thread\n        with self._coils_lock:\n            if (address >= 0) and (address + number <= len(self._coils)):\n                return self._coils[address: number + address]\n            else:\n                return None\n\n    def set_coils(self, address, bit_list, srv_info=None):\n        \"\"\"Write data to server coils space\n\n        :param address: start address\n        :type address: int\n        :param bit_list: a list of bool to write\n        :type bit_list: list\n        :param srv_info: some server info (must be set by server only)\n        :type srv_info: ModbusServerInfo\n        :returns: True if success or None if error\n        :rtype: bool or None\n        :raises ValueError: if bit_list members cannot be converted to bool\n        \"\"\"\n        # ensure bit_list values are bool\n        bit_list = [bool(b) for b in bit_list]\n        # keep trace of any changes\n        changes_list = []\n        # ensure atomic update of internal data\n        with self._coils_lock:\n            if (address >= 0) and (address + len(bit_list) <= len(self._coils)):\n                for offset, c_value in enumerate(bit_list):\n                    c_address = address + offset\n                    if self._coils[c_address] != c_value:\n                        changes_list.append((c_address, self._coils[c_address], c_value))\n                        self._coils[c_address] = c_value\n            else:\n                return None\n        # on server update\n        if srv_info:\n            # notify changes with on change method (after atomic update)\n            for address, from_value, to_value in changes_list:\n                self.on_coils_change(address, from_value, to_value, srv_info)\n        return True\n\n    def get_discrete_inputs(self, address, number=1, srv_info=None):\n        \"\"\"Read data on server discrete inputs space\n\n        :param address: start address\n        :type address: int\n        :param number: number of bits (optional)\n        :type number: int\n        :param srv_info: some server info (must be set by server only)\n        :type srv_info: ModbusServerInfo\n        :returns: list of bool or None if error\n        :rtype: list or None\n        \"\"\"\n        # secure extract of data from list used by server thread\n        with self._d_inputs_lock:\n            if (address >= 0) and (address + number <= len(self._d_inputs)):\n                return self._d_inputs[address: number + address]\n            else:\n                return None\n\n    def set_discrete_inputs(self, address, bit_list):\n        \"\"\"Write data to server discrete inputs space\n\n        :param address: start address\n        :type address: int\n        :param bit_list: a list of bool to write\n        :type bit_list: list\n        :returns: True if success or None if error\n        :rtype: bool or None\n        :raises ValueError: if bit_list members cannot be converted to bool\n        \"\"\"\n        # ensure bit_list values are bool\n        bit_list = [bool(b) for b in bit_list]\n        # ensure atomic update of internal data\n        with self._d_inputs_lock:\n            if (address >= 0) and (address + len(bit_list) <= len(self._d_inputs)):\n                for offset, b_value in enumerate(bit_list):\n                    self._d_inputs[address + offset] = b_value\n            else:\n                return None\n        return True\n\n    def get_holding_registers(self, address, number=1, srv_info=None):\n        \"\"\"Read data on server holding registers space\n\n        :param address: start address\n        :type address: int\n        :param number: number of words (optional)\n        :type number: int\n        :param srv_info: some server info (must be set by server only)\n        :type srv_info: ModbusServerInfo\n        :returns: list of int or None if error\n        :rtype: list or None\n        \"\"\"\n        # secure extract of data from list used by server thread\n        with self._h_regs_lock:\n            if (address >= 0) and (address + number <= len(self._h_regs)):\n                return self._h_regs[address: number + address]\n            else:\n                return None\n\n    def set_holding_registers(self, address, word_list, srv_info=None):\n        \"\"\"Write data to server holding registers space\n\n        :param address: start address\n        :type address: int\n        :param word_list: a list of word to write\n        :type word_list: list\n        :param srv_info: some server info (must be set by server only)\n        :type srv_info: ModbusServerInfo\n        :returns: True if success or None if error\n        :rtype: bool or None\n        :raises ValueError: if word_list members cannot be converted to int\n        \"\"\"\n        # ensure word_list values are int with a max bit length of 16\n        word_list = [int(w) & 0xffff for w in word_list]\n        # keep trace of any changes\n        changes_list = []\n        # ensure atomic update of internal data\n        with self._h_regs_lock:\n            if (address >= 0) and (address + len(word_list) <= len(self._h_regs)):\n                for offset, c_value in enumerate(word_list):\n                    c_address = address + offset\n                    if self._h_regs[c_address] != c_value:\n                        changes_list.append((c_address, self._h_regs[c_address], c_value))\n                        self._h_regs[c_address] = c_value\n            else:\n                return None\n        # on server update\n        if srv_info:\n            # notify changes with on change method (after atomic update)\n            for address, from_value, to_value in changes_list:\n                self.on_holding_registers_change(address, from_value, to_value, srv_info=srv_info)\n        return True\n\n    def get_input_registers(self, address, number=1, srv_info=None):\n        \"\"\"Read data on server input registers space\n\n        :param address: start address\n        :type address: int\n        :param number: number of words (optional)\n        :type number: int\n        :param srv_info: some server info (must be set by server only)\n        :type srv_info: ModbusServerInfo\n        :returns: list of int or None if error\n        :rtype: list or None\n        \"\"\"\n        # secure extract of data from list used by server thread\n        with self._i_regs_lock:\n            if (address >= 0) and (address + number <= len(self._i_regs)):\n                return self._i_regs[address: number + address]\n            else:\n                return None\n\n    def set_input_registers(self, address, word_list):\n        \"\"\"Write data to server input registers space\n\n        :param address: start address\n        :type address: int\n        :param word_list: a list of word to write\n        :type word_list: list\n        :returns: True if success or None if error\n        :rtype: bool or None\n        :raises ValueError: if word_list members cannot be converted to int\n        \"\"\"\n        # ensure word_list values are int with a max bit length of 16\n        word_list = [int(w) & 0xffff for w in word_list]\n        # ensure atomic update of internal data\n        with self._i_regs_lock:\n            if (address >= 0) and (address + len(word_list) <= len(self._i_regs)):\n                for offset, c_value in enumerate(word_list):\n                    c_address = address + offset\n                    if self._i_regs[c_address] != c_value:\n                        self._i_regs[c_address] = c_value\n            else:\n                return None\n        return True\n\n    def on_coils_change(self, address, from_value, to_value, srv_info):\n        \"\"\"Call by server when a value change occur in coils space\n\n        This method is provided to be overridden with user code to catch changes\n\n        :param address: address of coil\n        :type address: int\n        :param from_value: coil original value\n        :type from_value: bool\n        :param to_value: coil next value\n        :type to_value: bool\n        :param srv_info: some server info\n        :type srv_info: ModbusServerInfo\n        \"\"\"\n        pass\n\n    def on_holding_registers_change(self, address, from_value, to_value, srv_info):\n        \"\"\"Call by server when a value change occur in holding registers space\n\n        This method is provided to be overridden with user code to catch changes\n\n        :param address: address of register\n        :type address: int\n        :param from_value: register original value\n        :type from_value: int\n        :param to_value: register next value\n        :type to_value: int\n        :param srv_info: some server info\n        :type srv_info: ModbusServerInfo\n        \"\"\"\n        pass\n\n\nclass DataHandler:\n    \"\"\"Default data handler for ModbusServer, map server threads calls to DataBank.\n\n    Custom handler must derive from this class.\n    \"\"\"\n\n    class Return:\n        def __init__(self, exp_code, data=None):\n            self.exp_code = exp_code\n            self.data = data\n\n        @property\n        def ok(self):\n            return self.exp_code == EXP_NONE\n\n    def __init__(self, data_bank=None):\n        \"\"\"Constructor\n\n        Modbus server data handler constructor.\n\n        :param data_bank: a reference to custom DefaultDataBank\n        :type data_bank: DataBank\n        \"\"\"\n        # check data_bank type\n        if data_bank and not isinstance(data_bank, DataBank):\n            raise TypeError('data_bank arg is invalid')\n        # public\n        self.data_bank = data_bank or DataBank()\n\n    def __repr__(self):\n        return 'ModbusServerDataHandler(data_bank=%s)' % self.data_bank\n\n    def read_coils(self, address, count, srv_info):\n        \"\"\"Call by server for reading in coils space\n\n        :param address: start address\n        :type address: int\n        :param count: number of coils\n        :type count: int\n        :param srv_info: some server info\n        :type srv_info: ModbusServer.ServerInfo\n        :rtype: Return\n        \"\"\"\n        # read bits from DataBank\n        bits_l = self.data_bank.get_coils(address, count, srv_info)\n        # return DataStatus to server\n        if bits_l is not None:\n            return DataHandler.Return(exp_code=EXP_NONE, data=bits_l)\n        else:\n            return DataHandler.Return(exp_code=EXP_DATA_ADDRESS)\n\n    def write_coils(self, address, bits_l, srv_info):\n        \"\"\"Call by server for writing in the coils space\n\n        :param address: start address\n        :type address: int\n        :param bits_l: list of boolean to write\n        :type bits_l: list\n        :param srv_info: some server info\n        :type srv_info: ModbusServer.ServerInfo\n        :rtype: Return\n        \"\"\"\n        # write bits to DataBank\n        update_ok = self.data_bank.set_coils(address, bits_l, srv_info)\n        # return DataStatus to server\n        if update_ok:\n            return DataHandler.Return(exp_code=EXP_NONE)\n        else:\n            return DataHandler.Return(exp_code=EXP_DATA_ADDRESS)\n\n    def read_d_inputs(self, address, count, srv_info):\n        \"\"\"Call by server for reading in the discrete inputs space\n\n        :param address: start address\n        :type address: int\n        :param count: number of discrete inputs\n        :type count: int\n        :param srv_info: some server info\n        :type srv_info: ModbusServer.ServerInfo\n        :rtype: Return\n        \"\"\"\n        # read bits from DataBank\n        bits_l = self.data_bank.get_discrete_inputs(address, count, srv_info)\n        # return DataStatus to server\n        if bits_l is not None:\n            return DataHandler.Return(exp_code=EXP_NONE, data=bits_l)\n        else:\n            return DataHandler.Return(exp_code=EXP_DATA_ADDRESS)\n\n    def read_h_regs(self, address, count, srv_info):\n        \"\"\"Call by server for reading in the holding registers space\n\n        :param address: start address\n        :type address: int\n        :param count: number of holding registers\n        :type count: int\n        :param srv_info: some server info\n        :type srv_info: ModbusServer.ServerInfo\n        :rtype: Return\n        \"\"\"\n        # read words from DataBank\n        words_l = self.data_bank.get_holding_registers(address, count, srv_info)\n        # return DataStatus to server\n        if words_l is not None:\n            return DataHandler.Return(exp_code=EXP_NONE, data=words_l)\n        else:\n            return DataHandler.Return(exp_code=EXP_DATA_ADDRESS)\n\n    def write_h_regs(self, address, words_l, srv_info):\n        \"\"\"Call by server for writing in the holding registers space\n\n        :param address: start address\n        :type address: int\n        :param words_l: list of word value to write\n        :type words_l: list\n        :param srv_info: some server info\n        :type srv_info: ModbusServer.ServerInfo\n        :rtype: Return\n        \"\"\"\n        # write words to DataBank\n        update_ok = self.data_bank.set_holding_registers(address, words_l, srv_info)\n        # return DataStatus to server\n        if update_ok:\n            return DataHandler.Return(exp_code=EXP_NONE)\n        else:\n            return DataHandler.Return(exp_code=EXP_DATA_ADDRESS)\n\n    def read_i_regs(self, address, count, srv_info):\n        \"\"\"Call by server for reading in the input registers space\n\n        :param address: start address\n        :type address: int\n        :param count: number of input registers\n        :type count: int\n        :param srv_info: some server info\n        :type srv_info: ModbusServer.ServerInfo\n        :rtype: Return\n        \"\"\"\n        # read words from DataBank\n        words_l = self.data_bank.get_input_registers(address, count, srv_info)\n        # return DataStatus to server\n        if words_l is not None:\n            return DataHandler.Return(exp_code=EXP_NONE, data=words_l)\n        else:\n            return DataHandler.Return(exp_code=EXP_DATA_ADDRESS)\n\n\nclass DeviceIdentification:\n    \"\"\" Container class for device identification objects (MEI type 0x0E) return by function 0x2B. \"\"\"\n\n    def __init__(self, vendor_name=b'', product_code=b'', major_minor_revision=b'', vendor_url=b'',\n                 product_name=b'', model_name=b'', user_application_name=b'', objects_id=None):\n        \"\"\"\n        Constructor\n\n        :param vendor_name: VendorName mandatory object\n        :type vendor_name: bytes\n        :param product_code: ProductCode mandatory object\n        :type product_code: bytes\n        :param major_minor_revision: MajorMinorRevision mandatory object\n        :type major_minor_revision: bytes\n        :param vendor_url: VendorUrl regular object\n        :type vendor_url: bytes\n        :param product_name: ProductName regular object\n        :type product_name: bytes\n        :param model_name: ModelName regular object\n        :type model_name: bytes\n        :param user_application_name: UserApplicationName regular object\n        :type user_application_name: bytes\n        :param objects_id: Objects values by id as dict example: {42:b'value'} (optional)\n        :type objects_id: dict\n        \"\"\"\n        # private\n        self._objs_d = {}\n        self._objs_lock = Lock()\n        # default values\n        self.vendor_name = vendor_name\n        self.product_code = product_code\n        self.major_minor_revision = major_minor_revision\n        self.vendor_url = vendor_url\n        self.product_name = product_name\n        self.model_name = model_name\n        self.user_application_name = user_application_name\n        # process objects_id dict (populate no name objects)\n        if isinstance(objects_id, dict):\n            for key, value in objects_id.items():\n                self[key] = value\n\n    def __getitem__(self, key):\n        if not isinstance(key, int):\n            raise TypeError('key must be an int')\n        with self._objs_lock:\n            return self._objs_d[key]\n\n    def __setitem__(self, key, value):\n        if not isinstance(key, int):\n            raise TypeError('key must be an int')\n        if 0xff >= key >= 0x00:\n            if not isinstance(value, bytes):\n                raise TypeError('this object is of type bytes only')\n            with self._objs_lock:\n                self._objs_d[key] = value\n        else:\n            raise ValueError('key not in valid range (0 to 255)')\n\n    def __repr__(self):\n        named_params = ''\n        # add named parameters\n        for prop_name in ('vendor_name', 'product_code', 'major_minor_revision', 'vendor_url',\n                          'product_name', 'model_name', 'user_application_name'):\n            prop_value = getattr(self, prop_name)\n            if prop_value:\n                if named_params:\n                    named_params += ', '\n                named_params += '%s=%r' % (prop_name, getattr(self, prop_name))\n        # add parameters without shortcut name\n        objs_id_d_str = ''\n        for _id in range(0x07, 0x100):\n            try:\n                obj_id_item = '%r: %r' % (_id, self[_id])\n                if objs_id_d_str:\n                    objs_id_d_str += ', '\n                objs_id_d_str += obj_id_item\n            except KeyError:\n                pass\n        # format str: classname(params_name=value, ..., objects_id={42: 'value'})\n        class_args = named_params\n        if objs_id_d_str:\n            if class_args:\n                class_args += ', '\n            class_args += 'objects_id={%s}' % objs_id_d_str\n        return '%s(%s)' % (self.__class__.__name__, class_args)\n\n    @property\n    def vendor_name(self):\n        return self[0]\n\n    @vendor_name.setter\n    def vendor_name(self, value):\n        self[0] = value\n\n    @property\n    def product_code(self):\n        return self[1]\n\n    @product_code.setter\n    def product_code(self, value):\n        self[1] = value\n\n    @property\n    def major_minor_revision(self):\n        return self[2]\n\n    @major_minor_revision.setter\n    def major_minor_revision(self, value):\n        self[2] = value\n\n    @property\n    def vendor_url(self):\n        return self[3]\n\n    @vendor_url.setter\n    def vendor_url(self, value):\n        self[3] = value\n\n    @property\n    def product_name(self):\n        return self[4]\n\n    @product_name.setter\n    def product_name(self, value):\n        self[4] = value\n\n    @property\n    def model_name(self):\n        return self[5]\n\n    @model_name.setter\n    def model_name(self, value):\n        self[5] = value\n\n    @property\n    def user_application_name(self):\n        return self[6]\n\n    @user_application_name.setter\n    def user_application_name(self, value):\n        self[6] = value\n\n    def items(self, start=0x00, end=0xff):\n        items_l = []\n        for obj_id in range(start, end + 1):\n            try:\n                items_l.append((obj_id, self[obj_id]))\n            except KeyError:\n                pass\n        return items_l\n\n\nclass ModbusServer:\n    \"\"\" Modbus TCP server \"\"\"\n\n    class Error(Exception):\n        \"\"\" Base exception for ModbusServer related errors. \"\"\"\n        pass\n\n    class NetworkError(Error):\n        \"\"\" Exception raise by ModbusServer on I/O errors. \"\"\"\n        pass\n\n    class DataFormatError(Error):\n        \"\"\" Exception raise by ModbusServer for data format errors. \"\"\"\n        pass\n\n    class ClientInfo:\n        \"\"\" Container class for client information \"\"\"\n\n        def __init__(self, address='', port=0):\n            self.address = address\n            self.port = port\n\n        def __repr__(self):\n            return 'ClientInfo(address=%r, port=%r)' % (self.address, self.port)\n\n    class ServerInfo:\n        \"\"\" Container class for server information \"\"\"\n\n        def __init__(self):\n            self.client = ModbusServer.ClientInfo()\n            self.recv_frame = ModbusServer.Frame()\n\n    class SessionData:\n        \"\"\" Container class for server session data. \"\"\"\n\n        def __init__(self):\n            self.client = ModbusServer.ClientInfo()\n            self.request = ModbusServer.Frame()\n            self.response = ModbusServer.Frame()\n\n        @property\n        def srv_info(self):\n            info = ModbusServer.ServerInfo()\n            info.client = self.client\n            info.recv_frame = self.request\n            return info\n\n        def new_request(self):\n            self.request = ModbusServer.Frame()\n            self.response = ModbusServer.Frame()\n\n        def set_response_mbap(self):\n            self.response.mbap.transaction_id = self.request.mbap.transaction_id\n            self.response.mbap.protocol_id = self.request.mbap.protocol_id\n            self.response.mbap.unit_id = self.request.mbap.unit_id\n\n    class Frame:\n        def __init__(self):\n            \"\"\" Modbus Frame container. \"\"\"\n            self.mbap = ModbusServer.MBAP()\n            self.pdu = ModbusServer.PDU()\n\n        @property\n        def raw(self):\n            self.mbap.length = len(self.pdu) + 1\n            return self.mbap.raw + self.pdu.raw\n\n    class MBAP:\n        \"\"\" MBAP (Modbus Application Protocol) container class. \"\"\"\n\n        def __init__(self, transaction_id=0, protocol_id=0, length=0, unit_id=0):\n            # public\n            self.transaction_id = transaction_id\n            self.protocol_id = protocol_id\n            self.length = length\n            self.unit_id = unit_id\n\n        @property\n        def raw(self):\n            try:\n                return struct.pack('>HHHB', self.transaction_id,\n                                   self.protocol_id, self.length,\n                                   self.unit_id)\n            except struct.error as e:\n                raise ModbusServer.DataFormatError('MBAP raw encode pack error: %s' % e)\n\n        @raw.setter\n        def raw(self, value):\n            # close connection if no standard 7 bytes mbap header\n            if not (value and len(value) == 7):\n                raise ModbusServer.DataFormatError('MBAP must have a length of 7 bytes')\n            # decode header\n            (self.transaction_id, self.protocol_id,\n             self.length, self.unit_id) = struct.unpack('>HHHB', value)\n            # check frame header content inconsistency\n            if self.protocol_id != 0:\n                raise ModbusServer.DataFormatError('MBAP protocol ID must be 0')\n            if not 2 < self.length < 256:\n                raise ModbusServer.DataFormatError('MBAP length must be between 2 and 256')\n\n    class PDU:\n        \"\"\" PDU (Protocol Data Unit) container class. \"\"\"\n\n        def __init__(self, raw=b''):\n            \"\"\"\n            Constructor\n\n            :param raw: raw PDU\n            :type raw: bytes\n            \"\"\"\n            self.raw = raw\n\n        def __len__(self):\n            return len(self.raw)\n\n        @property\n        def func_code(self):\n            return self.raw[0]\n\n        @property\n        def except_code(self):\n            return self.raw[1]\n\n        @property\n        def is_except(self):\n            return self.func_code > 0x7F\n\n        @property\n        def is_valid(self):\n            # PDU min length is 2 bytes\n            return self.__len__() < 2\n\n        def clear(self):\n            self.raw = b''\n\n        def build_except(self, func_code, exp_status):\n            self.clear()\n            self.add_pack('BB', func_code + 0x80, exp_status)\n            return self\n\n        def add_pack(self, fmt, *args):\n            try:\n                self.raw += struct.pack(fmt, *args)\n            except struct.error:\n                err_msg = 'unable to format PDU message (fmt: %s, values: %s)' % (fmt, args)\n                raise ModbusServer.DataFormatError(err_msg)\n\n        def unpack(self, fmt, from_byte=None, to_byte=None):\n            raw_section = self.raw[from_byte:to_byte]\n            try:\n                return struct.unpack(fmt, raw_section)\n            except struct.error:\n                err_msg = 'unable to decode PDU message  (fmt: %s, values: %s)' % (fmt, raw_section)\n                raise ModbusServer.DataFormatError(err_msg)\n\n    class ModbusService(BaseRequestHandler):\n\n        @property\n        def server_running(self):\n            return self.server.evt_running.is_set()\n\n        def _send_all(self, data):\n            try:\n                self.request.sendall(data)\n                return True\n            except socket.timeout:\n                return False\n\n        def _recv_all(self, size):\n            data = b''\n            while len(data) < size:\n                try:\n                    # avoid keeping this TCP thread run after server.stop() on main server\n                    if not self.server_running:\n                        raise ModbusServer.NetworkError('main server is not running')\n                    # recv all data or a chunk of it\n                    data_chunk = self.request.recv(size - len(data))\n                    # check data chunk\n                    if data_chunk:\n                        data += data_chunk\n                    else:\n                        raise ModbusServer.NetworkError('recv return null')\n                except socket.timeout:\n                    # just redo main server run test and recv operations on timeout\n                    pass\n            return data\n\n        def setup(self):\n            # set a socket timeout of 1s on blocking operations (like send/recv)\n            # this avoids hang thread deletion when main server exit (see _recv_all method)\n            self.request.settimeout(1.0)\n\n        def handle(self):\n            # try/except: end current thread on ModbusServer._InternalError, OSError or socket.error\n            # this also close the current TCP session associated with it\n            try:\n                # init and update server info structure\n                session_data = ModbusServer.SessionData()\n                (session_data.client.address, session_data.client.port) = self.request.getpeername()\n                # debug message\n                logger.debug('accept new connection from %r', session_data.client)\n                # main processing loop\n                while True:\n                    # init session data for new request\n                    session_data.new_request()\n                    # receive mbap from client\n                    session_data.request.mbap.raw = self._recv_all(7)\n                    # receive pdu from client\n                    session_data.request.pdu.raw = self._recv_all(session_data.request.mbap.length - 1)\n                    # update response MBAP fields with request data\n                    session_data.set_response_mbap()\n                    # pass the current session data to request engine\n                    self.server.engine(session_data)\n                    # send the tx pdu with the last rx mbap (only length field change)\n                    self._send_all(session_data.response.raw)\n            except (ModbusServer.Error, OSError, socket.error) as e:\n                # debug message\n                logger.debug('Exception during request handling: %r', e)\n                # on main loop except: exit from it and cleanly close the current socket\n                self.request.close()\n\n    def __init__(self, host='localhost', port=502, no_block=False, ipv6=False,\n                 data_bank=None, data_hdl=None, ext_engine=None, device_id=None):\n        \"\"\"Constructor\n\n        Modbus server constructor.\n\n        :param host: hostname or IPv4/IPv6 address server address (default is 'localhost')\n        :type host: str\n        :param port: TCP port number (default is 502)\n        :type port: int\n        :param no_block: no block mode, i.e. start() will return (default is False)\n        :type no_block: bool\n        :param ipv6: use ipv6 stack (default is False)\n        :type ipv6: bool\n        :param data_bank: instance of custom data bank, if you don't want the default one (optional)\n        :type data_bank: DataBank\n        :param data_hdl: instance of custom data handler, if you don't want the default one (optional)\n        :type data_hdl: DataHandler\n        :param ext_engine: an external engine reference (ref to ext_engine(session_data)) (optional)\n        :type ext_engine: callable\n        :param device_id: instance of DeviceIdentification class for read device identification request (optional)\n        :type device_id: DeviceIdentification\n        \"\"\"\n        # check data_bank\n        if data_bank and not isinstance(data_bank, DataBank):\n            raise TypeError('data_bank is not a DataBank instance')\n        # check data_hdl\n        if data_hdl and not isinstance(data_hdl, DataHandler):\n            raise TypeError('data_hdl is not a DataHandler instance')\n        # data_hdl and data_bank can't be set at same time\n        if data_hdl and data_bank:\n            raise ValueError('when data_hdl is set, you must define data_bank in it')\n        # check ext_engine\n        if ext_engine and not callable(ext_engine):\n            raise TypeError('ext_engine must be callable')\n        # check device_id\n        if device_id and not isinstance(device_id, DeviceIdentification):\n            raise TypeError('device_id is not a DeviceIdentification instance')\n        # public\n        self.host = host\n        self.port = port\n        self.no_block = no_block\n        self.ipv6 = ipv6\n        self.ext_engine = ext_engine\n        # First, internal data_bank will be linked to an external data handler if defined.\n        # If not, an external or internal DataBank will be used instead.\n        # \"virtual mode\" will be set for save memory if an external engine is in use.\n        self.data_bank = data_hdl.data_bank if data_hdl else data_bank or DataBank(virtual_mode=bool(ext_engine))\n        self.data_hdl = data_hdl or DataHandler(data_bank=self.data_bank)\n        self.device_id = device_id\n        # private\n        self._evt_running = Event()\n        self._service = None\n        self._serve_th = None\n        # modbus default functions map\n        self._func_map = {READ_COILS: self._read_bits,\n                          READ_DISCRETE_INPUTS: self._read_bits,\n                          READ_HOLDING_REGISTERS: self._read_words,\n                          READ_INPUT_REGISTERS: self._read_words,\n                          WRITE_SINGLE_COIL: self._write_single_coil,\n                          WRITE_SINGLE_REGISTER: self._write_single_register,\n                          WRITE_MULTIPLE_COILS: self._write_multiple_coils,\n                          WRITE_MULTIPLE_REGISTERS: self._write_multiple_registers,\n                          WRITE_READ_MULTIPLE_REGISTERS: self._write_read_multiple_registers,\n                          ENCAPSULATED_INTERFACE_TRANSPORT: self._encapsulated_interface_transport}\n\n    def __repr__(self):\n        r_str = 'ModbusServer(host=\\'%s\\', port=%d, no_block=%s, ipv6=%s, data_bank=%s, data_hdl=%s, ext_engine=%s)'\n        r_str %= (self.host, self.port, self.no_block, self.ipv6, self.data_bank, self.data_hdl, self.ext_engine)\n        return r_str\n\n    def _engine(self, session_data):\n        \"\"\"Main request processing engine.\n\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # call external engine or internal one (if ext_engine undefined)\n        if callable(self.ext_engine):\n            try:\n                self.ext_engine(session_data)\n            except Exception as e:\n                raise ModbusServer.Error('external engine raise an exception: %r' % e)\n        else:\n            self._internal_engine(session_data)\n\n    def _internal_engine(self, session_data):\n        \"\"\"Default internal processing engine: call default modbus func.\n\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        try:\n            # call the ad-hoc function, if none exists, send an \"illegal function\" exception\n            func = self._func_map[session_data.request.pdu.func_code]\n            # check function found is callable\n            if not callable(func):\n                raise TypeError\n            # call ad-hoc func\n            func(session_data)\n        except (TypeError, KeyError):\n            session_data.response.pdu.build_except(session_data.request.pdu.func_code, EXP_ILLEGAL_FUNCTION)\n\n    def _read_bits(self, session_data):\n        \"\"\"\n        Functions Read Coils (0x01) or Read Discrete Inputs (0x02).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (start_address, quantity_bits) = recv_pdu.unpack('>HH', from_byte=1, to_byte=5)\n        # check quantity of requested bits\n        if 0x0001 <= quantity_bits <= 0x07D0:\n            # data handler read request: for coils or discrete inputs space\n            if recv_pdu.func_code == READ_COILS:\n                ret_hdl = self.data_hdl.read_coils(start_address, quantity_bits, session_data.srv_info)\n            else:\n                ret_hdl = self.data_hdl.read_d_inputs(start_address, quantity_bits, session_data.srv_info)\n            # format regular or except response\n            if ret_hdl.ok:\n                # allocate bytes list\n                b_size = (quantity_bits + 7) // 8\n                bytes_l = [0] * b_size\n                # populate bytes list with data bank bits\n                for i, item in enumerate(ret_hdl.data):\n                    if item:\n                        bytes_l[i // 8] = set_bit(bytes_l[i // 8], i % 8)\n                # build pdu\n                send_pdu.add_pack('BB', recv_pdu.func_code, len(bytes_l))\n                send_pdu.add_pack('%dB' % len(bytes_l), *bytes_l)\n            else:\n                send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n        else:\n            send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)\n\n    def _read_words(self, session_data):\n        \"\"\"\n        Functions Read Holding Registers (0x03) or Read Input Registers (0x04).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (start_addr, quantity_regs) = recv_pdu.unpack('>HH', from_byte=1, to_byte=5)\n        # check quantity of requested words\n        if 0x0001 <= quantity_regs <= 0x007D:\n            # data handler read request: for holding or input registers space\n            if recv_pdu.func_code == READ_HOLDING_REGISTERS:\n                ret_hdl = self.data_hdl.read_h_regs(start_addr, quantity_regs, session_data.srv_info)\n            else:\n                ret_hdl = self.data_hdl.read_i_regs(start_addr, quantity_regs, session_data.srv_info)\n            # format regular or except response\n            if ret_hdl.ok:\n                # build pdu\n                send_pdu.add_pack('BB', recv_pdu.func_code, quantity_regs * 2)\n                # add_pack requested words\n                send_pdu.add_pack('>%dH' % len(ret_hdl.data), *ret_hdl.data)\n            else:\n                send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n        else:\n            send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)\n\n    def _write_single_coil(self, session_data):\n        \"\"\"\n        Function Write Single Coil (0x05).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (coil_addr, coil_value) = recv_pdu.unpack('>HH', from_byte=1, to_byte=5)\n        # format coil raw value to bool\n        coil_as_bool = bool(coil_value == 0xFF00)\n        # data handler update request\n        ret_hdl = self.data_hdl.write_coils(coil_addr, [coil_as_bool], session_data.srv_info)\n        # format regular or except response\n        if ret_hdl.ok:\n            send_pdu.add_pack('>BHH', recv_pdu.func_code, coil_addr, coil_value)\n        else:\n            send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n\n    def _write_single_register(self, session_data):\n        \"\"\"\n        Functions Write Single Register (0x06).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (reg_addr, reg_value) = recv_pdu.unpack('>HH', from_byte=1, to_byte=5)\n        # data handler update request\n        ret_hdl = self.data_hdl.write_h_regs(reg_addr, [reg_value], session_data.srv_info)\n        # format regular or except response\n        if ret_hdl.ok:\n            send_pdu.add_pack('>BHH', recv_pdu.func_code, reg_addr, reg_value)\n        else:\n            send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n\n    def _write_multiple_coils(self, session_data):\n        \"\"\"\n        Function Write Multiple Coils (0x0F).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (start_addr, quantity_bits, byte_count) = recv_pdu.unpack('>HHB', from_byte=1, to_byte=6)\n        # ok flags: some tests on pdu fields\n        qty_bits_ok = 0x0001 <= quantity_bits <= 0x07B0\n        b_count_ok = byte_count >= (quantity_bits + 7) // 8\n        pdu_len_ok = len(recv_pdu.raw[6:]) >= byte_count\n        # test ok flags\n        if qty_bits_ok and b_count_ok and pdu_len_ok:\n            # allocate bits list\n            bits_l = [False] * quantity_bits\n            # populate bits list with bits from rx frame\n            for i, _ in enumerate(bits_l):\n                bit_val = recv_pdu.raw[i // 8 + 6]\n                bits_l[i] = test_bit(bit_val, i % 8)\n            # data handler update request\n            ret_hdl = self.data_hdl.write_coils(start_addr, bits_l, session_data.srv_info)\n            # format regular or except response\n            if ret_hdl.ok:\n                send_pdu.add_pack('>BHH', recv_pdu.func_code, start_addr, quantity_bits)\n            else:\n                send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n        else:\n            send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)\n\n    def _write_multiple_registers(self, session_data):\n        \"\"\"\n        Function Write Multiple Registers (0x10).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (start_addr, quantity_regs, byte_count) = recv_pdu.unpack('>HHB', from_byte=1, to_byte=6)\n        # ok flags: some tests on pdu fields\n        qty_regs_ok = 0x0001 <= quantity_regs <= 0x007B\n        b_count_ok = byte_count == quantity_regs * 2\n        pdu_len_ok = len(recv_pdu.raw[6:]) >= byte_count\n        # test ok flags\n        if qty_regs_ok and b_count_ok and pdu_len_ok:\n            # allocate words list\n            regs_l = [0] * quantity_regs\n            # populate words list with words from rx frame\n            for i, _ in enumerate(regs_l):\n                offset = i * 2 + 6\n                regs_l[i] = recv_pdu.unpack('>H', from_byte=offset, to_byte=offset + 2)[0]\n            # data handler update request\n            ret_hdl = self.data_hdl.write_h_regs(start_addr, regs_l, session_data.srv_info)\n            # format regular or except response\n            if ret_hdl.ok:\n                send_pdu.add_pack('>BHH', recv_pdu.func_code, start_addr, quantity_regs)\n            else:\n                send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n        else:\n            send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)\n\n    def _write_read_multiple_registers(self, session_data):\n        \"\"\"\n        Function Write Read Multiple Registers (0x17).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (read_start_addr,\n         read_quantity_regs,\n         write_start_addr,\n         write_quantity_regs,\n         byte_count) = recv_pdu.unpack('>HHHHB', from_byte=1, to_byte=10)\n        # ok flags: some tests on pdu fields\n        write_qty_regs_ok = 0x0001 <= write_quantity_regs <= 0x007B\n        write_b_count_ok = byte_count == write_quantity_regs * 2\n        write_pdu_len_ok = len(recv_pdu.raw[10:]) >= byte_count\n        read_qty_regs_ok = 0x0001 <= read_quantity_regs <= 0x007B\n        # test ok flags\n        if write_qty_regs_ok and write_b_count_ok and write_pdu_len_ok and read_qty_regs_ok:\n            # allocate words list\n            regs_l = [0] * write_quantity_regs\n            # populate words list with words from rx frame\n            for i, _ in enumerate(regs_l):\n                offset = i * 2 + 10\n                regs_l[i] = recv_pdu.unpack('>H', from_byte=offset, to_byte=offset + 2)[0]\n            # data handler update request\n            ret_hdl = self.data_hdl.write_h_regs(write_start_addr, regs_l, session_data.srv_info)\n            # format regular or except response\n            if ret_hdl.ok:\n                ret_hdl = self.data_hdl.read_h_regs(read_start_addr, read_quantity_regs, session_data.srv_info)\n                if ret_hdl.ok:\n                    # build pdu\n                    send_pdu.add_pack('BB', recv_pdu.func_code, read_quantity_regs * 2)\n                    # add_pack requested words\n                    send_pdu.add_pack('>%dH' % len(ret_hdl.data), *ret_hdl.data)\n                else:\n                    send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n            else:\n                send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)\n        else:\n            send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)\n\n    def _encapsulated_interface_transport(self, session_data):\n        \"\"\"\n        Modbus Encapsulated Interface transport (MEI) endpoint (0x2B).\n\n        :param session_data: server engine data\n        :type session_data: ModbusServer.SessionData\n        \"\"\"\n        # pdu alias\n        recv_pdu = session_data.request.pdu\n        send_pdu = session_data.response.pdu\n        # decode pdu\n        (mei_type,) = recv_pdu.unpack('B', from_byte=1, to_byte=2)\n        # MEI type: read device identification\n        if mei_type == MEI_TYPE_READ_DEVICE_ID:\n            # check device_id property is set (default is None)\n            if not self.device_id:\n                # return except 2 if unset\n                send_pdu.build_except(recv_pdu.func_code, EXP_DATA_ADDRESS)\n                return\n            # list of requested objects\n            req_objects_l = list()\n            (device_id_code, object_id) = recv_pdu.unpack('BB', from_byte=2, to_byte=4)\n            # get basic device id (object id from 0x00 to 0x02)\n            if device_id_code == 1:\n                start_id = object_id\n                req_objects_l.extend(self.device_id.items(start=start_id, end=0x2))\n            # get regular device id (object id 0x03 to 0x7f)\n            elif device_id_code == 2:\n                start_id = max(object_id, 0x03)\n                req_objects_l.extend(self.device_id.items(start=start_id, end=0x7f))\n            # get extended device id (object id 0x80 to 0xff)\n            elif device_id_code == 3:\n                start_id = max(object_id, 0x80)\n                req_objects_l.extend(self.device_id.items(start=start_id, end=0xff))\n            # get specific id object\n            elif device_id_code == 4:\n                start_id = object_id\n                req_objects_l.extend(self.device_id.items(start=start_id, end=start_id))\n            else:\n                # return except 3 for unknown device id code\n                send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)\n                return\n            # init variables for response PDU build\n            conformity_level = 0x83\n            more_follow = 0\n            next_obj_id = 0\n            number_of_objs = 0\n            fmt_pdu_head = 'BBBBBBB'\n            # format objects data part = [[obj id, obj len, obj val], ...]\n            obj_data_part = b''\n            for req_obj_id, req_obj_value in req_objects_l:\n                fmt_obj_blk = 'BB%ss' % len(req_obj_value)\n                # skip if the next add to data part will exceed max PDU size of modbus frame\n                if struct.calcsize(fmt_pdu_head) + len(obj_data_part) + struct.calcsize(fmt_obj_blk) > MAX_PDU_SIZE:\n                    # turn on \"more follow\" field and set \"next object id\" field with next object id to ask\n                    more_follow = 0xff\n                    next_obj_id = req_obj_id\n                    break\n                # ensure bytes type for object value\n                if isinstance(req_obj_value, str):\n                    req_obj_value = req_obj_value.encode()\n                # add current object to data part\n                obj_data_part += struct.pack(fmt_obj_blk, req_obj_id, len(req_obj_value), req_obj_value)\n                number_of_objs += 1\n            # full PDU response = [PDU header] + [objects data part]\n            send_pdu.add_pack(fmt_pdu_head, recv_pdu.func_code, mei_type, device_id_code,\n                              conformity_level, more_follow, next_obj_id, number_of_objs)\n            send_pdu.raw += obj_data_part\n        else:\n            # return except 2 for an unknown MEI type\n            send_pdu.build_except(recv_pdu.func_code, EXP_DATA_ADDRESS)\n\n    def start(self):\n        \"\"\"Start the server.\n\n        This function will block (or not if no_block flag is set).\n        \"\"\"\n        # do nothing if server is already running\n        if not self.is_run:\n            # set class attribute\n            ThreadingTCPServer.address_family = socket.AF_INET6 if self.ipv6 else socket.AF_INET\n            ThreadingTCPServer.daemon_threads = True\n            # init server\n            self._service = ThreadingTCPServer((self.host, self.port), self.ModbusService, bind_and_activate=False)\n            # pass some things shared with server threads (access via self.server in ModbusService.handle())\n            self._service.evt_running = self._evt_running\n            self._service.engine = self._engine\n            # set socket options\n            self._service.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            self._service.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)\n            # TODO test no_delay with bench\n            self._service.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)\n            # bind and activate\n            try:\n                self._service.server_bind()\n                self._service.server_activate()\n            except OSError as e:\n                raise ModbusServer.NetworkError(e)\n            # serve request\n            if self.no_block:\n                self._serve_th = Thread(target=self._serve)\n                self._serve_th.daemon = True\n                self._serve_th.start()\n            else:\n                self._serve()\n\n    def stop(self):\n        \"\"\"Stop the server.\"\"\"\n        if self.is_run:\n            self._service.shutdown()\n            self._service.server_close()\n\n    @property\n    def is_run(self):\n        \"\"\"Return True if server running.\n\n        \"\"\"\n        return self._evt_running.is_set()\n\n    def _serve(self):\n        try:\n            self._evt_running.set()\n            self._service.serve_forever()\n        except Exception:\n            self._service.server_close()\n            raise\n        except KeyboardInterrupt:\n            self._service.server_close()\n        finally:\n            self._evt_running.clear()\n"
  },
  {
    "path": "pyModbusTCP/utils.py",
    "content": "\"\"\" pyModbusTCP utils functions \"\"\"\n\nimport re\nimport socket\nimport struct\n\n\n###############\n# bits function\n###############\ndef get_bits_from_int(val_int, val_size=16):\n    \"\"\"Get the list of bits of val_int integer (default size is 16 bits).\n\n    Return bits list, the least significant bit first. Use list.reverse() for msb first.\n\n    :param val_int: integer value\n    :type val_int: int\n    :param val_size: bit length of integer (word = 16, long = 32) (optional)\n    :type val_size: int\n    :returns: list of boolean \"bits\" (the least significant first)\n    :rtype: list\n    \"\"\"\n    bits = []\n    # populate bits list with bool items of val_int\n    for i in range(val_size):\n        bits.append(bool((val_int >> i) & 0x01))\n    # return bits list\n    return bits\n\n\n# short alias\nint2bits = get_bits_from_int\n\n\ndef byte_length(bit_length):\n    \"\"\"Return the number of bytes needs to contain a bit_length structure.\n\n    :param bit_length: the number of bits\n    :type bit_length: int\n    :returns: the number of bytes\n    :rtype: int\n    \"\"\"\n    return (bit_length + 7) // 8\n\n\ndef test_bit(value, offset):\n    \"\"\"Test a bit at offset position.\n\n    :param value: value of integer to test\n    :type value: int\n    :param offset: bit offset (0 is lsb)\n    :type offset: int\n    :returns: value of bit at offset position\n    :rtype: bool\n    \"\"\"\n    mask = 1 << offset\n    return bool(value & mask)\n\n\ndef set_bit(value, offset):\n    \"\"\"Set a bit at offset position.\n\n    :param value: value of integer where set the bit\n    :type value: int\n    :param offset: bit offset (0 is lsb)\n    :type offset: int\n    :returns: value of integer with bit set\n    :rtype: int\n    \"\"\"\n    mask = 1 << offset\n    return int(value | mask)\n\n\ndef reset_bit(value, offset):\n    \"\"\"Reset a bit at offset position.\n\n    :param value: value of integer where reset the bit\n    :type value: int\n    :param offset: bit offset (0 is lsb)\n    :type offset: int\n    :returns: value of integer with bit reset\n    :rtype: int\n    \"\"\"\n    mask = ~(1 << offset)\n    return int(value & mask)\n\n\ndef toggle_bit(value, offset):\n    \"\"\"Return an integer with the bit at offset position inverted.\n\n    :param value: value of integer where invert the bit\n    :type value: int\n    :param offset: bit offset (0 is lsb)\n    :type offset: int\n    :returns: value of integer with bit inverted\n    :rtype: int\n    \"\"\"\n    mask = 1 << offset\n    return int(value ^ mask)\n\n\n########################\n# Word convert functions\n########################\ndef word_list_to_long(val_list, big_endian=True, long_long=False):\n    \"\"\"Word list (16 bits) to long (32 bits) or long long (64 bits) list.\n\n    By default, word_list_to_long() use big endian order. For use little endian, set\n    big_endian param to False. Output format could be long long with long_long.\n    option set to True.\n\n    :param val_list: list of 16 bits int value\n    :type val_list: list\n    :param big_endian: True for big endian/False for little (optional)\n    :type big_endian: bool\n    :param long_long: True for long long 64 bits, default is long 32 bits (optional)\n    :type long_long: bool\n    :returns: list of 32 bits int value\n    :rtype: list\n    \"\"\"\n    long_list = []\n    block_size = 4 if long_long else 2\n    # populate long_list (len is half or quarter of 16 bits val_list) with 32 or 64 bits value\n    for index in range(int(len(val_list) / block_size)):\n        start = block_size * index\n        long = 0\n        if big_endian:\n            if long_long:\n                long += (val_list[start] << 48) + (val_list[start + 1] << 32)\n                long += (val_list[start + 2] << 16) + (val_list[start + 3])\n            else:\n                long += (val_list[start] << 16) + val_list[start + 1]\n        else:\n            if long_long:\n                long += (val_list[start + 3] << 48) + (val_list[start + 2] << 32)\n            long += (val_list[start + 1] << 16) + val_list[start]\n        long_list.append(long)\n    # return long list\n    return long_list\n\n\n# short alias\nwords2longs = word_list_to_long\n\n\ndef long_list_to_word(val_list, big_endian=True, long_long=False):\n    \"\"\"Long (32 bits) or long long (64 bits) list to word (16 bits) list.\n\n    By default long_list_to_word() use big endian order. For use little endian, set\n    big_endian param to False. Input format could be long long with long_long\n    param to True.\n\n    :param val_list: list of 32 bits int value\n    :type val_list: list\n    :param big_endian: True for big endian/False for little (optional)\n    :type big_endian: bool\n    :param long_long: True for long long 64 bits, default is long 32 bits (optional)\n    :type long_long: bool\n    :returns: list of 16 bits int value\n    :rtype: list\n    \"\"\"\n    word_list = []\n    # populate 16 bits word_list with 32 or 64 bits value of val_list\n    for val in val_list:\n        block_l = [val & 0xffff, (val >> 16) & 0xffff]\n        if long_long:\n            block_l.append((val >> 32) & 0xffff)\n            block_l.append((val >> 48) & 0xffff)\n        if big_endian:\n            block_l.reverse()\n        word_list.extend(block_l)\n    # return long list\n    return word_list\n\n\n# short alias\nlongs2words = long_list_to_word\n\n\n##########################\n# 2's complement functions\n##########################\ndef get_2comp(val_int, val_size=16):\n    \"\"\"Get the 2's complement of Python int val_int.\n\n    :param val_int: int value to apply 2's complement\n    :type val_int: int\n    :param val_size: bit size of int value (word = 16, long = 32) (optional)\n    :type val_size: int\n    :returns: 2's complement result\n    :rtype: int\n    :raises ValueError: if mismatch between val_int and val_size\n    \"\"\"\n    # avoid overflow\n    if not (-1 << val_size - 1) <= val_int < (1 << val_size):\n        err_msg = 'could not compute two\\'s complement for %i on %i bits'\n        err_msg %= (val_int, val_size)\n        raise ValueError(err_msg)\n    # test negative int\n    if val_int < 0:\n        val_int += 1 << val_size\n    # test MSB (do two's comp if set)\n    elif val_int & (1 << (val_size - 1)):\n        val_int -= 1 << val_size\n    return val_int\n\n\n# short alias\ntwos_c = get_2comp\n\n\ndef get_list_2comp(val_list, val_size=16):\n    \"\"\"Get the 2's complement of Python list val_list.\n\n    :param val_list: list of int value to apply 2's complement\n    :type val_list: list\n    :param val_size: bit size of int value (word = 16, long = 32) (optional)\n    :type val_size: int\n    :returns: 2's complement result\n    :rtype: list\n    \"\"\"\n    return [get_2comp(val, val_size) for val in val_list]\n\n\n# short alias\ntwos_c_l = get_list_2comp\n\n\n###############################\n# IEEE floating-point functions\n###############################\ndef decode_ieee(val_int, double=False):\n    \"\"\"Decode Python int (32 bits integer) as an IEEE single or double precision format.\n\n    Support NaN.\n\n    :param val_int: a 32 or 64 bits integer as an int Python value\n    :type val_int: int\n    :param double: set to decode as a 64 bits double precision,\n                   default is 32 bits single (optional)\n    :type double: bool\n    :returns: float result\n    :rtype: float\n    \"\"\"\n    if double:\n        return struct.unpack(\"d\", struct.pack(\"Q\", val_int))[0]\n    else:\n        return struct.unpack(\"f\", struct.pack(\"I\", val_int))[0]\n\n\ndef encode_ieee(val_float, double=False):\n    \"\"\"Encode Python float to int (32 bits integer) as an IEEE single or double precision format.\n\n    Support NaN.\n\n    :param val_float: float value to convert\n    :type val_float: float\n    :param double: set to encode as a 64 bits double precision,\n                   default is 32 bits single (optional)\n    :type double: bool\n    :returns: IEEE 32 bits (single precision) as Python int\n    :rtype: int\n    \"\"\"\n    if double:\n        return struct.unpack(\"Q\", struct.pack(\"d\", val_float))[0]\n    else:\n        return struct.unpack(\"I\", struct.pack(\"f\", val_float))[0]\n\n\n################\n# misc functions\n################\ndef crc16(frame):\n    \"\"\"Compute CRC16.\n\n    :param frame: frame\n    :type frame: bytes\n    :returns: CRC16\n    :rtype: int\n    \"\"\"\n    crc = 0xFFFF\n    for item in frame:\n        next_byte = item\n        crc ^= next_byte\n        for _ in range(8):\n            lsb = crc & 1\n            crc >>= 1\n            if lsb:\n                crc ^= 0xA001\n    return crc\n\n\ndef valid_host(host_str):\n    \"\"\"Validate a host string.\n\n    Can be an IPv4/6 address or a valid hostname.\n\n    :param host_str: the host string to test\n    :type host_str: str\n    :returns: True if host_str is valid\n    :rtype: bool\n    \"\"\"\n    # IPv4 valid address ?\n    try:\n        socket.inet_pton(socket.AF_INET, host_str)\n        return True\n    except socket.error:\n        pass\n    # IPv6 valid address ?\n    try:\n        socket.inet_pton(socket.AF_INET6, host_str)\n        return True\n    except socket.error:\n        pass\n    # valid hostname ?\n    if len(host_str) > 255:\n        return False\n    # strip final dot, if present\n    if host_str[-1] == '.':\n        host_str = host_str[:-1]\n    # validate each part of the hostname (part_1.part_2.part_3)\n    re_part_ok = re.compile('(?!-)[a-z0-9-_]{1,63}(?<!-)$', re.IGNORECASE)\n    return all(re_part_ok.match(part) for part in host_str.split('.'))\n"
  },
  {
    "path": "setup.cfg",
    "content": "[build_sphinx]\nsource-dir = docs/\nall_files  = 1"
  },
  {
    "path": "setup.py",
    "content": "from pyModbusTCP import constants\nfrom setuptools import setup\n\nwith open('README.rst') as f:\n    readme = f.read()\n\nsetup(\n    name=\"pyModbusTCP\",\n    version=constants.VERSION,\n    description=\"A simple Modbus/TCP library for Python\",\n    long_description=readme,\n    author=\"Loic Lefebvre\",\n    author_email=\"loic.celine@free.fr\",\n    license=\"MIT\",\n    url=\"https://github.com/sourceperl/pyModbusTCP\",\n    packages=[\"pyModbusTCP\"],\n    platforms=\"any\",\n)\n"
  },
  {
    "path": "tests/test_client.py",
    "content": "\"\"\" Test of pyModbusTCP.ModbusClient \"\"\"\n\nimport unittest\nfrom pyModbusTCP.client import ModbusClient\n\n\nclass TestModbusClient(unittest.TestCase):\n    \"\"\" ModbusClient tests class. \"\"\"\n\n    def test_host(self):\n        \"\"\"Test of host property.\"\"\"\n        # default value\n        self.assertEqual(ModbusClient().host, 'localhost')\n        # should raise ValueError for bad value\n        self.assertRaises(ValueError, ModbusClient, host='wrong@host')\n        self.assertRaises(ValueError, ModbusClient, host='::notip:1')\n        # shouldn't raise ValueError for valid value\n        try:\n            [ModbusClient(host=h) for h in ['CamelCaseHost', 'plc-1.net', 'my.good.host',\n                                            '_test.example.com', '42.example.com',\n                                            '127.0.0.1', '::1']]\n        except ValueError:\n            self.fail('ModbusClient.host property raised ValueError unexpectedly')\n\n    def test_port(self):\n        \"\"\"Test of port property.\"\"\"\n        # default value\n        self.assertEqual(ModbusClient().port, 502)\n        # should raise an exception for bad value\n        self.assertRaises(TypeError, ModbusClient, port='amsterdam')\n        self.assertRaises(ValueError, ModbusClient, port=-1)\n        # shouldn't raise ValueError for valid value\n        try:\n            ModbusClient(port=5020)\n        except ValueError:\n            self.fail('ModbusClient.port property raised ValueError unexpectedly')\n\n    def test_unit_id(self):\n        \"\"\"Test of unit_id property.\"\"\"\n        # default value\n        self.assertEqual(ModbusClient().unit_id, 1)\n        # should raise an exception for bad unit_id\n        self.assertRaises(TypeError, ModbusClient, unit_id='@')\n        self.assertRaises(ValueError, ModbusClient, unit_id=420)\n        # shouldn't raise ValueError for valid value\n        try:\n            ModbusClient(port=5020)\n        except ValueError:\n            self.fail('ModbusClient.port property raised ValueError unexpectedly')\n\n    def test_misc(self):\n        \"\"\"Check of misc default values.\"\"\"\n        self.assertEqual(ModbusClient().auto_open, True)\n        self.assertEqual(ModbusClient().auto_close, False)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "tests/test_client_server.py",
    "content": "\"\"\" Test of pyModbusTCP client-server interaction \"\"\"\n\nimport unittest\nfrom random import randint, getrandbits, choice\nfrom string import ascii_letters\nfrom pyModbusTCP.server import ModbusServer, DeviceIdentification\nfrom pyModbusTCP.client import ModbusClient, DeviceIdentificationResponse\nfrom pyModbusTCP.constants import SUPPORTED_FUNCTION_CODES, \\\n    EXP_NONE, EXP_ILLEGAL_FUNCTION, EXP_DATA_ADDRESS, EXP_DATA_VALUE, MB_NO_ERR, MB_EXCEPT_ERR\n\n\n# some const\nMAX_READABLE_REGS = 125\nMAX_WRITABLE_REGS = 123\nMAX_WRITE_READ_REGS = 121\nMAX_READABLE_BITS = 2000\nMAX_WRITABLE_BITS = 1968\n\n\nclass TestClientServer(unittest.TestCase):\n    \"\"\" Client-server interaction test class. \"\"\"\n\n    def setUp(self):\n        \"\"\"Init client-server for test_xxx methods.\"\"\"\n        # modbus server\n        self.server = ModbusServer(port=5020, no_block=True)\n        self.server.start()\n        # modbus client\n        self.client = ModbusClient(port=5020)\n        self.client.open()\n\n    def tearDown(self):\n        \"\"\"Cleanning after test.\"\"\"\n        self.client.close()\n        self.server.stop()\n\n    def test_default_startup_values(self):\n        \"\"\"Some read at random address to test startup values.\"\"\"\n        for addr in [randint(0, 0xffff) for _ in range(100)]:\n            self.assertEqual(self.client.read_coils(addr), [False])\n            self.assertEqual(self.client.read_discrete_inputs(addr), [False])\n            self.assertEqual(self.client.read_holding_registers(addr), [0])\n            self.assertEqual(self.client.read_input_registers(addr), [0])\n\n    def test_read_write_requests(self):\n        \"\"\"Test standard modbus functions.\"\"\"\n        # coils\n        for addr in [0x0000, 0x1234, 0x2345, 0x10000 - MAX_WRITABLE_BITS]:\n            # coils space: single read/write\n            bit = bool(getrandbits(1))\n            self.assertEqual(self.client.write_single_coil(addr, bit), True)\n            self.assertEqual(self.client.read_coils(addr), [bit])\n            # coils space: multiple read/write at min size\n            bits_l = [bool(getrandbits(1))]\n            self.assertEqual(self.client.write_multiple_coils(addr, bits_l), True)\n            self.assertEqual(self.client.read_coils(addr, len(bits_l)), bits_l)\n            # coils space: multiple read/write at max size\n            bits_l = [bool(getrandbits(1)) for _ in range(MAX_WRITABLE_BITS)]\n            self.assertEqual(self.client.write_multiple_coils(addr, bits_l), True)\n            self.assertEqual(self.client.read_coils(addr, len(bits_l)), bits_l)\n            # coils space: oversized multi-write\n            bits_l.append(bool(getrandbits(1)))\n            self.assertRaises(ValueError, self.client.write_multiple_coils, addr, bits_l)\n        # coils space: read/write over limit\n        self.assertRaises(ValueError, self.client.read_coils, 0xfffe, 3)\n        self.assertRaises(ValueError, self.client.write_single_coil, 0x10000, False)\n        self.assertRaises(ValueError, self.client.write_multiple_coils, 0xfff0, [False] * 17)\n\n        # discrete inputs\n        for addr in [0x0000, 0x1234, 0x2345, 0x10000 - MAX_READABLE_BITS]:\n            # discrete inputs space: single read/write\n            bit = bool(getrandbits(1))\n            self.server.data_bank.set_discrete_inputs(addr, [bit])\n            self.assertEqual(self.client.read_discrete_inputs(addr), [bit])\n            # discrete inputs space: multiple read/write at min size\n            bits_l = [bool(getrandbits(1))]\n            self.server.data_bank.set_discrete_inputs(addr, bits_l)\n            self.assertEqual(self.client.read_discrete_inputs(addr, len(bits_l)), bits_l)\n            # discrete inputs space: multiple read/write at max size\n            bits_l = [bool(getrandbits(1)) for _ in range(MAX_READABLE_BITS)]\n            self.server.data_bank.set_discrete_inputs(addr, bits_l)\n            self.assertEqual(self.client.read_discrete_inputs(addr, len(bits_l)), bits_l)\n            # discrete inputs space: multiple read/write at max size\n            bits_l.append(bool(getrandbits(1)))\n            self.server.data_bank.set_discrete_inputs(addr, bits_l)\n            self.assertRaises(ValueError, self.client.read_discrete_inputs, addr, len(bits_l))\n        # discrete inputs space: read/write over limit\n        self.assertRaises(ValueError, self.client.read_discrete_inputs, 0xffff, 2)\n\n        # holding registers\n        for addr in [0x0000, 0x1234, 0x2345, 0x10000 - MAX_WRITABLE_REGS]:\n            # holding registers space: single read/write\n            word = randint(0, 0xffff)\n            self.assertEqual(self.client.write_single_register(addr, word), True)\n            self.assertEqual(self.client.read_holding_registers(addr), [word])\n            # holding registers space: multi-write at max size\n            words_l = [randint(0, 0xffff) for _ in range(MAX_WRITABLE_REGS)]\n            self.assertEqual(self.client.write_multiple_registers(addr, words_l), True)\n            self.assertEqual(self.client.read_holding_registers(addr, len(words_l)), words_l)\n            # holding registers space: multi-write at max size\n            words_l = [randint(0, 0xffff) for _ in range(MAX_WRITE_READ_REGS)]\n            self.assertEqual(self.client.write_read_multiple_registers(addr, words_l, addr, len(words_l)), words_l)\n            self.assertEqual(self.client.read_holding_registers(addr, len(words_l)), words_l)\n        # holding registers space: read/write over limit\n        self.assertRaises(ValueError, self.client.read_holding_registers, 0xfff0, 17)\n        self.assertRaises(ValueError, self.client.write_single_register, 0, 0x10000)\n        self.assertRaises(ValueError, self.client.write_single_register, 0x10000, 0)\n        self.assertRaises(ValueError, self.client.write_multiple_registers, 0x1000, [0x10000])\n        self.assertRaises(ValueError, self.client.write_multiple_registers, 0xfff0, [0] * 17)\n        self.assertRaises(ValueError, self.client.write_read_multiple_registers, 0x1000, [0x10000], 0x1000, 1)\n        self.assertRaises(ValueError, self.client.write_read_multiple_registers, 0xfff0, [0] * 17, 0xfff0, 1)\n        self.assertRaises(ValueError, self.client.write_read_multiple_registers, 0xfff0, [0] * 1, 0xfff0, 17)\n\n        # input registers\n        for addr in [0x0000, 0x1234, 0x2345, 0x10000 - MAX_READABLE_REGS]:\n            # input registers space: single read/write\n            word = randint(0, 0xffff)\n            self.server.data_bank.set_input_registers(addr, [word])\n            self.assertEqual(self.client.read_input_registers(addr), [word])\n            # input registers space: multiple read/write at max size\n            words_l = [randint(0, 0xffff) for _ in range(MAX_READABLE_REGS)]\n            self.server.data_bank.set_input_registers(addr, words_l)\n            self.assertEqual(self.client.read_input_registers(addr, len(words_l)), words_l)\n            # input registers space: multiple read/write over sized\n            words_l.append(randint(0, 0xffff))\n            self.server.data_bank.set_input_registers(addr, words_l)\n            self.assertRaises(ValueError, self.client.read_input_registers, addr, len(words_l))\n        # input registers space: read/write over limit\n        self.assertRaises(ValueError, self.client.read_input_registers, 0xfff0, 17)\n\n    def test_server_strength(self):\n        \"\"\"Test server responses to abnormal events.\"\"\"\n        # unsupported function codes must return except EXP_ILLEGAL_FUNCTION\n        for func_code in range(0x80):\n            if func_code not in SUPPORTED_FUNCTION_CODES:\n                # test with a min PDU length of 2 bytes (avoid short frame error)\n                self.assertEqual(self.client.custom_request(bytes([func_code, 0x00])), None)\n                self.assertEqual(self.client.last_error, MB_EXCEPT_ERR)\n                self.assertEqual(self.client.last_except, EXP_ILLEGAL_FUNCTION)\n        # check a regular request status: no error, no except\n        self.assertEqual(self.client.read_coils(0), [False])\n        self.assertEqual(self.client.last_error, MB_NO_ERR)\n        self.assertEqual(self.client.last_except, EXP_NONE)\n\n    def test_server_read_identification(self):\n        \"\"\"Test server device indentification function.\"\"\"\n        # forge a basic read identification on unconfigured server (return a data address except)\n        self.assertEqual(self.client.custom_request(b'\\x2b\\x0e\\x01\\x00'), None)\n        self.assertEqual(self.client.last_error, MB_EXCEPT_ERR)\n        self.assertEqual(self.client.last_except, EXP_DATA_ADDRESS)\n        # configure server\n        self.server.device_id = DeviceIdentification()\n        self.server.device_id.vendor_name = b'me'\n        self.server.device_id[0x80] = b'\\xc0\\xde'\n        # forge a basic read identification on a configured server (return a valid pdu)\n        self.assertNotEqual(self.client.custom_request(b'\\x2b\\x0e\\x01\\x00'), None)\n        # forge a read identifaction request with a bad read device id code (return except 3)\n        self.assertEqual(self.client.custom_request(b'\\x2b\\x0e\\x05\\x00'), None)\n        self.assertEqual(self.client.last_error, MB_EXCEPT_ERR)\n        self.assertEqual(self.client.last_except, EXP_DATA_VALUE)\n        # read VendorName str object(id #0) with individual access (read device id = 4)\n        ret_pdu = self.client.custom_request(b'\\x2b\\x0e\\x04\\x00')\n        self.assertEqual(ret_pdu, b'\\x2b\\x0e\\x04\\x83\\x00\\x00\\x01\\x00\\x02me')\n        # read private int object(id #0x80) with individual access (read device id = 4)\n        ret_pdu = self.client.custom_request(b'\\x2b\\x0e\\x04\\x80')\n        self.assertEqual(ret_pdu, b'\\x2b\\x0e\\x04\\x83\\x00\\x00\\x01\\x80\\x02\\xc0\\xde')\n        # restore default configuration\n        self.server.device_id = None\n\n    def test_client_read_identification(self):\n        \"\"\"Test client device indentification function.\"\"\"\n        # configure server\n        vendor_name = ''.join(choice(ascii_letters) for _ in range(16)).encode()\n        product_code = ''.join(choice(ascii_letters) for _ in range(32)).encode()\n        maj_min_rev = b'v2.0'\n        vendor_url = b'https://github.com/sourceperl/pyModbusTCP'\n        self.server.device_id = DeviceIdentification(vendor_name=vendor_name, product_code=product_code,\n                                                     major_minor_revision=maj_min_rev, vendor_url=vendor_url)\n        # read_device_identification: read basic device identification (stream access)\n        dev_id_resp = self.client.read_device_identification()\n        if not dev_id_resp:\n            self.fail('ModbusClient.read_device_identification() method failed unexpectedly')\n        else:\n            # return DeviceIdentificationResponse on success\n            self.assertEqual(isinstance(dev_id_resp, DeviceIdentificationResponse), True)\n            # check read data\n            self.assertEqual(len(dev_id_resp.objects_by_id), 3)\n            self.assertEqual(dev_id_resp.vendor_name, vendor_name)\n            self.assertEqual(dev_id_resp.objects_by_id.get(0), vendor_name)\n            self.assertEqual(dev_id_resp.product_code, product_code)\n            self.assertEqual(dev_id_resp.objects_by_id.get(1), product_code)\n            self.assertEqual(dev_id_resp.major_minor_revision, maj_min_rev)\n            self.assertEqual(dev_id_resp.objects_by_id.get(2), maj_min_rev)\n            self.assertEqual(dev_id_resp.vendor_url, None)\n            self.assertEqual(dev_id_resp.product_name, None)\n            self.assertEqual(dev_id_resp.model_name, None)\n            self.assertEqual(dev_id_resp.user_application_name, None)\n        # read_device_identification: read one specific identification object (individual access)\n        dev_id_resp = self.client.read_device_identification(read_code=4, object_id=3)\n        if not dev_id_resp:\n            self.fail('ModbusClient.read_device_identification() method failed unexpectedly')\n        else:\n            # return DeviceIdentificationResponse on success\n            self.assertEqual(isinstance(dev_id_resp, DeviceIdentificationResponse), True)\n            # check read data\n            self.assertEqual(len(dev_id_resp.objects_by_id), 1)\n            self.assertEqual(dev_id_resp.vendor_url, vendor_url)\n            self.assertEqual(dev_id_resp.objects_by_id.get(3), vendor_url)\n        # restore default configuration\n        self.server.device_id = None\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "tests/test_server.py",
    "content": "\"\"\" Test of pyModbusTCP.ModbusServer \"\"\"\n\nimport unittest\nfrom pyModbusTCP.server import ModbusServer, DeviceIdentification\n\n\nclass TestModbusServer(unittest.TestCase):\n    \"\"\" ModbusServer tests class. \"\"\"\n\n    def test_device_identification(self):\n        \"\"\"Some tests around modbus device identification.\"\"\"\n        # should raise exception\n        self.assertRaises(TypeError, ModbusServer, device_id=object())\n        # shouldn't raise exception\n        try:\n            ModbusServer(device_id=DeviceIdentification())\n        except Exception as e:\n            self.fail('ModbusServer raised exception \"%r\" unexpectedly' % e)\n        # init a DeviceIdentification class for test it\n        device_id = DeviceIdentification()\n        # should raise exception\n        with self.assertRaises(TypeError):\n            device_id['obj_name'] = 'anything'\n        with self.assertRaises(TypeError):\n            device_id[0] = 42\n        # shouldn't raise exception\n        try:\n            device_id.vendor_name = b'me'\n            device_id.user_application_name = b'unittest'\n            device_id[0x80] = b'feed'\n        except Exception as e:\n            self.fail('DeviceIdentification raised exception \"%r\" unexpectedly' % e)\n        # check access by shortcut name (str) or object id (int) return same value\n        self.assertEqual(device_id.vendor_name, device_id[0x00])\n        self.assertEqual(device_id.user_application_name, device_id[0x06])\n        # test __repr__\n        device_id = DeviceIdentification(\n            product_name=b'server', objects_id={42: b'this'})\n        self.assertEqual(repr(device_id), \"DeviceIdentification(product_name=b'server', objects_id={42: b'this'})\")\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "\"\"\" Test of pyModbusTCP.utils \"\"\"\n\nimport unittest\nimport math\nfrom pyModbusTCP.utils import \\\n    get_bits_from_int, int2bits, decode_ieee, encode_ieee, \\\n    word_list_to_long, words2longs, long_list_to_word, longs2words, \\\n    get_2comp, twos_c, get_list_2comp, twos_c_l\n\n\nclass TestUtils(unittest.TestCase):\n    \"\"\" pyModbusTCP.utils function test class. \"\"\"\n\n    def test_get_bits_from_int(self):\n        \"\"\"Test function get_bits_from_int and it's short alias int2bits.\"\"\"\n        # default bits list size is 16\n        self.assertEqual(len(get_bits_from_int(0)), 16)\n        # for 8 size (positional arg)\n        self.assertEqual(len(get_bits_from_int(0, 8)), 8)\n        # for 32 size (named arg)\n        self.assertEqual(len(get_bits_from_int(0, val_size=32)), 32)\n        # test binary decode\n        self.assertEqual(int2bits(0x0000), [False]*16)\n        self.assertEqual(int2bits(0xffff), [True]*16)\n        self.assertEqual(int2bits(0xf007), [True]*3 + [False]*9 + [True]*4)\n        self.assertEqual(int2bits(6, 4), [False, True, True, False])\n\n    def test_ieee(self):\n        \"\"\"Test IEEE functions: decode_ieee and encode_ieee.\"\"\"\n        # test IEEE NaN\n        self.assertTrue(math.isnan(decode_ieee(0x7fc00000)))\n        self.assertEqual(encode_ieee(float('nan')), 0x7fc00000)\n        # test +/- infinity\n        self.assertTrue(math.isinf(decode_ieee(0xff800000)))\n        self.assertTrue(math.isinf(decode_ieee(0x7f800000)))\n        # test big and small values\n        avogad = 6.022140857e+23\n        avo_32 = 0x66ff0c2f\n        avo_64 = 0x44dfe185d2f54b67\n        planck = 6.62606957e-34\n        pla_32 = 0x085c305e\n        pla_64 = 0x390b860bb596a559\n        # IEEE single or double precision format -> float\n        self.assertAlmostEqual(decode_ieee(avo_32), avogad, delta=avogad*1e-7)\n        self.assertAlmostEqual(decode_ieee(avo_64, double=True), avogad)\n        self.assertAlmostEqual(decode_ieee(pla_32), planck)\n        self.assertAlmostEqual(decode_ieee(pla_64, double=True), planck)\n        # float -> IEEE single or double precision format\n        self.assertAlmostEqual(encode_ieee(avogad), avo_32)\n        self.assertAlmostEqual(encode_ieee(avogad, double=True), avo_64)\n        self.assertAlmostEqual(encode_ieee(planck), pla_32)\n        self.assertAlmostEqual(encode_ieee(planck, double=True), pla_64)\n\n    def test_word_list_to_long(self):\n        \"\"\"Test function word_list_to_long and it 's short alias words2longs.\"\"\"\n        # empty list, return empty list\n        self.assertEqual(word_list_to_long([]), [])\n        # if len of list is odd ignore last value\n        self.assertEqual(word_list_to_long([0x1, 0x2, 0x3]), [0x10002])\n        # test convert with big and little endian\n        l1 = [0xdead, 0xbeef]\n        l2 = [0xfeed, 0xface, 0xcafe, 0xbeef]\n        big = dict(big_endian=True)\n        nobig = dict(big_endian=False)\n        big64 = dict(big_endian=True, long_long=True)\n        nobig64 = dict(big_endian=False, long_long=True)\n        self.assertEqual(words2longs(l1, **big), [0xdeadbeef])\n        self.assertEqual(words2longs(l2, **big), [0xfeedface, 0xcafebeef])\n        self.assertEqual(words2longs(l1, **nobig), [0xbeefdead])\n        self.assertEqual(words2longs(l2, **nobig), [0xfacefeed, 0xbeefcafe])\n        self.assertEqual(words2longs(l1*2, **big64), [0xdeadbeefdeadbeef])\n        self.assertEqual(words2longs(l2*2, **big64), [0xfeedfacecafebeef]*2)\n        self.assertEqual(words2longs(l1*2, **nobig64), [0xbeefdeadbeefdead])\n        self.assertEqual(words2longs(l2*2, **nobig64), [0xbeefcafefacefeed]*2)\n\n    def test_long_list_to_word(self):\n        \"\"\"Test function long_list_to_word and short alias longs2words.\"\"\"\n        # empty list, return empty list\n        self.assertEqual(long_list_to_word([]), [])\n        # test convert with big and little endian\n        l1 = [0xdeadbeef]\n        l1_big = [0xdead, 0xbeef]\n        l1_nobig = [0xbeef, 0xdead]\n        l1_big64 = [0x0000, 0x0000, 0xdead, 0xbeef]\n        l1_nobig64 = [0xbeef, 0xdead, 0x0000, 0x0000]\n        l2 = [0xfeedface, 0xcafebeef]\n        l2_big = [0xfeed, 0xface, 0xcafe, 0xbeef]\n        l2_nobig = [0xface, 0xfeed, 0xbeef, 0xcafe]\n        l3 = [0xfeedfacecafebeef]\n        l3_big64 = [0xfeed, 0xface, 0xcafe, 0xbeef]\n        l3_nobig64 = [0xbeef, 0xcafe, 0xface, 0xfeed]\n        big = dict(big_endian=True)\n        nobig = dict(big_endian=False)\n        big64 = dict(big_endian=True, long_long=True)\n        nobig64 = dict(big_endian=False, long_long=True)\n        self.assertEqual(longs2words(l1, **big), l1_big)\n        self.assertEqual(longs2words(l2, **big), l2_big)\n        self.assertEqual(longs2words(l1, **nobig), l1_nobig)\n        self.assertEqual(longs2words(l2, **nobig), l2_nobig)\n        self.assertEqual(longs2words(l1*2, **big64), l1_big64*2)\n        self.assertEqual(longs2words(l3*2, **big64), l3_big64*2)\n        self.assertEqual(longs2words(l1*4, **nobig64), l1_nobig64*4)\n        self.assertEqual(longs2words(l3*4, **nobig64), l3_nobig64*4)\n\n    def test_get_2comp(self):\n        \"\"\"Test function get_2comp and it's short alias twos_c.\"\"\"\n        # check if ValueError exception is raised\n        self.assertRaises(ValueError, get_2comp, 0x10000)\n        self.assertRaises(ValueError, get_2comp, -0x8001)\n        self.assertRaises(ValueError, twos_c, 0x100000000, val_size=32)\n        self.assertRaises(ValueError, twos_c, -0x80000001, val_size=32)\n        # 2's complement of 16bits values (default)\n        self.assertEqual(get_2comp(0x0001), 0x0001)\n        self.assertEqual(get_2comp(0x8000), -0x8000)\n        self.assertEqual(get_2comp(-0x8000), 0x8000)\n        self.assertEqual(get_2comp(0xffff), -0x0001)\n        self.assertEqual(get_2comp(-0x0001), 0xffff)\n        self.assertEqual(get_2comp(-0x00fa), 0xff06)\n        self.assertEqual(get_2comp(0xff06), -0x00fa)\n        # 2's complement of 32bits values\n        self.assertEqual(twos_c(0xfffffff, val_size=32), 0xfffffff)\n        self.assertEqual(twos_c(-1, val_size=32), 0xffffffff)\n        self.assertEqual(twos_c(0xffffffff, val_size=32), -1)\n        self.assertEqual(twos_c(125, val_size=32), 0x0000007d)\n        self.assertEqual(twos_c(0x0000007d, val_size=32), 125)\n        self.assertEqual(twos_c(-250, val_size=32), 0xffffff06)\n        self.assertEqual(twos_c(0xffffff06, val_size=32), -250)\n        self.assertEqual(twos_c(0xfffea2a5, val_size=32), -89435)\n        self.assertEqual(twos_c(-89435, val_size=32), 0xfffea2a5)\n\n    def test_get_list_2comp(self):\n        \"\"\"Test get_list_2comp and it's short alias twos_c_l.\"\"\"\n        self.assertEqual(get_list_2comp([0x8000], 16), [-32768])\n        in_l = [0x8000, 0xffff, 0x0042]\n        out_l = [-0x8000, -0x0001, 0x42]\n        self.assertEqual(twos_c_l(in_l, val_size=16), out_l)\n        in_l = [0x8000, 0xffffffff, 0xfffea2a5]\n        out_l = [0x8000, -0x0001, -89435]\n        self.assertEqual(twos_c_l(in_l, val_size=32), out_l)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  }
]