mirror of https://github.com/Nonannet/pyhoff.git
Compare commits
No commits in common. "2acffb90b5a71611d2792427b0b1643d87c98954" and "5aab2d6d74a9a49118584ceb481d6f3e71c2c613" have entirely different histories.
2acffb90b5
...
5aab2d6d74
|
@ -1,37 +0,0 @@
|
||||||
name: Build and Deploy Docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v3
|
|
||||||
with:
|
|
||||||
python-version: "3.x"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pip install sphinx sphinx_rtd_theme sphinx-autodoc-typehints myst-parser
|
|
||||||
- name: Generate Class List
|
|
||||||
run: |
|
|
||||||
pip install .
|
|
||||||
python ./docs/source/generate_class_list.py
|
|
||||||
- name: Build Docs
|
|
||||||
run: |
|
|
||||||
cd docs
|
|
||||||
sphinx-apidoc -o ./source/ ../src/ -M --no-toc
|
|
||||||
rm ./source/*.rst
|
|
||||||
make html
|
|
||||||
touch ./build/html/.nojekyll
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
uses: JamesIves/github-pages-deploy-action@v4
|
|
||||||
with:
|
|
||||||
branch: gh-pages
|
|
||||||
folder: docs/build/html
|
|
|
@ -1,35 +0,0 @@
|
||||||
name: Publish to PyPI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
name: Build and publish
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Ensure this is main branch
|
|
||||||
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
|
||||||
run: echo "Proceeding with publish"
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.11"
|
|
||||||
|
|
||||||
- name: Install build tools
|
|
||||||
run: python -m pip install --upgrade build twine
|
|
||||||
|
|
||||||
- name: Build package
|
|
||||||
run: python -m build
|
|
||||||
|
|
||||||
- name: Publish to PyPI
|
|
||||||
env:
|
|
||||||
TWINE_USERNAME: __token__
|
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
||||||
|
|
||||||
run: python -m twine upload dist/*
|
|
|
@ -71,9 +71,6 @@ instance/
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# Autogenerated documentation
|
|
||||||
docs/source/modules.md
|
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
.pybuilder/
|
.pybuilder/
|
||||||
target/
|
target/
|
||||||
|
|
|
@ -5,7 +5,7 @@ authors:
|
||||||
- family-names: Kruse
|
- family-names: Kruse
|
||||||
given-names: Nicolas
|
given-names: Nicolas
|
||||||
orcid: "https://orcid.org/0000-0001-6758-2269"
|
orcid: "https://orcid.org/0000-0001-6758-2269"
|
||||||
version: 1.1.0
|
version: 1.0.2
|
||||||
#date-released: "2025-04-01"
|
#date-released: "2025-04-01"
|
||||||
#identifiers:
|
#identifiers:
|
||||||
# - description: This is the collection of archived snapshots of all versions of My Research Software
|
# - description: This is the collection of archived snapshots of all versions of My Research Software
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Pyhoff
|
# pyhoff
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
The pyhoff package allows you to read and write the most common
|
The pyhoff package allows you to read and write the most common
|
||||||
|
@ -43,15 +43,15 @@ bk.add_bus_terminals(KL2404, KL2424, KL9100, KL1104, KL3202,
|
||||||
KL4004, KL9010)
|
KL4004, KL9010)
|
||||||
|
|
||||||
# Set 1. output of the first KL2404-type bus terminal to hi
|
# Set 1. output of the first KL2404-type bus terminal to hi
|
||||||
bk.select(KL2404, 0).write_coil(1, True)
|
KL2404.select(bk, 0).write_coil(1, True)
|
||||||
|
|
||||||
# read temperature from the 2. channel of the 2. KL3202-type
|
# read temperature from the 2. channel of the 2. KL3202-type
|
||||||
# bus terminal
|
# bus terminal
|
||||||
t = bk.select(KL3202, 1).read_temperature(2)
|
t = KL3202.select(bk, 1).read_temperature(2)
|
||||||
print(f"t = {t:.1f} °C")
|
print(f"t = {t:.1f} °C")
|
||||||
|
|
||||||
# Set 1. output of the 1. KL4002-type bus terminal to 4.2 V
|
# Set 1. output of the 1. KL4002-type bus terminal to 4.2 V
|
||||||
bk.select(KL4002, 0).set_voltage(1, 4.2)
|
KL4002.select(bk, 0).set_voltage(1, 4.2)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Minimal makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line, and also
|
|
||||||
# from the environment for the first two.
|
|
||||||
SPHINXOPTS ?=
|
|
||||||
SPHINXBUILD ?= sphinx-build
|
|
||||||
SOURCEDIR = source
|
|
||||||
BUILDDIR = build
|
|
||||||
|
|
||||||
# Put it first so that "make" without argument is like "make help".
|
|
||||||
help:
|
|
||||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
||||||
|
|
||||||
.PHONY: help Makefile
|
|
||||||
|
|
||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
||||||
%: Makefile
|
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
|
@ -1,35 +0,0 @@
|
||||||
@ECHO OFF
|
|
||||||
|
|
||||||
pushd %~dp0
|
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
|
||||||
set SPHINXBUILD=sphinx-build
|
|
||||||
)
|
|
||||||
set SOURCEDIR=source
|
|
||||||
set BUILDDIR=build
|
|
||||||
|
|
||||||
%SPHINXBUILD% >NUL 2>NUL
|
|
||||||
if errorlevel 9009 (
|
|
||||||
echo.
|
|
||||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
|
||||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
|
||||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
|
||||||
echo.may add the Sphinx directory to PATH.
|
|
||||||
echo.
|
|
||||||
echo.If you don't have Sphinx installed, grab it from
|
|
||||||
echo.https://www.sphinx-doc.org/
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "" goto help
|
|
||||||
|
|
||||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
||||||
goto end
|
|
||||||
|
|
||||||
:help
|
|
||||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
||||||
|
|
||||||
:end
|
|
||||||
popd
|
|
|
@ -1,32 +0,0 @@
|
||||||
# Configuration file for the Sphinx documentation builder.
|
|
||||||
#
|
|
||||||
# For the full list of built-in configuration values, see the documentation:
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
sys.path.insert(0, os.path.abspath("../src/"))
|
|
||||||
|
|
||||||
project = 'pyhoff'
|
|
||||||
copyright = '2025, Nicolas Kruse'
|
|
||||||
author = 'Nicolas Kruse'
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
||||||
|
|
||||||
extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_autodoc_typehints", "myst_parser"]
|
|
||||||
|
|
||||||
templates_path = ['_templates']
|
|
||||||
exclude_patterns = []
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
||||||
|
|
||||||
# html_theme = 'alabaster'
|
|
||||||
html_theme = 'sphinx_rtd_theme'
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
autodoc_inherit_docstrings = True
|
|
|
@ -1,44 +0,0 @@
|
||||||
import importlib
|
|
||||||
import inspect
|
|
||||||
import fnmatch
|
|
||||||
from io import TextIOWrapper
|
|
||||||
|
|
||||||
|
|
||||||
def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
|
||||||
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
|
|
||||||
classes = [
|
|
||||||
name for name, obj in inspect.getmembers(module, inspect.isclass)
|
|
||||||
if (obj.__module__ == module_name and
|
|
||||||
any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude) and
|
|
||||||
obj.__doc__ and '(Automatic generated stub)' not in obj.__doc__)
|
|
||||||
]
|
|
||||||
|
|
||||||
"""Write the classes to the file."""
|
|
||||||
f.write(f'## {title}\n\n')
|
|
||||||
if description:
|
|
||||||
f.write(f'{description}\n\n')
|
|
||||||
|
|
||||||
for cls in classes:
|
|
||||||
f.write('```{eval-rst}\n')
|
|
||||||
f.write(f'.. autoclass:: {module_name}.{cls}\n')
|
|
||||||
f.write(' :members:\n')
|
|
||||||
f.write(' :undoc-members:\n')
|
|
||||||
f.write(' :show-inheritance:\n')
|
|
||||||
f.write(' :inherited-members:\n')
|
|
||||||
if title != 'Base classes':
|
|
||||||
f.write(' :exclude-members: select\n')
|
|
||||||
f.write('```\n\n')
|
|
||||||
|
|
||||||
|
|
||||||
with open('docs/source/modules.md', 'w') as f:
|
|
||||||
f.write('# Classes\n\n')
|
|
||||||
write_classes(f, ['BK*', 'WAGO_750_352'], 'pyhoff.devices', title='Bus coupler',
|
|
||||||
description='These classes are bus couplers and are used to connect the IO bus terminals to a Ethernet interface.')
|
|
||||||
write_classes(f, ['KL*'], 'pyhoff.devices', title='Beckhoff bus terminals')
|
|
||||||
write_classes(f, ['WAGO*'], 'pyhoff.devices', title='WAGO bus terminals', exclude=['WAGO_750_352'])
|
|
||||||
write_classes(f, ['*'], 'pyhoff', title='Base classes',
|
|
||||||
description='These classes are base classes for devices and are typically not used directly.')
|
|
||||||
write_classes(f, ['*'], 'pyhoff.modbus', title='Modbus',
|
|
||||||
description='This modbus implementation is used internally.')
|
|
|
@ -1,10 +0,0 @@
|
||||||
```{toctree}
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: Contents:
|
|
||||||
|
|
||||||
readme
|
|
||||||
modules
|
|
||||||
```
|
|
||||||
|
|
||||||
```{include} ../../README.md
|
|
||||||
```
|
|
|
@ -1,2 +0,0 @@
|
||||||
```{include} ../../README.md
|
|
||||||
```
|
|
|
@ -1,22 +1,21 @@
|
||||||
[project]
|
[project]
|
||||||
name = "pyhoff"
|
name = "pyhoff"
|
||||||
version = "1.1.1"
|
version = "1.0.2"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Nicolas Kruse", email="nicolas.kruse@nonan.net" },
|
{ name="Nicolas Kruse", email="nicolas.kruse@nonan.net" },
|
||||||
]
|
]
|
||||||
description = "The pyhoff package allows easy accessing of Beckhoff and Wago terminals with python over ModBus TCP"
|
description = "The pyhoff package allows easy accessing of Beckhoff and Wago terminals with python over ModBus TCP"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.9"
|
||||||
license = "MIT"
|
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://github.com/Nonannet/pyhoff"
|
Homepage = "https://github.com/Nonannet/pyhoff"
|
||||||
Issues = "https://github.com/Nonannet/pyhoff/issues"
|
Issues = "https://github.com/Nonannet/pyhoff/issues"
|
||||||
documentation = "https://nonannet.github.io/pyhoff/"
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=61.0", "wheel"]
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
from .modbus import SimpleModbusClient
|
from .modbus import SimpleModbusClient
|
||||||
from typing import Iterable, TypeVar
|
from typing import Type, Iterable
|
||||||
|
|
||||||
_BT = TypeVar('_BT', bound='BusTerminal')
|
|
||||||
|
|
||||||
|
|
||||||
def _is_bus_terminal(bt_type: type['BusTerminal']) -> bool:
|
def _is_bus_terminal(bt_type: Type['BusTerminal']) -> bool:
|
||||||
if BusTerminal.__name__ == bt_type.__name__:
|
if BusTerminal.__name__ == bt_type.__name__:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -45,20 +43,7 @@ class BusTerminal():
|
||||||
self._mixed_mapping = mixed_mapping
|
self._mixed_mapping = mixed_mapping
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def select(cls: type[_BT], bus_coupler: 'BusCoupler', terminal_number: int = 0) -> _BT:
|
def select(cls, bus_coupler: 'BusCoupler', terminal_number: int = 0) -> 'BusTerminal':
|
||||||
"""
|
|
||||||
Returns the n-th bus terminal instance of the parent class
|
|
||||||
specified by terminal_number.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bus_coupler: The bus coupler to which the terminal is connected.
|
|
||||||
terminal_number: The index of the bus terminal to return. Counted for
|
|
||||||
all bus terminals of the same type, not all bus terminals. Started for the
|
|
||||||
first terminal with 0
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The selected bus terminal instance.
|
|
||||||
"""
|
|
||||||
terminal_list = [bt for bt in bus_coupler.bus_terminals if isinstance(bt, cls)]
|
terminal_list = [bt for bt in bus_coupler.bus_terminals if isinstance(bt, cls)]
|
||||||
assert terminal_list, f"No instance of {cls.__name__} configured at this BusCoupler"
|
assert terminal_list, f"No instance of {cls.__name__} configured at this BusCoupler"
|
||||||
assert 0 <= terminal_number < len(terminal_list), f"Out of range, select in range: 0..{len(terminal_list) - 1}"
|
assert 0 <= terminal_number < len(terminal_list), f"Out of range, select in range: 0..{len(terminal_list) - 1}"
|
||||||
|
@ -240,7 +225,7 @@ class BusCoupler():
|
||||||
modbus: The underlying modbus client used for the connection.
|
modbus: The underlying modbus client used for the connection.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host: str, port: int = 502, bus_terminals: Iterable[type[BusTerminal]] = [],
|
def __init__(self, host: str, port: int = 502, bus_terminals: Iterable[Type[BusTerminal]] = [],
|
||||||
timeout: float = 5, watchdog: float = 0, debug: bool = False):
|
timeout: float = 5, watchdog: float = 0, debug: bool = False):
|
||||||
"""
|
"""
|
||||||
Instantiate a new bus coupler base class.
|
Instantiate a new bus coupler base class.
|
||||||
|
@ -278,7 +263,7 @@ class BusCoupler():
|
||||||
def _init_hardware(self, watchdog: float) -> None:
|
def _init_hardware(self, watchdog: float) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def add_bus_terminals(self, *new_bus_terminals: type[BusTerminal] | Iterable[type[BusTerminal]]) -> list[BusTerminal]:
|
def add_bus_terminals(self, *new_bus_terminals: Type[BusTerminal] | Iterable[Type[BusTerminal]]) -> list[BusTerminal]:
|
||||||
"""
|
"""
|
||||||
Add bus terminals to the bus coupler.
|
Add bus terminals to the bus coupler.
|
||||||
|
|
||||||
|
@ -289,7 +274,7 @@ class BusCoupler():
|
||||||
The corresponding list of bus terminal objects.
|
The corresponding list of bus terminal objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
terminal_classes: list[type[BusTerminal]] = []
|
terminal_classes: list[Type[BusTerminal]] = []
|
||||||
for element in new_bus_terminals:
|
for element in new_bus_terminals:
|
||||||
if isinstance(element, Iterable):
|
if isinstance(element, Iterable):
|
||||||
for bt in element:
|
for bt in element:
|
||||||
|
@ -331,28 +316,6 @@ class BusCoupler():
|
||||||
|
|
||||||
return self.bus_terminals
|
return self.bus_terminals
|
||||||
|
|
||||||
def select(self, bus_terminal_type: type[_BT], terminal_number: int = 0) -> _BT:
|
|
||||||
"""
|
|
||||||
Returns the n-th bus terminal instance of the given bus terminal type and
|
|
||||||
terminal index.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bus_terminals_type: The bus terminal class to select from.
|
|
||||||
terminal_number: The index of the bus terminal to return. Counted for
|
|
||||||
all bus terminals of the same type, not all bus terminals. Started for the
|
|
||||||
first terminal with 0
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The selected bus terminal instance.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> from pyhoff.devices import *
|
|
||||||
>>> bk = BK9050("172.16.17.1", bus_terminals=[KL2404, KL2424])
|
|
||||||
>>> # Select the first KL2425 terminal:
|
|
||||||
>>> kl2404 = bk.select(KL2424, 0)
|
|
||||||
"""
|
|
||||||
return bus_terminal_type.select(self, terminal_number)
|
|
||||||
|
|
||||||
def get_error(self) -> str:
|
def get_error(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get the last error message.
|
Get the last error message.
|
||||||
|
|
|
@ -330,7 +330,7 @@ class SimpleModbusClient:
|
||||||
address: The register address to read from.
|
address: The register address to read from.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The value of the coil or None if error
|
The value of the coil.
|
||||||
"""
|
"""
|
||||||
value = self.read_coils(address)
|
value = self.read_coils(address)
|
||||||
if value:
|
if value:
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
from pyhoff.devices import KL2404, KL2424, KL9100, KL1104, \
|
|
||||||
KL3202, KL4002, KL9188, KL3054, KL3214, KL4004, KL9010, BK9050
|
|
||||||
|
|
||||||
|
|
||||||
def test_readme_example():
|
|
||||||
# connect to the BK9050 by tcp/ip on default port 502
|
|
||||||
bk = BK9050("172.16.17.1")
|
|
||||||
|
|
||||||
# add all bus terminals connected to the bus coupler
|
|
||||||
# in the order of the physical arrangement
|
|
||||||
bk.add_bus_terminals(KL2404, KL2424, KL9100, KL1104, KL3202,
|
|
||||||
KL3202, KL4002, KL9188, KL3054, KL3214,
|
|
||||||
KL4004, KL9010)
|
|
||||||
|
|
||||||
# Set 1. output of the first KL2404-type bus terminal to hi
|
|
||||||
bk.select(KL2404, 0).write_coil(1, True)
|
|
||||||
|
|
||||||
# read temperature from the 2. channel of the 2. KL3202-type
|
|
||||||
# bus terminal
|
|
||||||
t = bk.select(KL3202, 1).read_temperature(2)
|
|
||||||
print(f"t = {t:.1f} °C")
|
|
||||||
|
|
||||||
# Set 1. output of the 1. KL4002-type bus terminal to 4.2 V
|
|
||||||
bk.select(KL4002, 0).set_voltage(1, 4.2)
|
|
Loading…
Reference in New Issue