From 9667e8a689a778e665f565a15ff58ac20c969895 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Sun, 8 Jan 2023 11:26:34 +0100 Subject: [PATCH 01/21] split setup docs into new installation page, add mip usage example #44, update installation instructions without network #54 --- docs/INSTALLATION.md | 146 +++++++++++++++++++++++++++++++++++++++++++ docs/SETUP.md | 83 ------------------------ docs/index.rst | 1 + requirements.txt | 1 + 4 files changed, 148 insertions(+), 83 deletions(-) create mode 100644 docs/INSTALLATION.md diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 0000000..30b0f31 --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,146 @@ +# Installation + +Install the library on MicroPython boards + +--------------- + +## Install package via network + +This section describes the installation of the library on network capable +boards. + +This is an example on how to connect to a wireless network. + +```{note} +Not all MicroPython board have a network support, like the classic Raspberry +Pi Pico or the pyboard. Please check the port specific documentation. +``` + +```python +import network +station = network.WLAN(network.STA_IF) +station.connect('SSID', 'PASSWORD') +station.isconnected() +``` + +### Install with mip + +`mip` has been added in MicroPython 1.19.1 and later. For earlier MicroPython +versions check the [upip section below](#install-with-upip) + +> `mip` ("mip installs packages") is similar in concept to Python's `pip` +> tool, however it does not use the PyPI index, rather it uses micropython-lib +> as its index by default. + +As this library is pushed to [TestPyPi][ref-micropython-modbus-test-pypi] and +[PyPi][ref-micropython-modbus-pypi] the additional index has to be specified. + +```python +import mip +mip.install('micropython-modbus', index='https://pypi.org/pypi') +``` + +In order to install the latest release candidate version, set the index to +`'https://test.pypi.org/pypi'` + +### Install with upip + +```python +import upip +upip.install('micropython-modbus') +``` + +In order to install the latest release candidate version, use the following +commands + +```python +import upip +# overwrite index_urls to only take artifacts from test.pypi.org +upip.index_urls = ['https://test.pypi.org/pypi'] +upip.install('micropython-modbus') +``` + +## Install package without network + +### mpremote + +As of January 2022 the [`mpremote tool`][ref-mpremote] is available via `pip`. + +> It can be used from a host PC to install packages to a locally connected device + +As described in the `Install required tools` section of [SETUP](SETUP.md), the +tool will be installed with the `requirements.txt` file. Please follow the +[mpremote documentation][ref-mpremote-doc] to connect to a MicroPython device. + +To install the latest officially released library version use the following +command + +```bash +mpremote mip install --index https://pypi.org/pypi micropython-modbus +``` + +In order to install the latest release candidate version, use the following +command + +```bash +mpremote mip install --index https://test.pypi.org/pypi micropython-modbus +``` + +### Manually + +Copy all files of the [umodbus module][ref-umodbus-module] to the MicroPython +board using [Remote MicroPython shell][ref-remote-upy-shell] + +Open the remote shell with the following command. Additionally use `-b 115200` +in case no CP210x is used but a CH34x. + +```bash +rshell -p /dev/tty.SLAB_USBtoUART --editor nano +``` + +Perform the following command to copy all files and folders to the device + +```bash +mkdir /pyboard/lib +mkdir /pyboard/lib/umodbus + +cp umodbus/* /pyboard/lib/umodbus +``` + +## Additional MicroPython packages for examples + +To use this package with the provided [`boot.py`][ref-package-boot-file] and +[`main.py`][ref-package-boot-file] file to create a TCP-RTU bridge, additional +modules are required, which are not part of this repo/package. + +```bash +rshell -p /dev/tty.SLAB_USBtoUART --editor nano +``` + +Install the additional package `micropython-modbus` as described in the +previous section on the MicroPython device or download the +[brainelectronics MicroPython Helpers repo][ref-github-be-mircopython-modules] +and copy it to the device. + +Perform the following command to copy all files and folders to the device + +```bash +mkdir /pyboard/lib/be_helpers + +cp be_helpers/* /pyboard/lib/be_helpers +``` + +Additionally check the latest instructions of the +[brainelectronics MicroPython modules][ref-github-be-mircopython-modules] +README for further instructions. + + +[ref-micropython-modbus-test-pypi]: https://test.pypi.org/project/micropython-modbus/ +[ref-micropython-modbus-pypi]: https://pypi.org/project/micropython-modbus/ +[ref-mpremote]: https://docs.micropython.org/en/v1.19.1/reference/mpremote.html#mpremote +[ref-mpremote-doc]: https://docs.micropython.org/en/v1.19.1/reference/mpremote.html +[ref-remote-upy-shell]: https://github.com/dhylands/rshell +[ref-umodbus-module]: https://github.com/brainelectronics/micropython-modbus/tree/develop/umodbus +[ref-package-boot-file]: https://github.com/brainelectronics/micropython-modbus/blob/c45d6cc334b4adf0e0ffd9152c8f08724e1902d9/boot.py +[ref-package-main-file]: https://github.com/brainelectronics/micropython-modbus/blob/c45d6cc334b4adf0e0ffd9152c8f08724e1902d9/main.py +[ref-github-be-mircopython-modules]: https://github.com/brainelectronics/micropython-modules diff --git a/docs/SETUP.md b/docs/SETUP.md index b3b08f7..fb29aa6 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -63,89 +63,6 @@ esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART --baud 921600 write_flash -z 0x1000 esp32spiram-20220117-v1.18.bin ``` -### Install package with pip - -Connect to a network - -```python -import network -station = network.WLAN(network.STA_IF) -station.connect('SSID', 'PASSWORD') -station.isconnected() -``` - -and install this lib with all its dependencies on the MicroPython device like -this - -```python -import upip -upip.install('micropython-modbus') -``` - -### Without network connection - -Copy all files of the [umodbus module][ref-umodbus-module] to the MicroPython -board using [Remote MicroPython shell][ref-remote-upy-shell] - -Open the remote shell with the following command. Additionally use `-b 115200` -in case no CP210x is used but a CH34x. - -```bash -rshell -p /dev/tty.SLAB_USBtoUART --editor nano -``` - -Perform the following command to copy all files and folders to the device - -```bash -mkdir /pyboard/lib -mkdir /pyboard/lib/umodbus - -cp umodbus/* /pyboard/lib/umodbus -``` - -### Additional MicroPython packages for examples - -To use this package with the provided [`boot.py`][ref-package-boot-file] and -[`main.py`][ref-package-boot-file] file, additional modules are required, -which are not part of this repo/package. - -```bash -rshell -p /dev/tty.SLAB_USBtoUART --editor nano -``` - -#### Install additional package with pip - -Again connect to a network and install the additional package on the -MicroPython device with - -```python -import upip -upip.install('micropython-modbus') -``` - -#### Without network connection - -To install the additional modules on the device, download the -[brainelectronics MicroPython Helpers repo][ref-github-be-mircopython-modules] -and copy them to the device. - -Perform the following command to copy all files and folders to the device - -```bash -mkdir /pyboard/lib/be_helpers - -cp be_helpers/* /pyboard/lib/be_helpers -``` - -Additionally check the latest instructions of the -[brainelectronics MicroPython modules][ref-github-be-mircopython-modules] -README for further instructions. - [ref-github-be-python-modules]: https://github.com/brainelectronics/python-modules [ref-upy-firmware-download]: https://micropython.org/download/ -[ref-remote-upy-shell]: https://github.com/dhylands/rshell -[ref-umodbus-module]: https://github.com/brainelectronics/micropython-modbus/tree/develop/umodbus -[ref-package-boot-file]: https://github.com/brainelectronics/micropython-modbus/blob/c45d6cc334b4adf0e0ffd9152c8f08724e1902d9/boot.py -[ref-package-main-file]: https://github.com/brainelectronics/micropython-modbus/blob/c45d6cc334b4adf0e0ffd9152c8f08724e1902d9/main.py -[ref-github-be-mircopython-modules]: https://github.com/brainelectronics/micropython-modules diff --git a/docs/index.rst b/docs/index.rst index d45fdcc..1577854 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ Contents readme_link SETUP + INSTALLATION USAGE CONTRIBUTING umodbus diff --git a/requirements.txt b/requirements.txt index 55e0a02..f9dfa09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ # adafruit-ampy>=1.1.0,<2.0.0 esptool rshell>=0.0.30,<1.0.0 +mpremote>=0.4.0,<1 \ No newline at end of file From 2e06bacbfc92cbb915653e649710b41f1ec13981 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:12:45 +0100 Subject: [PATCH 02/21] update root README examples to latest state --- README.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5496d17..29114f3 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ Forked from [Exo Sense Py][ref-sferalabs-exo-sense], based on [PyCom Modbus][ref-pycom-modbus] and extended with other functionalities to become a powerfull MicroPython library -The latest documentation is available at -[MicroPython Modbus ReadTheDocs][ref-rtd-micropython-modbus] +:books: The latest documentation is available at +[MicroPython Modbus ReadTheDocs][ref-rtd-micropython-modbus] :books: - [Quickstart](#quickstart) - - [Install package on board with pip](#install-package-on-board-with-pip) + - [Install package on board with mip or upip](#install-package-on-board-with-mip-or-upip) - [Request coil status](#request-coil-status) - [TCP](#tcp) - [RTU](#rtu) @@ -41,17 +41,19 @@ This is a quickstart to install the `micropython-modbus` library on a MicroPython board. A more detailed guide of the development environment can be found in -[SETUP](SETUP.md). Further details about the usage can be found in -[USAGE](USAGE.md) +[SETUP](SETUP.md), further details about the usage can be found in +[USAGE](USAGE.md), descriptions for testing can be found in +[TESTING](TESTING.md) and several examples in [EXAMPLES](EXAMPLES.md) ```bash python3 -m venv .venv source .venv/bin/activate pip install 'rshell>=0.0.30,<1.0.0' +pip install 'mpremote>=0.4.0,<1' ``` -### Install package on board with pip +### Install package on board with mip or upip ```bash rshell -p /dev/tty.SLAB_USBtoUART --editor nano @@ -81,8 +83,7 @@ After a successful installation of the package and reboot of the system as described in the [installation section](#install-package-on-board-with-pip) the following commands can be used to request a coil state of a target/client device. Further usage examples can be found in the -[examples folder][ref-examples-folder] and in the -[Micropython section of USAGE](USAGE.md) +[examples folder][ref-examples-folder] and in the [USAGE chapter](USAGE.md) #### TCP @@ -110,15 +111,16 @@ print('Status of coil {}: {}'.format(coil_status, coil_address)) #### RTU ```python -from umodbus.serial import ModbusRTU +from umodbus.serial import Serial as ModbusRTUMaster -host = ModbusRTU( - addr=1, # address of this Master/Host on bus +host = ModbusRTUMaster( + pins=(25, 26), # given as tuple (TX, RX), check MicroPython port specific syntax # baudrate=9600, # optional, default 9600 # data_bits=8, # optional, default 8 # stop_bits=1, # optional, default 1 # parity=None, # optional, default None - pins=(25, 26) # (TX, RX) + # ctrl_pin=12, # optional, control DE/RE + # uart_id=1 # optional, see port specific documentation ) # address of the target/client/slave device on the bus @@ -130,7 +132,7 @@ coil_status = host.read_coils( slave_addr=slave_addr, starting_addr=coil_address, coil_qty=coil_qty) -print('Status of coil {}: {}'.format(coil_status, coil_address)) +print('Status of coil {}: {}'.format(coil_address, coil_status)) ``` ### Install additional MicroPython packages @@ -146,12 +148,12 @@ upip.install('micropython-brainelectronics-helpers') ``` Check also the README of the -[brainelectronics MicroPython modules][ref-github-be-mircopython-modules] -and the [SETUP guide](SETUP.md) +[brainelectronics MicroPython modules][ref-github-be-mircopython-modules], the +[INSTALLATION](INSTALLATION.md) and the [SETUP](SETUP.md) guides. ## Usage -See [USAGE](USAGE.md) +See [USAGE](USAGE.md) and [DOCUMENTATION](DOCUMENTATION.md) ## Supported Modbus functions From f3d80858fcb77c118c7fbe9e54a7b5df1d20c426 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:13:24 +0100 Subject: [PATCH 03/21] add mip usage to root README, relats to #44 --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 29114f3..c77931f 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,23 @@ rshell -p /dev/tty.SLAB_USBtoUART --editor nano Inside the [rshell][ref-remote-upy-shell] open a REPL and execute these commands inside the REPL +```python +import machine +import network +import time +import mip +station = network.WLAN(network.STA_IF) +station.active(True) +station.connect('SSID', 'PASSWORD') +time.sleep(1) +print('Device connected to network: {}'.format(station.isconnected())) +mip.install('micropython-modbus', index='https://pypi.org/pypi') +print('Installation completed') +machine.soft_reset() +``` + +For MicroPython versions below 1.19.1 use the `upip` package instead of `mip` + ```python import machine import network @@ -143,6 +160,11 @@ which are not part of this repo/package. To install these modules on the device, connect to a network and install them via `upip` as follows ```python +# with MicroPython version 1.19.1 or newer +import mip +mip.install('micropython-brainelectronics-helpers', index='https://pypi.org/pypi') + +# before MicroPython version 1.19.1 import upip upip.install('micropython-brainelectronics-helpers') ``` From 3ea5d95fa182951fd4772326951e69fc389f07f6 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:14:37 +0100 Subject: [PATCH 04/21] add mip usage commands to INSTALLATION, resolves #44 --- docs/INSTALLATION.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 30b0f31..f55565d 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -9,17 +9,18 @@ Install the library on MicroPython boards This section describes the installation of the library on network capable boards. -This is an example on how to connect to a wireless network. - ```{note} Not all MicroPython board have a network support, like the classic Raspberry Pi Pico or the pyboard. Please check the port specific documentation. ``` +This is an example on how to connect to a wireless network. + ```python import network station = network.WLAN(network.STA_IF) station.connect('SSID', 'PASSWORD') +# wait some time to establish the connection station.isconnected() ``` @@ -32,8 +33,10 @@ versions check the [upip section below](#install-with-upip) > tool, however it does not use the PyPI index, rather it uses micropython-lib > as its index by default. -As this library is pushed to [TestPyPi][ref-micropython-modbus-test-pypi] and -[PyPi][ref-micropython-modbus-pypi] the additional index has to be specified. +As this library is pushed to [PyPi][ref-micropython-modbus-pypi] and +[TestPyPi][ref-micropython-modbus-test-pypi], but not the default +[micropython-lib index](https://micropython.org/pi/v2) the additional index +has to be specified explicitly. ```python import mip @@ -43,6 +46,11 @@ mip.install('micropython-modbus', index='https://pypi.org/pypi') In order to install the latest release candidate version, set the index to `'https://test.pypi.org/pypi'` +```python +import mip +mip.install('micropython-modbus', index='https://test.pypi.org/pypi') +``` + ### Install with upip ```python @@ -64,26 +72,27 @@ upip.install('micropython-modbus') ### mpremote -As of January 2022 the [`mpremote tool`][ref-mpremote] is available via `pip`. +As of January 2022 the [`mpremote`][ref-mpremote] tool is available via `pip`. > It can be used from a host PC to install packages to a locally connected device As described in the `Install required tools` section of [SETUP](SETUP.md), the -tool will be installed with the `requirements.txt` file. Please follow the -[mpremote documentation][ref-mpremote-doc] to connect to a MicroPython device. +tool will be installed with the provided `requirements.txt` file. Please +follow the [mpremote documentation][ref-mpremote-doc] to connect to a +MicroPython device. To install the latest officially released library version use the following command ```bash -mpremote mip install --index https://pypi.org/pypi micropython-modbus +mpremote connect /dev/tty.SLAB_USBtoUART mip install --index https://pypi.org/pypi micropython-modbus ``` In order to install the latest release candidate version, use the following command ```bash -mpremote mip install --index https://test.pypi.org/pypi micropython-modbus +mpremote connect /dev/tty.SLAB_USBtoUART mip install --index https://test.pypi.org/pypi micropython-modbus ``` ### Manually From 2918bab7422a3d1776233d828adde99e897183f1 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:17:11 +0100 Subject: [PATCH 05/21] remove warning for #35 from USAGE, remove docker and documentation sections --- docs/USAGE.md | 434 ++++++++++++-------------------------------------- 1 file changed, 98 insertions(+), 336 deletions(-) mode change 100644 => 100755 docs/USAGE.md diff --git a/docs/USAGE.md b/docs/USAGE.md old mode 100644 new mode 100755 index b521511..8fddc04 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1,39 +1,17 @@ # Usage -Overview to use and test this `micropython-modbus` library +Overview to use this `micropython-modbus` library --------------- ```{note} The onwards described steps assume a successful setup as described in the [setup chapter](SETUP.md) -``` - -## MicroPython - -This section describes the necessary steps on the MicroPython device to get -ready to test and run the examples. -```bash -# Linux/Mac -source .venv/bin/activate - -rshell -p /dev/tty.SLAB_USBtoUART --editor nano +Further examples are available in the [examples chapter](EXAMPLES.md) ``` -On a Windows based system activate the virtual environment and enter the -remote shell like this - -``` -.venv\Scripts\activate.bat - -rshell -p COM9 -``` - -The onwards mentioned commands shall be performed inside the previously entered -remote shell. - -### Register configuration +## Register configuration The available registers can be defined by a JSON file, placed inside the `/pyboard/registers` folder or any other location on the board and loaded in @@ -43,7 +21,7 @@ As an [example the registers][ref-registers-MyEVSE] of a [brainelectronics MyEVSE][ref-myevse-be], [MyEVSE on Tindie][ref-myevse-tindie] board and others are provided with this repo. -#### Structure +### Structure If only an interaction with a single register is intended no dictionary needs to be defined of course. The onwards explanations assume a bigger setup of @@ -121,10 +99,10 @@ would look like In order to act as client/slave device the same structure can be used. If no `val` element is found in the structure the default values are -| Type | Function Code | Default value | -| ---- | ------------- | ------------- | +| Type | Function Code | Default value | +| ----- | ------------- | ------------- | | COILS | 0x01 | False (0x0) | -| ISTS | 0x02 | False (0x0) | +| ISTS | 0x02 | False (0x0) | | HREGS | 0x03 | 0 | | IREGS | 0x04 | 0 | @@ -142,28 +120,18 @@ The value of multiple registers can be set like this } ``` -```{eval-rst} -.. warning:: - As of version `2.0.0 `_ - of this package it is not possible to request only the holding register - `94`, which would hold `38` in the above example. - - This is a bug (non implemented feature) of the client/slave implementation. - For further details check `issue #35 `_ -``` - -#### Detailed key explanation +### Detailed key explanation The onwards described key explanations are valid for COIL, HREG, IST and IREG -##### Register +#### Register The key `register` defines the register to request or manipulate. According to the Modbus specification the register address has to be in the range of 0x0000 to 0xFFFF (65535) to be valid. -##### Length +#### Length The key `len` defines the amout of registers to be requested starting from/with the defined `register` address. @@ -171,12 +139,12 @@ the defined `register` address. According to the Modbus specification the length or amount depends on the type of the register as summarized in the table below. -| Type | Function Code | Valid range | -| ---- | ------------- | ----------- | -| COILS | 0x01 | 0x1 to 0x7D0 (2000) | -| ISTS | 0x02 | 0x1 to 0x7D0 (2000) | -| HREGS | 0x03 | 0x1 to 0x7D (125) | -| IREGS | 0x04 | 0x1 to 0x7D (125) | +| Type | Function Code | Valid range | +| ----- | ------------- | ----------- | +| COILS | 0x01 | 0x1 to 0x7D0 (2000) | +| ISTS | 0x02 | 0x1 to 0x7D0 (2000) | +| HREGS | 0x03 | 0x1 to 0x7D (125) | +| IREGS | 0x04 | 0x1 to 0x7D (125) | In order to read 5 coils starting at 124 use the following dictionary aka config @@ -195,7 +163,7 @@ config The output will be a list of 5 elements like `[True, False, False, True, True]` depending on the actual device coil states of course. -##### Value +#### Value The key `val` defines the value of registers to be set on the target/client device. @@ -208,27 +176,27 @@ of the register as summarized in the table below. | COILS | 0x05 | 0x0000 or 0xFF00 | This package maps `0` or `False` to `0x0000` and `1` or `True` to `0xFF00` | | HREGS | 0x06 | 0x0000 to 0xFFFF (65535) | | -##### Optional description +#### Optional description The optional key `description` can be used to provide an additional description of the register. This might be helpful if the register name is not meaninful enough or for any other reason of course. -##### Optional range +#### Optional range The optional key `range` can be used to indicate the possible value range of this specific target. For example a holding register for setting a PWM output might only support a range of 0 to 100. This might be especially helpful with the optional [`unit`](#optional-unit) key. -###### Optional unit +##### Optional unit The optional key `unit` can be used to provide further details about the unit of the register. In case of the PWM output register example of the [optional range key](#optional-range) the recommended value for this key could be `percent`. -###### Optional callbacks +##### Optional callbacks The optional keys `on_set_cb` and `on_get_cb` can be used to register a callback function on client side which is executed **after** a new value has @@ -286,6 +254,14 @@ print('Setting up registers ...') client.setup_registers(registers=register_definitions) # alternatively use dummy default values (True for bool regs, 999 otherwise) # client.setup_registers(registers=register_definitions, use_default_vals=True) + +# callbacks can also be defined after a register setup has been performed +client.add_coil( + address=123, + value=bool(1), + on_set_cb=my_coil_set_cb, + on_get_cb=my_coil_get_cb +) print('Register setup done') while True: @@ -314,7 +290,7 @@ functions can be used individually instead of setting up all registers with the - [`add_ist`](umodbus.modbus.Modbus.add_ist) - [`add_ireg`](umodbus.modbus.Modbus.add_ireg) -### Register usage +## Register usage This section describes the usage of the following implemented functions @@ -328,56 +304,17 @@ This section describes the usage of the following implemented functions - [0x10 `write_multiple_registers`](umodbus.common.CommonModbusFunctions.write_multiple_registers) which are available on Modbus RTU and Modbus TCP as shown in the -[examples](https://github.com/brainelectronics/micropython-modbus/tree/develop/examples) +[GitHub examples folder](https://github.com/brainelectronics/micropython-modbus/tree/develop/examples) and the [examples chapter](EXAMPLES.md) All described functions require a successful setup of a Host communicating to/with a Client device which is providing the data and accepting the new data. -#### TCP - -```python -from umodbus.tcp import TCP as ModbusTCPMaster - -slave_tcp_port = 502 # port to listen to -slave_addr = 10 # bus address of client -slave_ip = '192.168.178.69' # IP address, to be adjusted - -host = ModbusTCPMaster( - slave_ip=slave_ip, - slave_port=slave_tcp_port, - timeout=5) # optional, default 5 -``` - -#### RTU - -```python -from umodbus.serial import Serial as ModbusRTUMaster - -slave_addr = 10 # bus address of client - -# check MicroPython UART documentation -# https://docs.micropython.org/en/latest/library/machine.UART.html -# for Device/Port specific setup -# RP2 needs "rtu_pins = (Pin(4), Pin(5))" whereas ESP32 can use any pin -# the following example is for an ESP32 -rtu_pins = (25, 26) # (TX, RX) -host = ModbusRTUMaster( - baudrate=9600, # optional, default 9600 - pins=rtu_pins, # given as tuple (TX, RX) - # data_bits=8, # optional, default 8 - # stop_bits=1, # optional, default 1 - # parity=None, # optional, default None - # ctrl_pin=12, # optional, control DE/RE - # uart_id=1 # optional, see port specific documentation -) -``` - -#### Coils +### Coils Coils represent binary states, which can be get as and set to either `0` (off) or `1` (on). -##### Read +#### Read ```{note} The function code `0x01` is used to read from 1 to 2000 contiguous status of @@ -389,34 +326,24 @@ With the function a single coil status can be read. ```python -coil_address = 125 -coil_qty = 2 +coil_address = 125 # register to start reading +coil_qty = 2 # amount of registers to read + coil_status = host.read_coils( slave_addr=slave_addr, starting_addr=coil_address, coil_qty=coil_qty) -print('Status of COIL {}: {}'.format(coil_address, coil_status)) -# Status of COIL 125: [True, False] -``` -```{eval-rst} -.. warning:: - Please be aware of `bug #35 `_. - - It is not possible to read a specific position within a configured list of - multiple coils on a MicroPython Modbus TCP client device. Reading coil 126 - in the above example will throw an error. - - This bug affects only devices using this package. Other devices work as - expected and can be addressed as specified. +print('Status of COIL {}: {}'.format(coil_address, coil_status)) +# Status of COIL 125: [True, False] ``` -##### Write +#### Write Coils can be set with `False` or `0` to the `OFF` state and with `True` or `1` to the `ON` state. -###### Single +##### Single ```{note} The function code `0x05` is used to write a single output to either `ON` or @@ -428,28 +355,19 @@ With the function a single coil status can be set. ```python -coil_address = 123 -new_coil_val = 0 +coil_address = 123 # register to start writing +new_coil_val = 0 # new coil value + operation_status = host.write_single_coil( slave_addr=slave_addr, output_address=coil_address, output_value=new_coil_val) + print('Result of setting COIL {}: {}'.format(coil_address, operation_status)) # Result of setting COIL 123: True ``` -```{eval-rst} -.. warning:: - Please be aware of `bug #15 `_. - - It is not possible to write to a specific position within a configured - list of multiple coils on a MicroPython Modbus TCP client device. - - This bug affects only devices using this package. Other devices work as - expected and can be addressed as specified. -``` - -###### Multiple +##### Multiple ```{note} The function code `0x0F` is used to force each coil in a sequence of coils to @@ -461,34 +379,24 @@ With the function multiple coil states can be set at once. ```python -coil_address = 126 -new_coil_vals = [1, 1, 0] +coil_address = 126 # register to start writing +new_coil_vals = [1, 1, 0] # new coil values for 126, 127 and 128 + operation_status = self._host.write_multiple_coils( slave_addr=slave_addr, starting_address=coil_address, output_values=new_coil_vals) + print('Result of setting COIL {}: {}'.format(coil_address, operation_status)) # Result of setting COIL 126: True ``` -```{eval-rst} -.. warning:: - Please be aware of `bug #35 `_. - - It is not possible to write to a specific position within a configured - list of multiple coils on a MicroPython Modbus TCP client device. Setting - coil `127`, which is `1` in the above example will throw an error. - - This bug affects only devices using this package. Other devices work as - expected and can be addressed as specified. -``` - -#### Discrete inputs +### Discrete inputs Discrete inputs represent binary states, which can be get as either `0` (off) -or `1` (on). Unlike [coils](#coils), these cannot be set. +or `1` (on). Unlike [coils](USAGE.md#coils), discrete inputs cannot be set. -##### Read +#### Read ```{note} The function code `0x02` is used to read from 1 to 2000 contiguous status of @@ -500,23 +408,25 @@ With the function discrete inputs can be read. ```python -ist_address = 68 -input_qty = 2 +ist_address = 68 # register to start reading +input_qty = 2 # amount of registers to read + input_status = host.read_discrete_inputs( slave_addr=slave_addr, starting_addr=ist_address, input_qty=input_qty) + print('Status of IST {}: {}'.format(ist_address, input_status)) # Status of IST 68: [True, False] ``` -#### Holding registers +### Holding registers Holding registers can be get as and set to any value between `0` and `65535`. If supported by the client device, data can be marked as signed values to represent `-32768` through `32767`. -##### Read +#### Read ```{note} The function code `0x03` is used to read the contents of a contiguous block @@ -528,36 +438,25 @@ With the function a single holding register can be read. ```python -hreg_address = 94 -register_qty = 3 +hreg_address = 94 # register to start reading +register_qty = 3 # amount of registers to read + register_value = host.read_holding_registers( slave_addr=slave_addr, starting_addr=hreg_address, register_qty=register_qty, signed=False) + print('Status of HREG {}: {}'.format(hreg_address, register_value)) # Status of HREG 94: [29, 38, 0] ``` -```{eval-rst} -.. warning:: - Please be aware of `bug #35 `_. - - It is not possible to read a specific position within a configured list of - multiple holding registers on a MicroPython Modbus TCP client device. - Reading holding register `95`, holding the value `38` in the above example - will throw an error. - - This bug affects only devices using this package. Other devices work as - expected and can be addressed as specified. -``` - -##### Write +#### Write Holding registers can be set to `0` through `65535` or `-32768` through `32767` in case signed values are used. -###### Single +##### Single ```{note} The function code `0x06` is used to write a single holding register in a @@ -569,18 +468,20 @@ With the function a single holding register can be set. ```python -hreg_address = 93 -new_hreg_val = 44 +hreg_address = 93 # register to start writing +new_hreg_val = 44 # new holding register value + operation_status = host.write_single_register( slave_addr=slave_addr, register_address=hreg_address, register_value=new_hreg_val, signed=False) + print('Result of setting HREG {}: {}'.format(hreg_address, operation_status)) # Result of setting HREG 93: True ``` -###### Multiple +##### Multiple ```{note} The function code `0x10` is used to write a block of contiguous registers @@ -592,39 +493,27 @@ With the function holding register can be set at once. ```python -hreg_address = 94 -new_hreg_vals = [54, -12, 30001] +hreg_address = 94 # register to start writing +new_hreg_vals = [54, -12, 30001] # new holding register values for 94, 95, 96 + operation_status = self._host.write_multiple_registers( slave_addr=slave_addr, starting_address=hreg_address, register_values=new_hreg_vals, signed=True) + print('Result of setting HREG {}: {}'.format(hreg_address, operation_status)) # Result of setting HREG 94: True ``` -```{eval-rst} -.. warning:: - - Please be aware of `bug #35 `_. - - It is not possible to write to a specific position within a configured - list of multiple holding registers on a MicroPython Modbus TCP client - device. Setting holding register `95 + 96` to e.g. `[-12, 30001]` in the - above example will throw an error. - - This bug affects only devices using this package. Other devices work as - expected and can be addressed as specified. -``` - -#### Input registers +### Input registers Input registers can hold values between `0` and `65535`. If supported by the client device, data can be marked as signed values to represent `-32768` -through `32767`. Unlike [holding registers](#holding-registers), these cannot -be set. +through `32767`. Unlike [holding registers](USAGE.md#holding-registers), input +registers cannot be set. -##### Read +#### Read ```{note} The function code `0x04` is used to read from 1 to 125 contiguous input @@ -636,18 +525,20 @@ With the function input registers can be read. ```python -ireg_address = 11 -register_qty = 3 +ireg_address = 11 # register to start reading +register_qty = 3 # amount of registers to read + register_value = host.read_input_registers( slave_addr=slave_addr, starting_addr=ireg_address, register_qty=register_qty, signed=False) + print('Status of IREG {}: {}'.format(ireg_address, register_value)) # Status of IREG 11: [59123, 0, 390] ``` -### TCP +## TCP Get two network capable boards up and running, collecting and setting data on each other. @@ -655,7 +546,7 @@ each other. Adjust the WiFi network name (SSID) and password to be able to connect to your personal network or remove that section if a wired network connection is used. -#### Client +### Client The client, former known as slave, provides some dummy registers which can be read and updated by another device. @@ -681,7 +572,7 @@ Register setup done Serving as TCP client on 192.168.178.69:502 ``` -#### Host +### Host The host, former known as master, requests and updates some dummy registers of another device. @@ -716,13 +607,16 @@ Status of HREG 93: (44,) Status of IST 67: [False] Status of IREG 10: (60001,) +Resetting register data to default values... +Result of setting COIL 42: True + Finished requesting/setting data on client MicroPython v1.18 on 2022-01-17; ESP32 module (spiram) with ESP32 Type "help()" for more information. >>> ``` -### RTU +## RTU Get two UART/RS485 capable boards up and running, collecting and setting data on each other. @@ -733,7 +627,7 @@ as tuple of `Pin`, like `rtu_pins = (Pin(4), Pin(5))` and the specific `uart_id=1` for those, whereas ESP32 boards can use almost alls pins for UART communication and shall be given as `rtu_pins = (25, 26)`. -#### Client +### Client The client, former known as slave, provides some dummy registers which can be read and updated by another device. @@ -752,9 +646,10 @@ MPY: soft reboot System booted successfully! Setting up registers ... Register setup done +Serving as RTU client on address 10 at 9600 baud ``` -#### Host +### Host The host, former known as master, requests and updates some dummy registers of another device. @@ -772,7 +667,7 @@ look similar to this ``` MPY: soft reboot System booted successfully! -Requesting and updating data on RTU client at 10 with 9600 baud. +Requesting and updating data on RTU client at address 10 with 9600 baud Status of COIL 123: [True] Result of setting COIL 123: True @@ -785,13 +680,16 @@ Status of HREG 93: (44,) Status of IST 67: [False] Status of IREG 10: (60001,) +Resetting register data to default values... +Result of setting COIL 42: True + Finished requesting/setting data on client MicroPython v1.18 on 2022-01-17; ESP32 module (spiram) with ESP32 Type "help()" for more information. >>> ``` -### TCP-RTU bridge +## TCP-RTU bridge This example implementation shows how to act as bridge between an RTU (serial) connected device and another external TCP device. @@ -801,8 +699,8 @@ comment of [`main.py`][ref-package-main-file]. ## Classic development environment -This section describes the necessary steps on the computer to get ready to -test and run the examples. +This section describes the necessary steps on the computer to read and/or write +data from/to a Modbus TCP Client device. ```bash # Linux/Mac @@ -866,141 +764,6 @@ cd examples sh write_registers_tcp.sh 192.168.178.69 ../registers/set-example.json 502 ``` -## Docker development environment - -### Pull container - -Checkout the available -[MicroPython containers](https://hub.docker.com/r/micropython/unix/tags) - -```bash -docker pull micropython/unix:v1.18 -``` - -### Spin up container - -#### Simple container - -Use this command for your first tests or to run some MicroPython commands in -a simple REPL - -```bash -docker run -it \ ---name micropython-1.18 \ ---network=host \ ---entrypoint bash \ -micropython/unix:v1.18 -``` - -#### Enter MicroPython REPL - -Inside the container enter the REPL by running `micropython-dev`. The console -should now look similar to this - -``` -root@debian:/home# -MicroPython v1.18 on 2022-01-17; linux version -Use Ctrl-D to exit, Ctrl-E for paste mode ->>> -``` - -#### Manually run unittests - -In order to manually execute only a specific set of tests use the following -command inside the container - -```bash -# run all unittests defined in "tests" directory and exit with status result -micropython-dev -c "import unittest; unittest.main('tests')" - -# run all tests of "TestAbsoluteTruth" defined in tests/test_absolute_truth.py -# and exit with status result -micropython-dev -c "import unittest; unittest.main(name='tests.test_absolute_truth', fromlist=['TestAbsoluteTruth'])" -``` - -#### Custom container for unittests - -```bash -docker build \ ---tag micropython-test \ ---file Dockerfile.tests . -``` - -The unittests are executed during the building process. It will exit with a -non-zero status in case of a unittest failure. - -The return value can be collected by `echo $?` (on Linux based systems), which -will be either `0` in case all tests passed, or `1` if one or multiple tests -failed. - -#### Docker compose - -The following command uses the setup defined in the individual -`docker-compose-*-test.yaml` file to act as two MicroPython devices -communicating via TCP or RTU. The container `micropython-host-*` defined by -`Dockerfile.host_*` acts as host and sets/gets data at/from the client as -defined by `*_host_example.py`. On the other hand the container -`micropython-client-*` defined by `Dockerfile.client_*` acts as client and -provides data for the host as defined by `*_client_example.py`. - -The port defined in `tcp_host_example.py` and `tcp_client_example.py` has to -be open and optionally exposed in the `docker-compose-tcp-example.yaml` file. - -As the [MicroPython containers](https://hub.docker.com/r/micropython/unix/tags) -does not have a UART interface with is additionally not connectable via two -containers a UART fake has been implemented. It is using a socket connection -to exchange all the data. - -```bash -docker compose up --build --exit-code-from micropython-host -``` - -The option `--build` can be skipped on the second run, to avoid rebuilds of -the containers. All "dynamic" data is shared via `volumes` - -##### Test for TCP example - -```bash -docker compose -f docker-compose-tcp-test.yaml up --build --exit-code-from micropython-host --remove-orphans -``` - -##### Test for RTU example - -```bash -docker compose -f docker-compose-rtu-test.yaml up --build --exit-code-from micropython-host-rtu --remove-orphans -``` - -## Documentation - -The documentation is automatically generated on every merge to the develop -branch and available [here][ref-rtd-micropython-modbus] - -### Install required packages - -```bash -# create and activate virtual environment -python3 -m venv .venv -source .venv/bin/activate - -# install and upgrade required packages -pip install -U -r docs/requirements.txt -``` - -### Create documentation - -Some usefull checks have been disabled in the `docs/conf.py` file. Please -check the documentation build output locally before opening a PR. - -```bash -# perform link checks -sphinx-build docs/ docs/build/linkcheck -d docs/build/docs_doctree/ --color -blinkcheck -j auto -W - -# create documentation -sphinx-build docs/ docs/build/html/ -d docs/build/docs_doctree/ --color -bhtml -j auto -W -``` - -The created documentation can be found at [`docs/build/html`](docs/build/html). - [ref-registers-MyEVSE]: https://github.com/brainelectronics/micropython-modbus/blob/c45d6cc334b4adf0e0ffd9152c8f08724e1902d9/registers/modbusRegisters-MyEVSE.json [ref-myevse-be]: https://brainelectronics.de/ @@ -1009,4 +772,3 @@ The created documentation can be found at [`docs/build/html`](docs/build/html). [ref-uart-documentation]: https://docs.micropython.org/en/latest/library/machine.UART.html [ref-github-be-modbus-wrapper]: https://github.com/brainelectronics/be-modbus-wrapper [ref-modules-folder]: https://github.com/brainelectronics/python-modules/tree/43bad716b7db27db07c94c2d279cee57d0c8c753 -[ref-rtd-micropython-modbus]: https://micropython-modbus.readthedocs.io/en/latest/ From 25a3bb84e7a47181f17e06a0b2f01c42802cad1b Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:17:41 +0100 Subject: [PATCH 06/21] add documentation file, outsourced from usage --- docs/DOCUMENTATION.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 docs/DOCUMENTATION.md diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md new file mode 100755 index 0000000..4de132a --- /dev/null +++ b/docs/DOCUMENTATION.md @@ -0,0 +1,40 @@ +# Documentation + +Documentation is generated by using Sphinx and published on RTD + +--------------- + +## Documentation + +Documentation is automatically created on each merge to the development +branch, as well as with each pull request and available +[:books: here at Read the Docs][ref-rtd-micropython-modbus] + +### Install required packages + +```bash +# create and activate virtual environment +python3 -m venv .venv +source .venv/bin/activate + +# install and upgrade required packages +pip install -U -r docs/requirements.txt +``` + +### Create documentation + +Some usefull checks have been disabled in the `docs/conf.py` file. Please +check the documentation build output locally before opening a PR. + +```bash +# perform link checks +sphinx-build docs/ docs/build/linkcheck -d docs/build/docs_doctree/ --color -blinkcheck -j auto -W + +# create documentation +sphinx-build docs/ docs/build/html/ -d docs/build/docs_doctree/ --color -bhtml -j auto -W +``` + +The created documentation can be found at [`docs/build/html`](docs/build/html). + + +[ref-rtd-micropython-modbus]: https://micropython-modbus.readthedocs.io/en/latest/ From 65b578fe54b59bb56abaf938235ad88cd2f055a2 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:18:14 +0100 Subject: [PATCH 07/21] add examples file, outsourced from usage --- docs/EXAMPLES.md | 263 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100755 docs/EXAMPLES.md diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md new file mode 100755 index 0000000..90db8db --- /dev/null +++ b/docs/EXAMPLES.md @@ -0,0 +1,263 @@ +# Examples + +Usage examples of this `micropython-modbus` library + +--------------- + +## RTU + +```{note} +Check the port specific +[MicroPython UART documentation](https://docs.micropython.org/en/latest/library/machine.UART.html) +for further details. + +A Raspberry Pi Pico e.g. requires the UART pins as a tuple of `Pin`, like +`rtu_pins = (Pin(4), Pin(5))` and the corresponding `uart_id` for those pins, +whereas ESP32 boards can use almost any pin for UART communication as shown in +the following examples and shall be given as `rtu_pins = (25, 26)`. If +necessary, the `uart_id` parameter may has to be adapted to the pins used. +``` + +### Client/Slave + +With this example the device is acting as client (slave) and providing data via +RTU (serial/UART) to a requesting host device. + +```python +from umodbus.serial import ModbusRTU + +# RTU Client/Slave setup +# the following example is for an ESP32 +rtu_pins = (25, 26) # (TX, RX) +slave_addr = 10 # address on bus as client + +client = ModbusRTU( + addr=slave_addr, # address on bus + pins=rtu_pins, # given as tuple (TX, RX) + # baudrate=9600, # optional, default 9600 + # data_bits=8, # optional, default 8 + # stop_bits=1, # optional, default 1 + # parity=None, # optional, default None + # ctrl_pin=12, # optional, control DE/RE + # uart_id=1 # optional, see port specific documentation +) + +register_definitions = { + "COILS": { + "EXAMPLE_COIL": { + "register": 123, + "len": 1, + "val": 1 + } + }, + "HREGS": { + "EXAMPLE_HREG": { + "register": 93, + "len": 1, + "val": 19 + } + }, + "ISTS": { + "EXAMPLE_ISTS": { + "register": 67, + "len": 1, + "val": 0 + } + }, + "IREGS": { + "EXAMPLE_IREG": { + "register": 10, + "len": 1, + "val": 60001 + } + } +} + +# use the defined values of each register type provided by register_definitions +client.setup_registers(registers=register_definitions) + +while True: + try: + result = client.process() + except KeyboardInterrupt: + print('KeyboardInterrupt, stopping RTU client...') + break + except Exception as e: + print('Exception during execution: {}'.format(e)) +``` + +### Host/Master + +With this example the device is acting as host (master) and requesting on or +setting data at a RTU (serial/UART) client/slave. + +```python +from umodbus.serial import Serial as ModbusRTUMaster + +# RTU Host/Master setup +# the following example is for an ESP32 +rtu_pins = (25, 26) # (TX, RX) + +host = ModbusRTUMaster( + pins=rtu_pins, # given as tuple (TX, RX) + # baudrate=9600, # optional, default 9600 + # data_bits=8, # optional, default 8 + # stop_bits=1, # optional, default 1 + # parity=None, # optional, default None + # ctrl_pin=12, # optional, control DE/RE + # uart_id=1 # optional, see port specific documentation +) + +coil_status = host.read_coils(slave_addr=10, starting_addr=123, coil_qty=1) +print('Status of coil 123: {}'.format(coil_status)) +``` + +## TCP + +### Client/Slave + +With this example the device is acting as client (slave) and providing data via +TCP (socket) to a requesting host device. + +```python +import network +from umodbus.tcp import ModbusTCP + +# network connections shall be made here, check the MicroPython port specific +# documentation for connecting to or creating a network + +# TCP Client/Slave setup +# set IP address of this MicroPython device explicitly +# local_ip = '192.168.4.1' # IP address +# or get it from the system after a connection to the network has been made +# it is not the task of this lib to provide a detailed explanation for this +station = network.WLAN(network.STA_IF) +local_ip = station.ifconfig()[0] +tcp_port = 502 # port to listen for requests/providing data + +client = ModbusTCP() + +# check whether client has been bound to an IP and a port +if not client.get_bound_status(): + client.bind(local_ip=local_ip, local_port=tcp_port) + +register_definitions = { + "COILS": { + "EXAMPLE_COIL": { + "register": 123, + "len": 1, + "val": 1 + } + }, + "HREGS": { + "EXAMPLE_HREG": { + "register": 93, + "len": 1, + "val": 19 + } + }, + "ISTS": { + "EXAMPLE_ISTS": { + "register": 67, + "len": 1, + "val": 0 + } + }, + "IREGS": { + "EXAMPLE_IREG": { + "register": 10, + "len": 1, + "val": 60001 + } + } +} + +# use the defined values of each register type provided by register_definitions +client.setup_registers(registers=register_definitions) + +while True: + try: + result = client.process() + except KeyboardInterrupt: + print('KeyboardInterrupt, stopping TCP client...') + break + except Exception as e: + print('Exception during execution: {}'.format(e)) +``` + +### Host/Master + +With this example the device is acting as host (master) and requesting on or +setting data at a TCP (socket) client/slave. + +```python +from umodbus.tcp import TCP as ModbusTCPMaster + +# valid network connections shall be made here + +# RTU Host/Master setup +slave_tcp_port = 502 # port to send request on +slave_ip = '192.168.178.69' # IP address of client, to be adjusted + +host = ModbusTCPMaster( + slave_ip=slave_ip, + slave_port=slave_tcp_port, + # timeout=5.0 # optional, timeout in seconds, default 5.0 +) + +coil_status = host.read_coils(slave_addr=10, starting_addr=123, coil_qty=1) +print('Status of coil 123: {}'.format(coil_status)) +``` + +## Callbacks + +Callbacks can be registered to be executed *after* setting a register with +`on_set_cb` or to be executed *before* getting a register with `on_get_cb`. + +```{note} +Getter callbacks can be registered for all registers with the `on_get_cb` +parameter whereas the `on_set_cb` parameter is only available for coils and +holding registers as only those can be set by a external host. +``` + +```{eval-rst} +.. warning:: + Keep the get callback actions as short as possible to avoid potential + request timeouts due to a to long processing time. +``` + +```python +def my_coil_set_cb(reg_type, address, val): + print('Custom callback, called on setting {} at {} to: {}'. + format(reg_type, address, val)) + + +def my_coil_get_cb(reg_type, address, val): + print('Custom callback, called on getting {} at {}, currently: {}'. + format(reg_type, address, val)) + + +# define some registers, for simplicity only a single coil is used +register_definitions = { + "COILS": { + "EXAMPLE_COIL": { + "register": 123, + "len": 1, + "val": 1, + "on_get_cb": my_coil_get_cb, + "on_set_cb": my_coil_set_cb + } + } +} + +# use the defined values of each register type provided by register_definitions +client.setup_registers(registers=register_definitions) + +# callbacks can also be defined after a register setup has been performed +client.add_coil( + address=123, + value=bool(1), + on_set_cb=my_coil_set_cb, + on_get_cb=my_coil_get_cb +) +``` From beaca13db423d7352bc42239667c23c58e3c2f93 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:18:53 +0100 Subject: [PATCH 08/21] add testing file, outsourced from usage, add manual testing docker file --- Dockerfile.tests_manually | 18 ++++ docs/TESTING.md | 174 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 Dockerfile.tests_manually create mode 100755 docs/TESTING.md diff --git a/Dockerfile.tests_manually b/Dockerfile.tests_manually new file mode 100644 index 0000000..0753480 --- /dev/null +++ b/Dockerfile.tests_manually @@ -0,0 +1,18 @@ +# Build image +# $ docker build -t micropython-test-manually -f Dockerfile.tests_manually . +# if a unittest fails, it will exit with non-zero code +# +# Run image, only possible if all tests passed +# $ docker run -it --rm --name micropython-test-manually micropython-test-manually + +FROM micropython/unix:v1.18 + +COPY ./ /home +# keep examples and tests registers JSON file easily in sync +COPY registers/example.json /home/tests/test-registers.json +COPY umodbus /root/.micropython/lib/umodbus +COPY mpy_unittest.py /root/.micropython/lib/mpy_unittest.py + +RUN micropython-dev -m upip install micropython-ulogging + +ENTRYPOINT ["/bin/bash"] diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100755 index 0000000..35dc377 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,174 @@ +# Testing + +Testing is done inside MicroPython Docker container + +--------------- + +## Basics + +This library is as of now tested with the `v1.18` version of MicroPython + +### Pull container + +Checkout the available +[MicroPython containers](https://hub.docker.com/r/micropython/unix/tags) and +pull the `v1.18` locally. + +```bash +docker pull micropython/unix:v1.18 +``` + +### Spin up container + +#### Simple container + +Use this command for your first tests or to run some MicroPython commands in +a simple REPL + +```bash +docker run -it --name micropython-1.18 --network=host --entrypoint bash micropython/unix:v1.18 +``` + +#### Enter MicroPython REPL + +Inside the container enter the REPL by running `micropython-dev`. The console +should now look similar to this + +``` +root@debian:/home# +MicroPython v1.18 on 2022-01-17; linux version +Use Ctrl-D to exit, Ctrl-E for paste mode +>>> +``` + +## Testing + +All tests are automatically executed on a push to GitHub and have to be passed +in order to be allowed to merge to the main development branch, see also [CONTRIBUTING](CONTRIBUTING.md). + +### Run unittests manually + +First build and run the docker image with the following command + +```bash +docker build -t micropython-test-manually -f Dockerfile.tests_manually . +docker run -it --name micropython-test-manually micropython-test-manually +``` + +Run all unittests defined in the `tests` directory and exit with status result + +```bash +micropython-dev -c "import mpy_unittest as unittest; unittest.main('tests')" +``` + +In order to execute only a specific set of tests use the following command +inside the built and running MicroPython container + +```bash +# run all tests of "TestAbsoluteTruth" defined in tests/test_absolute_truth.py +# and exit with status result +micropython-dev -c "import mpy_unittest as unittest; unittest.main(name='tests.test_absolute_truth', fromlist=['TestAbsoluteTruth'])" +``` + +### Custom container for unittests + +```bash +docker build --tag micropython-test --file Dockerfile.tests . +``` + +As soon as the built image is executed all unittests are executed. The +container will exit with a non-zero status in case of a unittest failure. + +The return value can be collected by `echo $?` (on Linux based systems) or +`echo %errorlevel%` (on Windows), which will be either `0` in case all tests +passed, or `1` if one or multiple tests failed. + +### Docker compose + +For more complex unittests and integration tests, several Dockerfiles are +combined into a docker-compose file. + +The option `--build` can be skipped on the second run, to avoid rebuilds of +the containers. All "dynamic" data is shared via `volumes`. + +#### TCP integration tests + +##### Overview + +The TCP integration tests defined by `test_tcp_example.py` uses the +`Dockerfile.client_tcp` and `Dockerfile.test_examples`. + +A network bridge is created with fixed IPv4 addresses in order to bind the +client to a known IP and let the host reach it on that predefined IP. + +The port defined in `tcp_client_example.py`, executed by +`Dockerfile.client_tcp` has to be open, exposed to the `micropython-host` +service running `Dockerfile.test_examples` and optionally exposed in the +`docker-compose-tcp-test.yaml` file. + +Finally the tests defined in `TestTcpExample` are executed. The tests read and +write all available register types on the client and check the response with +the expected data. + +##### Usage + +```bash +docker compose -f docker-compose-tcp-test.yaml up --build --exit-code-from micropython-host --remove-orphans +``` + +#### RTU integration tests + +##### UART interface + +As the [MicroPython containers](https://hub.docker.com/r/micropython/unix/tags) +do not have a UART interface, which is additionally anyway not connectable via +two containers, a [`UART fake`](fakes.machine.UART) has been implemented. + +The fake [`UART`](fakes.machine.UART) class provides all required functions +like [`any()`](fakes.machine.UART.any), [`read()`](fakes.machine.UART.read) and +[`write()`](fakes.machine.UART.write) to simulate a UART interface. + +During the initialisation of the fake UART a simple and very basic socket +request is made to `172.25.0.2`, a predefined IP address, see +`docker-compose-rtu-test.yaml`. In case no response is received, a socket based +server is started. It is thereby important to start the RTU client before the +RTU host. The RTU host will perform the same check during the UART init, but +will reach the (already running) socket server and connect to it. + +The data provided to the [`write()`](fakes.machine.UART.write) call of the RTU +host, will be sent to the background socket server of the RTU client and be +read inside the [`get_request()`](umodbus.serial.Serial.get_request) function +which is constantly called by the [`process()`](umodbus.modbus.Modbus.process) +function. + +After it has been processed from Modbus perspective, the RTU client response +will then be put by the [`write()`](fakes.machine.UART.write) function into a +queue on RTU client side, picked up by the RTU client background socket server +thread and sent back to the RTU host where it is made available via the +[`read()`](fakes.machine.UART.read) function. + +##### Overview + +The RTU integration tests defined by `test_rtu_example.py` uses the +`Dockerfile.client_rtu` and `Dockerfile.test_examples`. + +A network bridge is created with fixed IPv4 addresses in order to bind the +client to a known IP and let the host reach it on that predefined IP. + +The port defined in `rtu_client_example.py`, executed by +`Dockerfile.client_rtu` has to be open, exposed to the `micropython-host` +service running `Dockerfile.test_examples` and optionally exposed in the +`docker-compose-rtu-test.yaml` file. + +Finally the tests defined in `TestRtuExample` are executed. The tests read and +write all available register types on the client and check the response with +the expected data. + +##### Usage + +```bash +docker compose -f docker-compose-rtu-test.yaml up --build --exit-code-from micropython-host-rtu --remove-orphans +``` + + +[ref-fakes]: https://github.com/brainelectronics/micropython-modbus/blob/develop/fakes/machine.py From 555e5efe016cea2e0c6b6d7f74245092553ce1b3 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:21:25 +0100 Subject: [PATCH 09/21] use callback to reset register data in RTU client example --- examples/rtu_client_example.py | 26 ++++++++++++++++++-------- examples/rtu_host_example.py | 21 +++++++++++---------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/examples/rtu_client_example.py b/examples/rtu_client_example.py index 7b0ef9b..572c05b 100644 --- a/examples/rtu_client_example.py +++ b/examples/rtu_client_example.py @@ -44,8 +44,8 @@ baudrate = 9600 client = ModbusRTU( addr=slave_addr, # address on bus - baudrate=baudrate, # optional, default 9600 pins=rtu_pins, # given as tuple (TX, RX) + baudrate=baudrate, # optional, default 9600 # data_bits=8, # optional, default 8 # stop_bits=1, # optional, default 1 # parity=None, # optional, default None @@ -57,6 +57,17 @@ # works only with fake machine UART assert client._itf._uart._is_server is True + +def reset_data_registers_cb(reg_type, address, val): + # usage of global isn't great, but okay for an example + global client + global register_definitions + + print('Resetting register data to default values ...') + client.setup_registers(registers=register_definitions) + print('Default values restored') + + # common slave register setup, to be used with the Master example above register_definitions = { "COILS": { @@ -101,6 +112,10 @@ with open('registers/example.json', 'r') as file: register_definitions = json.load(file) +# reset all registers back to their default value with a callback +register_definitions['COILS']['RESET_REGISTER_DATA_COIL']['on_set_cb'] = \ + reset_data_registers_cb + print('Setting up registers ...') # use the defined values of each register type provided by register_definitions client.setup_registers(registers=register_definitions) @@ -108,17 +123,12 @@ # client.setup_registers(registers=register_definitions, use_default_vals=True) print('Register setup done') -reset_data_register = \ - register_definitions['COILS']['RESET_REGISTER_DATA_COIL']['register'] +print('Serving as RTU client on address {} at {} baud'. + format(slave_addr, baudrate)) while True: try: result = client.process() - if reset_data_register in client.coils: - if client.get_coil(address=reset_data_register): - print('Resetting register data to default values ...') - client.setup_registers(registers=register_definitions) - print('Default values restored') except KeyboardInterrupt: print('KeyboardInterrupt, stopping RTU client...') break diff --git a/examples/rtu_host_example.py b/examples/rtu_host_example.py index c22923c..9802893 100644 --- a/examples/rtu_host_example.py +++ b/examples/rtu_host_example.py @@ -47,9 +47,10 @@ # the following example is for an ESP32 rtu_pins = (25, 26) # (TX, RX) baudrate = 9600 + host = ModbusRTUMaster( - baudrate=baudrate, # optional, default 9600 pins=rtu_pins, # given as tuple (TX, RX) + baudrate=baudrate, # optional, default 9600 # data_bits=8, # optional, default 8 # stop_bits=1, # optional, default 1 # parity=None, # optional, default None @@ -106,7 +107,7 @@ register_definitions = json.load(file) """ -print('Requesting and updating data on RTU client at {} with {} baud'. +print('Requesting and updating data on RTU client at address {} with {} baud'. format(slave_addr, baudrate)) print() @@ -117,7 +118,7 @@ slave_addr=slave_addr, starting_addr=coil_address, coil_qty=coil_qty) -print('Status of coil {}: {}'.format(coil_status, coil_address)) +print('Status of COIL {}: {}'.format(coil_address, coil_status)) time.sleep(1) # WRITE COILS @@ -126,7 +127,7 @@ slave_addr=slave_addr, output_address=coil_address, output_value=new_coil_val) -print('Result of setting coil {} to {}'.format(coil_address, operation_status)) +print('Result of setting COIL {} to {}'.format(coil_address, operation_status)) time.sleep(1) # READ COILS again @@ -134,7 +135,7 @@ slave_addr=slave_addr, starting_addr=coil_address, coil_qty=coil_qty) -print('Status of coil {}: {}'.format(coil_status, coil_address)) +print('Status of COIL {}: {}'.format(coil_address, coil_status)) time.sleep(1) print() @@ -147,7 +148,7 @@ starting_addr=hreg_address, register_qty=register_qty, signed=False) -print('Status of hreg {}: {}'.format(hreg_address, register_value)) +print('Status of HREG {}: {}'.format(hreg_address, register_value)) time.sleep(1) # WRITE HREGS @@ -157,7 +158,7 @@ register_address=hreg_address, register_value=new_hreg_val, signed=False) -print('Result of setting hreg {} to {}'.format(hreg_address, operation_status)) +print('Result of setting HREG {} to {}'.format(hreg_address, operation_status)) time.sleep(1) # READ HREGS again @@ -166,7 +167,7 @@ starting_addr=hreg_address, register_qty=register_qty, signed=False) -print('Status of hreg {}: {}'.format(hreg_address, register_value)) +print('Status of HREG {}: {}'.format(hreg_address, register_value)) time.sleep(1) print() @@ -178,7 +179,7 @@ slave_addr=slave_addr, starting_addr=ist_address, input_qty=input_qty) -print('Status of ist {}: {}'.format(ist_address, input_status)) +print('Status of IST {}: {}'.format(ist_address, input_status)) time.sleep(1) # READ IREGS @@ -189,7 +190,7 @@ starting_addr=ireg_address, register_qty=register_qty, signed=False) -print('Status of ireg {}: {}'.format(ireg_address, register_value)) +print('Status of IREG {}: {}'.format(ireg_address, register_value)) time.sleep(1) print() From a90f6a33408b9b0747e29deb07c4dbff754d5e20 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:22:02 +0100 Subject: [PATCH 10/21] minor cosmetic changes --- docs/CONTRIBUTING.md | 6 +++++- docs/SETUP.md | 2 +- examples/tcp_host_example.py | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index a6e4a9a..bd05ca4 100755 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,3 +1,7 @@ -# CONTRIBUTING +# Contributing Guideline to contribute to this package + +--------------- + +## TBD diff --git a/docs/SETUP.md b/docs/SETUP.md index fb29aa6..5329835 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -56,7 +56,7 @@ ready to test and run the examples. ### Flash firmware Flash the [MicroPython firmware][ref-upy-firmware-download] to the MicroPython -board with this call in case a ESP32 is used. +board. The following example call is valid for ESP32 boards. ```bash esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash diff --git a/examples/tcp_host_example.py b/examples/tcp_host_example.py index 98cc3ab..a5a7992 100644 --- a/examples/tcp_host_example.py +++ b/examples/tcp_host_example.py @@ -201,6 +201,8 @@ print('Status of IREG {}: {}'.format(ireg_address, register_value)) time.sleep(1) +print() + # reset all registers back to their default values on the client # WRITE COILS print('Resetting register data to default values...') From 91bb2834b0c4977294da579f986b26098a2984cf Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:22:26 +0100 Subject: [PATCH 11/21] add fakes rst file in docs --- docs/fakes.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/fakes.rst diff --git a/docs/fakes.rst b/docs/fakes.rst new file mode 100644 index 0000000..b3b988a --- /dev/null +++ b/docs/fakes.rst @@ -0,0 +1,13 @@ +Fakes API +======================= + +.. autosummary:: + :toctree: generated + +UART and Pin fakes +--------------------------------- + +.. automodule:: fakes.machine + :members: + :private-members: + :show-inheritance: From f0ffae6c1695c27d9ff6935a627cf575300894f2 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:23:21 +0100 Subject: [PATCH 12/21] update umodbus rst API name --- docs/umodbus.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/umodbus.rst b/docs/umodbus.rst index 6738458..ba4cc8c 100755 --- a/docs/umodbus.rst +++ b/docs/umodbus.rst @@ -1,4 +1,4 @@ -micropython-modbus API +API ======================= .. autosummary:: @@ -28,7 +28,7 @@ Common functions :private-members: :show-inheritance: -umodbus.modbus module +Modbus client module --------------------------------- .. automodule:: umodbus.modbus From 03941faa0eb9798b1ba78dceb72d3aa3b9e39d20 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:24:04 +0100 Subject: [PATCH 13/21] update copyright year in docs config, import machine from fakes instead of Mock --- docs/conf.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a225ebb..b0cd87f 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,15 +16,17 @@ try: import umodbus except ImportError: - raise SystemExit("umodbus has to be importable") + raise SystemExit("umodbus and fakes have to be importable") else: # Inject mock modules so that we can build the # documentation without having the real stuff available from mock import Mock + from fakes import machine - for mod_name in ['micropython', 'machine', 'urequests']: - sys.modules[mod_name] = Mock() - print("Mocked {}".format(mod_name)) + sys.modules['micropython'] = Mock() + print("Mocked 'micropython' module") + sys.modules['machine'] = machine + print("Imported 'machine' module from 'fakes'") # load elements of version.py exec(open(here / '..' / 'umodbus' / 'version.py').read()) @@ -32,7 +34,7 @@ # -- Project information project = 'micropython-modbus' -copyright = '2022, brainelectronics' +copyright = '2023, brainelectronics' author = 'brainelectronics' version = __version__ From 5e9c9bea7bb66e76d59e51142bd2e1b5d75a16b5 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:24:36 +0100 Subject: [PATCH 14/21] update doc string style of modbus.py for sphinx usage --- umodbus/modbus.py | 57 ++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/umodbus/modbus.py b/umodbus/modbus.py index f8e6416..56bd8e7 100644 --- a/umodbus/modbus.py +++ b/umodbus/modbus.py @@ -25,7 +25,14 @@ class Modbus(object): - """Modbus register abstraction.""" + """ + Modbus register abstraction + + :param itf: Abstraction interface + :type itf: Callable + :param addr_list: List of addresses + :type addr_list: List[int] + """ def __init__(self, itf, addr_list: List[int]) -> None: self._itf = itf self._addr_list = addr_list @@ -262,14 +269,14 @@ def add_coil(self, :type value: Union[bool, List[bool]], optional :param on_set_cb: Callback on setting the coil :type on_set_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] :param on_get_cb: Callback on getting the coil :type on_get_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] """ self._set_reg_in_dict(reg_type='COILS', address=address, @@ -412,9 +419,9 @@ def add_ist(self, :type value: bool or list of bool, optional :param on_get_cb: Callback on getting the discrete input register :type on_get_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] """ self._set_reg_in_dict(reg_type='ISTS', address=address, @@ -483,9 +490,9 @@ def add_ireg(self, :type value: Union[int, List[int]], optional :param on_get_cb: Callback on getting the input register :type on_get_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] """ self._set_reg_in_dict(reg_type='IREGS', address=address, @@ -561,14 +568,14 @@ def _set_reg_in_dict(self, :type value: Union[bool, int, List[bool], List[int]] :param on_set_cb: Callback on setting the register :type on_get_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] :param on_get_cb: Callback on getting the register :type on_get_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] :raise KeyError: No register at specified address found """ @@ -615,14 +622,14 @@ def _set_single_reg_in_dict(self, :type value: Union[bool, int] :param on_set_cb: Callback on setting the register :type on_get_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] :param on_get_cb: Callback on getting the register :type on_get_cb: Callable[ - [str, int, Union[List[bool], List[int]]], - None - ] + [str, int, Union[List[bool], List[int]]], + None + ] """ data = {'val': value} From a1a149776219419b375f802ebe07e7f191aa8463 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:25:12 +0100 Subject: [PATCH 15/21] add examples, testing, documentation and fakes files to documentation index rst file --- docs/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 1577854..f74b60b 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,8 +11,12 @@ Contents SETUP INSTALLATION USAGE + EXAMPLES + TESTING + DOCUMENTATION CONTRIBUTING umodbus + fakes UPGRADE changelog_link From cc38056e6332f43220954f67af567c28b6ee66ff Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Mon, 9 Jan 2023 22:28:05 +0100 Subject: [PATCH 16/21] update changelog --- changelog.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index a635dc5..38b45ce 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Released +## [2.3.2] - 2023-01-09 +### Added +- Installation instructions for `mip` usage on MicroPython 1.19.1 or newer, see #44 +- [Manual testing Dockerfile](Dockerfile.tests_manually) +- [INSTALLATION](docs/INSTALLATION.md), [TESTING](docs/TESTING.md) and [EXAMPLES](docs/EXAMPLES.md) files for simpler docs structure + +### Changed +- Split [SETUP](docs/SETUP.md) into [INSTALLATION](docs/INSTALLATION.md) +- Split [USAGE](docs/USAGE.md) into [TESTING](docs/TESTING.md) and [EXAMPLES](docs/EXAMPLES.md) +- Use callback to reset register data in [RTU client example](examples/rtu_client_example.py) +- Update docs copyright year to 2023 +- Use fakes machine module instead of classic Mock in docs config file + +### Fixed +- Basic RTU host example in root [README](README.md) uses correct init values, optional parameters are listed after mandatory ones +- Remove outdated warning sections about #35 bug from [USAGE](docs/USAGE.md) + ## [2.3.1] - 2023-01-06 ### Added - Unittest to read multiple coils at any location if defined as list, verifies #35 @@ -243,8 +260,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - PEP8 style issues on all files of [`lib/uModbus`](lib/uModbus) -[Unreleased]: https://github.com/brainelectronics/micropython-modbus/compare/2.3.1...develop +[Unreleased]: https://github.com/brainelectronics/micropython-modbus/compare/2.3.2...develop +[2.3.2]: https://github.com/brainelectronics/micropython-modbus/tree/2.3.2 [2.3.1]: https://github.com/brainelectronics/micropython-modbus/tree/2.3.1 [2.3.0]: https://github.com/brainelectronics/micropython-modbus/tree/2.3.0 [2.2.0]: https://github.com/brainelectronics/micropython-modbus/tree/2.2.0 From 29d7b7835596de90374e0f825fb9c6b17e2ab758 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Tue, 10 Jan 2023 22:51:36 +0100 Subject: [PATCH 17/21] fix book emoji, contributes to #55 --- README.md | 4 ++-- docs/DOCUMENTATION.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c77931f..3e52d62 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Forked from [Exo Sense Py][ref-sferalabs-exo-sense], based on [PyCom Modbus][ref-pycom-modbus] and extended with other functionalities to become a powerfull MicroPython library -:books: The latest documentation is available at -[MicroPython Modbus ReadTheDocs][ref-rtd-micropython-modbus] :books: +📚 The latest documentation is available at +[MicroPython Modbus ReadTheDocs][ref-rtd-micropython-modbus] 📚 diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 4de132a..aca0cd1 100755 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -8,7 +8,7 @@ Documentation is generated by using Sphinx and published on RTD Documentation is automatically created on each merge to the development branch, as well as with each pull request and available -[:books: here at Read the Docs][ref-rtd-micropython-modbus] +[📚 here at Read the Docs][ref-rtd-micropython-modbus] ### Install required packages From eacf0bf1808e58e6f0d28b3b59790e64d692d636 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Tue, 10 Jan 2023 22:52:55 +0100 Subject: [PATCH 18/21] differentiate between ISTS and IREG in USAGE --- docs/USAGE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index 8fddc04..621bedb 100755 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -56,12 +56,12 @@ The JSON file/dictionary shall follow the following pattern/structure "on_get_cb": some_function # callback function executed on the client after a value has been requested }, }, - "ISTS": { # this key shall contain all static input registers - "ISTS_NAME": { # custom name of a static input register - "register": 67, # register address of the static input register + "ISTS": { # this key shall contain all input status registers + "ISTS_NAME": { # custom name of a input status register + "register": 67, # register address of the input status register "len": 1, # amount of registers to request aka quantity "val": 0, # used to set a register, not possible for ISTS - "description": "Optional description of the static input register", + "description": "Optional description of the input status register", "range": "[0, 1]", "unit": "activated", "on_get_cb": some_function # callback function executed on the client after a value has been requested From 8852f14525f3a8c6130af368a0d6d294b8cd54e6 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Tue, 10 Jan 2023 22:53:27 +0100 Subject: [PATCH 19/21] add note for callback value to be always signed, see #51 --- docs/USAGE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/USAGE.md b/docs/USAGE.md index 621bedb..17ce2ed 100755 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -217,6 +217,13 @@ The callback function shall have the following three parameters: | `address` | int | Type of register. `COILS`, `HREGS`, `ISTS`, `IREGS` | | `val` | Union[bool, int, Tuple[bool], Tuple[int], List[bool], List[int]] | Current value of register | +```{note} +The function parameter `val` is always an unsigned value. The host device +requesting data is interpreting the data as signed or not, the client device +has no informations about it. Setting a holding register to `-4` will be +returned as `65532` on a registered callback. +``` + This example functions registered for e.g. coil 123 will output the following content after the coil has been requested and afterwards set to a different value From 2368c445d60a8695d63a0479e46473ca68e30390 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Tue, 10 Jan 2023 22:54:12 +0100 Subject: [PATCH 20/21] fix citation style issue in installation guide --- docs/INSTALLATION.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index f55565d..ba0c11d 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -29,9 +29,12 @@ station.isconnected() `mip` has been added in MicroPython 1.19.1 and later. For earlier MicroPython versions check the [upip section below](#install-with-upip) -> `mip` ("mip installs packages") is similar in concept to Python's `pip` -> tool, however it does not use the PyPI index, rather it uses micropython-lib -> as its index by default. +```{eval-rst} +.. epigraph:: + `mip` ("mip installs packages") is similar in concept to Python's `pip` + tool, however it does not use the PyPI index, rather it uses + micropython-lib as its index by default. +``` As this library is pushed to [PyPi][ref-micropython-modbus-pypi] and [TestPyPi][ref-micropython-modbus-test-pypi], but not the default @@ -72,9 +75,8 @@ upip.install('micropython-modbus') ### mpremote -As of January 2022 the [`mpremote`][ref-mpremote] tool is available via `pip`. - -> It can be used from a host PC to install packages to a locally connected device +As of January 2022 the [`mpremote`][ref-mpremote] tool is available via `pip` +and can be used to install packages on a connected device from a host machine. As described in the `Install required tools` section of [SETUP](SETUP.md), the tool will be installed with the provided `requirements.txt` file. Please From 9e14c5a26ebc51a208cad4a564e7dd4d403bad56 Mon Sep 17 00:00:00 2001 From: Jonas Scharpf Date: Tue, 10 Jan 2023 22:54:39 +0100 Subject: [PATCH 21/21] add mpremote instructions for manual lib installation --- docs/INSTALLATION.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index ba0c11d..8f32d0f 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -100,7 +100,18 @@ mpremote connect /dev/tty.SLAB_USBtoUART mip install --index https://test.pypi.o ### Manually Copy all files of the [umodbus module][ref-umodbus-module] to the MicroPython -board using [Remote MicroPython shell][ref-remote-upy-shell] +board using [Remote MicroPython shell][ref-remote-upy-shell] or +[mpremote][ref-mpremote] + +#### mpremote + +Perform the following command to copy all files and folders to the device + +```bash +mpremote connect /dev/tty.SLAB_USBtoUART cp -r umodbus/ : +``` + +#### rshell Open the remote shell with the following command. Additionally use `-b 115200` in case no CP210x is used but a CH34x.