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