Repository: brainelectronics/micropython-modbus (language: python)

Issue:
Push release candidate tag on every test deploy
As mentioned on #10, the same version that is released to Test PyPi should be available as tag on the repo.
Push release candidate tag on every test deploy
As mentioned on #10, the same version that is released to Test PyPi should be available as tag on the repo.
Create test framework
As found out during review of PR #10 and follow up tickets #12 and #15 the implementation does not 100% follow the Modbus requirements 

Create a test framework with different requests and check response data for single and multi registers


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 618ceb0..e63086b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,14 +9,14 @@ on:
       - develop
 
 permissions:
-  contents: read
+  contents: write
 
 jobs:
   deploy:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
     - name: Set up Python
       uses: actions/setup-python@v3
       with:
@@ -41,3 +41,16 @@ jobs:
         skip_existing: true
         verbose: true
         print_hash: true
+    - name: 'Create changelog based release'
+      uses: brainelectronics/changelog-based-release@v1
+      with:
+        # note you'll typically need to create a personal access token
+        # with permissions to create releases in the other repo
+        # or you set the "contents" permissions to "write" as in this example
+        changelog-path: changelog.md
+        tag-name-prefix: ''
+        tag-name-extension: ''
+        release-name-prefix: ''
+        release-name-extension: ''
+        draft-release: true
+        prerelease: false
diff --git a/README.md b/README.md
index 01899fd..105f012 100644
--- a/README.md
+++ b/README.md
@@ -16,82 +16,64 @@ 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
 
-## Installation
+<!-- MarkdownTOC -->
 
-<!--
-The current implementation does only run on a board with external SPI RAM. As
-of now up to 300kB of RAM are required. This is more than an ESP32-D4 Pico
-provides by default.
+- [Quickstart](#quickstart)
+    - [Install package on board with pip](#install-package-on-board-with-pip)
+    - [Install additional MicroPython packages](#install-additional-micropython-packages)
+- [Usage](#usage)
+    - [Master implementation](#master-implementation)
+    - [Slave implementation](#slave-implementation)
+    - [Register configuration](#register-configuration)
+- [Supported Modbus functions](#supported-modbus-functions)
+- [Credits](#credits)
 
-`esp32spiram-20220117-v1.18.bin` is used as MicroPython firmware
--->
+<!-- /MarkdownTOC -->
 
-### Install required tools
+## Quickstart
 
-Python3 must be installed on your system. Check the current Python version
-with the following command
+This is a quickstart to install the `micropython-modbus` library on a
+MicroPython board.
 
-```bash
-python --version
-python3 --version
-```
-
-Depending on which command `Python 3.x.y` (with x.y as some numbers) is
-returned, use that command to proceed.
+A more detailed guide of the development environment can be found in
+[SETUP](SETUP.md)
 
 ```bash
 python3 -m venv .venv
 source .venv/bin/activate
 
-pip install -r requirements.txt
+pip install 'rshell>=0.0.30,<1.0.0'
 ```
 
-## Setup
-
-### 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 on the MicroPython device like this
-
-```python
-import upip
-upip.install('micropython-modbus')
-```
-
-### Manually
-
-#### Upload files to board
-
-Copy the module to the MicroPython board and import them as shown below
-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.
+### Install package on board with pip
 
 ```bash
 rshell -p /dev/tty.SLAB_USBtoUART --editor nano
 ```
 
-Perform the following command to copy all files and folders to the device
+Inside the rshell
 
 ```bash
-mkdir /pyboard/lib
-mkdir /pyboard/lib/umodbus
-mkdir /pyboard/registers
-
-cp registers/modbusRegisters-MyEVSE.json /pyboard/registers/
-cp umodbus/* /pyboard/lib/umodbus
-
 cp main.py /pyboard
 cp boot.py /pyboard
+repl
+```
+
+Inside the REPL
+
+```python
+import machine
+import network
+import time
+import upip
+station = network.WLAN(network.STA_IF)
+station.active(True)
+station.connect('SSID', 'PASSWORD')
+time.sleep(1)
+print('Device connected to network: {}'.format(station.isconnected()))
+upip.install('micropython-modbus')
+print('Installation completed')
+machine.soft_reset()
 ```
 
 ### Install additional MicroPython packages
@@ -111,6 +93,8 @@ or check the README of the
 
 ## Usage
 
+See also [USAGE](USAGE.md)
+
 Start a REPL (may perform a soft reboot), wait for network connection and
 start performing Modbus requests to the device.
 
@@ -211,7 +195,7 @@ Use the provided example scripts [read RTU](examples/read_registers_rtu.sh) or
 [read TCP](examples/read_registers_tcp.sh) to read the data from the devices.
 This requires the [modules submodule][ref-github-be-python-modules] to be
 cloned as well and the required packages being installed as described in the
-modules README file.
+modules README file. For further details read the [SETUP](SETUP.md) guide.
 
 ### Register configuration
 
diff --git a/SETUP.md b/SETUP.md
new file mode 100644
index 0000000..3b4b068
--- /dev/null
+++ b/SETUP.md
@@ -0,0 +1,120 @@
+# Setup
+
+Setup the development environment and the MicroPython board
+
+---------------
+
+<!-- MarkdownTOC -->
+
+- [Development environment](#development-environment)
+	- [Update submodule](#update-submodule)
+	- [Install required tools](#install-required-tools)
+- [MicroPython](#micropython)
+	- [Flash firmware](#flash-firmware)
+	- [Install package with pip](#install-package-with-pip)
+	- [Without network connection](#without-network-connection)
+
+<!-- /MarkdownTOC -->
+
+## Development environment
+
+This section describes the necessary steps on the computer to get ready to
+test and run the examples.
+
+### Update submodule
+
+[brainelectronics python modules submodule][ref-github-be-python-modules] have
+to be cloned as well. A standard clone command won't clone the submodule.
+
+```bash
+git submodule update --init --recursive
+cd modules
+git fetch
+
+# maybe checkout a newer version with the following command
+git checkout x.y.z
+
+# or use the latest develop branch
+git checkout develop
+git pull
+```
+
+### Install required tools
+
+Python3 must be installed on your system. Check the current Python version
+with the following command
+
+```bash
+python --version
+python3 --version
+```
+
+Depending on which command `Python 3.x.y` (with x.y as some numbers) is
+returned, use that command to proceed.
+
+```bash
+python3 -m venv .venv
+source .venv/bin/activate
+
+pip install -r requirements.txt
+pip install -r modules/requirements.txt
+```
+
+## MicroPython
+
+This section describes the necessary steps on the MicroPython device to get
+ready to test and run the examples.
+
+### Flash firmware
+
+Flash the [MicroPython firmware][ref-upy-firmware-download] to the MicroPython
+board with this call
+
+```bash
+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 on the MicroPython device like this
+
+```python
+import upip
+upip.install('micropython-modbus')
+```
+
+### Without network connection
+
+Copy the module to the MicroPython board and import them as shown below
+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
+```
+
+<!-- Links -->
+[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
diff --git a/USAGE.md b/USAGE.md
new file mode 100644
index 0000000..c497f94
--- /dev/null
+++ b/USAGE.md
@@ -0,0 +1,198 @@
+# Usage
+
+Using and testing this `micropython-modbus` library
+
+---------------
+
+<!-- MarkdownTOC -->
+
+- [Development environment](#development-environment)
+	- [TCP](#tcp)
+		- [Read data](#read-data)
+		- [Write data](#write-data)
+- [MicroPython](#micropython)
+	- [TCP](#tcp-1)
+		- [Client](#client)
+		- [Host](#host)
+	- [RTU](#rtu)
+
+<!-- /MarkdownTOC -->
+
+The onwards described steps assume a successful setup as described in
+[SETUP.md](SETUP.md)
+
+## Development environment
+
+This section describes the necessary steps on the computer to get ready to
+test and run the examples.
+
+```bash
+# Linux/Mac
+source .venv/bin/activate
+```
+
+On a Windows based system activate the virtual environment like this
+
+```
+.venv\Scripts\activate.bat
+```
+
+The onwards mentioned commands shall be performed inside the previously
+activated virtual environment.
+
+### TCP
+
+Read and write the Modbus register data from a MicroPython device with the
+[brainelectronics ModbusWrapper][ref-github-be-modbus-wrapper] provided with
+the [modules submodule](modules)
+
+#### Read data
+
+```bash
+python modules/read_device_info_registers.py \
+--file=examples/example.json \
+--connection=tcp \
+--address=192.168.178.69 \
+--port=502 \
+--print \
+--pretty \
+--debug \
+--verbose=3
+```
+
+Or use the even more convenient wrapper script for the wrapper.
+
+```bash
+cd examples
+sh read_registers_tcp.sh 192.168.178.69 example.json 502
+```
+
+#### Write data
+
+```bash
+python modules/write_device_info_registers.py \
+--file=examples/set-example.json \
+--connection=tcp \
+--address=192.168.178.69 \
+--port=502 \
+--print \
+--pretty \
+--debug \
+--verbose=3
+```
+
+Or use the even more convenient wrapper script for the wrapper.
+
+```bash
+cd examples
+sh write_registers_tcp.sh 192.168.178.69 set-example.json 502
+```
+
+## 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
+```
+
+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.
+
+### TCP
+
+Get two network capable boards up and running, collecting and setting data on
+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
+
+The client, former known as slave, provides some dummy registers which can be
+read and updated by another device.
+
+```bash
+cp examples/tcp_client_example.py /pyboard/main.py
+cp examples/boot.py /pyboard/boot.py
+repl
+```
+
+Inside the REPL press CTRL+D to perform a soft reboot. The device will serve
+several registers now. The log output might look similar to this
+
+```
+MPY: soft reboot
+System booted successfully!
+Waiting for WiFi connection...
+Waiting for WiFi connection...
+Connected to WiFi.
+('192.168.178.69', '255.255.255.0', '192.168.178.1', '192.168.178.1')
+Setting up registers ...
+Register setup done
+Serving as TCP client on 192.168.178.69:502
+```
+
+#### Host
+
+The host, former known as master, requests and updates some dummy registers of
+another device.
+
+```bash
+cp examples/tcp_host_example.py /pyboard/main.py
+cp examples/boot.py /pyboard/boot.py
+repl
+```
+
+Inside the REPL press CTRL+D to perform a soft reboot. The device will request
+and update registers of the Client after a few seconds. The log output might
+look similar to this
+
+```
+MPY: soft reboot
+System booted successfully!
+Waiting for WiFi connection...
+Waiting for WiFi connection...
+Connected to WiFi.
+('192.168.178.42', '255.255.255.0', '192.168.178.1', '192.168.178.1')
+Requesting and updating data on TCP client at 192.168.178.69:502
+
+Status of COIL 123: [True, False, False, False, False, False, False, False]
+Result of setting COIL 123: True
+Status of COIL 123: [False, False, False, False, False, False, False, False]
+
+Status of HREG 93: (44,)
+Result of setting HREG 93: True
+Status of HREG 93: (44,)
+
+Status of IST 67: [False, False, False, False, False, False, False, False]
+Status of IREG 10: (60001,)
+
+Finished requesting/setting data on client
+MicroPython v1.18 on 2022-01-17; ESP32 module (spiram) with ESP32
+Type "help()" for more information.
+>>>
+```
+
+<!--
+### RTU
+
+Get two UART/RS485 capable boards up and running, collecting and setting data
+on each other.
+-->
+
+<!-- Links -->
+[ref-github-be-modbus-wrapper]: https://github.com/brainelectronics/be-modbus-wrapper
diff --git a/changelog.md b/changelog.md
index 410f37b..4683ee5 100644
--- a/changelog.md
+++ b/changelog.md
@@ -15,6 +15,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <!-- ## [Unreleased] -->
 
 ## Released
+## [1.2.0] - 2022-11-13
+### Added
+- [TCP host example script](examples/tcp_host_example.py)
+- JSON file to set registers on TCP/RTU device
+- Bash script to wrap manipulation of TCP modbus register data
+- [Example boot script](examples/boot.py)
+- TOC in [README](README.md)
+- Use [changelog-based-release action](https://github.com/brainelectronics/changelog-based-release) to create a draft release with every merge to develop
+- Use [changelog-based-release action](https://github.com/brainelectronics/changelog-based-release) to create a drafted prerelease release with every PR build, see #20
+- [USAGE](USAGE.md) and [SETUP](SETUP.md) files with more details
+
+### Changed
+- Add more info to [TCP client example script](examples/tcp_client_example.py)
+- Update [modules submodule](modules) to `1.3.0`
+- Line breaks are no longer used in this changelog for enumerations
+- Issues are referenced as `#123` instead of `[#123][ref-issue-123]` to avoid explicit references at the bottom or some other location in the file
+- Scope of contents permissions in release and test release workflow is now `write` to use auto release creation
+
+### Fixed
+- Typo in [RTU client example script](examples/rtu_client_example.py)
+
 ## [1.1.1] - 2022-11-09
 ### Fixed
 - Default value of `setup_registers` function parameter `use_default_vals`
@@ -94,8 +115,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)
 
 <!-- Links -->
-[Unreleased]: https://github.com/brainelectronics/micropython-modbus/compare/1.1.1...develop
+[Unreleased]: https://github.com/brainelectronics/micropython-modbus/compare/1.2.0...develop
 
+[1.2.0]: https://github.com/brainelectronics/micropython-modbus/tree/1.2.0
 [1.1.1]: https://github.com/brainelectronics/micropython-modbus/tree/1.1.1
 [1.1.0]: https://github.com/brainelectronics/micropython-modbus/tree/1.1.0
 [1.0.0]: https://github.com/brainelectronics/micropython-modbus/tree/1.0.0
diff --git a/examples/boot.py b/examples/boot.py
new file mode 100644
index 0000000..d73e539
--- /dev/null
+++ b/examples/boot.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Boot script
+
+Do initial stuff here, similar to the setup() function on Arduino
+"""
+
+# This file is executed on every boot (including wake-boot from deepsleep)
+# import esp
+# esp.osdebug(None)
+# import webrepl
+# webrepl.start()
+
+print('System booted successfully!')
diff --git a/examples/rtu_client_example.py b/examples/rtu_client_example.py
index da22197..b0016bb 100644
--- a/examples/rtu_client_example.py
+++ b/examples/rtu_client_example.py
@@ -6,7 +6,7 @@
 
 Do your stuff here, this file is similar to the loop() function on Arduino
 
-Create a Modbus RUT client (slave) which can be requested for data or set with
+Create a Modbus RTU client (slave) which can be requested for data or set with
 specific values by a host device.
 
 The RTU communication pins can be choosen freely. The register definitions of
diff --git a/examples/set-example.json b/examples/set-example.json
new file mode 100644
index 0000000..1faa0b8
--- /dev/null
+++ b/examples/set-example.json
@@ -0,0 +1,30 @@
+{
+    "COILS": {
+        "EXAMPLE_COIL": {
+            "register": 123,
+            "val": 0,
+            "description": "Example COILS register, Coils (setter+getter) [0, 1]",
+            "unit": ""
+        }
+    },
+    "HREGS": {
+        "EXAMPLE_HREG": {
+            "register": 93,
+            "len": 1,
+            "val": 19,
+            "description": "Example HREGS register, Hregs (setter+getter) [0, 65535]",
+            "unit": ""
+        }
+    },
+    "META": {
+        "created": "12.11.2022",
+        "modified": "12.11.2022"
+    },
+    "CONNECTION": {
+        "type": "rtu",
+        "unit": 10,
+        "address": "/dev/tty.SLAB_USBtoUART",
+        "baudrate": 9600,
+        "mode": "slave"
+    }
+}
\ No newline at end of file
diff --git a/examples/tcp_client_example.py b/examples/tcp_client_example.py
index 2bbd4cb..48793e9 100644
--- a/examples/tcp_client_example.py
+++ b/examples/tcp_client_example.py
@@ -99,12 +99,22 @@
     register_definitions = json.load(file)
 """
 
+print('Setting up registers ...')
 # use the defined values of each register type provided by register_definitions
 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)
+print('Register setup done')
+
+print('Serving as TCP client on {}:{}'.format(local_ip, tcp_port))
 
 while True:
-    result = client.process()
+    try:
+        result = client.process()
+    except KeyboardInterrupt:
+        print('KeyboardInterrupt, stopping TCP client...')
+        break
+    except Exception as e:
+        print('Exception during execution: {}'.format(e))
 
 print("Finished providing/accepting data as client")
diff --git a/examples/tcp_host_example.py b/examples/tcp_host_example.py
new file mode 100644
index 0000000..8932818
--- /dev/null
+++ b/examples/tcp_host_example.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Main script
+
+Do your stuff here, this file is similar to the loop() function on Arduino
+
+Create a Modbus TCP host (master) which requests or sets data on a client
+device.
+
+The TCP port and IP address can be choosen freely. The register definitions of
+the client can be defined by the user.
+"""
+
+# system packages
+import network
+import time
+
+# import modbus host classes
+# from umodbus.modbus import ModbusTCP
+from umodbus.tcp import TCP as ModbusTCPMaster
+
+# ===============================================
+# connect to a network
+station = network.WLAN(network.STA_IF)
+if station.active() and station.isconnected():
+    station.disconnect()
+    time.sleep(1)
+station.active(False)
+time.sleep(1)
+station.active(True)
+
+station.connect('SSID', 'PASSWORD')
+time.sleep(1)
+
+while True:
+    print('Waiting for WiFi connection...')
+    if station.isconnected():
+        print('Connected to WiFi.')
+        print(station.ifconfig())
+        break
+    time.sleep(2)
+
+# ===============================================
+# TCP Slave setup
+slave_tcp_port = 502            # port to listen to
+slave_addr = 10                 # bus address of client
+# set IP address of the MicroPython device acting as client (slave)
+slave_ip = '192.168.178.69'     # IP address
+
+# TCP Master setup
+# act as host, get Modbus data via TCP from a client device
+# ModbusTCPMaster can make TCP requests to a client device to get/set data
+# host = ModbusTCP(
+host = ModbusTCPMaster(
+    slave_ip=slave_ip,
+    slave_port=slave_tcp_port,
+    timeout=5)              # optional, default 5
+
+# commond slave register setup, to be used with the Master example above
+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": 2,
+            "val": 60001
+        }
+    }
+}
+
+"""
+# alternatively the register definitions can also be loaded from a JSON file
+import json
+
+with open('registers/example.json', 'r') as file:
+    register_definitions = json.load(file)
+"""
+
+print('Requesting and updating data on TCP client at {}:{}'.
+      format(slave_ip, slave_tcp_port))
+print()
+
+# READ COILS
+coil_address = register_definitions['COILS']['EXAMPLE_COIL']['register']
+coil_qty = register_definitions['COILS']['EXAMPLE_COIL']['len']
+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))
+time.sleep(1)
+
+# WRITE COILS
+new_coil_val = 0
+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))
+time.sleep(1)
+
+# READ COILS again
+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))
+time.sleep(1)
+
+print()
+
+# READ HREGS
+hreg_address = register_definitions['HREGS']['EXAMPLE_HREG']['register']
+register_qty = register_definitions['HREGS']['EXAMPLE_HREG']['len']
+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))
+time.sleep(1)
+
+# WRITE HREGS
+new_hreg_val = 44
+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))
+time.sleep(1)
+
+# READ HREGS again
+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))
+time.sleep(1)
+
+print()
+
+# READ ISTS
+ist_address = register_definitions['ISTS']['EXAMPLE_ISTS']['register']
+input_qty = register_definitions['ISTS']['EXAMPLE_ISTS']['len']
+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))
+time.sleep(1)
+
+# READ IREGS
+ireg_address = register_definitions['IREGS']['EXAMPLE_IREG']['register']
+register_qty = register_definitions['IREGS']['EXAMPLE_IREG']['len']
+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))
+time.sleep(1)
+
+print()
+
+print("Finished requesting/setting data on client")
diff --git a/examples/write_registers_tcp.sh b/examples/write_registers_tcp.sh
new file mode 100644
index 0000000..eb3e1bc
--- /dev/null
+++ b/examples/write_registers_tcp.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# title         :write_registers_tcp.sh
+# description   :Write Modbus registers via TCP based on register JSON
+# author        :brainelectronics
+# date          :20221112
+# version       :0.1.0
+# usage         :sh write_registers_tcp.sh \
+#                192.168.178.69 \
+#                set-example.json \
+#                502
+#                Register file and modbus port are optional
+# notes         :Install python3 and its requirements file to use this script.
+# bash_version  :3.2.53(1)-release
+#=============================================================================
+
+connection_address=$1
+register_file_path=$2
+modbus_port=$3
+
+if [[ -z "$register_file_path" ]]; then
+    register_file_path=set-example.json
+    echo "No register file given, using default at $register_file_path"
+fi
+
+if [[ -z "$modbus_port" ]]; then
+    modbus_port=255
+    echo "No modbus unit given, using default $modbus_port"
+fi
+
+echo "Write registers defined in $register_file_path via TCP from $connection_address"
+
+python3 ../modules/write_device_info_registers.py \
+--file=$register_file_path \
+--connection=tcp \
+--address=$connection_address \
+--port=$modbus_port \
+--print \
+--pretty \
+--debug \
+--verbose=3
diff --git a/modules b/modules
index f5208ae..43bad71 160000
--- a/modules
+++ b/modules
@@ -1,1 +1,1 @@
-Subproject commit f5208ae40e48d3289fc5fddd7148ae3eb12d80cb
+Subproject commit 43bad716b7db27db07c94c2d279cee57d0c8c753


Test patch:
diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml
index fa4d5d9..03ca848 100644
--- a/.github/workflows/test-release.yaml
+++ b/.github/workflows/test-release.yaml
@@ -6,14 +6,14 @@ name: Upload Python Package to test.pypi.org
 on: [pull_request]
 
 permissions:
-  contents: read
+  contents: write
 
 jobs:
   test-deploy:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
     - name: Set up Python
       uses: actions/setup-python@v3
       with:
@@ -51,3 +51,16 @@ jobs:
         skip_existing: true
         verbose: true
         print_hash: true
+    - name: 'Create changelog based prerelease'
+      uses: brainelectronics/changelog-based-release@v1
+      with:
+        # note you'll typically need to create a personal access token
+        # with permissions to create releases in the other repo
+        # or you set the "contents" permissions to "write" as in this example
+        changelog-path: changelog.md
+        tag-name-prefix: ''
+        tag-name-extension: '-rc${{ github.run_number }}.dev${{ github.event.number }}'
+        release-name-prefix: ''
+        release-name-extension: '-rc${{ github.run_number }}.dev${{ github.event.number }}'
+        draft-release: true
+        prerelease: true