diff --git a/.github/workflows/rtd.yml b/.github/workflows/rtd.yml new file mode 100644 index 0000000..64aaefa --- /dev/null +++ b/.github/workflows/rtd.yml @@ -0,0 +1,16 @@ +name: readthedocs/actions +on: + pull_request_target: + types: + - opened + +permissions: + pull-requests: write + +jobs: + documentation-links: + runs-on: ubuntu-latest + steps: + - uses: readthedocs/actions/preview@v1 + with: + project-slug: "readthedocs-preview" \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf..6c1bf1a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,10 +1,10 @@ # 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 +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = simpleble SOURCEDIR = source BUILDDIR = build diff --git a/docs/make.bat b/docs/make.bat index 9534b01..47d656b 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,35 +1,36 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%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.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=simpleble + +if "%1" == "" goto help + +%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.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt index 53fc1f3..7e95244 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ sphinx==7.1.2 sphinx-rtd-theme==1.3.0rc1 +sphinx-copybutton==0.5.2 diff --git a/docs/source/cli.rst b/docs/source/cli.rst new file mode 100644 index 0000000..cb59c71 --- /dev/null +++ b/docs/source/cli.rst @@ -0,0 +1,14 @@ +Command Line Interface +====================== + +The **python-thingset** library implements Python client for the `ThingSet `_ +protocol. + +.. code-block:: python + :linenos: + + from python_thingset import ThingSet + + with ThingSet() as ts: + response = ts.get(0xF03) + print(response) # 0x85 (CONTENT): native_posix diff --git a/docs/source/conf.py b/docs/source/conf.py index 2e10c5e..61953d9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,35 +1,162 @@ +# -*- coding: utf-8 -*- +# # Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/stable/config -# -- Project information +# -- Path setup -------------------------------------------------------------- -project = 'python_thingset' -copyright = '2025' +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + +# -- Project information ----------------------------------------------------- + +project = 'Python ThingSet' +copyright = '2025, Brill Power' author = 'Adam Mitchell' -release = '0.1' -version = '0.2.3' +# The short X.Y version +version = '1' +# The full version, including alpha/beta/rc tags +release = '0.0.1' + -# -- General configuration +# -- General configuration --------------------------------------------------- +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. extensions = [ - 'sphinx.ext.duration', - 'sphinx.ext.doctest', 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', + 'sphinx.ext.napoleon', + 'sphinx_copybutton', ] -intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), -} -intersphinx_disabled_domains = ['std'] - +# Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# -- Options for HTML output +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = [] +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# html_theme = 'sphinx_rtd_theme' -# -- Options for EPUB output -epub_show_urls = 'footnote' +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pytsdoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'python_thingset.tex', 'Python ThingSet Documentation', + 'Adam Mitchell', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pyts', 'Python ThingSet Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'pyts', 'Python Thingset Documentation', + author, 'pyts', 'ThingSet client for Python.', + 'Miscellaneous'), +] + + +# -- Extension configuration ------------------------------------------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index a744f72..925dedf 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,4 +1,32 @@ -python_thingset +python-thingset =============== -Hello, world! +The **python-thingset** library implements Python client for the `ThingSet `_ +protocol. Binary mode is supported when using either the CAN or TCP/IP transports and text mode is +supported when using the Serial transport. + +As well as providing an importable Python package, a fully-featured command line tool is included to +enable simple interaction with ThingSet-enabled devices. + +Below is a brief example showing how to use **python-thingset**. By default, if no arguments are +provided when instantiating a `ThingSet()` object, the Socket transport will be used and will attempt +to connect to a client running on *127.0.0.1*. In this example, a client defines a property containing +the string `native_posix` with the property identifier `0xF03` which is retrieved by the code below. + +.. code-block:: python + :linenos: + + from python_thingset import ThingSet + + with ThingSet() as ts: + response = ts.get(0xF03) + print(response) # 0x85 (CONTENT): native_posix + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + installation + usage + cli diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 0000000..4ffc287 --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,30 @@ +Installation +============ + +Installing **python-thingset** is very simple as the package is available on +`PyPI `_. + +To install the package: + +.. code-block:: shell + :linenos: + + pip install python-thingset + +To install the optional development dependencies required for linting and running +unit tests (quotation marks (`'...'`) may be required if using a shell like Zsh to +avoid issues whereby the square brackets are misinterpreted): + +.. code-block:: shell + :linenos: + + pip install 'python-thingset[dev]' + +To install the package in editable mode (only possible locally, so it is necessary +to clone the repository): + +.. code-block:: shell + :linenos: + + cd python-thingset + pip install -e . diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..db1fe82 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +python_thingset +=============== + +.. toctree:: + :maxdepth: 4 + + python_thingset diff --git a/docs/source/python_thingset.backends.rst b/docs/source/python_thingset.backends.rst new file mode 100644 index 0000000..a9e10cb --- /dev/null +++ b/docs/source/python_thingset.backends.rst @@ -0,0 +1,45 @@ +python\_thingset.backends package +================================= + +Submodules +---------- + +python\_thingset.backends.backend module +---------------------------------------- + +.. automodule:: python_thingset.backends.backend + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.backends.can module +------------------------------------ + +.. automodule:: python_thingset.backends.can + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.backends.serial module +--------------------------------------- + +.. automodule:: python_thingset.backends.serial + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.backends.socket module +--------------------------------------- + +.. automodule:: python_thingset.backends.socket + :members: + :show-inheritance: + :undoc-members: + +Module contents +--------------- + +.. automodule:: python_thingset.backends + :members: + :show-inheritance: + :undoc-members: diff --git a/docs/source/python_thingset.encoders.rst b/docs/source/python_thingset.encoders.rst new file mode 100644 index 0000000..431ba7c --- /dev/null +++ b/docs/source/python_thingset.encoders.rst @@ -0,0 +1,29 @@ +python\_thingset.encoders package +================================= + +Submodules +---------- + +python\_thingset.encoders.binary module +--------------------------------------- + +.. automodule:: python_thingset.encoders.binary + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.encoders.text module +------------------------------------- + +.. automodule:: python_thingset.encoders.text + :members: + :show-inheritance: + :undoc-members: + +Module contents +--------------- + +.. automodule:: python_thingset.encoders + :members: + :show-inheritance: + :undoc-members: diff --git a/docs/source/python_thingset.rst b/docs/source/python_thingset.rst new file mode 100644 index 0000000..4ac8664 --- /dev/null +++ b/docs/source/python_thingset.rst @@ -0,0 +1,70 @@ +python\_thingset package +======================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + python_thingset.backends + python_thingset.encoders + +Submodules +---------- + +python\_thingset.cli module +--------------------------- + +.. automodule:: python_thingset.cli + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.client module +------------------------------ + +.. automodule:: python_thingset.client + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.id module +-------------------------- + +.. automodule:: python_thingset.id + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.log module +--------------------------- + +.. automodule:: python_thingset.log + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.response module +-------------------------------- + +.. automodule:: python_thingset.response + :members: + :show-inheritance: + :undoc-members: + +python\_thingset.thingset module +-------------------------------- + +.. automodule:: python_thingset.thingset + :members: + :show-inheritance: + :undoc-members: + +Module contents +--------------- + +.. automodule:: python_thingset + :members: + :show-inheritance: + :undoc-members: diff --git a/docs/source/usage.rst b/docs/source/usage.rst new file mode 100644 index 0000000..33cb1b1 --- /dev/null +++ b/docs/source/usage.rst @@ -0,0 +1,23 @@ +Usage +===== + +The **python-thingset** library implements Python client for the `ThingSet `_ +protocol. Binary mode is supported when using either the CAN or TCP/IP transports and text mode is +supported when using the Serial transport. + +As well as providing an importable Python package, a fully-featured command line tool is included to +enable simple interaction with ThingSet-enabled devices. + +Below is a brief example showing how to use **python-thingset**. By default, if no arguments are +provided when instantiating a `ThingSet()` object, the Socket transport will be used and will attempt +to connect to a client running on *127.0.0.1*. In this example, a client defines a property containing +the string `native_posix` with the property identifier `0xF03` which is retrieved by the code below. + +.. code-block:: python + :linenos: + + from python_thingset import ThingSet + + with ThingSet() as ts: + response = ts.get(0xF03) + print(response) # 0x85 (CONTENT): native_posix diff --git a/python_thingset/client.py b/python_thingset/client.py index 3dfa2e1..02dbf62 100644 --- a/python_thingset/client.py +++ b/python_thingset/client.py @@ -15,6 +15,11 @@ class ThingSetClient(ABC): + """_summary_ + + Args: + ABC (_type_): _description_ + """ def fetch( self, parent_id: Union[int, str], @@ -22,6 +27,17 @@ def fetch( node_id: Union[int, None] = None, get_paths: bool = True, ) -> ThingSetResponse: + """_summary_ + + Args: + parent_id (Union[int, str]): _description_ + ids (List[Union[int, str]]): _description_ + node_id (Union[int, None], optional): _description_. Defaults to None. + get_paths (bool, optional): _description_. Defaults to True. + + Returns: + ThingSetResponse: _description_ + """ values = [] self._send(self.encode_fetch(parent_id, ids), node_id) @@ -57,6 +73,16 @@ def get( node_id: Union[int, None] = None, get_paths: bool = True, ) -> ThingSetResponse: + """_summary_ + + Args: + value_id (Union[int, str]): _description_ + node_id (Union[int, None], optional): _description_. Defaults to None. + get_paths (bool, optional): _description_. Defaults to True. + + Returns: + ThingSetResponse: _description_ + """ values = [] self._send(self.encode_get(value_id), node_id) @@ -82,6 +108,17 @@ def update( node_id: Union[int, None] = None, parent_id: Union[int, None] = None, ) -> ThingSetResponse: + """_summary_ + + Args: + value_id (Union[int, str]): _description_ + value (Any): _description_ + node_id (Union[int, None], optional): _description_. Defaults to None. + parent_id (Union[int, None], optional): _description_. Defaults to None. + + Returns: + ThingSetResponse: _description_ + """ self._send(self.encode_update(parent_id, value_id, value), node_id) return ThingSetResponse(self.backend, self._recv()) @@ -91,6 +128,16 @@ def exec( args: Union[List[Any], None], node_id: Union[int, None] = None, ) -> ThingSetResponse: + """_summary_ + + Args: + value_id (Union[int, str]): _description_ + args (Union[List[Any], None]): _description_ + node_id (Union[int, None], optional): _description_. Defaults to None. + + Returns: + ThingSetResponse: _description_ + """ self._send(self.encode_exec(value_id, args), node_id) return ThingSetResponse(self.backend, self._recv()) @@ -115,14 +162,27 @@ def _create_value( @abstractmethod def disconnect(self) -> None: + """_summary_ + """ pass @abstractmethod def _send(self, data: bytes, node_id: Union[int, None]) -> None: + """_summary_ + + Args: + data (bytes): _description_ + node_id (Union[int, None]): _description_ + """ pass @abstractmethod def _recv(self) -> bytes: + """_summary_ + + Returns: + bytes: _description_ + """ pass @property