diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..5fb23eea --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: cc0d7a238108b91a71d7022e49a51a70 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/_api.doctree b/.doctrees/_api.doctree new file mode 100644 index 00000000..1c1dd662 Binary files /dev/null and b/.doctrees/_api.doctree differ diff --git a/.doctrees/developer_guide/developing_lewis.doctree b/.doctrees/developer_guide/developing_lewis.doctree new file mode 100644 index 00000000..713bb527 Binary files /dev/null and b/.doctrees/developer_guide/developing_lewis.doctree differ diff --git a/.doctrees/developer_guide/framework_details.doctree b/.doctrees/developer_guide/framework_details.doctree new file mode 100644 index 00000000..fbb178b6 Binary files /dev/null and b/.doctrees/developer_guide/framework_details.doctree differ diff --git a/.doctrees/developer_guide/release_checklist.doctree b/.doctrees/developer_guide/release_checklist.doctree new file mode 100644 index 00000000..eaf1f3ed Binary files /dev/null and b/.doctrees/developer_guide/release_checklist.doctree differ diff --git a/.doctrees/developer_guide/writing_devices.doctree b/.doctrees/developer_guide/writing_devices.doctree new file mode 100644 index 00000000..0c3ea1fb Binary files /dev/null and b/.doctrees/developer_guide/writing_devices.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 00000000..a612634a Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/generated/lewis.adapters.doctree b/.doctrees/generated/lewis.adapters.doctree new file mode 100644 index 00000000..8ca55e7b Binary files /dev/null and b/.doctrees/generated/lewis.adapters.doctree differ diff --git a/.doctrees/generated/lewis.adapters.epics.doctree b/.doctrees/generated/lewis.adapters.epics.doctree new file mode 100644 index 00000000..d9e6c26a Binary files /dev/null and b/.doctrees/generated/lewis.adapters.epics.doctree differ diff --git a/.doctrees/generated/lewis.adapters.modbus.doctree b/.doctrees/generated/lewis.adapters.modbus.doctree new file mode 100644 index 00000000..089295eb Binary files /dev/null and b/.doctrees/generated/lewis.adapters.modbus.doctree differ diff --git a/.doctrees/generated/lewis.adapters.stream.doctree b/.doctrees/generated/lewis.adapters.stream.doctree new file mode 100644 index 00000000..ffe76bb7 Binary files /dev/null and b/.doctrees/generated/lewis.adapters.stream.doctree differ diff --git a/.doctrees/generated/lewis.core.adapters.doctree b/.doctrees/generated/lewis.core.adapters.doctree new file mode 100644 index 00000000..a2a3ca36 Binary files /dev/null and b/.doctrees/generated/lewis.core.adapters.doctree differ diff --git a/.doctrees/generated/lewis.core.approaches.doctree b/.doctrees/generated/lewis.core.approaches.doctree new file mode 100644 index 00000000..5bef648f Binary files /dev/null and b/.doctrees/generated/lewis.core.approaches.doctree differ diff --git a/.doctrees/generated/lewis.core.control_client.doctree b/.doctrees/generated/lewis.core.control_client.doctree new file mode 100644 index 00000000..5755a0fc Binary files /dev/null and b/.doctrees/generated/lewis.core.control_client.doctree differ diff --git a/.doctrees/generated/lewis.core.control_server.doctree b/.doctrees/generated/lewis.core.control_server.doctree new file mode 100644 index 00000000..a5133c3d Binary files /dev/null and b/.doctrees/generated/lewis.core.control_server.doctree differ diff --git a/.doctrees/generated/lewis.core.devices.doctree b/.doctrees/generated/lewis.core.devices.doctree new file mode 100644 index 00000000..9b8b13f7 Binary files /dev/null and b/.doctrees/generated/lewis.core.devices.doctree differ diff --git a/.doctrees/generated/lewis.core.doctree b/.doctrees/generated/lewis.core.doctree new file mode 100644 index 00000000..9d90b94b Binary files /dev/null and b/.doctrees/generated/lewis.core.doctree differ diff --git a/.doctrees/generated/lewis.core.exceptions.doctree b/.doctrees/generated/lewis.core.exceptions.doctree new file mode 100644 index 00000000..4b03dfcf Binary files /dev/null and b/.doctrees/generated/lewis.core.exceptions.doctree differ diff --git a/.doctrees/generated/lewis.core.logging.doctree b/.doctrees/generated/lewis.core.logging.doctree new file mode 100644 index 00000000..44061803 Binary files /dev/null and b/.doctrees/generated/lewis.core.logging.doctree differ diff --git a/.doctrees/generated/lewis.core.processor.doctree b/.doctrees/generated/lewis.core.processor.doctree new file mode 100644 index 00000000..98e655da Binary files /dev/null and b/.doctrees/generated/lewis.core.processor.doctree differ diff --git a/.doctrees/generated/lewis.core.simulation.doctree b/.doctrees/generated/lewis.core.simulation.doctree new file mode 100644 index 00000000..715e03b2 Binary files /dev/null and b/.doctrees/generated/lewis.core.simulation.doctree differ diff --git a/.doctrees/generated/lewis.core.statemachine.doctree b/.doctrees/generated/lewis.core.statemachine.doctree new file mode 100644 index 00000000..e9e18d5f Binary files /dev/null and b/.doctrees/generated/lewis.core.statemachine.doctree differ diff --git a/.doctrees/generated/lewis.core.utils.doctree b/.doctrees/generated/lewis.core.utils.doctree new file mode 100644 index 00000000..aacfa5e3 Binary files /dev/null and b/.doctrees/generated/lewis.core.utils.doctree differ diff --git a/.doctrees/generated/lewis.devices.chopper.devices.bearings.doctree b/.doctrees/generated/lewis.devices.chopper.devices.bearings.doctree new file mode 100644 index 00000000..cb0149c1 Binary files /dev/null and b/.doctrees/generated/lewis.devices.chopper.devices.bearings.doctree differ diff --git a/.doctrees/generated/lewis.devices.chopper.devices.device.doctree b/.doctrees/generated/lewis.devices.chopper.devices.device.doctree new file mode 100644 index 00000000..531a8dab Binary files /dev/null and b/.doctrees/generated/lewis.devices.chopper.devices.device.doctree differ diff --git a/.doctrees/generated/lewis.devices.chopper.devices.doctree b/.doctrees/generated/lewis.devices.chopper.devices.doctree new file mode 100644 index 00000000..58cb816d Binary files /dev/null and b/.doctrees/generated/lewis.devices.chopper.devices.doctree differ diff --git a/.doctrees/generated/lewis.devices.chopper.devices.states.doctree b/.doctrees/generated/lewis.devices.chopper.devices.states.doctree new file mode 100644 index 00000000..db28a884 Binary files /dev/null and b/.doctrees/generated/lewis.devices.chopper.devices.states.doctree differ diff --git a/.doctrees/generated/lewis.devices.chopper.doctree b/.doctrees/generated/lewis.devices.chopper.doctree new file mode 100644 index 00000000..aad519a3 Binary files /dev/null and b/.doctrees/generated/lewis.devices.chopper.doctree differ diff --git a/.doctrees/generated/lewis.devices.chopper.interfaces.doctree b/.doctrees/generated/lewis.devices.chopper.interfaces.doctree new file mode 100644 index 00000000..aae8e8c1 Binary files /dev/null and b/.doctrees/generated/lewis.devices.chopper.interfaces.doctree differ diff --git a/.doctrees/generated/lewis.devices.chopper.interfaces.epics_interface.doctree b/.doctrees/generated/lewis.devices.chopper.interfaces.epics_interface.doctree new file mode 100644 index 00000000..3e3d1ff9 Binary files /dev/null and b/.doctrees/generated/lewis.devices.chopper.interfaces.epics_interface.doctree differ diff --git a/.doctrees/generated/lewis.devices.doctree b/.doctrees/generated/lewis.devices.doctree new file mode 100644 index 00000000..30a52daa Binary files /dev/null and b/.doctrees/generated/lewis.devices.doctree differ diff --git a/.doctrees/generated/lewis.devices.julabo.devices.device.doctree b/.doctrees/generated/lewis.devices.julabo.devices.device.doctree new file mode 100644 index 00000000..289d2d8e Binary files /dev/null and b/.doctrees/generated/lewis.devices.julabo.devices.device.doctree differ diff --git a/.doctrees/generated/lewis.devices.julabo.devices.doctree b/.doctrees/generated/lewis.devices.julabo.devices.doctree new file mode 100644 index 00000000..5e25ebd0 Binary files /dev/null and b/.doctrees/generated/lewis.devices.julabo.devices.doctree differ diff --git a/.doctrees/generated/lewis.devices.julabo.devices.states.doctree b/.doctrees/generated/lewis.devices.julabo.devices.states.doctree new file mode 100644 index 00000000..611d0823 Binary files /dev/null and b/.doctrees/generated/lewis.devices.julabo.devices.states.doctree differ diff --git a/.doctrees/generated/lewis.devices.julabo.doctree b/.doctrees/generated/lewis.devices.julabo.doctree new file mode 100644 index 00000000..32035f2d Binary files /dev/null and b/.doctrees/generated/lewis.devices.julabo.doctree differ diff --git a/.doctrees/generated/lewis.devices.julabo.interfaces.doctree b/.doctrees/generated/lewis.devices.julabo.interfaces.doctree new file mode 100644 index 00000000..ad8cd31b Binary files /dev/null and b/.doctrees/generated/lewis.devices.julabo.interfaces.doctree differ diff --git a/.doctrees/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.doctree b/.doctrees/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.doctree new file mode 100644 index 00000000..6c526b26 Binary files /dev/null and b/.doctrees/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.doctree differ diff --git a/.doctrees/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.doctree b/.doctrees/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.doctree new file mode 100644 index 00000000..744a1d8e Binary files /dev/null and b/.doctrees/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.doctree differ diff --git a/.doctrees/generated/lewis.devices.linkam_t95.devices.device.doctree b/.doctrees/generated/lewis.devices.linkam_t95.devices.device.doctree new file mode 100644 index 00000000..192e881e Binary files /dev/null and b/.doctrees/generated/lewis.devices.linkam_t95.devices.device.doctree differ diff --git a/.doctrees/generated/lewis.devices.linkam_t95.devices.doctree b/.doctrees/generated/lewis.devices.linkam_t95.devices.doctree new file mode 100644 index 00000000..d456482d Binary files /dev/null and b/.doctrees/generated/lewis.devices.linkam_t95.devices.doctree differ diff --git a/.doctrees/generated/lewis.devices.linkam_t95.devices.states.doctree b/.doctrees/generated/lewis.devices.linkam_t95.devices.states.doctree new file mode 100644 index 00000000..c22c3ad2 Binary files /dev/null and b/.doctrees/generated/lewis.devices.linkam_t95.devices.states.doctree differ diff --git a/.doctrees/generated/lewis.devices.linkam_t95.doctree b/.doctrees/generated/lewis.devices.linkam_t95.doctree new file mode 100644 index 00000000..53adbd03 Binary files /dev/null and b/.doctrees/generated/lewis.devices.linkam_t95.doctree differ diff --git a/.doctrees/generated/lewis.devices.linkam_t95.interfaces.doctree b/.doctrees/generated/lewis.devices.linkam_t95.interfaces.doctree new file mode 100644 index 00000000..273feea3 Binary files /dev/null and b/.doctrees/generated/lewis.devices.linkam_t95.interfaces.doctree differ diff --git a/.doctrees/generated/lewis.devices.linkam_t95.interfaces.stream_interface.doctree b/.doctrees/generated/lewis.devices.linkam_t95.interfaces.stream_interface.doctree new file mode 100644 index 00000000..312fc30e Binary files /dev/null and b/.doctrees/generated/lewis.devices.linkam_t95.interfaces.stream_interface.doctree differ diff --git a/.doctrees/generated/lewis.doctree b/.doctrees/generated/lewis.doctree new file mode 100644 index 00000000..c1409864 Binary files /dev/null and b/.doctrees/generated/lewis.doctree differ diff --git a/.doctrees/generated/lewis.examples.doctree b/.doctrees/generated/lewis.examples.doctree new file mode 100644 index 00000000..55ac695c Binary files /dev/null and b/.doctrees/generated/lewis.examples.doctree differ diff --git a/.doctrees/generated/lewis.examples.dual_device.doctree b/.doctrees/generated/lewis.examples.dual_device.doctree new file mode 100644 index 00000000..9acfdce8 Binary files /dev/null and b/.doctrees/generated/lewis.examples.dual_device.doctree differ diff --git a/.doctrees/generated/lewis.examples.example_motor.doctree b/.doctrees/generated/lewis.examples.example_motor.doctree new file mode 100644 index 00000000..07aa36b7 Binary files /dev/null and b/.doctrees/generated/lewis.examples.example_motor.doctree differ diff --git a/.doctrees/generated/lewis.examples.modbus_device.doctree b/.doctrees/generated/lewis.examples.modbus_device.doctree new file mode 100644 index 00000000..b212b98c Binary files /dev/null and b/.doctrees/generated/lewis.examples.modbus_device.doctree differ diff --git a/.doctrees/generated/lewis.examples.simple_device.doctree b/.doctrees/generated/lewis.examples.simple_device.doctree new file mode 100644 index 00000000..0de282b7 Binary files /dev/null and b/.doctrees/generated/lewis.examples.simple_device.doctree differ diff --git a/.doctrees/generated/lewis.examples.timeout_device.doctree b/.doctrees/generated/lewis.examples.timeout_device.doctree new file mode 100644 index 00000000..e923d4c0 Binary files /dev/null and b/.doctrees/generated/lewis.examples.timeout_device.doctree differ diff --git a/.doctrees/generated/lewis.scripts.control.doctree b/.doctrees/generated/lewis.scripts.control.doctree new file mode 100644 index 00000000..86cf6870 Binary files /dev/null and b/.doctrees/generated/lewis.scripts.control.doctree differ diff --git a/.doctrees/generated/lewis.scripts.doctree b/.doctrees/generated/lewis.scripts.doctree new file mode 100644 index 00000000..a73ce051 Binary files /dev/null and b/.doctrees/generated/lewis.scripts.doctree differ diff --git a/.doctrees/generated/lewis.scripts.run.doctree b/.doctrees/generated/lewis.scripts.run.doctree new file mode 100644 index 00000000..30000a69 Binary files /dev/null and b/.doctrees/generated/lewis.scripts.run.doctree differ diff --git a/.doctrees/generated/lewis.utils.byte_conversions.doctree b/.doctrees/generated/lewis.utils.byte_conversions.doctree new file mode 100644 index 00000000..66236d93 Binary files /dev/null and b/.doctrees/generated/lewis.utils.byte_conversions.doctree differ diff --git a/.doctrees/generated/lewis.utils.command_builder.doctree b/.doctrees/generated/lewis.utils.command_builder.doctree new file mode 100644 index 00000000..bf9eba4f Binary files /dev/null and b/.doctrees/generated/lewis.utils.command_builder.doctree differ diff --git a/.doctrees/generated/lewis.utils.constants.doctree b/.doctrees/generated/lewis.utils.constants.doctree new file mode 100644 index 00000000..229b7319 Binary files /dev/null and b/.doctrees/generated/lewis.utils.constants.doctree differ diff --git a/.doctrees/generated/lewis.utils.doctree b/.doctrees/generated/lewis.utils.doctree new file mode 100644 index 00000000..2f3d6d0f Binary files /dev/null and b/.doctrees/generated/lewis.utils.doctree differ diff --git a/.doctrees/generated/lewis.utils.replies.doctree b/.doctrees/generated/lewis.utils.replies.doctree new file mode 100644 index 00000000..f9bd8aba Binary files /dev/null and b/.doctrees/generated/lewis.utils.replies.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 00000000..c792fe45 Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/quickstart.doctree b/.doctrees/quickstart.doctree new file mode 100644 index 00000000..09fe892b Binary files /dev/null and b/.doctrees/quickstart.doctree differ diff --git a/.doctrees/release_notes/release_1_0_0.doctree b/.doctrees/release_notes/release_1_0_0.doctree new file mode 100644 index 00000000..e0d519c7 Binary files /dev/null and b/.doctrees/release_notes/release_1_0_0.doctree differ diff --git a/.doctrees/release_notes/release_1_0_1.doctree b/.doctrees/release_notes/release_1_0_1.doctree new file mode 100644 index 00000000..ee18e76b Binary files /dev/null and b/.doctrees/release_notes/release_1_0_1.doctree differ diff --git a/.doctrees/release_notes/release_1_0_2.doctree b/.doctrees/release_notes/release_1_0_2.doctree new file mode 100644 index 00000000..e31927cf Binary files /dev/null and b/.doctrees/release_notes/release_1_0_2.doctree differ diff --git a/.doctrees/release_notes/release_1_0_3.doctree b/.doctrees/release_notes/release_1_0_3.doctree new file mode 100644 index 00000000..b45fe6b7 Binary files /dev/null and b/.doctrees/release_notes/release_1_0_3.doctree differ diff --git a/.doctrees/release_notes/release_1_1_0.doctree b/.doctrees/release_notes/release_1_1_0.doctree new file mode 100644 index 00000000..2602b593 Binary files /dev/null and b/.doctrees/release_notes/release_1_1_0.doctree differ diff --git a/.doctrees/release_notes/release_1_1_1.doctree b/.doctrees/release_notes/release_1_1_1.doctree new file mode 100644 index 00000000..5e6fb2ea Binary files /dev/null and b/.doctrees/release_notes/release_1_1_1.doctree differ diff --git a/.doctrees/release_notes/release_1_2_0.doctree b/.doctrees/release_notes/release_1_2_0.doctree new file mode 100644 index 00000000..cc10a24b Binary files /dev/null and b/.doctrees/release_notes/release_1_2_0.doctree differ diff --git a/.doctrees/release_notes/release_1_2_1.doctree b/.doctrees/release_notes/release_1_2_1.doctree new file mode 100644 index 00000000..47ca24b9 Binary files /dev/null and b/.doctrees/release_notes/release_1_2_1.doctree differ diff --git a/.doctrees/release_notes/release_1_2_2.doctree b/.doctrees/release_notes/release_1_2_2.doctree new file mode 100644 index 00000000..04f55373 Binary files /dev/null and b/.doctrees/release_notes/release_1_2_2.doctree differ diff --git a/.doctrees/release_notes/release_1_3_0.doctree b/.doctrees/release_notes/release_1_3_0.doctree new file mode 100644 index 00000000..b9a8b927 Binary files /dev/null and b/.doctrees/release_notes/release_1_3_0.doctree differ diff --git a/.doctrees/release_notes/release_1_3_1.doctree b/.doctrees/release_notes/release_1_3_1.doctree new file mode 100644 index 00000000..21b28969 Binary files /dev/null and b/.doctrees/release_notes/release_1_3_1.doctree differ diff --git a/.doctrees/release_notes/release_1_3_2.doctree b/.doctrees/release_notes/release_1_3_2.doctree new file mode 100644 index 00000000..bb41b0b3 Binary files /dev/null and b/.doctrees/release_notes/release_1_3_2.doctree differ diff --git a/.doctrees/release_notes/release_1_3_3.doctree b/.doctrees/release_notes/release_1_3_3.doctree new file mode 100644 index 00000000..a83b9d9f Binary files /dev/null and b/.doctrees/release_notes/release_1_3_3.doctree differ diff --git a/.doctrees/release_notes/release_notes.doctree b/.doctrees/release_notes/release_notes.doctree new file mode 100644 index 00000000..ef950fc5 Binary files /dev/null and b/.doctrees/release_notes/release_notes.doctree differ diff --git a/.doctrees/user_guide/adapter_specifics.doctree b/.doctrees/user_guide/adapter_specifics.doctree new file mode 100644 index 00000000..72284305 Binary files /dev/null and b/.doctrees/user_guide/adapter_specifics.doctree differ diff --git a/.doctrees/user_guide/command_line_tools.doctree b/.doctrees/user_guide/command_line_tools.doctree new file mode 100644 index 00000000..082c1aef Binary files /dev/null and b/.doctrees/user_guide/command_line_tools.doctree differ diff --git a/.doctrees/user_guide/remote_access_devices.doctree b/.doctrees/user_guide/remote_access_devices.doctree new file mode 100644 index 00000000..c8d9bb32 Binary files /dev/null and b/.doctrees/user_guide/remote_access_devices.doctree differ diff --git a/.doctrees/user_guide/remote_access_simulation.doctree b/.doctrees/user_guide/remote_access_simulation.doctree new file mode 100644 index 00000000..48898059 Binary files /dev/null and b/.doctrees/user_guide/remote_access_simulation.doctree differ diff --git a/.doctrees/user_guide/usage_with_python.doctree b/.doctrees/user_guide/usage_with_python.doctree new file mode 100644 index 00000000..82b49607 Binary files /dev/null and b/.doctrees/user_guide/usage_with_python.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/_api.html b/_api.html new file mode 100644 index 00000000..89546e74 --- /dev/null +++ b/_api.html @@ -0,0 +1,143 @@ + + + + + + + + + API — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

API

+ + + + + + +

lewis

Lewis - a library for creating hardware device simulators

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_images/SimulationCycles.png b/_images/SimulationCycles.png new file mode 100644 index 00000000..aac5e26a Binary files /dev/null and b/_images/SimulationCycles.png differ diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 00000000..baee8d6e --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,159 @@ + + + + + + + + Overview: module code — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/adapters/epics.html b/_modules/lewis/adapters/epics.html new file mode 100644 index 00000000..e79a46a5 --- /dev/null +++ b/_modules/lewis/adapters/epics.html @@ -0,0 +1,863 @@ + + + + + + + + lewis.adapters.epics — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.adapters.epics

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+import inspect
+from datetime import datetime
+from functools import wraps
+
+from lewis.core.adapters import Adapter
+from lewis.core.devices import InterfaceBase
+from lewis.core.exceptions import (
+    AccessViolationException,
+    LewisException,
+    LimitViolationException,
+)
+from lewis.core.logging import has_log
+from lewis.core.utils import FromOptionalDependency, format_doc_text, seconds_since
+
+# pcaspy might not be available. To make EPICS-based adapters show up
+# in the listed adapters anyway dummy types are created in this case
+# and the failure is postponed to runtime, where a more appropriate
+# LewisException can be raised.
+missing_pcaspy_exception = LewisException(
+    "In order to use EPICS-interfaces, pcaspy must be installed:\n"
+    "\tpip install pcaspy\n"
+    "A fully working installation of EPICS-base is required for this package. "
+    "Please refer to the documentation for advice."
+)
+
+Driver, SimpleServer = FromOptionalDependency("pcaspy", missing_pcaspy_exception).do_import(
+    "Driver", "SimpleServer"
+)
+
+pcaspy_manager = FromOptionalDependency("pcaspy.driver", missing_pcaspy_exception).do_import(
+    "manager"
+)
+
+
+
+[docs] +class BoundPV: + """ + Class to represent PVs that are bound to an adapter + + This class is very similar to :class:`~lewis.adapters.stream.Func`, in that + it is the result of a binding operation between a user-specified :class:`PV`-object + and a Device and/or Adapter object. Also, it should rarely be used directly. objects + are generated automatically by :class:`EpicsAdapter`. + + The binding happens by supplying a ``target``-object which has an attribute or a property + named according to the property-name stored in the PV-object, and a ``meta_target``-object + which has an attribute named according to the meta_data_property in PV. + + The properties ``read_only``, ``config``, and ``poll_interval`` simply forward the + data of PV, while ``doc`` uses the target object to potentially obtain the property's + docstring. + + To get and set the value of the property on the target, the ``value``-property of + this class can be used, to get the meta data dict, there's a ``meta``-property. + + :param pv: PV object to bind to target and meta_target. + :param target: Object that has an attribute named pv.property. + :param meta_target: Object that has an attribute named pv.meta_data_property. + """ + + def __init__(self, pv, target, meta_target=None) -> None: + self._meta_target = meta_target + self._target = target + self._pv = pv + + @property + def value(self): + """Value of the bound property on the target.""" + return getattr(self._target, self._pv.property) + + @value.setter + def value(self, new_value) -> None: + if self.read_only: + raise AccessViolationException( + "The property {} is read only.".format(self._pv.property) + ) + + setattr(self._target, self._pv.property, new_value) + + @property + def meta(self): + """Value of the bound meta-property on the target.""" + if not self._pv.meta_data_property or not self._meta_target: + return {} + + return getattr(self._meta_target, self._pv.meta_data_property) + + @property + def read_only(self): + """True if the PV is read-only.""" + return self._pv.read_only + + @property + def config(self): + """Config dict passed on to pcaspy-machinery.""" + return self._pv.config + + @property + def poll_interval(self): + """Interval at which to update PV in pcaspy.""" + return self._pv.poll_interval + + @property + def doc(self): + """Docstring of property on target or override specified on PV-object.""" + return ( + self._pv.doc + or inspect.getdoc(getattr(type(self._target), self._pv.property, None)) + or "" + )
+ + + +
+[docs] +class PV: + """ + The PV-class is used to declare the EPICS-interface exposed by a sub-class of + EpicsAdapter. The ``target_property`` argument specifies which property of the adapter + the PV maps to. To make development easier it can also be a part of the exposed + device. If the property exists on both the Adapter-subclass and the device, the former + has precedence. This is useful for overriding behavior for protocol specific "quirks". + + If the PV should be read only, this needs to be specified via + the corresponding parameter. The information about the poll interval is used + py EpicsAdapter to update the PV in regular intervals. All other named arguments + are forwarded to the pcaspy server's `pvdb`, so it's possible to pass on + limits, types, enum-values and so on. + + In case those arguments change at runtime, it's possible to provide ``meta_data_property``, + which should contain the name of a property that returns a dict containing these values. + For example if limits change: + + .. sourcecode:: Python + + class Interface(EpicsInterface): + pvs = {"example": PV("example", meta_data_property="example_meta")} + + @property + def example_meta(self): + return { + "lolim": self.device._example_low_limit, + "hilim": self.device._example_high_limit, + } + + The PV infos are then updated together with the value, determined by ``poll_interval``. + + In cases where the device is accessed via properties alone, this class provides the possibility + to expose methods as PVs. A common use case would be to model a getter: + + .. sourcecode:: Python + + class SomeDevice(Device): + def get_example(self): + return 42 + + + class Interface(EpicsInterface): + pvs = {"example": PV("get_example")} + + It is also possible to model a getter/setter pair, in this case a tuple has to be provided: + + .. sourcecode:: Python + + class SomeDevice(Device): + _ex = 40 + + def get_example(self): + return self._ex + 2 + + def set_example(self, new_example): + self._ex = new_example - 2 + + + class Interface(EpicsInterface): + pvs = {"example": PV(("get_example", "set_example"))} + + Any of the two members in the tuple can be substituted with ``None`` in case it does not apply. + Besides method names it is also allowed to provide callables. Valid callables are for example + bound methods and free functions, but also lambda expressions and partials. + + There are however restrictions for the supplied functions (be it as method names or directly + as callables) with respect to their signature. Getter functions must be callable without any + arguments, setter functions must be callable with exactly one argument. The ``self`` of + methods does not count towards this. + + + :param target_property: Property or method name, getter function, tuple of getter/setter. + :param poll_interval: Update interval of the PV. + :param read_only: Should be True if the PV is read only. If not specified, the PV is + read_only if only a getter is supplied. + :param meta_data_property: Property or method name, getter function, tuple of getter/setter. + :param doc: Description of the PV. If not supplied, docstring of mapped property is used. + :param kwargs: Arguments forwarded into pcaspy pvdb-dict. + """ + + def __init__( + self, + target_property, + poll_interval=1.0, + read_only=False, + meta_data_property=None, + doc=None, + **kwargs, + ) -> None: + self.property = "value" + self.read_only = read_only + self.poll_interval = poll_interval + self.meta_data_property = "meta" + self.doc = doc + self.config = kwargs + + value = self._get_specification(target_property) + meta = self._get_specification(meta_data_property) + + self._specifications = {"value": value, "meta": meta} + +
+[docs] + def bind(self, *targets): + """ + Tries to bind the PV to one of the supplied targets. Targets are inspected according to + the order in which they are supplied. + + :param targets: Objects to inspect from. + :return: BoundPV instance with the PV bound to the target property. + """ + self.property = "value" + self.meta_data_property = "meta" + + return BoundPV( + self, + self._get_target(self.property, *targets), + self._get_target(self.meta_data_property, *targets), + )
+ + + def _get_specification(self, spec): + """ + Helper method to create a homogeneous representation of a specified getter or + getter/setter pair. + + :param spec: Function specification 'getter', (getter,) or (getter,setter) + :return: Harmonized getter/setter specification, (getter, setter) + """ + if spec is None or callable(spec) or isinstance(spec, str): + spec = (spec,) + if len(spec) == 1: + spec = (spec[0], None) + return spec + + def _get_target(self, prop, *targets): + """ + The actual target methods are retrieved (possibly from the list of targets) and a + wrapper-property is installed on a throwaway type that is specifically created for + the purpose of holding this property if necessary. In that case, an instance of this type + (with the wrapper-property forwarding calls to the correct target) is returned so + that :class:`BoundPV` can do the right thing. + + .. seealso:: :meth:`_create_getter`, :meth:`_create_setter` + + :param prop: Property, is either 'value' or 'meta'. + :param targets: List of targets with decreasing priority for finding the wrapped method. + :return: Target object to be used by :class:`BoundPV`. + """ + + if prop is None: + return None + + raw_getter, raw_setter = self._specifications.get(prop, (None, None)) + + target = None + + if isinstance(raw_getter, str): + target = next( + ( + obj + for obj in targets + if isinstance(getattr(type(obj), raw_getter, None), property) + or not callable(getattr(obj, raw_getter, lambda: True)) + ), + None, + ) + + if target is not None: + # If the property is an actual property and has no setter, read_only can be + # set to True at this point automatically. + target_prop = getattr(type(target), raw_getter, None) + + if prop == "value" and isinstance(target_prop, property) and target_prop.fset is None: + self.read_only = True + + # Now the target does not need to be constructed, property or meta_data_property + # needs to change. + setattr( + self, + "property" if prop == "value" else "meta_data_property", + raw_getter, + ) + return target + + getter = self._create_getter(raw_getter, *targets) + setter = self._create_setter(raw_setter, *targets) + + if getter is None and setter is None: + return None + + if prop == "value" and setter is None: + self.read_only = True + + return type(prop, (object,), {prop: property(getter, setter)})() + + def _create_getter(self, func, *targets): + """ + Returns a function wrapping the supplied function. The returned wrapper can be used as the + getter in a property definition. Raises a RuntimeError if the signature of the supplied + function is not compatible with the getter-concept (no arguments except self). + + :param func: Callable or name of method on one object in targets. + :param targets: List of targets with decreasing priority for finding func. + :return: Getter function for constructing a wrapper-property. + """ + if not func: + return None + + final_callable = self._get_callable(func, *targets) + + if not self._function_has_n_args(final_callable, 0): + raise RuntimeError( + "The function '{}' does not look like a getter function. A valid getter " + "function has no arguments that do not have a default. The self-argument of " + "methods does not count towards that number.".format(final_callable.__name__) + ) + + @wraps(final_callable) + def getter(obj): + return final_callable() + + return getter + + def _create_setter(self, func, *targets): + """ + Returns a function wrapping the supplied function. The returned wrapper can be used as the + setter in a property definition. Raises a RuntimeError if the signature of the supplied + function is not compatible with the setter-concept (exactly one argument except self). + + :param func: Callable or name of method on one object in targets. + :param targets: List of targets with decreasing priority for finding func. + :return: Setter function for constructing a wrapper-property or ``None``. + """ + if not func: + return None + + func = self._get_callable(func, *targets) + + if not self._function_has_n_args(func, 1): + raise RuntimeError( + "The function '{}' does not look like a setter function. A valid setter " + "function has exactly one argument without a default. The self-argument of " + "methods does not count towards that number.".format(func.__name__) + ) + + def setter(obj, value) -> None: + func(value) + + return setter + + def _get_callable(self, func, *targets): + """ + If func is already a callable, it is returned directly. If it's a string, it is assumed + to be a method on one of the objects supplied in targets and that is returned. If no + method with the specified name is found, an AttributeError is raised. + + :param func: Callable or name of method on one object in targets. + :param targets: List of targets with decreasing priority for finding func. + :return: Callable. + """ + if not callable(func): + func_name = func + func = next((getattr(obj, func, None) for obj in targets if func in dir(obj)), None) + + if not func: + raise AttributeError( + "No method with the name '{}' could be found on any of the target objects " + "(device, interface). Please check the spelling.".format(func_name) + ) + + return func + + def _function_has_n_args(self, func, n): + """ + Returns true if func has n arguments. Arguments with default and self for + methods are not considered. + """ + if inspect.ismethod(func): + n += 1 + + argspec = inspect.getargspec(func) + defaults = argspec.defaults or () + + return len(argspec.args) - len(defaults) == n
+ + + +@has_log +class PropertyExposingDriver(Driver): + def __init__(self, interface, device_lock) -> None: + super(PropertyExposingDriver, self).__init__() + + self._interface = interface + self._device_lock = device_lock + self._set_logging_context(interface) + + self._timers = {k: 0.0 for k in self._interface.bound_pvs.keys()} + self._last_update_call = None + + def write(self, pv, value) -> bool: + self.log.debug("PV put request: %s=%s", pv, value) + + pv_object = self._interface.bound_pvs.get(pv) + + if not pv_object: + return False + + try: + with self._device_lock: + pv_object.value = value + self.setParam(pv, pv_object.value) + + return True + except LimitViolationException as e: + self.log.warning( + "Rejected writing value %s to PV %s due to limit " "violation. %s", + value, + pv, + e, + ) + except AccessViolationException: + self.log.warning( + "Rejected writing value %s to PV %s due to access " "violation, PV is read-only.", + value, + pv, + ) + + return False + + def _get_param_info(self, pv, meta_keys): + """ + Get PV info fields from pcaspy's "manager" object. This function returns a dictionary + with info/value pairs, where each entry of meta_keys results in a dictionary entry if + pcaspy's PVInfo-object has such an attribute. Attributes that do not exist are ignored. + Valid attributes are the same as specified in the ``pvdb``-argument that + + :param pv: PV base name + :param meta_keys: List of keys for what information to obtain + :return: + """ + # TODO: Submit upstream patch to make this method available in base class + pv = pcaspy_manager.pvs[self.port][pv] + + info_dict = {} + for key in meta_keys: + if hasattr(pv.info, key): + info_dict[key] = getattr(pv.info, key) + + return info_dict + + def process_pv_updates(self, force=False) -> None: + """ + Update PV values that have changed for PVs that are due to update according to their + respective poll interval timers. + + :param force: If True, will force updates to all PVs regardless of timers. + """ + dt = seconds_since(self._last_update_call or datetime.now()) + + # Cache details of PVs that need to update + value_updates = [] + meta_updates = [] + + with self._device_lock: + for pv, pv_object in self._interface.bound_pvs.items(): + self._timers[pv] = self._timers.get(pv, 0.0) + dt + if self._timers[pv] >= pv_object.poll_interval or force: + try: + if self.getParam(pv) != pv_object.value or force: + value_updates.append((pv, pv_object.value)) + + pv_meta = pv_object.meta + if self._get_param_info(pv, pv_meta.keys()) != pv_meta or force: + meta_updates.append((pv, pv_meta)) + + except (AttributeError, TypeError): + self.log.exception("An error occurred while updating PV %s.", pv) + finally: + self._timers[pv] = 0.0 + + self._process_value_updates(value_updates) + self._process_meta_updates(meta_updates) + + self._last_update_call = datetime.now() + + def _process_value_updates(self, updates) -> None: + if updates: + update_log = [] + for pv, value in updates: + self.setParam(pv, value) + update_log.append("{}={}".format(pv, value)) + + self.log.info("Processed PV updates: %s", ", ".join(update_log)) + + # Calling this manually is only required for values, not for meta + self.updatePVs() + + def _process_meta_updates(self, updates) -> None: + if updates: + update_log = [] + for pv, info in updates: + self.setParamInfo(pv, info) + update_log.append("{}={}".format(pv, info)) + + self.log.info("Processed PV-info updates: %s", ", ".join(update_log)) + + +
+[docs] +class EpicsAdapter(Adapter): + """ + This adapter provides ChannelAccess server functionality through the pcaspy module. + + It's possible to configure the prefix for the PVs provided by this adapter. The + corresponding key in the ``options`` dictionary is called ``prefix``: + + .. sourcecode:: Python + + options = {"prefix": "PVPREFIX:"} + + :param options: Dictionary with options. + """ + + default_options = {"prefix": ""} + + def __init__(self, options=None) -> None: + super(EpicsAdapter, self).__init__(options) + + self._server = None + self._driver = None + + @property + def documentation(self): + pvs = [] + + for name, pv in self.interface.bound_pvs.items(): + complete_name = self._options.prefix + name + + data_type = pv.config.get("type", "float") + read_only_tag = ", read only" if pv.read_only else "" + + pvs.append( + "{} ({}{}):\n{}".format( + complete_name, data_type, read_only_tag, format_doc_text(pv.doc) + ) + ) + + return "\n\n".join([inspect.getdoc(self.interface) or "", "PVs\n==="] + pvs) + +
+[docs] + def start_server(self) -> None: + """ + Creates a pcaspy-server. + + .. note:: + + The server does not process requests unless :meth:`handle` is called regularly. + """ + if self._server is None: + self._server = SimpleServer() + self._server.createPV( + prefix=self._options.prefix, + pvdb={k: v.config for k, v in self.interface.bound_pvs.items()}, + ) + self._driver = PropertyExposingDriver( + interface=self.interface, device_lock=self.device_lock + ) + self._driver.process_pv_updates(force=True) + + self.log.info( + "Started serving PVs: %s", + ", ".join((self._options.prefix + pv for pv in self.interface.bound_pvs.keys())), + )
+ + +
+[docs] + def stop_server(self) -> None: + self._driver = None + self._server = None
+ + + @property + def is_running(self): + return self._server is not None + +
+[docs] + def handle(self, cycle_delay=0.1) -> None: + """ + Call this method to spend about ``cycle_delay`` seconds processing + requests in the pcaspy server. Under load, for example when running ``caget`` at a + high frequency, the actual time spent in the method may be much shorter. This effect + is not corrected for. + + :param cycle_delay: Approximate time to be spent processing requests in pcaspy server. + """ + if self._server is not None: + self._server.process(cycle_delay) + self._driver.process_pv_updates()
+
+ + + +
+[docs] +class EpicsInterface(InterfaceBase): + """ + Inheriting from this class provides an EPICS-interface to a device for use with + :class:`EpicsAdapter`. In the simplest case all that is required is to inherit + from this class and override the ``pvs``-member. It should be a dictionary + that contains PV-names (without prefix) as keys and instances of PV as + values. The prefix is handled by ``EpicsAdapter``. + + For a simple device with two properties, speed and position, the first of which + should be read-only, it's enough to define the following: + + .. sourcecode:: Python + + class SimpleDeviceEpicsInterface(EpicsInterface): + pvs = {"VELO": PV("speed", read_only=True), "POS": PV("position", lolo=0, hihi=100)} + + For more complex behavior, the interface could contain properties that do not + exist in the device itself. If the device should also have a PV called STOP + that "stops the device", the interface could look like this: + + .. sourcecode:: Python + + class SimpleDeviceEpicsInterface(EpicsInterface): + pvs = { + "VELO": PV("speed", read_only=True), + "POS": PV("position", lolo=0, hihi=100), + "STOP": PV("stop", type="int"), + } + + @property + def stop(self): + return 0 + + @stop.setter + def stop(self, value): + if value == 1: + self.device.halt() + + Even though the device does *not* have a property called ``stop`` (but a method called + ``halt``), issuing the command + + :: + + $ caput STOP 1 + + will achieve the desired behavior, because ``EpicsInterface`` merges the properties + of the device into ``SimpleDeviceEpicsInterface`` itself, so that it is does not + matter whether the specified property in PV exists in the device or the adapter. + + The intention of this design is to keep device classes small and free of + protocol specific stuff, such as in the case above where stopping a device + via EPICS might involve writing a value to a PV, whereas other protocols may + offer an RPC-way of achieving the same thing. + """ + + protocol = "epics" + pvs = None + + def __init__(self) -> None: + super(EpicsInterface, self).__init__() + self.bound_pvs = None + + @property + def adapter(self): + return EpicsAdapter + + def _bind_device(self) -> None: + """ + This method transforms the ``self.pvs`` dict of :class:`PV` objects ``self.bound_pvs``, + a dict of :class:`BoundPV` objects, the keys are always the PV-names that are exposed + via ChannelAccess. + + In the transformation process, the method tries to find whether the attribute specified by + PV's ``property`` (and ``meta_data_property``) is part of the internally stored device + or the interface and constructs a BoundPV, which acts as a forwarder to the appropriate + objects. + """ + self.bound_pvs = {} + + for pv_name, pv in self.pvs.items(): + try: + self.bound_pvs[pv_name] = pv.bind(self, self.device) + except (AttributeError, RuntimeError) as e: + self.log.debug( + "An exception was caught during the binding step of PV '%s'.", + pv_name, + exc_info=e, + ) + raise LewisException( + "The binding step for PV '{}' failed, please check the interface-" + "definition or contact the device author. More information is " + "available with debug-level logging (-o debug).".format(pv_name) + )
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/adapters/modbus.html b/_modules/lewis/adapters/modbus.html new file mode 100644 index 00000000..1bf7580c --- /dev/null +++ b/_modules/lewis/adapters/modbus.html @@ -0,0 +1,806 @@ + + + + + + + + lewis.adapters.modbus — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.adapters.modbus

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module provides components to expose a Device via a Modbus-interface. The following resources
+were used as guidelines and references for implementing the protocol:
+
+ - http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf
+ - http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf
+ - https://github.com/sourceperl/pyModbusTCP
+ - https://github.com/bashwork/pymodbus
+
+.. note::
+
+    For an example how Modbus can be used in the current implementation, please look
+    at lewis/examples/modbus_device.
+"""
+
+import asyncore
+import socket
+import struct
+from copy import deepcopy
+from math import ceil
+
+from lewis.core.adapters import Adapter
+from lewis.core.devices import InterfaceBase
+from lewis.core.logging import has_log
+
+
+
+[docs] +class ModbusDataBank: + """ + Preliminary DataBank implementation for Modbus. + + This is a very generic implementation of a databank for Modbus. It's meant to set the + groundwork for future implementations. Only derived classes should be instantiated, not + this class directly. The signature of this __init__ method is subject to change. + + :param kwargs: Configuration + """ + + def __init__(self, **kwargs) -> None: + self._data = kwargs["data"] + self._start_addr = kwargs["start_addr"] + +
+[docs] + def get(self, addr, count): + """ + Read list of ``count`` values at ``addr`` memory location in DataBank. + + :param addr: Address to read from + :param count: Number of entries to retrieve + :return: list of entry values + :except IndexError: Raised if address range falls outside valid range + """ + addr -= self._start_addr + data = self._data[addr : addr + count] + if len(data) != count: + addr += self._start_addr + raise IndexError("Invalid address range [{:#06x} - {:#06x}]".format(addr, addr + count)) + return data
+ + +
+[docs] + def set(self, addr, values) -> None: + """ + Write list ``values`` to ``addr`` memory location in DataBank. + + :param addr: Address to write to + :param values: list of values to write + :except IndexError: Raised if address range falls outside valid range + """ + addr -= self._start_addr + end = addr + len(values) + if not 0 <= addr <= end <= len(self._data): + addr += self._start_addr + raise IndexError( + "Invalid address range [{:#06x} - {:#06x}]".format(addr, addr + len(values)) + ) + self._data[addr:end] = values
+
+ + + +
+[docs] +class ModbusBasicDataBank(ModbusDataBank): + """ + A basic ModbusDataBank instance. + + This type of DataBank simply serves as a memory space for Modbus requests to read from and + write to. It does not support binding addresses to attributes or functions of the device + or interface. Example usage: + + .. sourcecode:: Python + + di = ModbusBasicDataBank(False, 0x1000, 0x1FFF) + + :param default_value: Value to initialize memory with + :param start_addr: First valid address + :param last_addr: Last valid address + """ + + def __init__(self, default_value=0, start_addr=0x0000, last_addr=0xFFFF) -> None: + super(ModbusBasicDataBank, self).__init__( + start_addr=start_addr, data=[default_value] * (last_addr - start_addr + 1) + )
+ + + +
+[docs] +class ModbusDataStore: + """Convenience struct to hold the four types of DataBanks in Modbus""" + + def __init__(self, di=None, co=None, ir=None, hr=None) -> None: + self.di = di + self.co = co + self.ir = ir + self.hr = hr
+ + + +
+[docs] +class MBEX: + """Modbus standard exception codes""" + + ILLEGAL_FUNCTION = 0x01 + DATA_ADDRESS = 0x02 + DATA_VALUE = 0x03 + SLAVE_DEVICE_FAILURE = 0x04 + ACKNOWLEDGE = 0x05 + SLAVE_DEVICE_BUSY = 0x06 + MEMORY_PARITY_ERROR = 0x08 + GATEWAY_PATH_UNAVAILABLE = 0x0A + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0B
+ + + +
+[docs] +class ModbusTCPFrame: + """ + This class models a frame of the Modbus TCP protocol. + + It may be a request, a response or an exception. Typically, requests are constructed using the + init method, while responses and exceptions are constructed by called create_request or + create_exception on an instance that is a request. + + Note that data from the passed in bytearray stream is consumed. That is, bytes will be removed + from the front of the bytearray if construction is successful. + + :param stream: bytearray to consume data from to construct this frame. + :except EOFError: Not enough data for complete frame; no data consumed. + """ + + def __init__(self, stream=None) -> None: + self.transaction_id = 0 + self.protocol_id = 0 + self.length = 2 + self.unit_id = 0 + self.fcode = 0 + self.data = bytearray() + + if stream is not None: + self.from_bytearray(stream) + +
+[docs] + def from_bytearray(self, stream) -> None: + """ + Constructs this frame from input data stream, consuming as many bytes as necessary from + the beginning of the stream. + + If stream does not contain enough data to construct a complete modbus frame, an EOFError + is raised and no data is consumed. + + :param stream: bytearray to consume data from to construct this frame. + :except EOFError: Not enough data for complete frame; no data consumed. + """ + fmt = ">HHHBB" + size_header = struct.calcsize(fmt) + if len(stream) < size_header: + raise EOFError + + ( + self.transaction_id, + self.protocol_id, + self.length, + self.unit_id, + self.fcode, + ) = struct.unpack(fmt, bytes(stream[:size_header])) + + size_total = size_header + self.length - 2 + if len(stream) < size_total: + raise EOFError + + self.data = stream[size_header:size_total] + del stream[:size_total]
+ + +
+[docs] + def to_bytearray(self): + """ + Convert this frame into its bytearray representation. + + :return: bytearray representation of this frame. + """ + header = bytearray( + struct.pack( + ">HHHBB", + self.transaction_id, + self.protocol_id, + self.length, + self.unit_id, + self.fcode, + ) + ) + return header + self.data
+ + +
+[docs] + def is_valid(self): + """ + Check integrity and validity of this frame. + + :return: bool True if this frame is structurally valid. + """ + conditions = [ + self.protocol_id == 0, # Modbus always uses protocol 0 + 2 <= self.length <= 260, # Absolute length limits + len(self.data) == self.length - 2, # Total length matches data length + ] + return all(conditions)
+ + +
+[docs] + def create_exception(self, code): + """ + Create an exception frame based on this frame. + + :param code: Modbus exception code to use for this exception + :return: ModbusTCPFrame instance that represents an exception + """ + frame = deepcopy(self) + frame.length = 3 + frame.fcode += 0x80 + frame.data = bytearray(chr(code)) + return frame
+ + +
+[docs] + def create_response(self, data=None): + """ + Create a response frame based on this frame. + + :param data: Data section of response as bytearray. If None, request data section is kept. + :return: ModbusTCPFrame instance that represents a response + """ + frame = deepcopy(self) + if data is not None: + frame.data = data + frame.length = 2 + len(frame.data) + return frame
+
+ + + +
+[docs] +@has_log +class ModbusProtocol: + """ + This class implements the Modbus TCP Protocol. + + The user of this class should provide a ModbusDataStore instance that will be used to + fulfill read and write requests, and a callable `sender` which accepts one bytearray + parameter. The `sender` will be called whenever a response frame is generated, with a + bytearray containing the response frame as the parameter. + + Processing occurs when the user calls ModbusProtocol.process(), passing in the raw frame + data to process as a bytearray. The data may include multiple frames and partial frame + fragments. Any data that could not be processed (due to incomplete frames) is buffered for + the next call to process. + + :param sender: callable that accepts one bytearray parameter, called to send responses. + :param datastore: ModbusDataStore instance to reference when processing requests + """ + + def __init__(self, sender, datastore) -> None: + self._buffer = bytearray() + self._datastore = datastore + self._send = lambda req: sender(req.to_bytearray()) + + # Lookup table to handle requests as per Modbus Application Protocol v1.1b3, Section 6. + self._fcode_handler_map = { + 0x01: self._handle_read_coils, + 0x02: self._handle_read_discrete_inputs, + 0x03: self._handle_read_holding_registers, + 0x04: self._handle_read_input_registers, + 0x05: self._handle_write_single_coil, + 0x06: self._handle_write_single_register, + 0x0F: self._handle_write_multiple_coils, + 0x10: self._handle_write_multiple_registers, + } + +
+[docs] + def process(self, data, device_lock) -> None: + """ + Process as much of given data as possible. + + Any remainder, in case there is an incomplete frame at the end, is stored so that + processing may continue where it left off when more data is provided. + + :param data: Incoming byte data. Must be compatible with bytearray. + :param device_lock: threading.Lock instance that is acquired for device interaction. + """ + self._buffer.extend(bytearray(data)) + + with device_lock: + for request in self._buffered_requests(): + self.log.debug( + "Request: %s", + str(["{:#04x}".format(c) for c in request.to_bytearray()]), + ) + + handler = self._get_handler(request.fcode) + response = handler(request) + + self.log.debug( + "Response: %s", + str(["{:#04x}".format(c) for c in response.to_bytearray()]), + ) + + self._send(response)
+ + + def _buffered_requests(self): + """Generator to yield all complete modbus requests in the internal buffer""" + try: + while True: + # ModbusTCPFrame constructor consumes bytes from front of buffer + yield ModbusTCPFrame(self._buffer) + except EOFError: + pass + + def _get_handler(self, fcode): + """ + Get an appropriate handler function for given Function Code. + + Will always return a valid handler function. But, if the Function Code is invalid or not + supported, the handler function will merely return an ILLEGAL_FUNCTION exception frame. + + :param fcode: int Function Code which needs to be handled + :return: callable which takes one request frame parameter and returns a response frame + """ + return self._fcode_handler_map.get(fcode, self._illegal_function_exception) + + def _illegal_function_exception(self, request): + """Log and return an illegal function code exception""" + self.log.error("Unsupported Function Code: {0} ({0:#04x})".format(request.fcode)) + return request.create_exception(MBEX.ILLEGAL_FUNCTION) + + def _handle_read_coils(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.1 - (0x01) Read Coils + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + return self._do_read_bits(self._datastore.co, request) + + def _handle_read_discrete_inputs(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.2 - (0x02) Read Discrete Inputs + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + return self._do_read_bits(self._datastore.di, request) + + def _do_read_bits(self, databank, request): + """ + Shared handler for FC 0x01 and FC 0x02. + + :param databank: DataBank to execute against (di or co) + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + addr, count = struct.unpack(">HH", bytes(request.data)) + + if not 0x0001 <= count <= 0x07D0: + return request.create_exception(MBEX.DATA_VALUE) + + try: + bits = databank.get(addr, count) + bits = [bool(bit) for bit in bits] + except IndexError: + return request.create_exception(MBEX.DATA_ADDRESS) + + # Bits to bytes: LSB -> MSB, first byte -> last byte + byte_count = int(ceil(len(bits) / 8)) + byte_list = bytearray(byte_count) + for i, bit in enumerate(bits): + byte_list[i // 8] |= bit << i % 8 + + # Construct response + data = struct.pack(">B%dB" % byte_count, byte_count, *list(byte_list)) + return request.create_response(data) + + def _handle_read_holding_registers(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.3 - (0x03) Read Holding Registers + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + return self._do_read_registers(self._datastore.hr, request) + + def _handle_read_input_registers(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.4 - (0x04) Read Input Registers + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + return self._do_read_registers(self._datastore.ir, request) + + def _do_read_registers(self, databank, request): + """ + Shared handler for FC 0x03 and FC 0x04. + + :param databank: DataBank to execute against (ir or hr) + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + addr, count = struct.unpack(">HH", bytes(request.data)) + + if not 0x0001 <= count <= 0x007D: + return request.create_exception(MBEX.DATA_VALUE) + + try: + words = databank.get(addr, count) + words = [word & 0xFFFF for word in words] + except IndexError: + return request.create_exception(MBEX.DATA_ADDRESS) + + # Construct response + data = struct.pack(">B%dH" % len(words), len(words) * 2, *words) + return request.create_response(data) + + def _handle_write_single_coil(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.5 - (0x05) Write Single Coil + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + addr, value = struct.unpack(">HH", bytes(request.data)) + value = {0x0000: False, 0xFF00: True}.get(value, None) + + if value is None: + return request.create_exception(MBEX.DATA_VALUE) + + try: + self._datastore.co.set(addr, [value]) + except IndexError: + return request.create_exception(MBEX.DATA_ADDRESS) + + # Respond to confirm + return request.create_response() + + def _handle_write_single_register(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.6 - (0x06) Write Single Register + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + addr, value = struct.unpack(">HH", bytes(request.data)) + + try: + self._datastore.hr.set(addr, [value]) + except IndexError: + return request.create_exception(MBEX.DATA_ADDRESS) + + # Respond to confirm + return request.create_response() + + def _handle_write_multiple_coils(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.11 - (0x0F) Write Multiple Coils + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + addr, bit_count, byte_count = struct.unpack(">HHB", bytes(request.data[:5])) + data = request.data[5:] + + if not 0x0001 <= bit_count <= 0x07B0 or byte_count != ceil(bit_count / 8): + return request.create_exception(MBEX.DATA_VALUE) + + # Bytes to bits: first byte -> last byte, LSB -> MSB + bits = [False] * bit_count + for i in range(bit_count): + bits[i] = bool(data[i // 8] & (1 << i % 8)) + + try: + self._datastore.co.set(addr, bits) + except IndexError: + return request.create_exception(MBEX.DATA_ADDRESS) + + # Respond to confirm + return request.create_response(request.data[:4]) + + def _handle_write_multiple_registers(self, request): + """ + Handle request as per Modbus Application Protocol v1.1b3: + Section 6.12 - (0x10) Write Multiple registers + + :param request: ModbusTCPFrame containing the request + :return: ModbusTCPFrame response to the request + """ + addr, reg_count, byte_count = struct.unpack(">HHB", bytes(request.data[:5])) + data = request.data[5:] + + if not 0x0001 <= reg_count <= 0x007B or byte_count != reg_count * 2: + return request.create_exception(MBEX.DATA_VALUE) + + try: + words = list(struct.unpack(">%dH" % reg_count, data)) + self._datastore.hr.set(addr, words) + except IndexError: + return request.create_exception(MBEX.DATA_ADDRESS) + + # Respond to confirm + return request.create_response(request.data[:4])
+ + + +@has_log +class ModbusHandler(asyncore.dispatcher_with_send): + def __init__(self, sock, interface, server) -> None: + asyncore.dispatcher_with_send.__init__(self, sock=sock) + self._datastore = ModbusDataStore(interface.di, interface.co, interface.ir, interface.hr) + self._modbus = ModbusProtocol(self.send, self._datastore) + self._server = server + + self._set_logging_context(interface) + self.log.info("Client connected from %s:%s", *sock.getpeername()) + + def handle_read(self) -> None: + data = self.recv(8192) + self._modbus.process(data, self._server.device_lock) + + def handle_close(self) -> None: + self.log.info("Closing connection to client %s:%s", *self.socket.getpeername()) + self._server.remove_handler(self) + self.close() + + +@has_log +class ModbusServer(asyncore.dispatcher): + def __init__(self, host, port, interface, device_lock) -> None: + asyncore.dispatcher.__init__(self) + self.device_lock = device_lock + self.interface = interface + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind((host, port)) + self.listen(5) + + self._set_logging_context(interface) + self.log.info("Listening on %s:%s", host, port) + + self._accepted_connections = [] + + def handle_accept(self) -> None: + pair = self.accept() + if pair is not None: + sock, _ = pair + handler = ModbusHandler(sock, self.interface, self) + self._accepted_connections.append(handler) + + def remove_handler(self, handler) -> None: + self._accepted_connections.remove(handler) + + def handle_close(self) -> None: + self.log.info("Shutting down server, closing all remaining client connections.") + + for handler in self._accepted_connections: + handler.close() + self._accepted_connections = [] + self.close() + + +
+[docs] +class ModbusAdapter(Adapter): + default_options = {"bind_address": "0.0.0.0", "port": 502} + + def __init__(self, options=None) -> None: + super(ModbusAdapter, self).__init__(options) + self._server = None + +
+[docs] + def start_server(self) -> None: + self._server = ModbusServer( + self._options.bind_address, + self._options.port, + self.interface, + self.device_lock, + )
+ + +
+[docs] + def stop_server(self) -> None: + if self._server is not None: + self._server.close() + self._server = None
+ + + @property + def is_running(self): + return self._server is not None + +
+[docs] + def handle(self, cycle_delay=0.1) -> None: + asyncore.loop(cycle_delay, count=1)
+
+ + + +
+[docs] +class ModbusInterface(InterfaceBase): + protocol = "modbus" + di = None + co = None + ir = None + hr = None + + @property + def adapter(self): + return ModbusAdapter
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/adapters/stream.html b/_modules/lewis/adapters/stream.html new file mode 100644 index 00000000..fe025f64 --- /dev/null +++ b/_modules/lewis/adapters/stream.html @@ -0,0 +1,1040 @@ + + + + + + + + lewis.adapters.stream — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.adapters.stream

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+import asynchat
+import asyncore
+import inspect
+import re
+import socket
+from typing import NoReturn
+
+from scanf import scanf_compile
+
+from lewis.core.adapters import Adapter
+from lewis.core.devices import InterfaceBase
+from lewis.core.logging import has_log
+from lewis.core.utils import format_doc_text
+
+
+
+[docs] +@has_log +class StreamHandler(asynchat.async_chat): + def __init__(self, sock, target, stream_server) -> None: + asynchat.async_chat.__init__(self, sock=sock) + self.set_terminator(target.in_terminator.encode()) + self._readtimeout = target.readtimeout + self._readtimer = 0 + self._target = target + self._buffer = [] + + self._stream_server = stream_server + self._target.handler = self + + self._set_logging_context(target) + self.log.info("Client connected from %s:%s", *sock.getpeername()) + + def process(self, msec) -> None: + if not self._buffer: + return + + if self._readtimer >= self._readtimeout and self._readtimeout != 0: + if not self.get_terminator(): + # If no terminator is set, this timeout is the terminator + self.found_terminator() + else: + self._readtimer = 0 + request = self._get_request() + with self._stream_server.device_lock: + error = RuntimeError("ReadTimeout while waiting for command terminator.") + reply = self._handle_error(request, error) + self._send_reply(reply) + + if self._buffer: + self._readtimer += msec + + def collect_incoming_data(self, data) -> None: + self._buffer.append(data) + self._readtimer = 0 + + def _get_request(self): + request = b"".join(self._buffer) + self._buffer = [] + self.log.debug("Got request %s", request) + return request + + def _push(self, reply) -> None: + try: + if isinstance(reply, str): + reply = reply.encode() + out_terminator = ( + self._target.out_terminator.encode() + if isinstance(self._target.out_terminator, str) + else self._target.out_terminator + ) + self.push(reply + out_terminator) + except TypeError as e: + self.log.error("Problem creating reply, type error {}!".format(e)) + + def _send_reply(self, reply) -> None: + if reply is not None: + self.log.debug("Sending reply %s", reply) + self._push(reply) + + def _handle_error(self, request, error): + self.log.debug("Error while processing request", exc_info=error) + return self._target.handle_error(request, error) + + def found_terminator(self) -> None: + self._readtimer = 0 + + request = self._get_request() + + with self._stream_server.device_lock: + try: + cmd = next( + (cmd for cmd in self._target.bound_commands if cmd.can_process(request)), + None, + ) + + if cmd is None: + raise RuntimeError("None of the device's commands matched.") + + self.log.info( + "Processing request %s using command %s", + request, + cmd.matcher.pattern, + ) + + reply = cmd.process_request(request) + except Exception as error: + reply = self._handle_error(request, error) + + self._send_reply(reply) + + def unsolicited_reply(self, reply) -> None: + self.log.debug("Sending unsolicited reply %s", reply) + self._push(reply) + + def handle_close(self) -> None: + self.log.info("Closing connection to client %s:%s", *self.socket.getpeername()) + self._stream_server.remove_handler(self) + asynchat.async_chat.handle_close(self)
+ + + +@has_log +class StreamServer(asyncore.dispatcher): + def __init__(self, host, port, target, device_lock) -> None: + asyncore.dispatcher.__init__(self) + self.target = target + self.device_lock = device_lock + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.set_reuse_addr() + self.bind((host, port)) + self.listen(5) + + self._set_logging_context(target) + self.log.info("Listening on %s:%s", host, port) + + self._accepted_connections = [] + + def handle_accept(self) -> None: + pair = self.accept() + if pair is not None: + sock, addr = pair + handler = StreamHandler(sock, self.target, self) + + self._accepted_connections.append(handler) + + def remove_handler(self, handler) -> None: + self._accepted_connections.remove(handler) + + def close(self) -> None: + # As this is an old style class, the base class method must + # be called directly. This is important to still perform all + # the teardown-work that asyncore.dispatcher does. + self.log.info("Shutting down server, closing all remaining client connections.") + asyncore.dispatcher.close(self) + + # But in addition, close all open sockets and clear the connection list. + for handler in self._accepted_connections: + handler.close() + + self._accepted_connections = [] + + def process(self, msec) -> None: + for handler in self._accepted_connections: + handler.process(msec) + + +
+[docs] +class PatternMatcher: + """ + This class defines an interface for general command-matchers that use any kind of + technique to match a certain request in string form. It is used by :class:`Func` to check + whether a request can be processed using a function and to extract any function arguments. + + Sub-classes must implement all defined abstract methods/properties. + + .. seealso:: + + :class:`regex`, :class:`scanf` are concrete implementations of this class. + """ + + def __init__(self, pattern) -> None: + self._pattern = pattern + + @property + def pattern(self): + """The pattern definition used for matching a request.""" + return self._pattern + + @property + def arg_count(self) -> NoReturn: + """Number of arguments that are matched in a request.""" + raise NotImplementedError("The arg_count property must be implemented.") + + @property + def argument_mappings(self) -> NoReturn: + """Mapping functions that can be applied to the arguments returned by :meth:`match`.""" + raise NotImplementedError("The argument_mappings property must be implemented.") + +
+[docs] + def match(self, request) -> NoReturn: + """ + Tries to match the request against the internally stored pattern. Returns any matched + function arguments. + + :param request: Request to attempt matching. + :return: List of matched argument values (possibly empty) or None if not matching. + """ + raise NotImplementedError("The match-method must be implemented.")
+
+ + + +
+[docs] +class regex(PatternMatcher): + """ + Implementation of :class:`PatternMatcher` that compiles the specified pattern into a regular + expression. + """ + + def __init__(self, pattern) -> None: + super(regex, self).__init__(pattern) + + self.compiled_pattern = re.compile(pattern.encode()) + + @property + def arg_count(self): + return self.compiled_pattern.groups + + @property + def argument_mappings(self) -> None: + return None + +
+[docs] + def match(self, request): + match = self.compiled_pattern.match(request) + + if match is None: + return None + + return match.groups()
+
+ + + +
+[docs] +class scanf(regex): + """ + Interprets the specified pattern as a scanf format. Internally, the scanf_ package is used + to transform the format into a regular expression. Please consult the documentation of scanf_ + for valid pattern specifications. + + By default, the resulting regular expression matches exactly. Consider this example: + + .. sourcecode:: Python + + exact = scanf("T=%f") + not_exact = scanf("T=%f", exact_match=False) + + The first pattern only matches the string ``T=4.0``, whereas the second would also match + ``T=4.0garbage``. Please note that the specifiers like ``%f`` are automatically turned into + groups in the generated regular expression. + + :param pattern: Scanf format specification. + :param exact_match: Match only if the entire string matches. + + .. _scanf: https://github.com/joshburnett/scanf + """ + + def __init__(self, pattern, exact_match=True) -> None: + self._scanf_pattern = pattern + + generated_regex, self._argument_mappings = scanf_compile(pattern) + regex_pattern = generated_regex.pattern + + if exact_match: + regex_pattern = "^{}$".format(regex_pattern) + + super(scanf, self).__init__(regex_pattern) + + @property + def pattern(self): + return self._scanf_pattern + + @property + def argument_mappings(self): + return self._argument_mappings
+ + + +
+[docs] +class Func: + """ + Objects of this type connect a callable object to a pattern matcher (:class:`PatternMatcher`), + which currently comprises :class:`regex` and :class:`scanf`. Strings are also + accepted, they are treated like a regular expression internally. This preserves default + behavior from older versions of Lewis. + + In general, Func-objects should not be created directly, instead they are created by one of + the sub-classes of :class:`CommandBase` using :meth:`~CommandBase.bind`. + + Function arguments are indicated by groups in the regular expression. The number of + groups has to match the number of arguments of the function. In earlier versions of Lewis it + was possible to pass flags to ``re.compile``, this has been removed for consistency issues + in :class:`Var`. It is however still possible to use the exact same flags as part of the + regular expression. In the documentation of re_, this is outlined, simply add a group to the + expression that contains the flags, for example ``(?i)`` to make the expression case + insensitive. This special group does not count towards the matching groups used for argument + capture. + + The optional argument_mappings can be an iterable of callables with one parameter of the + same length as the number of arguments of the function. The first parameter will be + transformed using the first function, the second using the second function and so on. + This can be useful to automatically transform strings provided by the adapter into a proper + data type such as ``int`` or ``float`` before they are passed to the function. In case the + pattern is of type :class:`scanf`, this is optional (but will override the mappings + provided by the matcher). + + The return_mapping argument is similar, it should map the return value of the function + to a string. The default map function only does that when the supplied value + is not None. It can also be set to a numeric value or a string constant so that the + command always returns the same value. If it is ``None``, the return value is not + modified at all. + + Finally, documentation can be provided by passing the doc-argument. If it is omitted, + the docstring of the bound function is used and if that is not present, left empty. + + :param func: Function to be called when pattern matches or member of device/interface. + :param pattern: :class:`regex`, :class:`scanf` object or string. + :param argument_mappings: Iterable with mapping functions from string to some type. + :param return_mapping: Mapping function for return value of method. + :param doc: Description of the command. If not supplied, the docstring is used. + + :raises: RuntimeError: If the function cannot be mapped for any reason. + + .. _re: https://docs.python.org/2/library/re.html#regular-expression-syntax + """ + + def __init__( + self, func, pattern, argument_mappings=None, return_mapping=None, doc=None + ) -> None: + if not callable(func): + raise RuntimeError("Can not construct a Func-object from a non callable object.") + + self.func = func + + func_name = getattr(func, "__name__", repr(func)) + + if isinstance(pattern, str): + try: + pattern = regex(pattern) + except re.error as e: + raise RuntimeError( + f"The pattern '{pattern}' for function '{func_name}' is invalid regex: {e}" + ) + + self.matcher = pattern + + if argument_mappings is None: + argument_mappings = self.matcher.argument_mappings or None + + try: + inspect.getcallargs(func, *[None] * self.matcher.arg_count) + except TypeError: + raise RuntimeError( + "The number of arguments for function '{}' matched by pattern " + "'{}' is not compatible with number of defined " + "groups in pattern ({}).".format( + func_name, + self.matcher.pattern, + self.matcher.arg_count, + ) + ) + + if argument_mappings is not None and (self.matcher.arg_count != len(argument_mappings)): + raise RuntimeError( + "Supplied argument mappings for function matched by pattern '{}' specify {} " + "argument(s), but the function has {} arguments.".format( + self.matcher, len(argument_mappings), self.matcher.arg_count + ) + ) + + self.argument_mappings = argument_mappings + self.return_mapping = return_mapping + self.doc = doc or (inspect.getdoc(self.func) if callable(self.func) else None) + + def can_process(self, request): + return self.matcher.match(request) is not None + + def process_request(self, request): + match = self.matcher.match(request) + + if match is None: + raise RuntimeError("Request can not be processed.") + + args = self.map_arguments(match) + + return self.map_return_value(self.func(*args)) + +
+[docs] + def map_arguments(self, arguments): + """ + Returns the mapped function arguments. If no mapping functions are defined, the arguments + are returned as they were supplied. + + :param arguments: List of arguments for bound function as strings. + :return: Mapped arguments. + """ + if self.argument_mappings is None: + return arguments + + return [f(a) for f, a in zip(self.argument_mappings, arguments)]
+ + +
+[docs] + def map_return_value(self, return_value): + """ + Returns the mapped return_value of a processed request. If no return_mapping has been + defined, the value is returned as is. If return_mapping is a static value, that value + is returned, ignoring return_value completely. + + :param return_value: Value to map. + :return: Mapped return value. + """ + if callable(self.return_mapping): + return self.return_mapping(return_value) + + if self.return_mapping is not None: + return self.return_mapping + + return return_value
+
+ + + +
+[docs] +class CommandBase: + """ + This is the common base class of :class:`Cmd` and :class:`Var`. The concept of commands for + the stream adapter is based on connecting a callable object to a pattern that matches an + inbound request. + + The type of pattern can be either an implementation of :class:`PatternMatcher` + (regex or scanf format specification) or a plain string (which is treated as a regular + expression). + + For free function and lambda expressions this is straightforward: the function object can + simply be stored together with the pattern. Most often however, the callable + is a method of the device or interface object - these do not exist when the commands are + defined. + + This problem is solved by introducing a "bind"-step in :class:`StreamAdapter`. So instead + of a function object, both :class:`Cmd` and :class:`Var` store the name of a member of device + or interface. At "bind-time", this is translated into the correct callable. + + So instead of using :class:`Cmd` or :class:`Var` directly, both classes' :meth:`bind`-methods + return an iterable of :class:`Func`-objects which can be used for processing requests. + :class:`StreamAdapter` performs this bind-step when it's constructed. For details regarding + the implementations, please see the corresponding classes. + + .. seealso:: + + Please take a look at :class:`Cmd` for exposing callable objects or methods of + device/interface and :class:`Var` for exposing attributes and properties. + + To see how argument_mappings, return_mapping and doc are applied, please look at + :class:`Func`. + + :param func: Function to be called when pattern matches or member of device/interface. + :param pattern: Pattern to match (:class:`PatternMatcher` or string). + :param argument_mappings: Iterable with mapping functions from string to some type. + :param return_mapping: Mapping function for return value of method. + :param doc: Description of the command. If not supplied, the docstring is used. + """ + + def __init__( + self, func, pattern, argument_mappings=None, return_mapping=None, doc=None + ) -> None: + super(CommandBase, self).__init__() + + self.func = func + self.pattern = pattern + self.argument_mappings = argument_mappings + self.return_mapping = return_mapping + self.doc = doc + + def bind(self, target) -> NoReturn: + raise NotImplementedError("Binders need to implement the bind method.")
+ + + +
+[docs] +class Cmd(CommandBase): + """ + This class is an implementation of :class:`CommandBase` that can expose a callable object + or a named method of the device/interface controlled by :class:`StreamAdapter`. + + .. sourcecode:: Python + + def random(): + return 6 + + SomeInterface(StreamInterface): + commands = { + Cmd(lambda: 4, pattern='^R$', doc='Returns a random number.'), + Cmd('random', pattern='^RR$', doc='Better random number.'), + Cmd(random, pattern='^RRR$', doc='The best random number.'), + } + + def random(self): + return 5 + + The interface defined by the above example has three commands, ``R`` which calls a lambda + function that always returns 4, ``RR``, which calls ``SomeInterface.random`` and returns 5 and + lastly ``RRR`` which calls the free function defined above and returns the best random number. + + For a detailed explanation of requirements to the constructor arguments, please refer to the + documentation of :class:`Func`, to which the arguments are forwarded. + + .. seealso :: + + :class:`Var` exposes attributes and properties of a device object. The documentation + of :class:`Func` provides more information about the common constructor arguments. + + :param func: Function to be called when pattern matches or member of device/interface. + :param pattern: Pattern to match (:class:`PatternMatcher` or string). + :param argument_mappings: Iterable with mapping functions from string to some type. + :param return_mapping: Mapping function for return value of method. + :param doc: Description of the command. If not supplied, the docstring is used. + """ + + def __init__( + self, + func, + pattern, + argument_mappings=None, + return_mapping=lambda x: None if x is None else str(x), + doc=None, + ) -> None: + super(Cmd, self).__init__(func, pattern, argument_mappings, return_mapping, doc) + + def bind(self, target): + method = self.func if callable(self.func) else getattr(target, self.func, None) + + if method is None: + return None + + return [ + Func( + method, + self.pattern, + self.argument_mappings, + self.return_mapping, + self.doc, + ) + ]
+ + + +
+[docs] +class Var(CommandBase): + r""" + With this implementation of :class:`CommandBase` it's possible to expose plain data attributes + or properties of device or interface. Getting and setting a value are separate procedures + which both have their own pattern, read_pattern and write_pattern to match a command each. + Please note that write_pattern has to have exactly one group defined to match a parameter. + + Due to this separation, parameters can be made read-only, write-only or read-write in the + interface: + + .. sourcecode:: Python + + class SomeInterface(StreamInterface): + commands = { + Var('foo', read_pattern='^F$', write_pattern=r'^F=(\d+)$', + argument_mappings=(int,), doc='An integer attribute.'), + Var('bar' read_pattern='^B$') + } + + foo = 10 + + @property + def bar(self): + return self.foo + 5 + + @bar.setter + def bar(self, new_bar): + self.foo = new_bar - 5 + + In the above example, the foo attribute can be read and written, it's automatically converted + to an integer, while bar is a property that can only be read via the stream protocol. + + .. seealso:: + + For exposing methods and free functions, there's the :class:`Cmd`-class. + + :param target_member: Attribute or property of device/interface to expose. + :param read_pattern: Pattern to match for getter (:class:`PatternMatcher` or string). + :param write_pattern: Pattern to match for setter (:class:`PatternMatcher` or string). + :param argument_mappings: Iterable with mapping functions from string to some type, + only applied to setter. + :param return_mapping: Mapping function for return value of method, + applied to getter and setter. + :param doc: Description of the command. If not supplied, the docstring is used. For plain data + attributes the only way to get docs is to supply this argument. + """ + + def __init__( + self, + target_member, + read_pattern=None, + write_pattern=None, + argument_mappings=None, + return_mapping=lambda x: None if x is None else str(x), + doc=None, + ) -> None: + super(Var, self).__init__(target_member, None, argument_mappings, return_mapping, doc) + + self.target = None + + self.read_pattern = read_pattern + self.write_pattern = write_pattern + + def bind(self, target): + if self.func not in dir(target): + return None + + funcs = [] + + if self.read_pattern is not None: + + def getter(): + return getattr(target, self.func) + + # Copy docstring if target is a @property + prop = getattr(type(target), self.func, None) + if prop and inspect.isdatadescriptor(prop): + getter.__doc__ = "Getter: " + inspect.getdoc(prop) + + funcs.append( + Func( + getter, + self.read_pattern, + return_mapping=self.return_mapping, + doc=self.doc, + ) + ) + + if self.write_pattern is not None: + + def setter(new_value) -> None: + setattr(target, self.func, new_value) + + # Copy docstring if target is a @property + prop = getattr(type(target), self.func, None) + if prop and inspect.isdatadescriptor(prop): + setter.__doc__ = "Setter: " + inspect.getdoc(prop) + + funcs.append( + Func( + setter, + self.write_pattern, + argument_mappings=self.argument_mappings, + return_mapping=self.return_mapping, + doc=self.doc, + ) + ) + + return funcs
+ + + +
+[docs] +class StreamAdapter(Adapter): + """ + The StreamAdapter is the bridge between the Device Interface and the TCP Stream networking + backend implementation. + + Available adapter options are: + + - bind_address: IP of network adapter to bind on (defaults to 0.0.0.0, or all adapters) + - port: Port to listen on (defaults to 9999) + - telnet_mode: When True, overrides in- and out-terminator for CRNL (defaults to False) + + :param options: Dictionary with options. + """ + + default_options = {"telnet_mode": False, "bind_address": "0.0.0.0", "port": 9999} + + def __init__(self, options=None) -> None: + super(StreamAdapter, self).__init__(options) + self._server = None + + @property + def documentation(self): + commands = [ + "{}:\n{}".format( + cmd.matcher.pattern, + format_doc_text(cmd.doc or inspect.getdoc(cmd.func) or ""), + ) + for cmd in sorted(self.interface.bound_commands, key=lambda x: x.matcher.pattern) + ] + + options = format_doc_text( + "Listening on: {}\nPort: {}\nRequest terminator: {}\nReply terminator: {}".format( + self._options.bind_address, + self._options.port, + repr(self.interface.in_terminator), + repr(self.interface.out_terminator), + ) + ) + + return "\n\n".join( + [ + inspect.getdoc(self.interface) or "", + "Parameters\n==========", + options, + "Commands\n========", + ] + + commands + ) + +
+[docs] + def start_server(self) -> None: + """ + Starts the TCP stream server, binding to the configured host and port. + Host and port are configured via the command line arguments. + + .. note:: The server does not process requests unless + :meth:`handle` is called in regular intervals. + + """ + if self._server is None: + if self._options.telnet_mode: + self.interface.in_terminator = "\r\n" + self.interface.out_terminator = "\r\n" + + self._server = StreamServer( + self._options.bind_address, + self._options.port, + self.interface, + self.device_lock, + )
+ + +
+[docs] + def stop_server(self) -> None: + if self._server is not None: + self._server.close() + self._server = None
+ + + @property + def is_running(self): + return self._server is not None + +
+[docs] + def handle(self, cycle_delay=0.1) -> None: + """ + Spend approximately ``cycle_delay`` seconds to process requests to the server. + + :param cycle_delay: S + """ + asyncore.loop(cycle_delay, count=1) + self._server.process(int(cycle_delay * 1000))
+
+ + + +
+[docs] +class StreamInterface(InterfaceBase): + r""" + This class is used to provide a TCP-stream based interface to a device. + + Many hardware devices use a protocol that is based on exchanging text with a client via + a TCP stream. Sometimes RS232-based devices are also exposed this way via an adapter-box. + This adapter makes it easy to mimic such a protocol. + + This class has the following attributes which may be overridden by subclasses: + + - protocol: What this interface is called for purposes of the -p commandline option. + Defaults to "stream". + - in_terminator, out_terminator: These define how lines are terminated when transferred + to and from the device respectively. They are stripped/added automatically. + Inverse of protocol file InTerminator and OutTerminator. The default is ``\\r``. + - readtimeout: How many msec to wait for additional data between packets, once transmission + of an incoming command has begun. Inverse of ReadTimeout in protocol files. + Defaults to 100 (ms). Set to 0 to disable timeout completely. + - commands: A list of :class:`~CommandBase`-objects that define mappings between protocol + and device/interface methods/attributes. + + By default, commands are expressed as regular expressions, a simple example may look like this: + + .. sourcecode:: Python + + class SimpleDeviceStreamInterface(StreamInterface): + commands = [ + Cmd('set_speed', r'^S=([0-9]+)$', argument_mappings=[int]), + Cmd('get_speed', r'^S\?$') + Var('speed', read_pattern=r'^V\?$', write_pattern=r'^V=([0-9]+)$') + ] + + def set_speed(self, new_speed): + self.device.speed = new_speed + + def get_speed(self): + return self.device.speed + + The interface has two commands, ``S?`` to return the speed and ``S=10`` to set the speed + to an integer value. It also exposes the same speed attribute as a variable, using auto- + generated ``V?`` and ``V=10`` commands. + + As in the :class:`lewis.adapters.epics.EpicsInterface`, it does not matter whether the + wrapped method is a part of the device or of the interface, this is handled automatically when + a new device is assigned to the ``device``-property. + + In addition, the :meth:`handle_error`-method can be overridden. It is called when an exception + is raised while handling commands. + """ + + protocol = "stream" + + in_terminator = "\r" + out_terminator = "\r" + + readtimeout = 100 + + commands = None + + def __init__(self) -> None: + super(StreamInterface, self).__init__() + self.bound_commands = None + + @property + def adapter(self): + return StreamAdapter + + def _bind_device(self) -> None: + """ + This method implements ``_bind_device`` from :class:`~lewis.core.devices.InterfaceBase`. + It binds Cmd and Var definitions to implementations in Interface and Device. + """ + patterns = set() + + self.bound_commands = [] + + for cmd in self.commands: + bound = cmd.bind(self) or cmd.bind(self.device) or None + + if bound is None: + raise RuntimeError( + "Unable to produce callable object for non-existing member '{}' " + "of device or interface.".format(cmd.func) + ) + + for bound_cmd in bound: + pattern = bound_cmd.matcher.pattern + if pattern in patterns: + raise RuntimeError( + "The regular expression {} is " "associated with multiple commands.".format( + pattern + ) + ) + + patterns.add(pattern) + + self.bound_commands.append(bound_cmd) + +
+[docs] + def handle_error(self, request, error) -> None: + """ + Override this method to handle exceptions that are raised during command processing. + The default implementation does nothing, so that any errors are silently ignored. + + :param request: The request that resulted in the error. + :param error: The exception that was raised. + """
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/adapters.html b/_modules/lewis/core/adapters.html new file mode 100644 index 00000000..38da9f98 --- /dev/null +++ b/_modules/lewis/core/adapters.html @@ -0,0 +1,580 @@ + + + + + + + + lewis.core.adapters — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.adapters

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module contains :class:`Adapter`, which serves as a base class for concrete adapter
+implementations in :mod:`lewis.adapters`. It also contains :class:`AdapterCollection` which can
+be used to store multiple adapters and manage them together.
+"""
+
+import inspect
+import logging
+import threading
+from collections import namedtuple
+from types import TracebackType
+from typing import Any, Optional, Type
+
+from lewis.core.devices import DeviceBase, InterfaceBase
+from lewis.core.exceptions import LewisException
+from lewis.core.logging import has_log
+from lewis.core.utils import dict_strict_update
+
+
+
+[docs] +class NoLock: + """ + A dummy context manager that raises a RuntimeError when it's used. This makes it easier to + detect cases where an :class:`Adapter` has not received the proper lock-object to make sure + that device/interface access is synchronous. + """ + + def __enter__(self) -> None: + raise RuntimeError( + "The attempted action requires a proper threading.Lock-object, " + "but none was available." + ) + + def __exit__( + self, + exctype: Optional[Type[BaseException]], + excinst: Optional[BaseException], + exctb: Optional[TracebackType], + ) -> None: + pass
+ + + +
+[docs] +@has_log +class Adapter: + """ + Base class for adapters + + This class serves as a base class for concrete adapter implementations that expose a device via + a certain communication protocol. It defines the minimal interface that an adapter must provide + in order to fit seamlessly into other parts of the framework + (most importantly :class:`~lewis.core.simulation.Simulation`). + + Sub-classes should re-define the ``protocol``-member to something appropriate. While it is + explicitly supported to modify it in concrete device interface implementations, it is good + to have a default (for example ``epics`` or ``stream``). + + An adapter should provide everything that is needed for the communication via the protocol it + defines. This might involve constructing a server-object, configuring it and starting the + service (this should happen in :meth:`start_server`). Due to the large differences between + protocols it is very hard to provide general guidelines here. Please take a look at the + implementations of existing adapters (:class:`~lewis.adapters.epics.EpicsAdapter`, + :class:`~lewis.adapters.stream.StreamAdapter`),to get some examples. + + In principle, an adapter can exist on its own, but it only really becomes useful when a device + is bound to it. To do this, assign an object derived from + :class:`lewis.core.devices.DeviceBase` to the ``device``-property. Sub-classes have to + implement :meth:`_bind_device` to achieve actual binding behavior. + + It is possible to pass a dictionary with configuration options to Adapter. The keys of + the dictionary are accessible as properties of the ``_options``-member. Only keys that are + in the ``default_options`` member of the class are accepted. Inheriting classes must override + ``default_options`` to be a dictionary with the possible options for the adapter. + + Each adapter has a ``lock`` member, which contains a :class:`NoLock` by default. To make + device access thread-safe, any adapter should acquire this lock before interacting with + the device (or interface). This means that before starting the server component of an Adapter, + a proper Lock-object needs to be assigned to ``lock``. + + :param options: Configuration options for the adapter. + """ + + default_options = {} + + def __init__(self, options: dict[str, Any] | None = None) -> None: + super(Adapter, self).__init__() + self._interface = None + + self.device_lock: threading.Lock | NoLock = NoLock() + + options = options or {} + combined_options = dict(self.default_options) + + try: + dict_strict_update(combined_options, options) + except RuntimeError as e: + raise LewisException( + "Invalid options found: {}. Valid options are: {}".format( + ", ".join(e.args[1]), ", ".join(self.default_options.keys()) + ) + ) + + options_type = namedtuple("adapter_options", list(combined_options.keys())) + self._options = options_type(**combined_options) + + @property + def protocol(self) -> str | None: + if self.interface is None: + return None + + return self.interface.protocol + + @property + def interface(self) -> InterfaceBase | None: + """ + The device property contains the device-object exposed by the adapter. + + The property can be set from the outside, at that point the adapter will + call :meth:`_bind_device` (which is implemented in each adapter sub-class) + and thus re-bind its commands etc. to call the new device. + """ + return self._interface + + @interface.setter + def interface(self, new_interface: InterfaceBase | None) -> None: + self._interface = new_interface + + @property + def documentation(self) -> str: + """ + This property can be overridden in a sub-class to provide protocol documentation to users + at runtime. By default it returns the indentation cleaned-up docstring of the class. + """ + return inspect.getdoc(self) or "" + +
+[docs] + def start_server(self) -> None: + """ + This method must be re-implemented to start the infrastructure required for the + protocol in question. These startup operations are not supposed to be carried out on + construction of the adapter in order to preserve control over when services are + started during a run of a simulation. + + .. note:: + + This method may be called multiple times over the lifetime of the Adapter, so it is + important to make sure that this does not cause problems. + + .. seealso:: See :meth:`stop_server` for shutting down the adapter. + """ + raise NotImplementedError( + "Adapters must implement start_server to construct and setup any servers or mechanism " + "required for network communication." + )
+ + +
+[docs] + def stop_server(self) -> None: + """ + This method must be re-implemented to stop and tear down anything that has been setup + in :meth:`start_server`. This method should close all connections to clients that have + been established since the adapter has been started. + + .. note:: + + This method may be called multiple times over the lifetime of the Adapter, so it is + important to make sure that this does not cause problems. + """ + raise NotImplementedError( + "Adapters must implement stop_server to tear down anything that has been setup in " + "start_server." + )
+ + + @property + def is_running(self) -> bool: + """ + This property indicates whether the Adapter's server is running and listening. The result + of calls to :meth:`start_server` and :meth:`stop_server` should be reflected as expected. + """ + raise NotImplementedError( + "Adapters must implement the is_running property to indicate whether " + "a server is currently running and listening for requests." + ) + +
+[docs] + def handle(self, cycle_delay: float = 0.1) -> None: + """ + This function is called on each cycle of a simulation. It should process requests that are + made via the protocol that exposes the device. The time spent processing should be + approximately ``cycle_delay`` seconds, during which the adapter may block the current + process. It is desirable to stick to the provided time, but deviations are permissible if + necessary due to the way the protocol works. + + :param cycle_delay: Approximate time spent processing requests. + """ + pass
+
+ + + +
+[docs] +@has_log +class AdapterCollection: + """ + A container to manage the adapters of a device + + This container is designed to keep all adapters that expose a device in one place and interact + with them in a uniform way. + + Adapters can be passed as arguments upon construction or added later on using + :meth:`add_adapter` (and removed using :meth:`remove_adapter`). The available protocols can be + queried using the :meth:`protocols` property. + + Each adapter can be started and stopped separately by supplying protocol names to + :meth:`connect` and :meth:`disconnect`, both methods accept an arbitrary number of arguments, + so that any subset of the stored protocols can be handled at any time. Supplying no protocol + names at all will start/stop all adapters. These semantics also apply for :meth:`is_connected` + and `documentation`. + + This class also makes sure that all adapters use the same Lock for device interaction. + + :param args: List of adapters to add to the container + """ + + def __init__(self, *args: Adapter) -> None: + self._adapters = {} + + self._threads = {} + self._running = {} + self._lock = threading.Lock() + self.log: logging.Logger + + for adapter in args: + self.add_adapter(adapter) + + @property + def device_lock(self) -> threading.Lock: + """ + This lock is passed to each adapter when it's started. It's supposed to be used to ensure + that the device is only accessed from one thread at a time, for example during network IO. + :class:`~lewis.core.simulation.Simulation` uses this lock to block the device during the + simulation cycle calculations. + """ + return self._lock + +
+[docs] + def set_device(self, new_device: DeviceBase) -> None: + """Bind the new device to all interfaces managed by the adapters in the collection.""" + for adapter in self._adapters.values(): + adapter.interface.device = new_device
+ + +
+[docs] + def add_adapter(self, adapter: Adapter) -> None: + """ + Adds the supplied adapter to the container but raises a ``RuntimeError`` if there's + already an adapter registered for the same protocol. + + :param adapter: Adapter to add to the container + """ + if adapter.protocol in self._adapters: + raise RuntimeError( + "Adapter for protocol '{}' is already registered.".format(adapter.protocol) + ) + + self._adapters[adapter.protocol] = adapter
+ + +
+[docs] + def remove_adapter(self, protocol: str) -> None: + """ + Tries to remove the adapter for the specified protocol, raises a ``RuntimeError`` if there + is no adapter registered for that particular protocol. + + :param protocol: Protocol to remove from container + """ + if protocol not in self._adapters: + raise RuntimeError( + "Can not remove adapter for protocol '{}', none registered.".format(protocol) + ) + + del self._adapters[protocol]
+ + + @property + def protocols(self) -> list[str]: + """List of protocols for which adapters are registered.""" + return list(self._adapters.keys()) + +
+[docs] + def connect(self, *args: str) -> None: + """ + This method starts an adapter for each specified protocol in a separate thread, if the + adapter is not already running. + + :param args: List of protocols for which to start adapters or empty for all. + """ + for adapter in self._get_adapters(list(args)): + self._start_server(adapter)
+ + + def _start_server(self, adapter: Adapter) -> None: + if adapter.protocol not in self._threads: + self.log.info("Connecting device interface for protocol '%s'", adapter.protocol) + + adapter_thread = threading.Thread(target=self._adapter_loop, args=(adapter, 0.01)) + adapter_thread.daemon = True + + self._threads[adapter.protocol] = adapter_thread + self._running[adapter.protocol] = threading.Event() + + adapter_thread.start() + + # Block until server is actually listening + self._running[adapter.protocol].wait(2.0) + if not self._running[adapter.protocol].is_set(): + raise LewisException("Adapter for '%s' failed to start!" % adapter.protocol) + + def _adapter_loop(self, adapter: Adapter, dt: float) -> None: + adapter.device_lock = self._lock # This ensures that the adapter is using the correct lock + adapter.start_server() + + self._running[adapter.protocol].set() + + self.log.debug("Starting adapter loop for protocol %s.", adapter.protocol) + while self._running[adapter.protocol].is_set(): + adapter.handle(dt) + + adapter.stop_server() + +
+[docs] + def disconnect(self, *args: str) -> None: + """ + Stops all adapters for the specified protocols. The method waits for each adapter thread + to join, so it might hang if the thread is not terminating correctly. + + :param args: List of protocols for which to stop adapters or empty for all. + """ + for adapter in self._get_adapters(list(args)): + self._stop_server(adapter)
+ + + def _stop_server(self, adapter: Adapter) -> None: + if adapter.protocol in self._threads: + self.log.info("Disconnecting device interface for protocol '%s'", adapter.protocol) + + self._running[adapter.protocol].clear() + self._threads[adapter.protocol].join() + + del self._threads[adapter.protocol] + del self._running[adapter.protocol] + +
+[docs] + def is_connected(self, *args: str) -> bool | dict[str | None, bool]: + """ + If only one protocol is supplied, a single bool is returned with the connection status. + Otherwise, this method returns a dictionary of adapter connection statuses for the supplied + protocols. If no protocols are supplied, all adapter statuses are returned. + + :param args: List of protocols for which to start adapters or empty for all. + :return: Boolean for single adapter or dict of statuses for multiple. + """ + status_dict = { + adapter.protocol: adapter.is_running for adapter in self._get_adapters(list(args)) + } + + if len(args) == 1: + status = list(status_dict.values())[0] + if status is None: + return False + return status + + return status_dict
+ + +
+[docs] + def configuration(self, *args: str) -> dict[str | None, dict[str, Any]]: + """ + Returns a dictionary that contains the options for the specified adapter. The dictionary + keys are the adapter protocols. + + :param args: List of protocols for which to list options, empty for all adapters. + :return: Dict of protocol: option-dict pairs. + """ + return { + adapter.protocol: adapter._options._asdict() + for adapter in self._get_adapters(list(args)) + }
+ + +
+[docs] + def documentation(self, *args: str) -> str: + """ + Returns the concatenated documentation for the adapters specified by the supplied + protocols or all of them if no arguments are provided. + + :param args: List of protocols for which to get documentation or empty for all. + :return: Documentation for all selected adapters. + """ + return "\n\n".join(adapter.documentation for adapter in self._get_adapters(list(args)))
+ + + def _get_adapters(self, protocols: list[str]) -> list[Adapter]: + """ + Internal method to map protocols back to adapters. If the list of protocols contains an + invalid entry (e.g. a protocol for which there is no adapter), a ``RuntimeError`` + is raised. + + :param protocols: List of protocols, can be empty to return all adapters. + :return: Adapters according to the rules described above. + """ + invalid_protocols = set(protocols) - set(self.protocols) + + if invalid_protocols: + raise RuntimeError( + "No adapter registered for protocols: {}".format(", ".join(invalid_protocols)) + ) + + return [self._adapters[proto] for proto in protocols or self.protocols]
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/approaches.html b/_modules/lewis/core/approaches.html new file mode 100644 index 00000000..07e84adc --- /dev/null +++ b/_modules/lewis/core/approaches.html @@ -0,0 +1,189 @@ + + + + + + + + lewis.core.approaches — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.approaches

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+Defines functions that model typical behavior, such as a value approaching a target linearly at
+a certain rate.
+"""
+
+
+
+[docs] +def linear(current: float, target: float, rate: float, dt: float): + """ + This function returns the new value after moving towards + target at the given speed constantly for the time dt. + + If for example the current position is 10 and the target is -20, + the returned value will be less than 10 if rate and dt are greater + than 0: + + .. sourcecode:: Python + + new_pos = linear(10, -20, 10, 0.1) # new_pos = 9 + + The function makes sure that the returned value never overshoots: + + .. sourcecode:: Python + + new_pos = linear(10, -20, 10, 100) # new_pos = -20 + + :param current: The current value of the variable to be changed. + :param target: The target value to approach. + :param rate: The rate at which the parameter should move towards target. + :param dt: The time for which to calculate the change. + :return: The new variable value. + """ + sign = (target > current) - (target < current) + + if not sign: + return current + + new_value = current + sign * rate * dt + + if sign * new_value > sign * target: + return target + + return new_value
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/control_client.html b/_modules/lewis/core/control_client.html new file mode 100644 index 00000000..4235aa7b --- /dev/null +++ b/_modules/lewis/core/control_client.html @@ -0,0 +1,405 @@ + + + + + + + + lewis.core.control_client — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.control_client

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module provides client code for objects exposed via JSON-RPC over ZMQ.
+
+.. seealso::
+
+    The server-part for these client classes is defined
+    in the module :mod:`~lewis.core.control_server`.
+"""
+
+import types
+import uuid
+
+import zmq
+
+# This does not import .exceptions, because absolute_import from the __future__ module
+try:
+    import exceptions
+except ImportError:
+    import builtins as exceptions
+
+
+
+[docs] +class RemoteException(Exception): + """ + This exception type replaces exceptions that are raised on the server, + but unknown (i.e. not in the exceptions-module) on the client side. + To retain as much information as possible, the exception type on the server and + the message are stored. + + :param exception_type: Type of the exception on the server side. + :param message: Exception message on the server side. + """ + + def __init__(self, exception_type, message) -> None: + super(RemoteException, self).__init__( + "Exception on server side of type '{}': '{}'".format(exception_type, message) + ) + + self.server_side_type = exception_type + self.server_side_message = message
+ + + +
+[docs] +class ProtocolException(Exception): + """ + An exception type for exceptions related to the transport protocol, i.e. + malformed requests etc. + """
+ + + +
+[docs] +class ControlClient: + """ + This class provides an interface to a ControlServer instance on + the server side. Proxies to exposed objects can be obtained either + directly via get_object or, in case the server exposes a collection + of objects at the top level, a dictionary of named objects can be + obtained via get_object_collection. + + If a timeout is supplied, all underlying network operations time out + after the specified time (in milliseconds), for no timeout specify ``None``. + + :param host: Host the control server is running on. + :param port: Port on which the control server is listening. + :param timeout: Timeout in milliseconds for ZMQ operations. + """ + + def __init__(self, host="127.0.0.1", port="10000", timeout=3000) -> None: + self.timeout = timeout if timeout is not None else -1 + + self._socket = self._get_zmq_req_socket() + + self._connection_string = "tcp://{0}:{1}".format(host, port) + self._socket.connect(self._connection_string) + + def _get_zmq_req_socket(self): + context = zmq.Context() + context.setsockopt(zmq.REQ_CORRELATE, 1) + context.setsockopt(zmq.REQ_RELAXED, 1) + context.setsockopt(zmq.SNDTIMEO, self.timeout) + context.setsockopt(zmq.RCVTIMEO, self.timeout) + context.setsockopt(zmq.LINGER, 0) + return context.socket(zmq.REQ) + +
+[docs] + def json_rpc(self, method, *args): + """ + This method takes a ZMQ REQ-socket and submits a JSON-object containing + the RPC (JSON-RPC 2.0 format) to the supplied method with the supplied arguments. + Then it waits for a reply from the server and blocks until it has received + a JSON-response. The method returns the response and the id it used to tag + the original request, which is a random UUID (uuid.uuid4). + + :param method: Method to call on remote. + :param args: Arguments to method call. + :return: JSON result and request id. + """ + request_id = str(uuid.uuid4()) + + try: + self._socket.send_json( + {"method": method, "params": args, "jsonrpc": "2.0", "id": request_id} + ) + + return self._socket.recv_json(), request_id + except zmq.error.Again: + raise ProtocolException( + "The ZMQ connection to {} timed out after {:.2f}s.".format( + self._connection_string, self.timeout / 1000 + ) + )
+ + + def get_object(self, object_name=""): + api, request_id = self.json_rpc(object_name + ":api") + + if "result" not in api or api["id"] != request_id: + raise ProtocolException("Failed to retrieve API of remote object.") + + object_type = type(str(api["result"]["class"]), (ObjectProxy,), {}) + methods = api["result"]["methods"] + + glue = "." if object_name else "" + return object_type(self, methods, object_name + glue) + +
+[docs] + def get_object_collection(self, object_name=""): + """ + If the remote end exposes a collection of objects under the supplied object name (empty + for top level), this method returns a dictionary of these objects stored under their + names on the server. + + This function performs n + 1 calls to the server, where n is the number of objects. + + :param object_name: Object name on the server. This is required if the object collection + is not the top level object. + """ + + object_names = self.get_object(object_name).get_objects() + + return {obj: self.get_object(obj) for obj in object_names}
+
+ + + +
+[docs] +class ObjectProxy: + """ + This class serves as a base class for dynamically created classes on the + client side that represent server-side objects. Upon initialization, + this class takes the supplied methods and installs appropriate proxy methods + or properties into the object and class respectively. Because of that + class manipulation, this class must never be used directly. + Instead, it should be used as a base-class for dynamically created types + that mirror types on the server, like this: + + .. sourcecode:: Python + + proxy = type("SomeClassName", (ObjectProxy,), {})(connection, methods, prefix) + + There is however, the class ControlClient, which automates all that + and provides objects that are ready to use. + + Exceptions on the server are propagated to the client. If the exception is not part + of the exceptions-module (builtins for Python 3), a RemoteException is raised instead + which contains information about the server side exception. + + All RPC method names are prefixed with the supplied prefix, which is usually the + object name on the server plus a dot. + + :param connection: ControlClient-object for remote calls. + :param members: List of strings to generate methods and properties. + :param prefix: Usually object name on the server plus dot. + """ + + def __init__(self, connection, members, prefix="") -> None: + self._properties = set() + + self._connection = connection + self._prefix = prefix + self._add_member_proxies(members) + + def _make_request(self, method, *args): + """ + This method performs a JSON-RPC request via the object's ZMQ socket. If successful, + the result is returned, otherwise exceptions are raised. Server side exceptions are + raised using the same type as on the server if they are part of the exceptions-module. + Otherwise, a RemoteException is raised. + + :param method: Method of the object to call on the remote. + :param args: Positional arguments to the method call. + :return: Result of the remote call if successful. + """ + response, request_id = self._connection.json_rpc(self._prefix + method, *args) + + if "id" not in response: + raise ProtocolException("JSON-RPC response does not contain ID field.") + + if response["id"] != request_id: + raise ProtocolException( + "ID of JSON-RPC request ({}) did not match response ({}).".format( + request_id, response["id"] + ) + ) + + if "result" in response: + return response["result"] + + if "error" in response: + if "data" in response["error"]: + exception_type = response["error"]["data"]["type"] + exception_message = response["error"]["data"]["message"] + + if not hasattr(exceptions, exception_type): + raise RemoteException(exception_type, exception_message) + else: + exception = getattr(exceptions, exception_type) + raise exception(exception_message) + else: + raise ProtocolException(response["error"]["message"]) + + def _add_member_proxies(self, members) -> None: + for member in [str(m) for m in members]: + if ":set" in member or ":get" in member: + self._properties.add(member.split(":")[-2].split(".")[-1]) + else: + setattr(self, member, self._create_method_proxy(member)) + + for prop in self._properties: + setattr( + type(self), + prop, + property(self._create_getter_proxy(prop), self._create_setter_proxy(prop)), + ) + + def _create_getter_proxy(self, property_name): + def getter(obj): + return obj._make_request(property_name + ":get") + + return getter + + def _create_setter_proxy(self, property_name): + def setter(obj, value): + return obj._make_request(property_name + ":set", value) + + return setter + + def _create_method_proxy(self, method_name): + def method_wrapper(obj, *args): + return obj._make_request(method_name, *args) + + return types.MethodType(method_wrapper, self)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/control_server.html b/_modules/lewis/core/control_server.html new file mode 100644 index 00000000..4dd92504 --- /dev/null +++ b/_modules/lewis/core/control_server.html @@ -0,0 +1,519 @@ + + + + + + + + lewis.core.control_server — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.control_server

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module contains classes to expose objects via a JSON-RPC over ZMQ server. Lewis uses
+this infrastructure in :class:`~lewis.core.simulation.Simulation`.
+
+.. seealso::
+
+    Client classes for the service defined in this module can be found in
+    :mod:`~lewis.core.control_client`.
+
+"""
+
+import inspect
+import json
+import socket
+
+import zmq
+from jsonrpc import JSONRPCResponseManager
+
+from lewis.core.exceptions import LewisException
+from lewis.core.logging import has_log
+
+
+
+[docs] +class ExposedObject: + """ + ExposedObject is a class that makes it easy to expose an object via the + JSONRPCResponseManager from the json-rpc package, where it can serve as a dispatcher. + For this purpose it exposes a read-only dict-like interface. + + The basic problem solved by this wrapper is that plain data members of an object are not + really captured well by the RPC-approach, where a client performs function calls on a + remote machine and gets the result back. + + The supplied object is inspected using dir(object) and all entries that do not start + with a _ are exposed in a way that depends on whether the corresponding member + is a method or a property (either in the Python-sense or the general OO-sense). Methods + are stored directly, and stored in an internal dict where the method name is the key and + the callable method object is the value. For properties, a getter- and a setter function + are generated, which are then stored in the same dict. The names of these methods for + a property called ``a`` are ``a:get`` and ``a:set``. The separator has been chosen to be + colon because it can't be part of a valid Python identifier. + + If the second argument is not empty, it is interpreted to be the list of members + to expose and only those are actually exposed. This can be used to explicitly expose + members of an object that start with an underscore. If all but one or two members + should be exposed, it's also possible to use the exclude-argument to explicitly + exclude a few members. Both parameters can be used in combination, the exclude-list + takes precedence. + + In certain situations it is desirable to acquire a lock before accessing the exposed object, + for example when multiple threads are accessing it on the server side. For this purpose, + the ``lock``-parameter can be used. If it is not ``None``, the exposed methods are wrapped + in a function that acquires the lock before accessing ``obj``, and releases it afterwards. + + :param obj: The object to expose. + :param members: This list of methods will be exposed. (defaults to all public members) + :param exclude: Members in this list will not be exposed. + :param exclude_inherited: Should inherited members be excluded? (defaults to False) + :param lock: ``threading.Lock`` that is used when accessing ``obj``. + """ + + def __init__(self, obj, members=None, exclude=None, exclude_inherited=False, lock=None) -> None: + super(ExposedObject, self).__init__() + + self._object = obj + self._function_map = {} + self._lock = lock + + self._add_function(":api", self.get_api) + + exposed_members = members if members else self._public_members() + exclude = list(exclude or []) + if exclude_inherited: + for base in inspect.getmro(type(obj))[1:]: + exclude += dir(base) + + for method in exposed_members: + if method not in exclude: + self._add_member_wrappers(method) + + def _public_members(self): + """ + Returns a list of members that do not start with an underscore. + """ + return [prop for prop in dir(self._object) if not prop.startswith("_")] + + def _add_member_wrappers(self, member) -> None: + """ + This method probes the supplied member of the wrapped object and inserts an appropriate + entry into the internal method map. Getters and setters for properties get a suffix + ':get' and ':set' respectively. + + :param member: The member of the wrapped object to expose + """ + method_object = getattr(type(self._object), member, None) or getattr(self._object, member) + + if callable(method_object): + self._add_function(member, getattr(self._object, member)) + else: + self._add_property(member) + +
+[docs] + def get_api(self): + """ + This method returns the class name and a list of exposed methods. + It is exposed to RPC-clients by an instance of ExposedObjectCollection. + + :return: A dictionary describing the exposed API (consisting of a class name and methods). + """ + return { + "class": type(self._object).__name__, + "methods": list(self._function_map.keys()), + }
+ + + def __getitem__(self, item): + return self._function_map[item] + + def __len__(self) -> int: + return len(self._function_map) + + def __iter__(self): + return iter(self._function_map) + + def __contains__(self, item) -> bool: + return item in self._function_map + + def _add_property(self, name) -> None: + self._add_function("{}:get".format(name), lambda: getattr(self._object, name)) + self._add_function("{}:set".format(name), lambda value: setattr(self._object, name, value)) + + def _add_function(self, name, function) -> None: + if not callable(function): + raise TypeError("Only callable objects can be exposed.") + + if self._lock is not None: + + def create_locking_wrapper(f): + def locking_wrapper_function(*args, **kwargs): + with self._lock: + return f(*args, **kwargs) + + return locking_wrapper_function + + function = create_locking_wrapper(function) + + self._function_map[name] = function + + def _remove_function(self, name) -> None: + del self._function_map[name]
+ + + +
+[docs] +class ExposedObjectCollection(ExposedObject): + """ + This class helps expose a number of objects (plain or RPCObject) by + exposing the methods of each object as + + .. sourcecode:: Python + + name.method + + Furthermore it exposes each object's API as a method with the following name: + + .. sourcecode:: Python + + name: api + + A list of exposed objects can be obtained by calling the following method from the client: + + .. sourcecode:: Python + + :objects + + :param named_objects: Dictionary of of name: object pairs. + """ + + def __init__(self, named_objects) -> None: + super(ExposedObjectCollection, self).__init__(self, ("get_objects",)) + self._object_map = {} + + if named_objects: + for name, obj in named_objects.items(): + self.add_object(obj, name) + + self._add_function("get_objects", self.get_objects) + +
+[docs] + def add_object(self, obj, name) -> None: + """ + Adds the supplied object to the collection under the supplied name. If the name is already + in use, a RuntimeError is raised. If the object is not an instance of + :class:`ExposedObject`, the method automatically constructs one. + + :param obj: Object to add to the collection. + :param name: Name of the exposed object. + """ + if name in self._object_map: + raise RuntimeError("An object is already registered under that name.") + + exposed_object = self._object_map[name] = ( + obj if isinstance(obj, ExposedObject) else ExposedObject(obj) + ) + + for method_name in exposed_object: + glue = "." if not method_name.startswith(":") else "" + self._add_function(name + glue + method_name, exposed_object[method_name])
+ + +
+[docs] + def remove_object(self, name) -> None: + """ + Remove the object exposed under that name. If no object is registered under the supplied + name, a RuntimeError is raised. + + :param name: Name of object to be removed. + """ + if name not in self._object_map: + raise RuntimeError("No object with name {} is registered.".format(name)) + + for fn_name in list(self._function_map.keys()): + if fn_name.startswith(name + ".") or fn_name.startswith(name + ":"): + self._remove_function(fn_name) + + del self._object_map[name]
+ + +
+[docs] + def get_objects(self): + """Returns the names of the exposed objects.""" + return list(self._object_map.keys())
+
+ + + +
+[docs] +@has_log +class ControlServer: + """ + This server opens a ZMQ REP-socket at the given host and port when start_server + is called. + + The server constructs an :class:`ExposedObjectCollection` from the supplied + name: object-dictionary and uses that as a handler for JSON-RPC requests. If it is an + instance of :class:`ExposedObject`, that is used directly. + + Each time process is called, the server tries to get request data and responds to that. + If there is no data, the method does nothing. + + Please note that this RPC-service comes without any security, authentication, etc. + Only use it to expose objects on a trusted network and be aware that anyone on that + network can access the exposed objects without any restrictions. + + :param object_map: Dictionary with name: object-pairs to construct an + ExposedObjectCollection or ExposedObject + :param connection_string: String with host:port pair for binding control server. + """ + + def __init__(self, object_map, connection_string) -> None: + super(ControlServer, self).__init__() + + try: + host, port = connection_string.split(":") + except ValueError: + raise LewisException( + "'{}' is not a valid control server initialization string. " + 'A string of the form "host:port" is expected.'.format(connection_string) + ) + + try: + self.host = socket.gethostbyname(host) + except socket.gaierror: + raise LewisException("Could not resolve control server host: {}".format(host)) + + self.port = port + + if isinstance(object_map, ExposedObject): + self._exposed_object = object_map + else: + self._exposed_object = ExposedObjectCollection(object_map) + + self._socket = None + + @property + def is_running(self): + """ + This property is ``True`` if the server is running. + """ + return self._socket is not None + + @property + def exposed_object(self): + """ + The exposed object. This is a read only property. + """ + return self._exposed_object + +
+[docs] + def start_server(self) -> None: + """ + Binds the server to the configured host and port and starts listening. + """ + if self._socket is None: + context = zmq.Context() + self._socket = context.socket(zmq.REP) + self._socket.setsockopt(zmq.RCVTIMEO, 100) + self._socket.bind("tcp://{0}:{1}".format(self.host, self.port)) + + self.log.info("Listening on %s:%s", self.host, self.port)
+ + + def _unhandled_exception_response(self, request_id, exception): + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "message": "Server error", + "code": -32000, + "data": { + "message": exception.args, + "args": [exception.args], + "type": type(exception).__name__, + }, + }, + } + +
+[docs] + def process(self, blocking=False) -> None: + """ + Each time this method is called, the socket tries to retrieve data and passes + it to the JSONRPCResponseManager, which in turn passes the RPC to the + ExposedObjectCollection. + + In case no data are available, the method does nothing. This behavior is required for + Lewis where everything is running in one thread. The central loop can call process + at some point to process remote calls, so the RPC-server does not introduce its own + infinite processing loop. + + If the server has not been started yet (via :meth:`start_server`), a RuntimeError + is raised. + + :param blocking: If True, this function will block until it has received data or a timeout + is triggered. Default is False to preserve behavior of prior versions. + """ + if self._socket is None: + raise RuntimeError("The server has not been started yet, use start_server to do so.") + + try: + request = self._socket.recv_unicode(flags=zmq.NOBLOCK if not blocking else 0) + + self.log.debug("Got request %s", request) + + try: + response = JSONRPCResponseManager.handle(request, self._exposed_object) + self._socket.send_unicode(response.json) + + self.log.debug("Sent response %s", response.json) + except TypeError as e: + self._socket.send_json( + self._unhandled_exception_response(json.loads(request)["id"], e) + ) + except zmq.Again: + pass
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/devices.html b/_modules/lewis/core/devices.html new file mode 100644 index 00000000..97a36a0d --- /dev/null +++ b/_modules/lewis/core/devices.html @@ -0,0 +1,631 @@ + + + + + + + + lewis.core.devices — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.devices

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module contains :class:`DeviceBase` as a base class for other device classes and
+infrastructure that can import devices from a module (:class:`DeviceRegistry`). The latter also
+produces factory-like objects that create device instances and interfaces based on setups
+(:class:`DeviceBuilder`).
+"""
+
+import importlib
+from typing import NoReturn
+
+from lewis.core.exceptions import LewisException
+from lewis.core.logging import has_log
+from lewis.core.utils import get_members, get_submodules
+
+
+
+[docs] +@has_log +class DeviceBase: + """ + This class is a common base for :class:`~lewis.devices.Device` and + :class:`~lewis.devices.StateMachineDevice`. It is mainly used in the device + discovery process. + """
+ + + +
+[docs] +@has_log +class InterfaceBase: + """ + This class is a common base for protocol specific interfaces that are exposed by a subclass of + :class:`~lewis.core.adapters.Adapter`. This base class is not meant to be used directly in + a device package - this is what the interfaces in :mod:`lewis.adapters` are for. + + There is a 1:1 correspondence between device and interface, where the interface holds a + reference to the device. It can be changed through the ``device``-property. + """ + + protocol = None + + def __init__(self) -> None: + super(InterfaceBase, self).__init__() + self._device = None + + @property + def adapter(self) -> NoReturn: + """ + Adapter type that is required to process and expose interfaces of this type. Must be + implemented in subclasses. + """ + raise NotImplementedError( + "An interface type must specify which adapter it is compatible " + "with. Please implement the adapter-property." + ) + + @property + def device(self): + """ + The device this interface is bound to. When a new device is set, :meth:`_bind_device` is + called, where the interface can react to the device change if necessary. + """ + return self._device + + @device.setter + def device(self, new_device) -> None: + self._device = new_device + self._bind_device() + + def _bind_device(self) -> None: + """ + This method should perform any binding steps between device and interface. The result + of this binding step is generally used by the adapter to process network traffic. + + The default implementation does nothing. + """ + pass
+ + + +
+[docs] +def is_device(obj): + """ + Returns True if obj is a device type (derived from DeviceBase), but not defined in + :mod:`lewis.core.devices` or :mod:`lewis.devices`. + + :param obj: Object to test. + :return: True if obj is a device type. + """ + return ( + isinstance(obj, type) + and issubclass(obj, DeviceBase) + and obj.__module__ not in ("lewis.devices", "lewis.core.devices") + )
+ + + +
+[docs] +def is_interface(obj): + """ + Returns True if obj is an interface (derived from :class:`InterfaceBase`), but not defined in + :mod:`lewis.adapters`, where concrete interfaces for protocols are defined. + + :param obj: Object to test. + :return: True if obj is an interface type. + """ + return ( + isinstance(obj, type) + and issubclass(obj, InterfaceBase) + and not ( + obj.__module__.startswith("lewis.core.devices") + or obj.__module__.startswith("lewis.adapters") + ) + )
+ + + +
+[docs] +@has_log +class DeviceBuilder: + """ + This class takes a module object (for example imported via importlib.import_module or via the + :class:`DeviceRegistry`) and inspects it so that it's possible to construct devices and + interfaces. + + In order for the class to work properly, the device module has to adhere to a few rules. + Device types, which means classes inheriting from :class:`DeviceBase`, are imported directly + from the device module, equivalent to the following: + + .. sourcecode :: Python + + from device_name import SimulatedDeviceType + + If ``SimulatedDeviceType`` is defined in the ``__init__.py``, there's nothing else to do. If + the device class is defined elsewhere, it must be imported in the ``__init__.py`` file as + written above. If there is only one device type (which is probably the most common case), it is + assumed to be default device type. + + Setups are discovered in two locations, the first one is a dict called ``setups`` in the device + module, which must contain setup names as keys and as values again a dict. This inner dict has + one mandatory key called ``device_type`` and one optional key ``parameters`` containing the + constructor arguments for the specified device type: + + .. sourcecode:: Python + + setups = dict( + broken=dict( + device_type=SimulatedDeviceType, + parameters=dict( + override_initial_state="error", + override_initial_data=dict(target=-10, position=-20.0), + ), + ) + ) + + The other location is a sub-package called `setups`, which should in turn contain modules. Each + module must contain a variable ``device_type`` and a variable ``parameters`` which are + analogous to the keys in the dict described above. This allows for more complex setups which + define additional classes and so on. + + The ``default`` setup is special, it is used when no setup is supplied to + :meth:`create_device`. If the setup ``default`` is not defined, one is created with the default + device type. This has two consequences, no setups need to be defined for very simple devices, + but if multiple device types are defined, a ``default`` setup must be defined. + + A setup can be supplied to the :meth:`create_device`. + + Lastly, the builder tries to discover device interfaces, which are currently classes based on + :class:`lewis.adapters.InterfaceBase`. These are looked for in the module and in a sub-package + called ``interfaces`` (which should contain modules with adapters like the ``setups`` package). + + Each interface has a protocol, if a protocol occurs more than once in a device module, + a RuntimeError is raised. + """ + + def __init__(self, module) -> None: + self._module = module + + submodules = get_submodules(self._module) + + self._device_types = self._discover_devices(submodules.get("devices")) + self._setups = self._discover_setups(submodules.get("setups")) + self._interfaces = self._discover_interfaces(submodules.get("interfaces")) + + self.log.debug( + "Discovered the following items in '%s': Devices: %s; Setups: %s; Interfaces: %s", + self._module.__name__, + ", ".join(device_t.__name__ for device_t in self._device_types), + ", ".join(self._setups.keys()), + ", ".join("(%s: %s)" % (k, v.__name__) for k, v in self._interfaces.items()), + ) + + def _discover_devices(self, devices_package): + devices = list(get_members(self._module, is_device).values()) + + if devices_package is None: + return devices + + for module in get_submodules(devices_package).values(): + devices += list(get_members(module, is_device).values()) + + return devices + + def _discover_setups(self, setups_package): + setups = getattr(self._module, "setups", {}) + + all_setups = setups if isinstance(setups, dict) else {} + + if setups_package is not None: + for name, setup_module in get_submodules(setups_package).items(): + existing_setup = all_setups.get(name) + + if existing_setup is not None: + raise RuntimeError( + "The setup '{}' is defined twice in device '{}'.".format( + existing_setup, self.name + ) + ) + + all_setups[name] = { + "device_type": getattr(setup_module, "device_type", self.default_device_type), + "parameters": getattr(setup_module, "parameters", {}), + } + + if "default" not in all_setups: + all_setups["default"] = {"device_type": self.default_device_type} + + return all_setups + + def _discover_interfaces(self, interface_package): + all_interfaces = [] + + if interface_package is not None: + for interface_module in get_submodules(interface_package).values(): + all_interfaces += list(get_members(interface_module, is_interface).values()) + + all_interfaces += list(get_members(self._module, is_interface).values()) + + interfaces = {} + for interface in all_interfaces: + existing_interface = interfaces.get(interface.protocol) + + if existing_interface is not None: + raise RuntimeError( + "The protocol '{}' is defined in two interfaces for device '{}':\n" + " {} (in {})\n" + " {} (in {})\n" + "One of the protocol names needs to be changed.".format( + interface.protocol, + self.name, + existing_interface.__name__, + existing_interface.__module__, + interface.__name__, + interface.__module__, + ) + ) + + interfaces[interface.protocol] = interface + + return interfaces + + @property + def framework_version(self): + return getattr(self._module, "framework_version", None) + + @property + def name(self): + """ + The name of the device, which is also the name of the device module. + """ + return self._module.__name__.split(".")[-1] + + @property + def device_types(self): + """ + This property contains a dict of all device types in the device module. The keys are + type names, the values are the types themselves. + """ + return self._device_types + + @property + def default_device_type(self): + """ + If the module only defines one device type, it is the default device type. It is used + whenever a setup does not provide a ``device_type``. + """ + if len(self.device_types) == 1: + return self.device_types[0] + + return None + + @property + def interfaces(self): + """ + This property contains a map with protocols as keys and interface types as values. + The types are imported from the ``interfaces`` sub-module and from the device module + itself. If two interfaces with the same protocol are discovered, a RuntimeError is raiesed. + """ + return self._interfaces + + @property + def protocols(self): + """All available protocols for this device.""" + return list(self.interfaces.keys()) + + @property + def default_protocol(self): + """In case only one protocol exists for the device, this is the default protocol.""" + if len(self.protocols) == 1: + return self.protocols[0] + + return None + + @property + def setups(self): + """ + A map with all available setups. Setups are imported from the ``setups`` dictionary + in a device module and from the ``setups`` sub-module. If no ``default``-setup exists, + one is created using the default_device_type. If there are several device types in + the module, the default setup must be provided explicitly. + """ + return self._setups + + def _create_device_instance(self, device_type, **kwargs): + if device_type not in self.device_types: + raise RuntimeError("Can not create instance of non-device type.") + + return device_type(**kwargs) + +
+[docs] + def create_device(self, setup=None): + """ + Creates a device object according to the provided setup. If no setup is provided, + the default setup is used. If the setup can't be found, a LewisException is raised. + This can also happen if the device type specified in the setup is invalid. + + :param setup: Name of the setup from which to create device. + :return: Device object initialized according to the provided setup. + """ + setup_name = setup if setup is not None else "default" + + if setup_name not in self.setups: + raise LewisException( + "Failed to find setup '{}' for device '{}'. " + "Available setups are:\n {}".format( + setup, self.name, "\n ".join(self.setups.keys()) + ) + ) + + setup_data = self.setups[setup_name] + device_type = setup_data.get("device_type") or self.default_device_type + + self.log.debug( + "Trying to create device '%s' (setup: %s, device type: %s)", + self.name, + setup_name, + device_type.__name__ if device_type else "", + ) + + try: + return self._create_device_instance(device_type, **setup_data.get("parameters", {})) + except RuntimeError: + raise LewisException( + "The setup '{}' you tried to load does not specify a valid device type, but the " + "device module '{}' provides multiple device types so that no meaningful " + "default can be deduced.".format(setup_name, self.name) + )
+ + + def get_interface_type(self, protocol=None): + return self.interfaces[protocol] + +
+[docs] + def create_interface(self, protocol=None, *args, **kwargs): + """ + Returns an interface that implements the provided protocol. If the protocol is not + known, a LewisException is raised. All additional arguments are forwarded + to the interface constructor (see :class:`~lewis.adapters.Adapter` for details). + + :param protocol: Protocol which the interface must implement. + :param args: Positional arguments that are passed on to the interface. + :param kwargs: Keyword arguments that are passed on to the interface. + :return: Instance of the interface type. + """ + protocol = protocol if protocol is not None else self.default_protocol + + self.log.debug("Trying to create interface for protocol '%s'", protocol) + + try: + return self.interfaces[protocol](*args, **kwargs) + except KeyError: + raise LewisException( + "'{}' is not a valid protocol for device '{}', select one via the -p option.\n" + "Available protocols are: \n {}".format( + protocol, self.name, "\n ".join(self.interfaces.keys()) + ) + )
+
+ + + +
+[docs] +@has_log +class DeviceRegistry: + """ + This class takes the name of a module and constructs a :class:`DeviceBuilder` from + each sub-module. The available devices can be queried and a DeviceBuilder can be + obtained for each device: + + .. sourcecode:: Python + + from lewis.core.devices import DeviceRegistry + + registry = DeviceRegistry("lewis.devices") + chopper_builder = registry.device_builder("chopper") + + # construct device, interface, ... + + If the module can not be imported, a LewisException is raised. + + :param device_module: Name of device module from which devices are loaded. + """ + + def __init__(self, device_module) -> None: + try: + self._device_module = importlib.import_module(device_module) + except ImportError: + raise LewisException( + "Failed to import module '{}' for device discovery. " + "Make sure that it is in the PYTHONPATH.\n" + "See also the -a option of lewis.".format(device_module) + ) + + self._devices = { + name: DeviceBuilder(module) + for name, module in get_submodules(self._device_module).items() + } + + self.log.debug( + "Devices loaded from '%s': %s", + device_module, + ", ".join(self._devices.keys()), + ) + + @property + def devices(self): + """All available device names.""" + return self._devices.keys() + +
+[docs] + def device_builder(self, name): + """ + Returns a :class:`DeviceBuilder` instance that can be used to create device objects + based on setups, as well as device interfaces. If the device name is not stored + in the internal map, a LewisException is raised. + + Each DeviceBuilder has a ``framework_version``-member, which specifies the version + of Lewis the device has been written for. If the version does not match the current + framework version, it is only possible to obtain those device builders calling the + method with ``strict_versions`` set to ``False``, otherwise a + :class:`~lewis.core.exceptions.LewisException` is raised. A warning message is logged + in all cases. If ``framework_version`` is ``None`` (e.g. not specified at all), it + is accepted unless ``strict_versions`` is set to ``True``. + + :param name: Name of the device. + :return: :class:`DeviceBuilder`-object for requested device. + """ + try: + return self._devices[name] + except KeyError: + raise LewisException( + "No device with the name '{}' could be found. " + "Possible names are:\n {}\n" + "See also the -k option to add inspect a different module.".format( + name, "\n ".join(self.devices) + ) + )
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/exceptions.html b/_modules/lewis/core/exceptions.html new file mode 100644 index 00000000..72b25a85 --- /dev/null +++ b/_modules/lewis/core/exceptions.html @@ -0,0 +1,180 @@ + + + + + + + + lewis.core.exceptions — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.exceptions

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+Defines exception types specific to lewis. The main intention of these exception types is
+that they can be caught and meaningful messages can be displayed to the user.
+"""
+
+
+
+[docs] +class LewisException(Exception): + """ + This exception type is used to distinguish exceptions that are expected + from unexpected ones. This enables better error handling and more importantly + better presentation of errors to the users. + """
+ + + +
+[docs] +class LimitViolationException(Exception): + """ + An exception that can be raised in a device to indicate a limit violation. It is for example + raised by the :class:`~lewis.core.utils.check_limits`. + """
+ + + +
+[docs] +class AccessViolationException(Exception): + """ + This exception can be raised in situation where the performed action (accessing a property or + similar) is not allowed. An example is :class:`~lewis.adapters.epics.BoundPV` for enforcing + read-only PVs. + """
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/logging.html b/_modules/lewis/core/logging.html new file mode 100644 index 00000000..f5c6c2ad --- /dev/null +++ b/_modules/lewis/core/logging.html @@ -0,0 +1,265 @@ + + + + + + + + lewis.core.logging — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.logging

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module contains everything logging-related in Lewis. There is one relevant
+module level variable that defines the default log format, ``default_log_format``.
+
+All places that use logging in Lewis prefix their logger names with ``lewis`` so
+that you can easily control the logs caused by Lewis if you use it as a library.
+Lewis uses the default settings of the logging module, so if you use Lewis as a
+library and do not have any logging enabled, messages that are more severe than ``WARNING``
+are printed to stdout. For details on how to disable that behavior, change levels
+for certain loggers and so on, please refer to the documentation
+of the standard `logging`_ library.
+
+.. _logging: https://docs.python.org/2/library/logging.html
+"""
+
+import logging
+from typing import Callable, ParamSpec, Protocol, Type, TypeVar, overload
+
+
+
+[docs] +class HasLog(Protocol): + log: logging.Logger
+ + + +P = ParamSpec("P") +T = TypeVar("T") + +root_logger_name = "lewis" +default_log_format = "%(asctime)s %(levelname)s %(name)s: %(message)s" + + +@overload +def has_log(target: Type[T]) -> Type[T]: ... +@overload +def has_log(target: Callable[P, T]) -> Callable[P, T]: ... + + +
+[docs] +def has_log(target): + """ + This is a decorator to add logging functionality to a class or function. + + Applying this decorator to a class or function will add two new members: + + - ``log`` is an instance of ``logging.Logger``. The name of the logger is + set to ``lewis.Foo`` for a class named Foo. + - ``_set_logging_context`` is a method that modifies the name of the logger + when the class is used in a certain context. + + If ``context`` is a string, that string is directly inserted between ``lewis`` + and ``Foo``, so that the logger name would be ``lewis.bar.Foo`` if context + was ``'bar'``. The more common case is probably ``context`` being an object of + some class, in which case the class name is inserted. If ``context`` is an object + of type ``Bar``, the logger name of ``Foo`` would be ``lewis.Bar.Foo``. + + To provide a more concrete example in terms of Lewis, this is used for the state + machine logger in a device. So the logs of the state machine belonging to a certain + device appear in the log as originating from ``lewis.DeviceName.StateMachine``, which + makes it possible to distinguish between messages from different state machines. + + Example for how to use logging in a class: + + .. sourcecode:: Python + + from lewis.core.logging import has_log + + + @has_log + class Foo(Base): + def __init__(self): + super(Foo, self).__init__() + + def bar(self, baz): + self.log.debug("Called bar with parameter baz=%s", baz) + return baz is not None + + It works similarly for free functions, although the actual logging calls are a bit different: + + .. sourcecode:: Python + + from lewis.core.logging import has_log + + + @has_log + def foo(bar): + foo.log.info("Called with argument bar=%s", bar) + return bar + + The name of the logger is ``lewis.foo``, the context could also be modified by calling + ``foo._set_logging_context``. + + :param target: Target to decorate with logging functionality. + """ + logger_name = target.__name__ + + def get_logger_name(context: object = None) -> str: + log_names = [root_logger_name, logger_name] + + if context is not None: + log_names.insert(1, context if isinstance(context, str) else context.__class__.__name__) + + return ".".join(log_names) + + def _set_logging_context(obj: HasLog, context: object) -> None: + """ + Changes the logger name of this class using the supplied context + according to the rules described in the documentation of :func:`has_log`. To + clear the context of a class logger, supply ``None`` as the argument. + + :param context: String or object, ``None`` to clear context. + """ + obj.log.name = get_logger_name(context) + + target.log = logging.getLogger(get_logger_name()) + target._set_logging_context = _set_logging_context + + return target
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/processor.html b/_modules/lewis/core/processor.html new file mode 100644 index 00000000..31dbe0a9 --- /dev/null +++ b/_modules/lewis/core/processor.html @@ -0,0 +1,228 @@ + + + + + + + + lewis.core.processor — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.processor

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module defines two classes related to one of lewis' essential concepts, namely
+the cycle-based approach. :class:`CanProcess` and :class:`CanProcessComposite` implement the
+composite design pattern so that it's possible to form a tree of objects which can perform
+calculations based on an elapsed time Δt.
+"""
+
+
+
+[docs] +class CanProcess: + """ + The CanProcess class is meant as a base for all things that + are able to process on the basis of a time delta (dt). + + The base implementation does nothing. + + There are three methods that can be implemented by sub-classes and are called in the + process-method in this order: + + 1. doBeforeProcess + 2. doProcess + 3. doAfterProcess + + The doBefore- and doAfterProcess methods are only called if a doProcess-method exists. + """ + + def __init__(self) -> None: + super(CanProcess, self).__init__() + + def __call__(self, dt=0): + self.process(dt) + + def process(self, dt=0) -> None: + if hasattr(self, "doProcess"): + if hasattr(self, "doBeforeProcess"): + self.doBeforeProcess(dt) + + self.doProcess(dt) + + if hasattr(self, "doAfterProcess"): + self.doAfterProcess(dt)
+ + + +
+[docs] +class CanProcessComposite(CanProcess): + """ + This subclass of CanProcess is a convenient way of collecting + multiple items that implement the CanProcess interface. + + Items can be added to the composite like this: + + .. sourcecode:: Python + + composite = CanProcessComposite() + composite.add_processor(item_that_implements_CanProcess) + + The process-method calls the process-method of each contained + item. Specific things that have to be done before or after the + containing items are processed can be implemented in the doBefore- + and doAfterProcess methods. + """ + + def __init__(self, iterable=()) -> None: + super(CanProcessComposite, self).__init__() + + self._processors = [] + + for item in iterable: + self.add_processor(item) + + def add_processor(self, other) -> None: + if isinstance(other, CanProcess): + self._append_processor(other) + + def _append_processor(self, processor) -> None: + self._processors.append(processor) + + def doProcess(self, dt) -> None: + for processor in self._processors: + processor.process(dt)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/simulation.html b/_modules/lewis/core/simulation.html new file mode 100644 index 00000000..c8039a81 --- /dev/null +++ b/_modules/lewis/core/simulation.html @@ -0,0 +1,640 @@ + + + + + + + + lewis.core.simulation — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.simulation

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+A :class:`~Simulation` combines a :mod:`Device <lewis.devices>` and its interface (derived from
+an :mod:`Adapter <lewis.adapters>`).
+"""
+
+from datetime import datetime
+from threading import Thread
+from time import sleep
+
+from lewis.core.adapters import AdapterCollection
+from lewis.core.control_server import ControlServer, ExposedObject
+from lewis.core.devices import DeviceRegistry
+from lewis.core.logging import has_log
+from lewis.core.utils import seconds_since
+
+
+
+[docs] +@has_log +class Simulation: + """ + The Simulation class controls certain aspects of a device simulation, + the most important one being time. + + Once :meth:`start` is called, the process-method of the device + is called in regular intervals. The time between these calls is + influenced by the cycle_delay property. Because of the way some + network protocols work, the actual processing time can be + longer or shorter, so cycle_delay should be seen as a guideline + rather than a guaranteed parameter. + + In the simplest case, the actual time-delta between two cycles + is passed to the simulated device so that it can update its internal + state according to the elapsed time. It is however possible to set + a simulation speed, which serves as a multiplier for this time. + If the speed is set to 2 and 0.1 seconds pass between two cycles, + the simulation is asked to simulate 0.2 seconds, and so on. Speed 0 + effectively stops all time dependent calculations in the + simulated device. + + Another possibility to pause the simulation is the pause-method. After + calling it, all processing in the device is suspended, while the communication + adapters continue to work. This can be used to simulate that a device is "hanging". + The simulation can be continued using the resume-method. + + A number of status properties provide information about the simulation. + The total uptime (in actually elapsed time) can be obtained through the + uptime-property, whereas the runtime-property contains the simulated time. + The cycles-property indicates the total number of simulation cycles, which + does not increase when the simulation is paused. + + Finally, the simulation can be stopped entirely with the stop-method. + + All functionality except for the start-method can be made available to remote + computers via a :class:`ControlServer`-instance. The way to expose device and simulation + is to pass a 'host:port'-string as the control_server argument, + which will construct the control server. Simulation will try to start the + control server using the start_server method. + + :param device: The simulated device. + :param adapters: Adapters which expose the simulated device. + :param device_builder: :class:`~lewis.core.devices.DeviceBuilder` instance to enable setup- + switching at runtime. + :param control_server: 'host:port'-string to construct control server or None. + """ + + def __init__(self, device, adapters=(), device_builder=None, control_server=None) -> None: + super(Simulation, self).__init__() + + self._device_builder = device_builder + + self._device = device + self._adapters = AdapterCollection(*adapters) + + self._speed = 1.0 # Multiplier for delta t + self._cycle_delay = 0.1 # Target time between cycles + + self._start_time = None # Real time when the simulation started + self._cycles = 0 # Number of cycles processed + self._runtime = 0.0 # Total simulation time processed + + self._running = False + self._started = False + self._stop_commanded = False + + # Constructing the control server must be deferred until the end, + # because the construction is not complete at this point + self._control_server = None # Just initialize to None and use property setter afterwards + self._control_server_thread = None + self.control_server = control_server + + self.log.debug( + "Created simulation. Device type: %s, Protocol(s): %s, Possible setups for " + "switching: %s, Control server: %s", + device.__class__.__name__, + ", ".join(self._adapters.protocols), + ", ".join(device_builder.setups.keys()) if device_builder else None, + control_server, + ) + + def _create_control_server(self, control_server): + if control_server is None: + return None + + return ControlServer( + { + "device": ExposedObject( + self._device, + exclude_inherited=True, + lock=self._adapters.device_lock, + ), + "simulation": ExposedObject( + self, + exclude=("start", "control_server", "log"), + exclude_inherited=True, + ), + "interface": ExposedObject( + self._adapters, + exclude=( + "device_lock", + "add_adapter", + "remove_adapter", + "handle", + "log", + ), + exclude_inherited=True, + ), + }, + control_server, + ) + + @property + def setups(self): + """ + A list of setups that are available. Use :meth:`switch_setup` to + change the setup. + """ + return list(self._device_builder.setups.keys()) if self._device_builder is not None else [] + +
+[docs] + def switch_setup(self, new_setup) -> None: + """ + This method switches the setup, which means that it replaces the currently + simulated device with a new device, as defined by the setup. + + If any error occurs during setup switching it is logged and re-raised. + + :param new_setup: Name of the new setup to load. + """ + try: + self._device = self._device_builder.create_device(new_setup) + self._adapters.set_device(self._device) + self.log.info("Switched setup to '%s'", new_setup) + except Exception as e: + self.log.error( + "Caught an error while trying to switch setups. Setup not switched, " + "simulation continues: %s", + e, + ) + raise
+ + +
+[docs] + def start(self) -> None: + """ + Starts the simulation. + """ + self.log.info("Starting simulation") + + self._running = True + self._started = True + self._stop_commanded = False + + self._start_control_server() + + self._adapters.connect() + + self._start_time = datetime.now() + + delta = 0.0 + + while not self._stop_commanded: + delta = self._process_cycle(delta) + + self._running = False + self._started = False + + self.log.info("Simulation has ended.")
+ + + def _start_control_server(self) -> None: + if self._control_server is not None and self._control_server_thread is None: + + def control_server_loop() -> None: + self._control_server.start_server() + + while not self._stop_commanded: + self._control_server.process(blocking=True) + + self.log.info("Stopped processing control server commands, ending thread.") + + self._control_server_thread = Thread(target=control_server_loop) + self._control_server_thread.start() + + def _stop_control_server(self) -> None: + if self._control_server_thread is not None: + self._control_server_thread.join(timeout=1.0) + self._control_server_thread = None + + def _process_cycle(self, delta): + """ + Processes one cycle, which consists of one simulation cycle and processing + of control server commands. The method measures how long all this takes + and returns the elapsed time in seconds. + + :param delta: Elapsed time in last cycle, passed to simulation. + :return: Elapsed time in this cycle. + """ + start = datetime.now() + + self._process_simulation_cycle(delta) + + delta = seconds_since(start) + + return delta + + def _process_simulation_cycle(self, delta) -> None: + """ + If the simulation is not paused, the device's process-method is + called with the supplied delta, multiplied by the simulation speed. + + If the simulation is paused, the process sleeps for the duration + of one cycle_delay. + + :param delta: Time delta passed to simulation. + """ + self.log.debug("Cycle, dt=%s", delta) + + sleep(self._cycle_delay) + + if self._running: + delta_simulation = delta * self._speed + + with self._adapters.device_lock: + self._device.process(delta_simulation) + + self._cycles += 1 + self._runtime += delta_simulation + + @property + def cycle_delay(self): + """ + Desired time between simulation cycles, this can not be negative. + Use 0 for highest possible processing rate. + """ + return self._cycle_delay + + @cycle_delay.setter + def cycle_delay(self, delay) -> None: + if delay < 0.0: + raise ValueError("Cycle delay can not be negative.") + + self._cycle_delay = delay + + self.log.info("Changed cycle delay to %s", self._cycle_delay) + + @property + def cycles(self): + """ + Simulation cycles processed since start has been called. + """ + return self._cycles + + @property + def uptime(self): + """ + Elapsed time in seconds since the simulation has been started. + """ + if not self._started: + return 0.0 + return seconds_since(self._start_time) + + @property + def speed(self): + """ + Simulation speed. Actual elapsed time is multiplied with this property + to determine simulated time. Values greater than 1 increase the simulation + speed, values between 1 and 0 decrease it. A speed of 0 effectively pauses + the simulation. + """ + return self._speed + + @speed.setter + def speed(self, new_speed) -> None: + if new_speed < 0: + raise ValueError("Speed can not be negative.") + + self._speed = new_speed + + self.log.info("Changed speed to %s", self._speed) + + @property + def runtime(self): + """ + The accumulated simulation time. Whenever speed is different from 1, this + progresses at a different rate than uptime. + """ + return self._runtime + +
+[docs] + def set_device_parameters(self, parameters) -> None: + """ + Set multiple parameters of the simulated device "simultaneously". The passed + parameter is assumed to be device parameter/value dict. + The method only allows to set existing attributes. If there are invalid + attribute names, the attributes are not updated, instead a RuntimeError + is raised. The same happens if any of the parameters are methods, which + can not be updated with this mechanisms. + + :param parameters: Dict of device attribute/values to update the device. + """ + invalid_parameters = set(parameters.keys()) - set( + x for x in dir(self._device) if not callable(getattr(self._device, x)) + ) + if invalid_parameters: + raise RuntimeError( + "The following parameters do not exist in the device or are methods: {}." + "Parameters not updated.".format(invalid_parameters) + ) + + with self._adapters.device_lock: + for name, value in parameters.items(): + setattr(self._device, name, value) + + self.log.debug("Updated device parameters: %s", parameters)
+ + +
+[docs] + def pause(self) -> None: + """ + Pause the simulation. Can only be called after start has been called. + """ + if not self._running: + raise RuntimeError("Can only pause a running simulation.") + + self.log.info("Pausing simulation") + + self._running = False
+ + +
+[docs] + def resume(self) -> None: + """ + Resume a paused simulation. Can only be called after start + and pause have been called. + """ + if not self._started or self._running: + raise RuntimeError("Can only resume a paused simulation.") + + self.log.info("Resuming simulation") + + self._running = True
+ + +
+[docs] + def stop(self) -> None: + """ + Stops the simulation entirely. + """ + if self.is_started: + self.log.warning("Stopping simulation") + + self._stop_commanded = True + + self._stop_control_server() + self._adapters.disconnect()
+ + + @property + def is_started(self): + """ + This property is true if the simulation has been started. + """ + return self._started + + @property + def is_paused(self): + """ + True if the simulation is paused (implies that the simulation has been started). + """ + return self._started and not self._running + + @property + def control_server(self): + """ + ControlServer-instance that exposes the object to remote machines. Can only + be set before start has been called or on a running simulation if no + control server was previously present. If the server is not running, it will be started + after it has been set. + """ + return self._control_server + + @control_server.setter + def control_server(self, control_server) -> None: + if self.is_started and self._control_server: + raise RuntimeError("Can not replace control server while simulation is running.") + + self._control_server = self._create_control_server(control_server) + + if self.is_started and self._control_server is not None: + self._control_server.start_server()
+ + + +
+[docs] +class SimulationFactory: + """ + This class is used to create :class:`Simulation`-objects according to a certain + set of parameters, such as device, setup and protocol. To create a simulation, it needs to + know where devices are stored: + + .. sourcecode:: Python + + factory = SimulationFactory("lewis.devices") + + The actual creation happens via the :meth:`create`-method: + + .. sourcecode:: Python + + simulation = factory.create("device_name", protocol="protocol") + + The simulation can then be started and stopped as desired. + + .. warning:: This class is meant for internal use at the moment and may change frequently. + """ + + def __init__(self, devices_package) -> None: + self._reg = DeviceRegistry(devices_package) + + @property + def devices(self): + """Names of available devices.""" + return self._reg.devices + +
+[docs] + def get_protocols(self, device): + """Returns a list of available protocols for the specified device.""" + return self._reg.device_builder(device).protocols
+ + +
+[docs] + def create(self, device, setup=None, protocols=None, control_server=None): + """ + Creates a :class:`Simulation` according to the supplied parameters. + + :param device: Name of device. + :param setup: Name of the setup for device creation. + :param protocols: Dictionary where each key is assigned a dictionary with options for the + corresponding :class:`~lewis.core.adapters.Adapter`. For available + protocols, see :meth:`get_protocols`. + :param control_server: String to construct a control server (host:port). + :return: Simulation object according to input parameters. + """ + + device_builder = self._reg.device_builder(device) + device = device_builder.create_device(setup) + + adapters = [] + + if protocols is not None: + for protocol, options in protocols.items(): + interface = device_builder.create_interface(protocol) + interface.device = device + + adapter = interface.adapter(options=options or {}) + adapter.interface = interface + + adapters.append(adapter) + + return Simulation( + device=device, + adapters=adapters, + device_builder=device_builder, + control_server=control_server, + )
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/statemachine.html b/_modules/lewis/core/statemachine.html new file mode 100644 index 00000000..b8c5c7ce --- /dev/null +++ b/_modules/lewis/core/statemachine.html @@ -0,0 +1,621 @@ + + + + + + + + lewis.core.statemachine — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.statemachine

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+The statemachine module contains one of lewis' central parts, the cycle-based
+:class:`StateMachine`. The module also contains classes that make it easier to define the
+state machine (:class:`State`, :class:`Transition`). Despite its central nature, it's unlikely
+to be used directly in client code for device simulations - these should be based on
+:class:`StateMachineDevice`, which provides a more convenient interface for that purpose.
+"""
+
+from lewis.core.logging import has_log
+from lewis.core.processor import CanProcess
+
+
+
+[docs] +class StateMachineException(Exception): + """ + Classes in this module should only raise this type of Exception. + """ + + pass
+ + + +
+[docs] +class HasContext: + """ + Mixin to provide a Context. + + Creates a `_context` member variable that can be assigned with :meth:`set_context`. + + Any state handler or transition callable that derives from this mixin will + receive a context from its :class:`StateMachine` upon initialization (assuming the + StateMachine was provided with a context itself). + """ + + def __init__(self) -> None: + super(HasContext, self).__init__() + self._context = None + +
+[docs] + def set_context(self, new_context) -> None: + """Assigns the new context to the member variable ``_context``.""" + self._context = new_context + + if hasattr(self, "_set_logging_context"): + self._set_logging_context(self._context)
+
+ + + +
+[docs] +@has_log +class State(HasContext): + """ + StateMachine state handler base class. + + Provides a way to implement StateMachine event handling behaviour using an + object-oriented interface. Once the StateMachine is configured to do so, it + will automatically invoke the events in this class when appropriate. + + To use this class, create a derived class and override any events that need + custom behaviour. Device context is provided via :class:`HasContext` mixin. + """ + + def __init__(self) -> None: + super(State, self).__init__() + +
+[docs] + def on_entry(self, dt) -> None: + """ + Handle entry event. Raised once, when this state is entered. + + :param dt: Delta T since last cycle. + """ + pass
+ + +
+[docs] + def in_state(self, dt) -> None: + """ + Handle in-state event. + + Raised repeatedly, once per cycle, while idling in this state. Exactly one + in-state event occurs per cycle for every StateMachine. This is always the + last event of the cycle. + + :param dt: Delta T since last cycle. + """ + pass
+ + +
+[docs] + def on_exit(self, dt) -> None: + """ + Handle exit event. Raised once, when this state is exited. + + :param dt: Delta T since last cycle. + """ + pass
+
+ + + +
+[docs] +@has_log +class Transition(HasContext): + """ + StateMachine transition condition base class. + + Provides a way to implement a transition that requires access to the device + context. The device context is provided via :class:`HasContext` mixin, and can be + accessed as `self._context`. + + To use this class, create a derived class and override the :meth:`__call__` attribute. + """ + + def __init__(self) -> None: + super(Transition, self).__init__() + + def __call__(self): + """ + This is invoked when the StateMachine wants to check whether this transition + should occur. This happens on cycles when the StateMachine starts the cycle + in the source state of this transition. + + If this call returns True, the StateMachine will transition to the destination + state. Any remaining transition checks for the source state are not checked. + + :return: True or False / Should transition occur or not + """ + return True
+ + + +
+[docs] +@has_log +class StateMachine(CanProcess): + """ + Cycle based state machine. + + :param cfg: dict which contains state machine configuration. + :param context: object which is assigned to State and Transition objects as their _context. + + The configuration dict may contain the following keys: + + - initial: Name of the initial state of this machine + - states: [optional] Dict of custom state handlers + - transitions: [optional] Dict of transitions in this state machine. + + State handlers may be given as a dict, list or State class: + + - dict: May contain keys 'on_entry', 'in_state' and 'on_exit'. + - list: May contain up to 3 entries, above events in that order. + - class: Should be an instance of a class that derives from State. + + In case of handlers being provided as a dict or a list, values should be callable + and may take a single parameter: the Delta T since the last cycle. + + Transitions should be provided as a dict where: + + - Each key is a tuple of two values, the FROM and TO states respectively. + - Each value is a callable transition condition that return True or False. + + Transition conditions are called once per cycle when in the FROM state. If one of + the transition conditions returns True, the transition is executed that cycle. The + remaining conditions aren't called. + + Consider using an OrderedDict if order matters. + + Only one transition may occur per cycle. Every cycle will, at the very least, + trigger an in_state event against the current state. + + .. seealso:: See :meth:`~StateMachine.doProcess` for details. + """ + + def __init__(self, cfg, context=None) -> None: + super(StateMachine, self).__init__() + + self._set_logging_context(context) + + self._state = None # We start outside of any state, first cycle enters initial state + self._handler = {} # Nested dict mapping [state][event] = handler + self._transition = {} # Dict mapping [from_state] = [ (to_state, transition), ... ] + self._prefix = { # Default prefixes used when calling handler functions by name + "on_entry": "_on_entry_", + "in_state": "_in_state_", + "on_exit": "_on_exit_", + } + + # Specifying an initial state is not optional + if "initial" not in cfg: + raise StateMachineException( + "StateMachine configuration must include " "'initial' to specify starting state." + ) + self._initial = cfg["initial"] + self._set_handlers(self._initial) + + self._setup_state_handlers(cfg.get("states", {}), context) + self._setup_transition_handlers(cfg.get("transitions", {}), context) + + def _setup_state_handlers(self, state_handler_configuration, context) -> None: + """ + This method constructs the state handlers from a user-provided dict. + + :param state_handler_configuration: Dictionary with state handler + definitions. + :param context: Context is provided to state handlers that inherit + from HasContext. + """ + for state_name, handlers in state_handler_configuration.items(): + if isinstance(handlers, HasContext): + handlers.set_context(context) + + try: + if isinstance(handlers, State): + self._set_handlers( + state_name, + handlers.on_entry, + handlers.in_state, + handlers.on_exit, + ) + elif isinstance(handlers, dict): + self._set_handlers(state_name, **handlers) + elif hasattr(handlers, "__iter__"): + self._set_handlers(state_name, *handlers) + else: + raise RuntimeError("Handler is not State, dict or __iter__.") + except Exception: + raise StateMachineException( + "Failed to parse state handlers for state '%s'. " + "Must be dict or iterable." % state_name + ) + + def _setup_transition_handlers(self, transition_handler_configuration, context) -> None: + """ + This method constructs the transition handlers from a user-provided + dict. + + :param transition_handler_configuration: Dictionary with transition + handler definitions. + :param context: Context is provided to transition handlers that inherit + from HasContext. + """ + for states, check_func in transition_handler_configuration.items(): + from_state, to_state = states + + # Set up default handlers if this state hasn't been mentioned before + if from_state not in self._handler: + self._set_handlers(from_state) + if to_state not in self._handler: + self._set_handlers(to_state) + + if isinstance(check_func, HasContext): + check_func.set_context(context) + + # Set up the transition + self._set_transition(from_state, to_state, check_func) + + @property + def state(self): + """Name of the current state.""" + return self._state + +
+[docs] + def can(self, state): + """ + Returns true if the transition to 'state' is allowed from the current state. + + :param state: State to check transition to + :return: True if state is reachable from current + """ + if self._state is None: + return state == self._initial + + return state in (transition[0] for transition in self._transition[self._state])
+ + +
+[docs] + def bind_handlers_by_name(self, instance, override=False, prefix=None) -> None: + """ + Auto-bind state handlers based on naming convention. + + :param instance: Target object instance to search for handlers and bind events to. + :param override: If set to True, matching handlers will replace + previously registered handlers. + :param prefix: Dict or list of prefixes to override defaults + (keys: on_entry, in_state, on_exit) + + This function enables automatically binding state handlers to events without having to + specify them in the constructor. When called, this function searches `instance` for + member functions that match the following patterns for all known states + (states mentioned in 'states' or 'transitions' dicts of cfg): + + - ``instance._on_entry_[state]`` + - ``instance._in_state_[state]`` + - ``instance._on_exit_[state]`` + + The default prefixes may be overridden using the prefix parameter. Supported keys are + 'on_entry', 'in_state', and 'on_exit'. Values should include any and + all desired underscores. + + Matching functions are assigned as handlers to the corresponding state events, + iff no handler was previously assigned to that event. + + If a state event already had a handler assigned (during construction or previous call + to this function), no changes are made even if a matching function is found. To force + previously assigned handlers to be overwritten, set the third parameter to True. + This may be useful to implement inheritance-like specialization using multiple + implementation classes but only one StateMachine instance. + """ + if prefix is None: + prefix = {} + if not isinstance(prefix, dict) and hasattr(prefix, "__iter__"): + prefix = dict(zip(["on_entry", "in_state", "on_exit"], prefix)) + + # Merge prefix defaults with any provided prefixes + prefix = dict(list(self._prefix.items()) + list(prefix.items())) + + # Bind handlers + for state, handlers in self._handler.items(): + for event, handler in handlers.items(): + if handler is None or override: + named_handler = getattr(instance, prefix[event] + state, None) + if callable(named_handler): + self._handler[state][event] = named_handler
+ + +
+[docs] + def doProcess(self, dt) -> None: + """ + Process a cycle of this state machine. + + :param dt: Delta T. "Time" passed since last cycle, passed on to event handlers. + + A cycle will perform at most one transition and exactly one in_state event. + + A transition will only occur if one of the transition condition functions leaving + the current state returns True. + + When a transition occurs, the following events are raised: + - on_exit_old_state() + - on_entry_new_state() + - in_state_new_state() + + The first cycle after init or reset will never call transition checks and, instead, + always performs on_entry and in_state on the initial state. + + Whether a transition occurs or not, and regardless of any other circumstances, a + cycle always ends by raising an in_state event on the current (potentially new) + state. + """ + # Initial transition on first cycle / after a reset() + if self._state is None: + self.log.debug('Entering initial state "%s"', self._initial) + self._state = self._initial + self._raise_event("on_entry", 0) + self._raise_event("in_state", 0) + return + + # General transition + for target_state, check_func in self._transition.get(self._state, []): + if check_func(): + self.log.debug("Transition triggered (%s -> %s)", self._state, target_state) + self._raise_event("on_exit", dt) + self._state = target_state + self._raise_event("on_entry", dt) + break + + # Always end with an in_state + self._raise_event("in_state", dt)
+ + +
+[docs] + def reset(self) -> None: + """ + Reset the state machine to before the first cycle. The next process() will + enter the initial state. + """ + self._state = None
+ + + def _set_handlers(self, state, *args, **kwargs) -> None: + """ + Add or update state handlers. + + :param state: Name of state to be added or updated + :param on_entry: Handler for on_entry events. May be None, callable, or list of callables. + :param in_state: Handler for in_state events. May be None, callable, or list of callables. + :param on_exit: Handler for on_exit events. May be None, callable, or list of callables. + + Handlers may take up to one parameter (not counting self), delta T since last cycle, + and should return nothing. + + When handlers are omitted or set to None, no event will be raised at all. + """ + # Variable arguments for state handlers + # Default to calling target.on_entry_state_name(), etc + on_entry = args[0] if len(args) > 0 else kwargs.get("on_entry", None) + in_state = args[1] if len(args) > 1 else kwargs.get("in_state", None) + on_exit = args[2] if len(args) > 2 else kwargs.get("on_exit", None) + + self._handler[state] = { + "on_entry": on_entry, + "in_state": in_state, + "on_exit": on_exit, + } + + def _set_transition(self, from_state, to_state, transition_check) -> None: + """ + Add or update a transition and its condition function. + + :param from_state: Name of state this transition leaves + :param to_state: Name of state this transition enters + :param transition_check: Callable condition under which this transition occurs. + Should return True or False. + + The transition_check function should return True if the transition should occur. + Otherwise, False. + + Transition condition functions should take no parameters (not counting self). + """ + if not callable(transition_check): + raise StateMachineException("Transition condition must be callable.") + + if from_state not in self._transition.keys(): + self._transition[from_state] = [] + + # Remove previously added transition with same From -> To mapping + try: + del self._transition[from_state][ + [x[0] for x in self._transition[from_state]].index(to_state) + ] + except Exception: + pass + + self._transition[from_state].append( + ( + to_state, + transition_check, + ) + ) + + def _raise_event(self, event, dt) -> None: + """ + Invoke the given event name for the current state, passing dt as a parameter. + + :param event: Name of event to raise on current state. + :param dt: Delta T since last cycle. + """ + # May be None, function reference, or list of function refs + self.log.debug("Processing state=%s, handler=%s", self._state, event) + handlers = self._handler[self._state][event] + + if handlers is None: + handlers = [] + + if callable(handlers): + handlers = [handlers] + + for handler in handlers: + try: + handler(dt) + except TypeError: + handler()
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/core/utils.html b/_modules/lewis/core/utils.html new file mode 100644 index 00000000..3d69a65d --- /dev/null +++ b/_modules/lewis/core/utils.html @@ -0,0 +1,514 @@ + + + + + + + + lewis.core.utils — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.core.utils

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module contains some useful helper classes and functions that are not specific to a certain
+module contained in the Core API.
+"""
+
+import functools
+import importlib
+import inspect
+import textwrap
+from datetime import datetime
+from os import listdir
+from os import path as osp
+from types import ModuleType
+from typing import NoReturn
+
+from lewis.core.exceptions import LewisException, LimitViolationException
+from lewis.core.logging import has_log
+
+
+
+[docs] +@has_log +def get_submodules(module) -> dict[str, ModuleType]: + """ + This function imports all sub-modules of the supplied module and returns a dictionary + with module names as keys and the sub-module objects as values. If the supplied parameter + is not a module object, a RuntimeError is raised. + + :param module: Module object from which to import sub-modules. + :return: Dict with name-module pairs. + """ + if not inspect.ismodule(module): + raise RuntimeError( + "Can only extract submodules from a module object, " + "for example imported via importlib.import_module" + ) + + submodules = get_members(module, inspect.ismodule) + + module_path = list(getattr(module, "__path__", [None]))[0] + + if module_path is not None: + for item in listdir(module_path): + module_name = extract_module_name(osp.join(module_path, item)) + + if module_name is not None: + try: + submodules[module_name] = importlib.import_module( + ".{}".format(module_name), package=module.__name__ + ) + except ImportError as import_error: + # This is necessary in case random directories are in the path or things can + # just not be imported due to other ImportErrors. + get_submodules.log.error( + "ImportError for {module}: {error}".format( + module=module_name, error=import_error + ) + ) + + return submodules
+ + + +
+[docs] +def get_members(obj, predicate=None): + """ + Returns all members of an object for which the supplied predicate is true and that do not + begin with __. Keep in mind that the supplied function must accept a potentially very broad + range of inputs, because the members of an object can be of any type. The function puts + those members into a dict with the member names as keys and returns it. If no predicate is + supplied, all members are put into the dict. + + :param obj: Object from which to get the members. + :param predicate: Filter function for the members, only members for which True is returned are + part of the resulting dict. + :return: Dict with name-object pairs of members of obj for which predicate returns true. + """ + members = {member: getattr(obj, member) for member in dir(obj) if not member.startswith("__")} + + if predicate is None: + return members + + return {name: member for name, member in members.items() if predicate(member)}
+ + + +
+[docs] +def extract_module_name(absolute_path): + """ + This function tries to extract a valid module name from the basename of the supplied path. + If it's a directory, the directory name is returned, if it's a file, the file name + without extension is returned. If the basename starts with _ or . or it's a file with an + ending different from .py, the function returns None + + :param absolute_path: Absolute path of something that might be a module. + :return: Module name or None. + """ + base_name = osp.basename(osp.normpath(absolute_path)) + + # If the basename starts with _ it's probably __init__.py or __pycache__ or something internal. + # At the moment there seems to be no use case for those + if base_name[0] in (".", "_"): + return None + + # If it's a directory, there's nothing else to check, so it can be returned directly + if osp.isdir(absolute_path): + return base_name + + module_name, extension = osp.splitext(base_name) + + # If it's a file, it must have a .py ending + if extension == ".py": + return module_name + + return None
+ + + +
+[docs] +def dict_strict_update(base_dict, update_dict) -> None: + """ + This function updates base_dict with update_dict if and only if update_dict does not contain + keys that are not already in base_dict. It is essentially a more strict interpretation of the + term "updating" the dict. + + If update_dict contains keys that are not in base_dict, a RuntimeError is raised. + + :param base_dict: The dict that is to be updated. This dict is modified. + :param update_dict: The dict containing the new values. + """ + additional_keys = set(update_dict.keys()) - set(base_dict.keys()) + if len(additional_keys) > 0: + raise RuntimeError( + "The update dictionary contains keys that are not part of " + "the base dictionary: {}".format(str(additional_keys)), + additional_keys, + ) + + base_dict.update(update_dict)
+ + + +
+[docs] +def seconds_since(start): + """ + This is a small helper function that returns the elapsed seconds + since start using datetime.datetime.now(). + + :param start: Start time. + :return: Elapsed seconds since start time. + """ + return (datetime.now() - start).total_seconds()
+ + + +
+[docs] +class FromOptionalDependency: + """ + This is a utility class for importing classes from a module or + replacing them with dummy types if the module can not be loaded. + + Assume module 'a' that does: + + .. sourcecode:: Python + + from b import C, D + + and module 'e' which does: + + .. sourcecode:: Python + + from a import F + + where 'b' is a hard to install dependency which is thus optional. + To still be able to do: + + .. sourcecode:: Python + + import e + + without raising an error, for example for inspection purposes, + this class can be used as a workaround in module 'a': + + .. sourcecode:: Python + + C, D = FromOptionalDependency("b").do_import("C", "D") + + which is not as pretty as the actual syntax, but at least it + can be read in a similar way. If the module 'b' can not be imported, + stub-types are created that are called 'C' and 'D'. Everything depending + on these types will work until any of those are instantiated - in that + case an exception is raised. + + The exception can be controlled via the exception-parameter. If it is a + string, a LewisException is constructed from it. Alternatively it can + be an instance of an exception-type. If not provided, a LewisException + with a standard message is constructed. If it is anything else, a RuntimeError + is raised. + + Essentially, this class helps deferring ImportErrors until anything from + the module that was attempted to load is actually used. + + :param module: Module from that symbols should be imported. + :param exception: Text for LewisException or custom exception object. + """ + + def __init__(self, module, exception=None) -> None: + self._module = module + + if exception is None: + exception = ( + "The optional dependency '{}' is required for the " + "functionality you tried to use.".format(self._module) + ) + + if isinstance(exception, str): + exception = LewisException(exception) + + if not isinstance(exception, BaseException): + raise RuntimeError( + "The exception parameter has to be either a string or a an instance of an " + "exception type (derived from BaseException)." + ) + + self._exception = exception + +
+[docs] + def do_import(self, *names): + """ + Tries to import names from the module specified on initialization + of the FromOptionalDependency-object. In case an ImportError occurs, + the requested names are replaced with stub objects. + + :param names: List of strings that are used as type names. + :return: Tuple of actual symbols or stub types with provided names. If there is only one + element in the tuple, that element is returned. + """ + try: + module_object = importlib.import_module(self._module) + + objects = tuple(getattr(module_object, name) for name in names) + except ImportError: + + def failing_init(obj, *args, **kwargs) -> NoReturn: + raise self._exception + + objects = tuple(type(name, (object,), {"__init__": failing_init}) for name in names) + + return objects if len(objects) != 1 else objects[0]
+
+ + + +
+[docs] +def format_doc_text(text): + """ + A very thin wrapper around textwrap.fill to consistently wrap documentation text + for display in a command line environment. The text is wrapped to 99 characters with an + indentation depth of 4 spaces. Each line is wrapped independently in order to preserve + manually added line breaks. + + :param text: The text to format, it is cleaned by inspect.cleandoc. + :return: The formatted doc text. + """ + + return "\n".join( + textwrap.fill(line, width=99, initial_indent=" ", subsequent_indent=" ") + for line in inspect.cleandoc(text).splitlines() + )
+ + + +
+[docs] +class check_limits: + """ + This decorator helps to make sure that the parameter of a property setter (or any other + method with one argument) is within certain numerical limits. + + It's possible to set static limits using floats or ints: + + .. sourcecode:: Python + + class Foo: + _bar = 0 + + @property + def bar(self): + return self._bar + + @bar.setter + @check_limits(0, 15) + def bar(self, new_value): + self._bar = new_value + + But sometimes this is not flexible enough, so it's also possible to supply strings, which + are the names of attributes of the object the decorated method belongs with: + + .. sourcecode:: Python + + class Foo: + _bar = 0 + + bar_min = 0 + bar_max = 24 + + @property + def bar(self): + return self._bar + + @bar.setter + @check_limits("bar_min", "bar_max") + def bar(self, new_value): + self._bar = new_value + + This will make sure that the new value is always between ``bar_min`` and ``bar_max``, even + if they change at runtime. If the limit is ``None`` (default), the value will not be limited + in that direction. + + Upper and lower limit can also be used exclusively, for example for a property that has a lower + bound but not an upper, say a temperature: + + .. sourcecode:: Python + + class Foo: + _temp = 273.15 + + @check_limits(lower=0) + def set_temperature(self, t_in_kelvin): + self._temp = t_in_kelvin + + + If the value is outside the specified limits, the decorated function is not called and a + :class:`~lewis.core.exceptions.LimitViolationException` is raised if the ``silent``- + parameter is ``False`` (default). If that option is active, the call is simply silently + ignored. + + :param lower: Numerical lower limit or name of attribute that contains limit. + :param upper: Numerical upper limit or name of attribute that contains limit. + :param silent: A limit violation will not raise an exception if this option is ``True``. + """ + + def __init__(self, lower=None, upper=None, silent=False) -> None: + self._lower = lower + self._upper = upper + self._silent = silent + + def __call__(self, f): + @functools.wraps(f) + def limit_checked(obj, new_value): + lower = getattr(obj, self._lower) if isinstance(self._lower, str) else self._lower + upper = getattr(obj, self._upper) if isinstance(self._upper, str) else self._upper + + if (lower is None or lower <= new_value) and (upper is None or new_value <= upper): + return f(obj, new_value) + + if not self._silent: + raise LimitViolationException( + "%f is outside limits (%r, %r)" % (new_value, lower, upper) + ) + + return limit_checked
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices.html b/_modules/lewis/devices.html new file mode 100644 index 00000000..be5dfb9f --- /dev/null +++ b/_modules/lewis/devices.html @@ -0,0 +1,337 @@ + + + + + + + + lewis.devices — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+This module contains base classes for devices. Inherit from :class:`Device` for simple devices
+or from :class:`StateMachineDevice` for devices that are more complex and can be described
+using a state machine.
+"""
+
+from logging import Logger
+from typing import Callable
+
+from lewis.core.devices import DeviceBase
+from lewis.core.processor import CanProcess, CanProcessComposite
+from lewis.core.statemachine import State, StateMachine
+from lewis.core.utils import dict_strict_update
+
+
+
+[docs] +class Device(DeviceBase, CanProcess): + """ + This class exists mainly for consistency. It is meant to implement very simple devices that + do not require a state machine for their simulation. For such devices, all that is required + is subclassing from `Device` and possibly implementing `doProcess`, but this is optional. + + StateMachineDevice offers more functionality and is more likely to be useful for implementing + simulations of real devices. + """ + + def __init__(self) -> None: + super(Device, self).__init__()
+ + + +
+[docs] +class StateMachineDevice(DeviceBase, CanProcessComposite): + """ + This class is intended to be sub-classed to implement devices using a finite state machine + internally. + + Implementing such a device is straightforward, there are three methods + that *must* be overridden: + + - :meth:`_get_state_handlers` + - :meth:`_get_initial_state` + - :meth:`_get_transition_handlers` + + The first method is supposed to return a dictionary with state handlers for each state + of the state machine, the second method must return the name of the initial state. + The third method must return a dict-like object (often an OrderedDict from collections) + that defines the conditions for transitions between the states of the state machine. + + They are implemented as methods and not as plain class member variables, because often + they use the `self`-variable, which does not exist at the class level. + + From these three methods, a :class:`~lewis.core.statemachine.StateMachine`-instance is + constructed, it's available as the device's ``_csm``-member. CSM is short for + "cycle-based state machine". + + Most device implementation will also want to override this method: + + - :meth:`_initialize_data` + + This method should initialise device state variables (such as temperature, speed, etc.). + Having this in a separate method from ``__init__`` has the advantage that it can be used + to reset those variables at a later stage, without having to write the same code again. + + Following this scheme, inheriting from StateMachineDevice also provides the possibility + for users of the class to override the states, the transitions, the initial state and + even the data. For states, transitions and data, dicts need to be passed to the + constructor, for the initial state that should be a string. + + All these overrides can be used to define device setups to describe certain scenarios + more easily. + + :param override_states: Dict with one entry per state. Only states defined in the state + machine are allowed. + :param override_transitions: Dict with (state, state) tuples as keys and + callables as values. + :param override_initial_state: The initial state. + :param override_initial_data: A dict that contains data members + that should be overwritten on construction. + """ + + def __init__( + self, + override_states: dict[str, State] | None = None, + override_transitions: dict[tuple[State, State], Callable[[], bool]] | None = None, + override_initial_state: State | None = None, + override_initial_data: dict[str, float] | None = None, + ) -> None: + super(StateMachineDevice, self).__init__() + + self.log: Logger + self.log.info("Creating device, setting up state machine") + + self._initialize_data() + self._override_data(override_initial_data) + + state_handlers = self._get_final_state_handlers(override_states) + initial = override_initial_state or self._get_initial_state() + + if initial not in state_handlers: + raise RuntimeError("Initial state '{}' is not a valid state.".format(initial)) + + self._csm = StateMachine( + { + "initial": initial, + "states": state_handlers, + "transitions": self._get_final_transition_handlers(override_transitions), + }, + context=self, + ) + + self.add_processor(self._csm) + + def _get_state_handlers(self) -> dict[str, State]: + """ + Implement this method to return a dict-like object with state handlers + (see :class:`~lewis.core.statemachine.State`) for each state of the state machine. + The default implementation raises a ``NotImplementedError``. + + :return: A dict-like object containing named state handlers. + """ + raise NotImplementedError( + "_get_state_handlers must be implemented in a StateMachineDevice." + ) + + def _get_initial_state(self) -> State: + """ + Implement this method to return the initial state of the internal state machine. + The default implementation raises a ``NotImplementedError``. + + :return: The initial state of the state machine. + """ + raise NotImplementedError("_get_initial_state must be implemented in a StateMachineDevice.") + + def _get_transition_handlers(self) -> dict[tuple[State, State], Callable[[], bool]]: + """ + Implement this method to return transition handlers for the internal state machine. + The keys should be (state, state)-tuples and the values functions that return true + if the transition should be triggered. The default implementation raises a + ``NotImplementedError``. + + :return: A dict-like object containing transition handlers. + """ + raise NotImplementedError( + "_get_transition_handlers must be implemented in a StateMachineDevice." + ) + + def _initialize_data(self) -> None: + """ + Implement this method to initialize data members of the device, such as temperature, + speed and others. It gets called first in the __init__-method. The default implementation + does nothing. + """ + pass + + def _get_final_state_handlers(self, overrides: dict[str, State] | None) -> dict[str, State]: + states = self._get_state_handlers() + + if overrides is not None: + dict_strict_update(states, overrides) + + return states + + def _get_final_transition_handlers( + self, overrides: dict[tuple[State, State], Callable[[], bool]] | None + ) -> dict[tuple[State, State], Callable[[], bool]]: + transitions = self._get_transition_handlers() + + if overrides is not None: + dict_strict_update(transitions, overrides) + + return transitions + + def _override_data(self, overrides: dict[str, float] | None) -> None: + """ + This method overrides data members of the class, but does not allow for adding new members. + + :param overrides: Dict with data overrides. + """ + if overrides is not None: + for name, val in overrides.items(): + self.log.debug("Trying to override initial data (%s=%s)", name, val) + if name not in dir(self): + raise AttributeError( + "Can not override non-existing attribute" "'{}' of class '{}'.".format( + name, type(self).__name__ + ) + ) + + setattr(self, name, val)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/chopper/devices/device.html b/_modules/lewis/devices/chopper/devices/device.html new file mode 100644 index 00000000..8ec19ed4 --- /dev/null +++ b/_modules/lewis/devices/chopper/devices/device.html @@ -0,0 +1,354 @@ + + + + + + + + lewis.devices.chopper.devices.device — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.chopper.devices.device

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from collections import OrderedDict
+
+from lewis.core.processor import CanProcess
+from lewis.core.statemachine import StateMachine
+from lewis.devices import StateMachineDevice
+
+from . import states
+from .bearings import MagneticBearings
+
+
+
+[docs] +class SimulatedBearings(CanProcess, MagneticBearings): + def __init__(self) -> None: + super(SimulatedBearings, self).__init__() + + self._csm = StateMachine( + { + "initial": "resting", + "transitions": { + ("resting", "levitating"): lambda: self._levitate, + ("levitating", "levitated"): self.levitationComplete, + ("levitated", "delevitating"): lambda: not self._levitate, + ("delevitating", "resting"): self.delevitationComplete, + }, + } + ) + + self._levitate = False + + def engage(self) -> None: + self.levitate() + + def disengage(self) -> None: + self.delevitate() + + def levitate(self) -> None: + self._levitate = True + + def delevitate(self) -> None: + self._levitate = False + + def levitationComplete(self) -> bool: + return True + + def delevitationComplete(self) -> bool: + return True + + def doProcess(self, dt) -> None: + self._csm.process(dt) + + @property + def ready(self): + return self._csm.state == "levitated" and self._levitate + + @property + def idle(self): + return self._csm.state == "resting" and not self._levitate
+ + + +
+[docs] +class SimulatedChopper(StateMachineDevice): + _bearings = None + + def _initialize_data(self) -> None: + self.speed = 0.0 + self.target_speed = 0.0 + + self.phase = 0.0 + self.target_phase = 0.0 + + self.parking_position = 0.0 + self.target_parking_position = 0.0 + self.auto_park = False + + self._park_commanded = False + self._stop_commanded = False + self._start_commanded = False + self._idle_commanded = False + self._phase_commanded = False + self._shutdown_commanded = False + self._initialized = False + + if self._bearings is None: + self._bearings = SimulatedBearings() + + def _get_state_handlers(self): + return { + "init": states.DefaultInitState(), + "bearings": {"in_state": self._bearings}, + "stopped": states.DefaultStoppedState(), + "stopping": states.DefaultStoppingState(), + "accelerating": states.DefaultAcceleratingState(), + "phase_locking": states.DefaultPhaseLockingState(), + "phase_locked": states.DefaultPhaseLockedState(), + "idle": states.DefaultIdleState(), + "parking": states.DefaultParkingState(), + "parked": states.DefaultParkedState(), + } + + def _get_initial_state(self) -> str: + return "init" + + def _get_transition_handlers(self): + return OrderedDict( + [ + (("init", "bearings"), lambda: self.initialized), + (("bearings", "stopped"), lambda: self._bearings.ready), + (("bearings", "init"), lambda: self._bearings.idle), + ( + ("parking", "parked"), + lambda: self.parking_position == self.target_parking_position, + ), + (("parking", "stopping"), lambda: self._stop_commanded), + (("parked", "stopping"), lambda: self._stop_commanded), + (("parked", "accelerating"), lambda: self._start_commanded), + (("stopped", "accelerating"), lambda: self._start_commanded), + (("stopped", "parking"), lambda: self._park_commanded), + (("stopped", "bearings"), lambda: self._shutdown_commanded), + (("accelerating", "stopping"), lambda: self._stop_commanded), + (("accelerating", "idle"), lambda: self._idle_commanded), + ( + ("accelerating", "phase_locking"), + lambda: self.speed == self.target_speed, + ), + (("idle", "accelerating"), lambda: self._start_commanded), + (("idle", "stopping"), lambda: self._stop_commanded), + (("phase_locking", "stopping"), lambda: self._stop_commanded), + ( + ("phase_locking", "phase_locked"), + lambda: self.phase == self.target_phase, + ), + (("phase_locking", "idle"), lambda: self._idle_commanded), + (("phase_locked", "accelerating"), lambda: self._start_commanded), + (("phase_locked", "phase_locking"), lambda: self._phase_commanded), + (("phase_locked", "stopping"), lambda: self._stop_commanded), + (("phase_locked", "idle"), lambda: self._idle_commanded), + (("stopping", "accelerating"), lambda: self._start_commanded), + (("stopping", "stopped"), lambda: self.speed == 0.0), + (("stopping", "idle"), lambda: self._idle_commanded), + ] + ) + + @property + def state(self): + """ + The current state of the chopper. This parameter is read-only, it is + determined by the internal state machine of the device. + """ + return self._csm.state + + @property + def initialized(self): + return self._initialized + + def initialize(self) -> None: + if self._csm.can("bearings") and not self.initialized: + self._initialized = True + self._bearings.engage() + + def deinitialize(self) -> None: + if self._csm.can("bearings") and self.initialized: + self._shutdown_commanded = True + self._bearings.disengage() + + def park(self) -> None: + if self._csm.can("parking"): + self._park_commanded = True + + @property + def parked(self): + return self._csm.state == "parked" + + def stop(self) -> None: + if self._csm.can("stopping"): + self._stop_commanded = True + + @property + def stopped(self): + return self._csm.state == "stopped" + + def start(self) -> None: + if self._csm.can("accelerating") and self.target_speed > 0.0: + self._start_commanded = True + else: + self.stop() + + @property + def started(self): + return self._csm.state == "accelerating" + + def unlock(self) -> None: + if self._csm.can("idle"): + self._idle_commanded = True + + @property + def idle(self): + return self._csm.state == "idle" + + def lock_phase(self) -> None: + if self._csm.can("phase_locking"): + self._phase_commanded = True + + @property + def phase_locked(self): + return self._csm.state == "phase_locked"
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/chopper/devices/states.html b/_modules/lewis/devices/chopper/devices/states.html new file mode 100644 index 00000000..5cc2bd2b --- /dev/null +++ b/_modules/lewis/devices/chopper/devices/states.html @@ -0,0 +1,301 @@ + + + + + + + + lewis.devices.chopper.devices.states — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.chopper.devices.states

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.core import approaches
+from lewis.core.statemachine import State
+
+
+
+[docs] +class DefaultInitState(State): +
+[docs] + def on_entry(self, dt) -> None: + self._context._initialize_data()
+
+ + + +
+[docs] +class DefaultParkingState(State): + def __init__(self, parking_speed=5.0) -> None: + super(DefaultParkingState, self).__init__() + self._parking_speed = parking_speed + +
+[docs] + def in_state(self, dt) -> None: + self._context.parking_position = approaches.linear( + self._context.parking_position, + self._context.target_parking_position, + self._parking_speed, + dt, + )
+ + +
+[docs] + def on_entry(self, dt) -> None: + self._context._park_commanded = False
+
+ + + +
+[docs] +class DefaultParkedState(State): + pass
+ + + +
+[docs] +class DefaultStoppingState(State): + def __init__(self, acceleration=5.0) -> None: + super(DefaultStoppingState, self).__init__() + self._acceleration = acceleration + +
+[docs] + def in_state(self, dt) -> None: + self._context.speed = approaches.linear(self._context.speed, 0.0, self._acceleration, dt)
+ + +
+[docs] + def on_entry(self, dt) -> None: + self._context._stop_commanded = False
+
+ + + +
+[docs] +class DefaultStoppedState(State): +
+[docs] + def on_entry(self, dt) -> None: + if self._context.auto_park: + self._context._park_commanded = True
+
+ + + +
+[docs] +class DefaultIdleState(State): + def __init__(self, acceleration=0.05) -> None: + super(DefaultIdleState, self).__init__() + self._acceleration = acceleration + +
+[docs] + def in_state(self, dt) -> None: + self._context.speed = approaches.linear( + self._context.speed, self._context.target_speed, self._acceleration, dt + )
+
+ + + +def on_entry(self, dt) -> None: + self._context._idle_commanded = False + + +
+[docs] +class DefaultAcceleratingState(State): + def __init__(self, acceleration=5.0) -> None: + super(DefaultAcceleratingState, self).__init__() + self._acceleration = acceleration + +
+[docs] + def in_state(self, dt) -> None: + self._context.speed = approaches.linear( + self._context.speed, self._context.target_speed, self._acceleration, dt + )
+ + +
+[docs] + def on_entry(self, dt) -> None: + self._context._start_commanded = False
+
+ + + +
+[docs] +class DefaultPhaseLockingState(State): + def __init__(self, phase_locking_speed=5.0) -> None: + super(DefaultPhaseLockingState, self).__init__() + self._phase_locking_speed = phase_locking_speed + +
+[docs] + def in_state(self, dt) -> None: + self._context.phase = approaches.linear( + self._context.phase, + self._context.target_phase, + self._phase_locking_speed, + dt, + )
+ + +
+[docs] + def on_entry(self, dt) -> None: + self._context._phase_commanded = False
+
+ + + +
+[docs] +class DefaultPhaseLockedState(State): + pass
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/chopper/interfaces/epics_interface.html b/_modules/lewis/devices/chopper/interfaces/epics_interface.html new file mode 100644 index 00000000..d3026432 --- /dev/null +++ b/_modules/lewis/devices/chopper/interfaces/epics_interface.html @@ -0,0 +1,261 @@ + + + + + + + + lewis.devices.chopper.interfaces.epics_interface — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.chopper.interfaces.epics_interface

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.epics import PV, EpicsInterface
+
+
+
+[docs] +class ChopperEpicsInterface(EpicsInterface): + """ + ESS chopper EPICS interface + + Interaction with this interface should happen via ChannelAccess (CA). The PV-names + usually carry a prefix which depends on the concrete device and environment, so + it is omitted in this description. The dynamically generated description of the PVs + does however contain the prefix so that the names can be copy-pasted easily. + + The first step is to initialize the chopper, for example via caput on the command line: + + $ caput CmdS init + + After this, the chopper is in a state where it can be started: + + $ caget State + State stopped + + To set a specific speed and phase, the setpoints have to be configured via caput: + + $ caput Spd 100 + $ caput Phs 34.5 + + Then the chopper can be commanded to move towards those values: + + $ caput CmdS start + + Now the disc accelerates to the setpoints, the state should now be different: + + $ caget State + State accelerating + + The possible commands are part of the PV-specific documentation. + """ + + pvs = { + "Spd-RB": PV( + "target_speed", + read_only=True, + doc="Readback value of the speed setpoint in Hz.", + ), + "Spd": PV("target_speed", doc="Speed setpoint in Hz."), + "ActSpd": PV( + "speed", + read_only=True, + doc="Current rotation speed of the chopper disc in Hz.", + ), + "Phs-RB": PV( + "target_phase", + read_only=True, + doc="Readback value of phase setpoint in degrees.", + ), + "Phs": PV("target_phase", doc="Phase setpoint in degrees."), + "ActPhs": PV("phase", read_only=True, doc="Current phase of the chopper disc in degrees."), + "ParkAng-RB": PV( + "target_parking_position", + read_only=True, + doc="Readback value of the discs parking position setpoint in degrees.", + ), + "ParkAng": PV( + "target_parking_position", + doc="The discs parking position setpoint in degrees.", + ), + "AutoPark": PV( + "auto_park", + doc="If enabled, the chopper disc will be moved to the parking " + "position automatically when the speed is 0 or the chopper " + "is otherwise stopped. 0 means False, 1 means True, the string " + 'representations of the enum values are "false" and "true".', + type="enum", + enums=["false", "true"], + ), + "State": PV("state", read_only=True, type="string"), + "CmdS": PV("execute_command", type="string"), + "CmdL": PV("last_command", type="string", read_only=True), + } + + _commands = { + "start": "start", + "stop": "stop", + "set_phase": "lock_phase", + "unlock": "unlock", + "park": "park", + "init": "initialize", + "deinit": "deinitialize", + } + + _last_command = "" + + @property + def execute_command(self) -> str: + """ + Command to execute. Possible commands are start, stop, set_phase, + unlock, park, init, deinit. + """ + return "" + + @execute_command.setter + def execute_command(self, value) -> None: + command = self._commands.get(value) + + getattr(self.device, command)() + self._last_command = command + + @property + def last_command(self): + """ + The last command that was executed successfully. + """ + return self._last_command
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/julabo/devices/device.html b/_modules/lewis/devices/julabo/devices/device.html new file mode 100644 index 00000000..80a52cb6 --- /dev/null +++ b/_modules/lewis/devices/julabo/devices/device.html @@ -0,0 +1,326 @@ + + + + + + + + lewis.devices.julabo.devices.device — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.julabo.devices.device

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from collections import OrderedDict
+
+from lewis.core.utils import check_limits
+from lewis.devices import StateMachineDevice
+
+from . import states
+
+
+
+[docs] +class SimulatedJulabo(StateMachineDevice): + internal_p = 0.1 # The proportional + internal_i = 3 # The integral + internal_d = 0 # The derivative + external_p = 0.1 # The proportional + external_i = 3 # The integral + external_d = 0 # The derivative + temperature_low_limit = 0.0 # Usually set in the hardware + temperature_high_limit = 100.0 # Usually set in the hardware + set_point_temperature = 24.0 # Set point starts equal to the current temperature + heating_power = 5.0 # The heating power + version = "JULABO FP50_MH Simulator, ISIS" + status = "Hello from the simulated Julabo" + is_circulating = 0 # 0 for off, 1 for on + temperature = 24.0 # Current temperature in C + external_temperature = 26.0 # External temperature in C + circulate_commanded = False + temperature_ramp_rate = 5.0 # Guessed value in C/min + + def _initialize_data(self) -> None: + """ + This method is called once on construction. After that, it may be + manually called again to reset the device to its default state. + + After the first call during construction, the class is frozen. + + This means that attempting to define a new member variable will + raise an exception. This is to prevent typos from inadvertently + and silently adding new members instead of accessing existing ones. + """ + pass + + def _get_state_handlers(self): + return { + "circulate": states.DefaultCirculatingState(), + "not_circulate": states.DefaultNotCirculatingState(), + } + + def _get_initial_state(self) -> str: + return "not_circulate" + + def _get_transition_handlers(self): + return OrderedDict( + [ + (("not_circulate", "circulate"), lambda: self.circulate_commanded), + (("circulate", "not_circulate"), lambda: not self.circulate_commanded), + ] + ) + +
+[docs] + def set_set_point(self, param) -> str: + """ + Sets the target temperature. + + :param param: The new temperature in C. Must be positive. + :return: Empty string. + """ + if self.temperature_low_limit <= param <= self.temperature_high_limit: + self.set_point_temperature = param + return ""
+ + +
+[docs] + def set_circulating(self, param) -> str: + """ + Sets whether to circulate - in effect whether the heater is on. + + :param param: The mode to set, must be 0 or 1. + :return: Empty string. + """ + if param == 0: + self.is_circulating = param + self.circulate_commanded = False + elif param == 1: + self.is_circulating = param + self.circulate_commanded = True + return ""
+ + +
+[docs] + @check_limits(0.1, 99.9) + def set_internal_p(self, param) -> str: + """ + Sets the internal proportional. + Xp in Julabo speak. + + :param param: The value to set, must be between 0.1 and 99.9 + :return: Empty string. + """ + self.internal_p = param + return ""
+ + +
+[docs] + @check_limits(3, 9999) + def set_internal_i(self, param) -> str: + """ + Sets the internal integral. + Tn in Julabo speak. + + :param param: The value to set, must be an integer between 3 and 9999 + :return: Empty string. + """ + self.internal_i = param + return ""
+ + +
+[docs] + @check_limits(0, 999) + def set_internal_d(self, param) -> str: + """ + Sets the internal derivative. + Tv in Julabo speak. + + :param param: The value to set, must be an integer between 0 and 999 + :return: Empty string. + """ + self.internal_d = param + return ""
+ + +
+[docs] + @check_limits(0.1, 99.9) + def set_external_p(self, param) -> str: + """ + Sets the external proportional. + Xp in Julabo speak. + + :param param: The value to set, must be between 0.1 and 99.9 + :return: Empty string. + """ + self.external_p = param + return ""
+ + +
+[docs] + @check_limits(3, 9999) + def set_external_i(self, param) -> str: + """ + Sets the external integral. + Tn in Julabo speak. + + :param param: The value to set, must be an integer between 3 and 9999 + :return: Empty string. + """ + self.external_i = param + return ""
+ + +
+[docs] + @check_limits(0, 999) + def set_external_d(self, param) -> str: + """ + Sets the external derivative. + Tv in Julabo speak. + + :param param: The value to set, must be an integer between 0 and 999 + :return: Empty string. + """ + self.external_d = param + return ""
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/julabo/devices/states.html b/_modules/lewis/devices/julabo/devices/states.html new file mode 100644 index 00000000..75d58e2e --- /dev/null +++ b/_modules/lewis/devices/julabo/devices/states.html @@ -0,0 +1,171 @@ + + + + + + + + lewis.devices.julabo.devices.states — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.julabo.devices.states

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.core import approaches
+from lewis.core.statemachine import State
+
+
+
+[docs] +class DefaultNotCirculatingState(State): + pass
+ + + +
+[docs] +class DefaultCirculatingState(State): +
+[docs] + def in_state(self, dt) -> None: + # Approach target temperature at a set rate + self._context.temperature = approaches.linear( + self._context.temperature, + self._context.set_point_temperature, + self._context.heating_power / 60.0, + dt, + )
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/julabo/interfaces/julabo_stream_interface_1.html b/_modules/lewis/devices/julabo/interfaces/julabo_stream_interface_1.html new file mode 100644 index 00000000..4a5af2ed --- /dev/null +++ b/_modules/lewis/devices/julabo/interfaces/julabo_stream_interface_1.html @@ -0,0 +1,219 @@ + + + + + + + + lewis.devices.julabo.interfaces.julabo_stream_interface_1 — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.julabo.interfaces.julabo_stream_interface_1

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.stream import Cmd, StreamInterface, Var
+
+
+
+[docs] +class JulaboStreamInterfaceV1(StreamInterface): + """Julabos can have different commands sets depending on the version number of the hardware. + + This protocol matches that for: FP50_MH (confirmed). + """ + + protocol = "julabo-version-1" + + commands = { + Var("temperature", read_pattern="^IN_PV_00$", doc="The bath temperature."), + Var( + "external_temperature", + read_pattern="^IN_PV_01$", + doc="The external temperature.", + ), + Var("heating_power", read_pattern="^IN_PV_02$", doc="The heating power."), + Var( + "set_point_temperature", + read_pattern="^IN_SP_00$", + doc="The temperature setpoint.", + ), + Cmd( + "set_set_point", + r"^OUT_SP_00 ([0-9]*\.?[0-9]+)$", + argument_mappings=(float,), + ), + Var( + "temperature_high_limit", + read_pattern="^IN_SP_01$", + doc="The high limit - usually set in the hardware.", + ), + Var( + "temperature_low_limit", + read_pattern="^IN_SP_02$", + doc="The low limit - usually set in the hardware.", + ), + Var("version", read_pattern="^VERSION$", doc="The Julabo version."), + Var("status", read_pattern="^STATUS$", doc="The Julabo status."), + Var( + "is_circulating", + read_pattern="^IN_MODE_05$", + doc="Whether it is circulating.", + ), + Cmd("set_circulating", "^OUT_MODE_05 (0|1)$", argument_mappings=(int,)), + Var("internal_p", read_pattern="^IN_PAR_06$", doc="The internal proportional."), + Cmd( + "set_internal_p", + r"^OUT_PAR_06 ([0-9]*\.?[0-9]+)$", + argument_mappings=(float,), + ), + Var("internal_i", read_pattern="^IN_PAR_07$", doc="The internal integral."), + Cmd("set_internal_i", "^OUT_PAR_07 ([0-9]*)$", argument_mappings=(int,)), + Var("internal_d", read_pattern="^IN_PAR_08$", doc="The internal derivative."), + Cmd("set_internal_d", "^OUT_PAR_08 ([0-9]*)$", argument_mappings=(int,)), + Var("external_p", read_pattern="^IN_PAR_09$", doc="The external proportional."), + Cmd( + "set_external_p", + r"^OUT_PAR_09 ([0-9]*\.?[0-9]+)$", + argument_mappings=(float,), + ), + Var("external_i", read_pattern="^IN_PAR_11$", doc="The external integral."), + Cmd("set_external_i", "^OUT_PAR_11 ([0-9]*)$", argument_mappings=(int,)), + Var("external_d", read_pattern="^IN_PAR_12$", doc="The external derivative."), + Cmd("set_external_d", "^OUT_PAR_12 ([0-9]*)$", argument_mappings=(int,)), + } + + in_terminator = "\r" + out_terminator = "\r\n"
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/julabo/interfaces/julabo_stream_interface_2.html b/_modules/lewis/devices/julabo/interfaces/julabo_stream_interface_2.html new file mode 100644 index 00000000..fd9e9ac5 --- /dev/null +++ b/_modules/lewis/devices/julabo/interfaces/julabo_stream_interface_2.html @@ -0,0 +1,221 @@ + + + + + + + + lewis.devices.julabo.interfaces.julabo_stream_interface_2 — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.julabo.interfaces.julabo_stream_interface_2

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.stream import Cmd, StreamInterface, Var
+
+
+
+[docs] +class JulaboStreamInterfaceV2(StreamInterface): + """Julabos can have different commands sets depending on the version number of the hardware. + + This protocol matches that for: FP50-HE (unconfirmed). + """ + + protocol = "julabo-version-2" + + commands = { + Var("temperature", read_pattern="^IN_PV_00$", doc="The bath temperature."), + Var( + "external_temperature", + read_pattern="^IN_PV_01$", + doc="The external temperature.", + ), + Var("heating_power", read_pattern="^IN_PV_02$", doc="The heating power."), + Var( + "set_point_temperature", + read_pattern="^IN_SP_00$", + doc="The temperature setpoint.", + ), + Cmd( + "set_set_point", + r"^OUT_SP_00 ([0-9]*\.?[0-9]+)$", + argument_mappings=(float,), + ), + # Read pattern for high limit is different from version 1 + Var( + "temperature_high_limit", + read_pattern="^IN_SP_03$", + doc="The high limit - usually set in the hardware.", + ), + # Read pattern for low limit is different from version 1 + Var( + "temperature_low_limit", + read_pattern="^IN_SP_04$", + doc="The low limit - usually set in the hardware.", + ), + Var("version", read_pattern="^VERSION$", doc="The Julabo version."), + Var("status", read_pattern="^STATUS$", doc="The Julabo status."), + Var( + "is_circulating", + read_pattern="^IN_MODE_05$", + doc="Whether it is circulating.", + ), + Cmd("set_circulating", "^OUT_MODE_05 (0|1)$", argument_mappings=(int,)), + Var("internal_p", read_pattern="^IN_PAR_06$", doc="The internal proportional."), + Cmd( + "set_internal_p", + r"^OUT_PAR_06 ([0-9]*\.?[0-9]+)$", + argument_mappings=(float,), + ), + Var("internal_i", read_pattern="^IN_PAR_07$", doc="The internal integral."), + Cmd("set_internal_i", "^OUT_PAR_07 ([0-9]*)$", argument_mappings=(int,)), + Var("internal_d", read_pattern="^IN_PAR_08$", doc="The internal derivative."), + Cmd("set_internal_d", "^OUT_PAR_08 ([0-9]*)$", argument_mappings=(int,)), + Var("external_p", read_pattern="^IN_PAR_09$", doc="The external proportional."), + Cmd( + "set_external_p", + r"^OUT_PAR_09 ([0-9]*\.?[0-9]+)$", + argument_mappings=(float,), + ), + Var("external_i", read_pattern="^IN_PAR_11$", doc="The external integral."), + Cmd("set_external_i", "^OUT_PAR_11 ([0-9]*)$", argument_mappings=(int,)), + Var("external_d", read_pattern="^IN_PAR_12$", doc="The external derivative."), + Cmd("set_external_d", "^OUT_PAR_12 ([0-9]*)$", argument_mappings=(int,)), + } + + in_terminator = "\r" + out_terminator = "\n" # Different from version 1
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/linkam_t95/devices/device.html b/_modules/lewis/devices/linkam_t95/devices/device.html new file mode 100644 index 00000000..ee57944c --- /dev/null +++ b/_modules/lewis/devices/linkam_t95/devices/device.html @@ -0,0 +1,238 @@ + + + + + + + + lewis.devices.linkam_t95.devices.device — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.linkam_t95.devices.device

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from collections import OrderedDict
+
+from lewis.devices import StateMachineDevice
+
+from . import states
+
+
+
+[docs] +class SimulatedLinkamT95(StateMachineDevice): + def _initialize_data(self) -> None: + """ + This method is called once on construction. After that, it may be + manually called again to reset the device to its default state. + + After the first call during construction, the class is frozen. + + This means that attempting to define a new member variable will + raise an exception. This is to prevent typos from inadvertently + and silently adding new members instead of accessing existing ones. + """ + self.serial_command_mode = False + self.pump_overspeed = False + + self.start_commanded = False + self.stop_commanded = False + self.hold_commanded = False + + # Real device remembers values from last run, we use arbitrary defaults + self.temperature_rate = 5.0 # Rate of change of temperature in C/min + self.temperature_limit = 0.0 # Target temperature in C + + self.pump_speed = 0 # Pump speed in arbitrary unit, ranging 0 to 30 + self.temperature = 24.0 # Current temperature in C + + self.pump_manual_mode = False + self.manual_target_speed = 0 + + def _get_state_handlers(self): + return { + "init": states.DefaultInitState(), + "stopped": states.DefaultStoppedState(), + "started": states.DefaultStartedState(), + "heat": states.DefaultHeatState(), + "hold": states.DefaultHoldState(), + "cool": states.DefaultCoolState(), + } + + def _get_initial_state(self) -> str: + return "init" + + def _get_transition_handlers(self): + return OrderedDict( + [ + (("init", "stopped"), lambda: self.serial_command_mode), + (("stopped", "started"), lambda: self.start_commanded), + (("started", "stopped"), lambda: self.stop_commanded), + ( + ("started", "heat"), + lambda: self.temperature < self.temperature_limit, + ), + ( + ("started", "hold"), + lambda: self.temperature == self.temperature_limit, + ), + ( + ("started", "cool"), + lambda: self.temperature > self.temperature_limit, + ), + ( + ("heat", "hold"), + lambda: self.temperature == self.temperature_limit or self.hold_commanded, + ), + (("heat", "cool"), lambda: self.temperature > self.temperature_limit), + (("heat", "stopped"), lambda: self.stop_commanded), + ( + ("hold", "heat"), + lambda: self.temperature < self.temperature_limit and not self.hold_commanded, + ), + ( + ("hold", "cool"), + lambda: self.temperature > self.temperature_limit and not self.hold_commanded, + ), + (("hold", "stopped"), lambda: self.stop_commanded), + (("cool", "heat"), lambda: self.temperature < self.temperature_limit), + ( + ("cool", "hold"), + lambda: self.temperature == self.temperature_limit or self.hold_commanded, + ), + (("cool", "stopped"), lambda: self.stop_commanded), + ] + )
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/linkam_t95/devices/states.html b/_modules/lewis/devices/linkam_t95/devices/states.html new file mode 100644 index 00000000..5b64c06f --- /dev/null +++ b/_modules/lewis/devices/linkam_t95/devices/states.html @@ -0,0 +1,243 @@ + + + + + + + + lewis.devices.linkam_t95.devices.states — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.linkam_t95.devices.states

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.core import approaches
+from lewis.core.statemachine import State
+
+
+
+[docs] +class DefaultInitState(State): + pass
+ + + +
+[docs] +class DefaultStoppedState(State): +
+[docs] + def on_entry(self, dt) -> None: + # Reset the stop commanded flag once we enter the stopped state + self._context.stop_commanded = False
+
+ + + +
+[docs] +class DefaultStartedState(State): +
+[docs] + def on_entry(self, dt) -> None: + # Reset the start commanded flag once we enter the started state + self._context.start_commanded = False
+
+ + + +
+[docs] +class DefaultHeatState(State): +
+[docs] + def in_state(self, dt) -> None: + # Approach target temperature at set temperature rate + self._context.temperature = approaches.linear( + self._context.temperature, + self._context.temperature_limit, + self._context.temperature_rate / 60.0, + dt, + )
+
+ + + +
+[docs] +class DefaultHoldState(State): + pass
+ + + +
+[docs] +class DefaultCoolState(State): +
+[docs] + def in_state(self, dt) -> None: + # TODO: Does manual control work like this? Or is it perhaps a separate state? + if self._context.pump_manual_mode: + self._context.pump_speed = self._context.manual_target_speed + else: + # TODO: Figure out real correlation + self._context.pump_speed = 30 * (self._context.temperature_rate / 50.0) + + # Handle "cooling too fast" error + if self._context.pump_speed > 30: + self._context.pump_speed = 30 + self._context.pump_overspeed = True + else: + self._context.pump_speed = int(self._context.pump_speed) + self._context.pump_overspeed = False + + # Approach target temperature at set temperature rate + # TODO: Should be based on pump speed somehow + self._context.temperature = approaches.linear( + self._context.temperature, + self._context.temperature_limit, + self._context.temperature_rate / 60.0, + dt, + )
+ + +
+[docs] + def on_exit(self, dt) -> None: + # If we exit the cooling state, the cooling pump should no longer run + self._context.pump_overspeed = False + self._context.pump_speed = 0
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/devices/linkam_t95/interfaces/stream_interface.html b/_modules/lewis/devices/linkam_t95/interfaces/stream_interface.html new file mode 100644 index 00000000..b143b2d5 --- /dev/null +++ b/_modules/lewis/devices/linkam_t95/interfaces/stream_interface.html @@ -0,0 +1,362 @@ + + + + + + + + lewis.devices.linkam_t95.interfaces.stream_interface — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.devices.linkam_t95.interfaces.stream_interface

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.stream import Cmd, StreamInterface
+from lewis.core.logging import has_log
+
+
+
+[docs] +@has_log +class LinkamT95StreamInterface(StreamInterface): + """ + Linkam T95 TCP stream interface. + + This is the interface of a simulated Linkam T95 device. The device listens on a configured + host:port-combination, one option to connect to it is via telnet: + + $ telnet host port + + Once connected, it's possible to send the specified commands, described in the dynamically + generated documentation. Information about host, port and line terminators in the concrete + device instance are also generated dynamically. + """ + + out_terminator = b"\r" + + commands = { + Cmd("get_status", "^T$", return_mapping=lambda x: x), + Cmd("set_rate", "^R1([0-9]+)$"), + Cmd("set_limit", "^L1([0-9]+)$"), + Cmd("start", "^S$"), + Cmd("stop", "^E$"), + Cmd("hold", "^O$"), + Cmd("heat", "^H$"), + Cmd("cool", "^C$"), + Cmd("pump_command", "^P(a0|m0|[0123456789:;<=>?@ABCDEFGHIJKLMN]{1})$"), + } + +
+[docs] + def get_status(self): + """ + Models "T Command" functionality of device. + + Returns all available status information about the device as single byte array. + + :return: Byte array consisting of 10 status bytes. + """ + + # "The first command sent must be a 'T' command" from T95 manual + self.device.serial_command_mode = True + + Tarray = [0x80] * 10 + + # Status byte (SB1) + Tarray[0] = { + "stopped": 0x01, + "heat": 0x10, + "cool": 0x20, + "hold": 0x30, + }.get(self.device._csm.state, 0x01) + + if Tarray[0] == 0x30 and self.device.hold_commanded: + Tarray[0] = 0x50 + + # Error status byte (EB1) + if self.device.pump_overspeed: + Tarray[1] |= 0x01 + # TODO: Add support for other error conditions? + + # Pump status byte (PB1) + Tarray[2] = 0x80 + self.device.pump_speed + + # Temperature + Tarray[6:10] = [ord(x) for x in "%04x" % (int(self.device.temperature * 10) & 0xFFFF)] + + return bytes(Tarray)
+ + +
+[docs] + def set_rate(self, param) -> bytes: + """ + Models "Rate Command" functionality of device. + + Sets the target rate of temperature change. + + :param param: Rate of temperature change in C/min, multiplied by 100, as a string. Must be positive. + :return: Empty string. + """ + # TODO: Is not having leading zeroes / 4 digits an error? + rate = int(param) + if 1 <= rate <= 15000: + self.device.temperature_rate = rate / 100.0 + return b""
+ + +
+[docs] + def set_limit(self, param) -> bytes: + """ + Models "Limit Command" functionality of device. + + Sets the target temperate to be reached. + + :param param: Target temperature in C, multiplied by 10, as a string. Can be negative. + :return: Empty string. + """ + # TODO: Is not having leading zeroes / 4 digits an error? + limit = int(param) + if -2000 <= limit <= 6000: + self.device.temperature_limit = limit / 10.0 + return b""
+ + +
+[docs] + def start(self) -> bytes: + """ + Models "Start Command" functionality of device. + + Tells the T95 unit to start heating or cooling at the rate specified by setRate and to a + limit set by setLimit. + + :return: Empty string. + """ + self.device.start_commanded = True + return b""
+ + +
+[docs] + def stop(self) -> bytes: + """ + Models "Stop Command" functionality of device. + + Tells the T95 unit to stop heating or cooling. + + :return: Empty string. + """ + self.device.stop_commanded = True + return b""
+ + +
+[docs] + def hold(self) -> bytes: + """ + Models "Hold Command" functionality of device. + + Device will hold current temperature until a heat or cool command is issued. + + :return: Empty string. + """ + self.device.hold_commanded = True + return b""
+ + +
+[docs] + def heat(self) -> bytes: + """ + Models "Heat Command" functionality of device. + + :return: Empty string. + """ + # TODO: Is this really all it does? + self.device.hold_commanded = False + return b""
+ + +
+[docs] + def cool(self) -> bytes: + """ + Models "Cool Command" functionality of device. + + :return: Empty string. + """ + # TODO: Is this really all it does? + self.device.hold_commanded = False + return b""
+ + +
+[docs] + def pump_command(self, param) -> bytes: + """ + Models "LNP Pump Commands" functionality of device. + + Switches between automatic or manual pump mode, and adjusts speed when in manual mode. + + :param param: 'a0' for auto, 'm0' for manual, [0-N] for speed. + :return: + """ + lookup = b"0123456789:;<=>?@ABCDEFGHIJKLMN" + + if param == b"a0": + self.device.pump_manual_mode = False + elif param == b"m0": + self.device.pump_manual_mode = True + elif param in lookup: + self.device.manual_target_speed = lookup.index(param) + return b""
+ + +
+[docs] + def handle_error(self, request, error) -> None: + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error))
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/examples/dual_device.html b/_modules/lewis/examples/dual_device.html new file mode 100644 index 00000000..a01464bb --- /dev/null +++ b/_modules/lewis/examples/dual_device.html @@ -0,0 +1,226 @@ + + + + + + + + lewis.examples.dual_device — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.examples.dual_device

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.epics import PV, EpicsInterface
+from lewis.adapters.stream import StreamInterface, Var
+from lewis.core.utils import check_limits
+from lewis.devices import Device
+
+
+
+[docs] +class VerySimpleDevice(Device): + upper_limit = 100 + lower_limit = 0 + + param = 10 + _second = 2.0 + +
+[docs] + def get_param(self): + """The parameter multiplied by 2.""" + return self.param * 2
+ + + def set_param(self, new_param) -> None: + self.param = int(new_param / 2) + + @property + def second(self): + """A second (floating point) parameter.""" + return self._second + + @second.setter + @check_limits("lower_limit", "upper_limit") + def second(self, new_second) -> None: + self._second = new_second
+ + + +
+[docs] +class VerySimpleInterface(EpicsInterface): + """ + This is the EPICS interface to a quite simple device. It offers 5 PVs that expose + different things that are part of the device, the interface or neither. + """ + + pvs = { + "Param-Raw": PV("param", type="int", doc="The raw underlying parameter."), + "Param": PV(("get_param", "set_param"), type="int"), + "Second": PV("second", meta_data_property="param_raw_meta"), + "Second-Int": PV("second_int", type="int"), + "Constant": PV(lambda: 4, doc="A constant number."), + } + + @property + def param_raw_meta(self): + return {"lolo": self.device.lower_limit, "hihi": self.device.upper_limit} + + @property + def second_int(self): + """The second parameter as an integer.""" + return int(self.device.second)
+ + + +
+[docs] +class VerySimpleStreamInterface(StreamInterface): + """This is a TCP stream interface to the epics device, which only exposes param.""" + + commands = { + Var( + "param", + read_pattern=r"P\?$", + write_pattern=r"P=(\d+)", + argument_mappings=(int,), + doc="An integer parameter.", + ) + } + + in_terminator = "\r\n" + out_terminator = "\r\n"
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/examples/example_motor.html b/_modules/lewis/examples/example_motor.html new file mode 100644 index 00000000..976c4f94 --- /dev/null +++ b/_modules/lewis/examples/example_motor.html @@ -0,0 +1,303 @@ + + + + + + + + lewis.examples.example_motor — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.examples.example_motor

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from collections import OrderedDict
+
+from lewis.adapters.stream import Cmd, StreamInterface, regex, scanf
+from lewis.core import approaches
+from lewis.core.statemachine import State
+from lewis.devices import StateMachineDevice
+
+
+
+[docs] +class DefaultMovingState(State): +
+[docs] + def in_state(self, dt) -> None: + old_position = self._context.position + self._context.position = approaches.linear( + old_position, self._context.target, self._context.speed, dt + ) + self.log.info( + "Moved position (%s -> %s), target=%s, speed=%s", + old_position, + self._context.position, + self._context.target, + self._context.speed, + )
+
+ + + +
+[docs] +class SimulatedExampleMotor(StateMachineDevice): + def _initialize_data(self) -> None: + self.position = 0.0 + self._target = 0.0 + self.speed = 2.0 + + def _get_state_handlers(self): + return {"idle": State(), "moving": DefaultMovingState()} + + def _get_initial_state(self) -> str: + return "idle" + + def _get_transition_handlers(self): + return OrderedDict( + [ + (("idle", "moving"), lambda: self.position != self.target), + (("moving", "idle"), lambda: self.position == self.target), + ] + ) + + @property + def state(self): + return self._csm.state + + @property + def target(self): + return self._target + + @target.setter + def target(self, new_target) -> None: + if self.state == "moving": + raise RuntimeError("Can not set new target while moving.") + + if not (0 <= new_target <= 250): + raise ValueError("Target is out of range [0, 250]") + + self._target = new_target + +
+[docs] + def stop(self): + """Stops the motor and returns the new target and position, which are equal""" + + self._target = self.position + + self.log.info("Stopping movement after user request.") + + return self.target, self.position
+
+ + + +
+[docs] +class ExampleMotorStreamInterface(StreamInterface): + """ + TCP-stream based example motor interface + + This motor simulation can be controlled via telnet: + + $ telnet host port + + Where the host and port-parameter are part of the dynamically created documentation for + a concrete device instance. + + The motor starts moving immediately when a new target position is set. Once it's moving, + it has to be stopped to receive a new target, otherwise an error is generated. + """ + + commands = { + Cmd("get_status", regex(r"^S\?$")), # explicit regex + Cmd("get_position", r"^P\?$"), # implicit regex + Cmd("get_target", r"^T\?$"), + Cmd("set_target", scanf("T=%f")), # scanf format specification + Cmd("stop", r"^H$", return_mapping=lambda x: "T={},P={}".format(x[0], x[1])), + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + +
+[docs] + def get_status(self): + """Returns the status of the device, which is one of 'idle' or 'moving'.""" + return self.device.state
+ + +
+[docs] + def get_position(self): + """Returns the current position in mm.""" + return self.device.position
+ + +
+[docs] + def get_target(self): + """Returns the current target in mm.""" + return self.device.target
+ + +
+[docs] + def set_target(self, new_target): + """ + Sets the new target in mm, the movement starts immediately. If the value is outside + the interval [0, 250] or the motor is already moving, an error is returned, otherwise + the new target is returned.""" + try: + self.device.target = new_target + return "T={}".format(new_target) + except RuntimeError: + return "err: not idle" + except ValueError: + return "err: not 0<=T<=250"
+
+ + + +setups = dict( + moving=dict( + device_type=SimulatedExampleMotor, + parameters=dict( + override_initial_state="moving", + override_initial_data=dict(_target=120.0, position=20.0), + ), + ) +) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/examples/modbus_device.html b/_modules/lewis/examples/modbus_device.html new file mode 100644 index 00000000..c48a2fa8 --- /dev/null +++ b/_modules/lewis/examples/modbus_device.html @@ -0,0 +1,174 @@ + + + + + + + + lewis.examples.modbus_device — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.examples.modbus_device

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.modbus import ModbusBasicDataBank, ModbusInterface
+from lewis.devices import Device
+
+
+
+[docs] +class ModbusDevice(Device): + pass
+ + + +
+[docs] +class ExampleModbusInterface(ModbusInterface): + """ + The class attributes di, co, ir and hr represent Discrete Inputs, Coils, Input Registers and + Holding Registers, respectively. Each attribute should be assigned a ModbusDataBank instance + by the Interface implementation. + + Here, two basic ModbusDataBanks are created and initialized to a default value across the full + range of valid addresses. One DataBank is shared by di and co, and the other by ir and hr to + demonstrate overlaid memory segments. If you want each segment to have its own memory, just + create separate instances for all four. + """ + + di = ModbusBasicDataBank(False) + co = di + ir = ModbusBasicDataBank(0) + hr = ir
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/examples/simple_device.html b/_modules/lewis/examples/simple_device.html new file mode 100644 index 00000000..f392c4bd --- /dev/null +++ b/_modules/lewis/examples/simple_device.html @@ -0,0 +1,214 @@ + + + + + + + + lewis.examples.simple_device — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.examples.simple_device

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.stream import Cmd, StreamInterface, Var
+from lewis.devices import Device
+
+
+
+[docs] +class VerySimpleDevice(Device): + param = 10
+ + + +
+[docs] +class VerySimpleInterface(StreamInterface): + """ + A very simple device with TCP-stream interface + + The device has only one parameter, which can be set to an arbitrary + value. The interface consists of five commands which can be invoked via telnet. + To connect: + + $ telnet host port + + After that, typing either of the commands and pressing enter sends them to the server. + + The commands are: + + - ``V``: Returns the parameter as part of a verbose message. + - ``V=something``: Sets the parameter to ``something``. + - ``P``: Returns the device parameter unmodified. + - ``P=something``: Exactly the same as ``V=something``. + - ``R`` or ``r``: Returns the number 4. + + """ + + commands = { + Cmd("get_param", pattern="^V$", return_mapping="The value is {}".format), + Cmd("set_param", pattern="^V=(.+)$", argument_mappings=(int,)), + Var( + "param", + read_pattern="^P$", + write_pattern="^P=(.+)$", + doc="The only parameter.", + ), + Cmd(lambda: 4, pattern="^R$(?i)", doc='"Random" number (4).'), + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + +
+[docs] + def get_param(self): + """Returns the device parameter.""" + return self.device.param
+ + +
+[docs] + def set_param(self, new_param) -> None: + """Set the device parameter, does not return anything.""" + self.device.param = new_param
+ + +
+[docs] + def handle_error(self, request, error): + return "An error occurred: " + repr(error)
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/examples/timeout_device.html b/_modules/lewis/examples/timeout_device.html new file mode 100644 index 00000000..cbff7ab6 --- /dev/null +++ b/_modules/lewis/examples/timeout_device.html @@ -0,0 +1,214 @@ + + + + + + + + lewis.examples.timeout_device — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for lewis.examples.timeout_device

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from lewis.adapters.stream import Cmd, StreamInterface, Var, scanf
+from lewis.devices import Device
+
+
+
+[docs] +class TimeTerminatedDevice(Device): + param = 10 + + def say_world(self) -> str: + return "world!" + + def say_bar(self) -> str: + return "bar!"
+ + + +
+[docs] +class TimeTerminatedInterface(StreamInterface): + """ + A simple device where commands are terminated by a timeout. + + This demonstrates how to implement devices that do not have standard + terminators and where a command is considered terminated after a certain + time delay of not receiving more data. + + To interact with this device, you must switch telnet into char mode, or use + netcat with special tty settings: + + $ telnet host port + ^] + telnet> mode char + [type command and wait] + + $ stty -icanon && nc host port + hello world! + foobar! + + The following commands are available: + + - ``hello ``: Reply with "world!" + - ``foo``: Replay with "bar!" + - ``P``: Returns the device parameter + - ``P=something``: Set parameter to specified value + + """ + + commands = { + # Space as \x20 represents a custom 'terminator' for this command only + # However, waiting for the timeout still applies + Cmd("say_world", pattern=scanf("hello\x20")), + Cmd("say_bar", pattern=scanf("foo")), + Var("param", read_pattern=scanf("P"), write_pattern=scanf("P=%d")), + } + + # An empty in_terminator triggers "timeout mode" + # Otherwise, a ReadTimeout is considered an error. + in_terminator = "" + out_terminator = "\r\n" + + # Unusually long, for easier manual entry. + readtimeout = 2500 + +
+[docs] + def handle_error(self, request, error): + return "An error occurred: " + repr(error)
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/scripts.html b/_modules/lewis/scripts.html new file mode 100644 index 00000000..df57fde8 --- /dev/null +++ b/_modules/lewis/scripts.html @@ -0,0 +1,168 @@ + + + + + + + + lewis.scripts — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.scripts

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+from io import StringIO
+
+
+
+[docs] +def get_usage_text(parser, indent=None): + """ + This small helper function extracts the help information from an ArgumentParser instance + and indents the text by the number of spaces supplied in the indent-argument. + + :param parser: ArgumentParser object. + :param indent: Number of spaces to put before each line or None. + :return: Formatted help string of the supplied parser. + """ + usage_text = StringIO() + parser.print_help(usage_text) + + usage_string = usage_text.getvalue() + + if indent is None: + return usage_string + + return "\n".join([" " * indent + line for line in usage_string.split("\n")])
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/scripts/run.html b/_modules/lewis/scripts/run.html new file mode 100644 index 00000000..3a7ad707 --- /dev/null +++ b/_modules/lewis/scripts/run.html @@ -0,0 +1,423 @@ + + + + + + + + lewis.scripts.run — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.scripts.run

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+import argparse
+import os
+import sys
+
+import yaml
+
+from lewis import __version__
+from lewis.core.exceptions import LewisException
+from lewis.core.logging import default_log_format, logging
+from lewis.core.simulation import SimulationFactory
+from lewis.scripts import get_usage_text
+
+parser = argparse.ArgumentParser(
+    description="This script starts a simulated device that is exposed via the specified "
+    "communication protocol. Complete documentation of Lewis is available in "
+    "the online documentation on GitHub https://github.com/ess-dmsc/lewis/",
+    add_help=False,
+    prog="lewis",
+)
+
+positional_args = parser.add_argument_group("Positional arguments")
+
+positional_args.add_argument(
+    "device",
+    nargs="?",
+    help="Name of the device to simulate, omitting this argument prints out a list "
+    "of available devices.",
+)
+
+device_args = parser.add_argument_group(
+    "Device related parameters",
+    "Parameters that influence the selected device, such as setup or protocol.",
+)
+
+device_args.add_argument(
+    "-s",
+    "--setup",
+    default=None,
+    help="Name of the setup to load. If not provided, the default setup is selected. If there"
+    "is no default, a list of setups is printed.",
+)
+
+interface_args = device_args.add_mutually_exclusive_group()
+interface_args.add_argument(
+    "-n",
+    "--no-interface",
+    default=False,
+    action="store_true",
+    help="If supplied, the device simulation will not have any communication interface.",
+)
+interface_args.add_argument(
+    "-p",
+    "--adapter-options",
+    default=[],
+    action="append",
+    help="Supply the protocol name and adapter options in the format "
+    '"name:{opt1: val, opt2: val}". Use the -l flag to see which protocols '
+    "are available for the selected device. Can be supplied multiple times for "
+    "multiple protocols.",
+)
+device_args.add_argument(
+    "-l",
+    "--list-protocols",
+    action="store_true",
+    help="List available protocols for selected device.",
+)
+device_args.add_argument(
+    "-L",
+    "--list-adapter-options",
+    action="store_true",
+    help="List available configuration options and their value. Values that have not been "
+    "modified in the -p argument are default values.",
+)
+device_args.add_argument(
+    "-i",
+    "--show-interface",
+    action="store_true",
+    help="Show command interface of device interface.",
+)
+device_args.add_argument(
+    "-k",
+    "--device-package",
+    default="lewis.devices",
+    help="Name of packages where devices are found.",
+)
+device_args.add_argument(
+    "-a",
+    "--add-path",
+    default=None,
+    help="Path where the device package exists. Is added to the path.",
+)
+
+simulation_args = parser.add_argument_group(
+    "Simulation related parameters",
+    "Parameters that influence the simulation itself, such as timing and speed.",
+)
+
+simulation_args.add_argument(
+    "-c",
+    "--cycle-delay",
+    type=float,
+    default=0.1,
+    help="Approximate time to spend in each cycle of the simulation. "
+    "0 for maximum simulation rate.",
+)
+simulation_args.add_argument(
+    "-e",
+    "--speed",
+    type=float,
+    default=1.0,
+    help="Simulation speed. The actually elapsed time between two cycles is "
+    "multiplied with this speed to determine the simulated time.",
+)
+simulation_args.add_argument(
+    "-r",
+    "--rpc-host",
+    default=None,
+    help="HOST:PORT format string for exposing the device and the simulation via "
+    "JSON-RPC over ZMQ. Use lewis-control to access this service from the command line.",
+)
+
+other_args = parser.add_argument_group("Other arguments")
+
+other_args.add_argument(
+    "-o",
+    "--output-level",
+    default="info",
+    choices=["none", "critical", "error", "warning", "info", "debug"],
+    help="Level of detail for logging to stderr.",
+)
+other_args.add_argument(
+    "-V",
+    "--verify",
+    action="store_true",
+    help="Sets the output level to 'debug' and aborts before starting the device simulation. "
+    "This is intended to help with diagnosing problems with devices or input arguments.",
+)
+
+version_handling = other_args.add_mutually_exclusive_group()
+version_handling.add_argument(
+    "-I",
+    "--ignore-versions",
+    action="store_true",
+    help="Ignore version mismatches between device and framework. A warning will still "
+    "be logged.",
+)
+other_args.add_argument(
+    "-v", "--version", action="store_true", help="Prints the version and exits."
+)
+other_args.add_argument("-h", "--help", action="help", help="Shows this help message and exits.")
+
+deprecated_args = parser.add_argument_group("Deprecated arguments")
+deprecated_args.add_argument(
+    "-R",
+    "--relaxed-versions",
+    action="store_true",
+    help="Renamed to --I/--ignore-versions. Using this old option produces an error "
+    "and it will be removed in a future release.",
+)
+
+__doc__ = (
+    "This script is the main interaction point of the user with Lewis. The usage "
+    "is as follows:\n\n.. code-block:: none\n\n{}".format(get_usage_text(parser, indent=4))
+)
+
+
+def parse_adapter_options(raw_adapter_options):
+    if not raw_adapter_options:
+        return {None: {}}
+
+    protocols = {}
+
+    for option_string in raw_adapter_options:
+        try:
+            adapter_options = yaml.safe_load(option_string)
+        except yaml.YAMLError:
+            raise LewisException(
+                "It was not possible to parse this adapter option specification:\n"
+                "    %s\n"
+                "Correct formats for the -p argument are:\n"
+                "    -p protocol\n"
+                "    -p \"protocol: {option: 'val', option2: 34}\"\n"
+                "The spaces after the colons are significant!" % option_string
+            )
+
+        if isinstance(adapter_options, str):
+            protocols[adapter_options] = {}
+        else:
+            protocol = list(adapter_options.keys())[0]
+            protocols[protocol] = adapter_options.get(protocol, {})
+
+    return protocols
+
+
+
+[docs] +def run_simulation(argument_list=None) -> None: # noqa: C901 + """ + This is effectively the main function of a typical simulation run. Arguments passed in are + parsed and used to construct and run the simulation. + + This function only exits when the program has completed or is interrupted. + + :param argument_list: Argument list to pass to the argument parser declared in this module. + """ + try: + arguments = parser.parse_args(argument_list or sys.argv[1:]) + + if arguments.version: + print(__version__) + return + + if arguments.relaxed_versions: + print("Unknown option --relaxed-versions. Did you mean --ignore-versions?") + return + + loglevel = "debug" if arguments.verify else arguments.output_level + if loglevel != "none": + logging.basicConfig(level=getattr(logging, loglevel.upper()), format=default_log_format) + + if arguments.add_path is not None: + additional_path = os.path.abspath(arguments.add_path) + logging.getLogger().debug("Extending path with: %s", additional_path) + sys.path.append(additional_path) + + simulation_factory = SimulationFactory(arguments.device_package) + + if not arguments.device: + devices = ["Please specify a device to simulate. The following devices are available:"] + + for dev in sorted(simulation_factory.devices): + devices.append(" " + dev) + + print("\n".join(devices)) + return + + if arguments.list_protocols: + print("\n".join(simulation_factory.get_protocols(arguments.device))) + return + + protocols = ( + parse_adapter_options(arguments.adapter_options) if not arguments.no_interface else {} + ) + + simulation = simulation_factory.create( + arguments.device, arguments.setup, protocols, arguments.rpc_host + ) + + if arguments.show_interface: + print(simulation._adapters.documentation()) + return + + if arguments.list_adapter_options: + configurations = simulation._adapters.configuration() + + for protocol, options in configurations.items(): + print("{}:".format(protocol)) + + for opt, val in options.items(): + print(" {} = {}".format(opt, val)) + + return + + simulation.cycle_delay = arguments.cycle_delay + simulation.speed = arguments.speed + + if not arguments.verify: + try: + simulation.start() + except KeyboardInterrupt: + print("\nInterrupt received; shutting down. Goodbye, cruel world!") + simulation.log.critical("Simulation aborted by user interaction") + finally: + simulation.stop() + + except LewisException as e: + print("\n".join(("An error occurred:", str(e))))
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/utils/byte_conversions.html b/_modules/lewis/utils/byte_conversions.html new file mode 100644 index 00000000..91ee14fd --- /dev/null +++ b/_modules/lewis/utils/byte_conversions.html @@ -0,0 +1,224 @@ + + + + + + + + lewis.utils.byte_conversions — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.utils.byte_conversions

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+import struct
+from typing import Literal
+
+BYTE = 2**8
+
+
+def _get_byteorder_name(low_byte_first: bool) -> Literal["little", "big"]:
+    """
+    Get the python name for low byte first
+    :param low_byte_first: True for low byte first; False for MSB first
+    :return: name
+    """
+    return "little" if low_byte_first else "big"
+
+
+
+[docs] +def int_to_raw_bytes(integer: int, length: int, low_byte_first: bool) -> bytes: + """ + Converts an integer to an unsigned set of bytes with the specified length (represented as a string). Unless the + integer is negative in which case it converts to a signed integer. + + If low byte first is True, the least significant byte comes first, otherwise the most significant byte comes first. + + :param integer: The integer to convert. + :param length: The length of the result. + :param low_byte_first: Whether to put the least significant byte first. + + :return: string representation of the bytes. + """ + return integer.to_bytes( + length=length, byteorder=_get_byteorder_name(low_byte_first), signed=integer < 0 + )
+ + + +
+[docs] +def raw_bytes_to_int(raw_bytes: bytes, low_bytes_first: bool = True) -> int: + """ + Converts an unsigned set of bytes to an integer. + + :param raw_bytes: A string representation of the raw bytes. + :param low_bytes_first: Whether the given raw bytes are in little endian or not. True by default. + + :return: The integer represented by the raw bytes passed in. + """ + return int.from_bytes(raw_bytes, byteorder=_get_byteorder_name(low_bytes_first))
+ + + +
+[docs] +def float_to_raw_bytes(real_number: float, low_byte_first: bool = True) -> bytes: + """ + Converts an floating point number to an unsigned set of bytes. + + :param real_number: The float to convert. + :param low_byte_first: Whether to put the least significant byte first. True by default. + + :return: A string representation of the bytes. + """ + raw_bytes = bytes(struct.pack(">f", real_number)) + + return raw_bytes[::-1] if low_byte_first else raw_bytes
+ + + +
+[docs] +def raw_bytes_to_float(raw_bytes: bytes) -> float: + """ + Convert a set of bytes to a floating point number + + :param raw_bytes: A string representation of the raw bytes. + + :return: float: The floating point number represented by the given bytes. + """ + return struct.unpack("f", raw_bytes[::-1])[0]
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/utils/command_builder.html b/_modules/lewis/utils/command_builder.html new file mode 100644 index 00000000..27079c5f --- /dev/null +++ b/_modules/lewis/utils/command_builder.html @@ -0,0 +1,507 @@ + + + + + + + + lewis.utils.command_builder — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.utils.command_builder

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+"""
+A fluent command builder for lewis.
+"""
+
+import re
+from functools import partial
+from typing import AnyStr
+
+from lewis.adapters.stream import Cmd, regex
+from lewis.utils.constants import ACK, ENQ, EOT, ETX, STX
+
+string_arg = partial(str, encoding="utf-8")
+
+
+
+[docs] +class CmdBuilder(object): + """ + Build a command for the stream adapter. + + Do this by creating this object, adding the values and then building it (this uses a fluent interface). + + For example to read a pressure the ioc might send "pres?" and when that happens this should call get_pres + command would be: + >>> CmdBuilder("get_pres").escape("pres?").build() + This will generate the regex needed by Lewis. The escape is just making sure none of the characters are special + reg ex characters. + If you wanted to set a pressure the ioc might send "pres <pressure>" where <pressure> is a floating point number, + the interface should call set_pres with that number. Now use: + >>> CmdBuilder("set_pres").escape("pres ").float().build() + this add float as a regularly expression capture group for your argument. It is equivalent to: + >>> Cmd("set_pres", "pres ([+-]?\\d+\\.?\\d*)") + There are various arguments like int and digit. Finally some special characters are included so if your protocol + uses enquirey character ascii 5 you can match is using + >>> CmdBuilder("set_pres").escape("pres?").enq().build() + """ + + def __init__(self, target_method, arg_sep="", ignore="", ignore_case=False) -> None: + """ + Create a builder. Use build to create the final object + + :param target_method: name of the method target to call when the reg ex matches + :param arg_sep: separators between arguments which are next to each other + :param ignore: set of characters to ignore between text and arguments + :param ignore_case: ignore the case when matching command + """ + self._target_method = target_method + self._arg_sep = arg_sep + self._current_sep = "" + self.argument_mappings = [] + if ignore is None or ignore == "": + self._ignore = "" + else: + self._ignore = "[{0}]*".format(ignore) + self._reg_ex = self._ignore + + self._ignore_case = ignore_case + + def _add_to_regex(self, regex, is_arg: bool) -> None: + self._reg_ex += regex + self._ignore + if not is_arg: + self._current_sep = "" + +
+[docs] + def optional(self, text) -> "CmdBuilder": + """ + Add some escaped text which does not necessarily need to be there. For commands with optional parameters + :param text: Text to add + :return: builder + """ + self._add_to_regex("(?:" + re.escape(text) + ")?", False) + return self
+ + +
+[docs] + def escape(self, text) -> "CmdBuilder": + """ + Add some text to the regex which is escaped. + + :param text: text to add + :return: builder + """ + self._add_to_regex(re.escape(text), False) + return self
+ + +
+[docs] + def regex(self, new_regex: str) -> "CmdBuilder": + """ + Add a regex to match but not as an argument. + + :param new_regex: regex to add + :return: builder + """ + self._add_to_regex(new_regex, False) + return self
+ + +
+[docs] + def enum(self, *allowed_values: AnyStr) -> "CmdBuilder": + """ + Matches one of a set of specified strings. + + :param allowed_values: the values this function is allowed to match + :return: builder + """ + self._add_to_regex( + "({})".format("|".join([re.escape(arg) for arg in allowed_values])), + is_arg=True, + ) + self.argument_mappings.append(string_arg) + return self
+ + +
+[docs] + def spaces(self, at_least_one: bool = False) -> "CmdBuilder": + """ + Add a regex for any number of spaces + + :param at_least_one: true there must be at least one space; false there can be any number including zero + :return: builder + + """ + wildcard = "+" if at_least_one else "*" + + self._add_to_regex(" " + wildcard, False) + return self
+ + +
+[docs] + def arg(self, arg_regex, argument_mapping: partial = string_arg) -> "CmdBuilder": + """ + Add an argument to the command. + + :param arg_regex: regex for the argument (capture group will be added) + :param argument_mapping: the type mapping for the argument (default is str) + :return: builder + """ + self._add_to_regex(self._current_sep + "(" + arg_regex + ")", True) + self._current_sep = self._arg_sep + self.argument_mappings.append(argument_mapping) + return self
+ + +
+[docs] + def string(self, length: None | int = None) -> "CmdBuilder": + """ + Add an argument which is a string of a given length (if blank string is any length) + + :param length: length of string; None for any length + :return: builder + """ + if length is None: + self.arg(".+") + else: + self.arg(".{{{}}}".format(length)) + return self
+ + +
+[docs] + def float(self, mapping: type = float, ignore: bool = False) -> "CmdBuilder": + """ + Add a float argument. + + :param mapping: The type to cast the response to (default: float) + :param ignore: True to match with a float but ignore the returned value (default: False) + :return: builder + """ + regex = r"[+-]?\d+\.?\d*" + return self.regex(regex) if ignore else self.arg(regex, mapping)
+ + +
+[docs] + def digit(self, mapping: type = int, ignore: bool = False) -> "CmdBuilder": + """ + Add a single digit argument. + + :param mapping: The type to cast the response to (default: int) + :param ignore: True to match with a digit but ignore the returned value (default: False) + :return: builder + """ + return self.regex(r"\d") if ignore else self.arg(r"\d", mapping)
+ + +
+[docs] + def char(self, not_chars: None | list[str] | str = None, ignore=False) -> "CmdBuilder": + """ + Add a single character argument. + + :param not_chars: characters that the character can not be; None for can be anything + :param ignore: True to match with a char but ignore the returned value (default: False) + :return: builder + """ + _regex = r"." if not_chars is None else "[^{}]".format("".join(not_chars)) + return self.regex(_regex) if ignore else self.arg(_regex)
+ + +
+[docs] + def int(self, mapping: type = int, ignore: bool = False) -> "CmdBuilder": + """ + Add an integer argument. + + :param mapping: The type to cast the response to (default: int) + :param ignore: True to match with a int but ignore the returned value (default: False) + :return: builder + """ + _regex = r"[+-]?\d+" + return self.regex(_regex) if ignore else self.arg(_regex, mapping)
+ + +
+[docs] + def any(self) -> "CmdBuilder": + """ + Add an argument that matches anything. + + :return: builder + """ + return self.arg(r".*")
+ + +
+[docs] + def any_except(self, char: str) -> "CmdBuilder": + """ + Adds an argument that matches anything other than a specified character (useful for commands containing + delimiters) + + :param char: the character not to match + :return: builder + """ + return self.arg(r"[^{}]*".format(re.escape(char)))
+ + +
+[docs] + def build(self, *args, **kwargs) -> Cmd: + """ + Builds the CMd object based on the target and regular expression. + + :param args: arguments to pass to Cmd constructor + :param kwargs: key word arguments to pass to Cmd constructor + :return: Cmd object + """ + if self._ignore_case: + pattern = regex(self._reg_ex) + pattern.compiled_pattern = re.compile(self._reg_ex.encode(), re.IGNORECASE) + else: + pattern = self._reg_ex + return Cmd( + self._target_method, + pattern, + argument_mappings=self.argument_mappings, + *args, + **kwargs, + )
+ + +
+[docs] + def add_ascii_character(self, char_number: int) -> "CmdBuilder": + """ + Add a single character based on its integer value, e.g. 49 is 'a'. + + :param char_number: character number + :return: self + """ + self._add_to_regex(chr(char_number), False) + return self
+ + +
+[docs] + def stx(self) -> "CmdBuilder": + """ + Add the STX character (0x2) to the string. + + :return: builder + """ + return self.escape(STX)
+ + +
+[docs] + def etx(self) -> "CmdBuilder": + """ + Add the ETX character (0x3) to the string. + + :return: builder + """ + return self.escape(ETX)
+ + +
+[docs] + def eot(self) -> "CmdBuilder": + """ + Add the EOT character (0x4) to the string. + + :return: builder + """ + return self.escape(EOT)
+ + +
+[docs] + def enq(self) -> "CmdBuilder": + """ + Add the ENQ character (0x5) to the string. + + :return: builder + """ + return self.escape(ENQ)
+ + +
+[docs] + def ack(self) -> "CmdBuilder": + """ + Add the ACK character (0x6) to the string. + + :return: builder + """ + return self.escape(ACK)
+ + +
+[docs] + def eos(self) -> "CmdBuilder": + """ + Adds the regex end-of-string character to a command. + + :return: builder + """ + self._reg_ex += "$" + return self
+ + +
+[docs] + def get_multicommands(self, command_separator: AnyStr) -> "CmdBuilder": + """ + Allows emulator to split multiple commands separated by a defined command separator, e.g. ";". + Must be accompanied by stream device methods. See Keithley 2700 for examples + + :param command_separator: Character(s) that separate commands + :return: builder + """ + self.arg("[^" + re.escape(command_separator) + "]*").escape(command_separator).arg(".*") + return self
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/lewis/utils/replies.html b/_modules/lewis/utils/replies.html new file mode 100644 index 00000000..6d863c47 --- /dev/null +++ b/_modules/lewis/utils/replies.html @@ -0,0 +1,281 @@ + + + + + + + + lewis.utils.replies — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for lewis.utils.replies

+# -*- coding: utf-8 -*-
+# *********************************************************************
+# lewis - a library for creating hardware device simulators
+# Copyright (C) 2016-2021 European Spallation Source ERIC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# *********************************************************************
+
+import time
+from functools import wraps
+from typing import Callable, ParamSpec, TypeVar
+
+from lewis.adapters.stream import StreamInterface
+from lewis.core.logging import has_log
+
+T = TypeVar("T")
+P = ParamSpec("P")
+
+
+def _get_device_from(instance: StreamInterface):
+    try:
+        device = instance.device
+    except AttributeError:
+        try:
+            device = instance._device
+        except AttributeError:
+            raise AttributeError(
+                "Expected device to be accessible as either self.device or self._device"
+            )
+    return device
+
+
+
+[docs] +def conditional_reply(property_name: str, reply: str | None = None) -> Callable[P, T]: + """ + Decorator that executes the command and replies if the device has a member called + 'property name' and it is True in a boolean context. + + Example usage: + + .. sourcecode:: Python + + @conditional_reply("connected") + def acknowledge_pressure(channel): + return ACK + + :param property_name: The name of the property to look for on the device + :param reply: Desired output reply string when condition is false + + :return: The function returns as normal if property is true. + The command is not executed and there is no reply if property is false + + :except AttributeError if the first argument of the decorated function (self) + does not contain .device or ._device + :except AttributeError if the device does not contain a property called property_name + """ + + def decorator(func: Callable[P, T]) -> Callable[P, T]: + @wraps(func) + def wrapper(self: StreamInterface, *args: P.args, **kwargs: P.kwargs) -> T: + device = _get_device_from(self) + + try: + do_reply = getattr(device, property_name) + except AttributeError: + raise AttributeError( + f"Expected device to contain an attribute called '{property_name}' " + f"but it wasn't found." + ) + + return func(self, *args, **kwargs) if do_reply else reply + + return wrapper + + return decorator
+ + + +class _LastInput: + last_input_time = 0 + + +
+[docs] +@has_log +def timed_reply( + action: str, reply: str | None = None, minimum_time_delay: float = 0 +) -> Callable[P, T]: + """ + Decorator that inhibits a command and performs an action if call time is less than + some minimum time delay between the current and last input. + + Example usage: + + .. sourcecode:: Python + + @timed_reply(action="crash_pump", reply="WARNING: Input too quick", minimum_time_delay=150) + def acknowledge_pressure(channel): + return ACK + + :param action: The name of the method to execute for on the device + :param reply: Desired output reply string when input time delay is less than the minimum + :param minimum_time_delay: The minimum time (ms) between commands sent to the device + + :return: The function returns as normal if minimum delay exceeded. + The command is not executed and the action method is called on the device instead + + :except AttributeError if the first argument of the decorated function (self) + does not contain .device or ._device + + :except AttributeError if the device does not contain a property called action + """ + + def decorator(func: Callable[P, T]) -> Callable[P, T]: + @wraps(func) + def wrapper(self: StreamInterface, *args: P.args, **kwargs: P.kwargs) -> T: + try: + new_input_time = int(round(time.time() * 1000)) + time_since_last_request = new_input_time - _LastInput.last_input_time + valid_input = time_since_last_request > minimum_time_delay + if valid_input: + _LastInput.last_input_time = new_input_time + return func(self, *args, **kwargs) + else: + self.log.info( + f"Violated time tolerance ({minimum_time_delay}ms) was" + f" {time_since_last_request}ms." + f" Calling action ({action}) on device" + ) + device = _get_device_from(self) + action_function = getattr(device, action) + action_function() + return reply + + except AttributeError: + raise AttributeError( + f"Expected device to contain an attribute called '{self.action}' but it" + f" wasn't found." + ) + + return wrapper + + return decorator
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_sources/_api.rst.txt b/_sources/_api.rst.txt new file mode 100644 index 00000000..8d74436c --- /dev/null +++ b/_sources/_api.rst.txt @@ -0,0 +1,11 @@ +:orphan: + +API +=== + +.. autosummary:: + :toctree: generated + :template: custom-module-template.rst + :recursive: + + lewis diff --git a/_sources/developer_guide/developing_lewis.md.txt b/_sources/developer_guide/developing_lewis.md.txt new file mode 100644 index 00000000..173e9bf9 --- /dev/null +++ b/_sources/developer_guide/developing_lewis.md.txt @@ -0,0 +1,95 @@ +# Developing Lewis + +Begin by checking-out the source from GitHub: + +``` +(lewis-dev)$ git clone https://github.com/ISISComputingGroup/lewis.git +``` + +To develop Lewis, it is strongly recommended to work in a dedicated virtual environment, otherwise +it is not possible to have another version of Lewis installed system wide in parallel. + +With the virtual environment activated, Lewis can be installed as an editable package: + +``` +(lewis-dev)$ cd lewis +(lewis-dev)$ python -m pip install ".[dev]" +``` + +Now the Lewis package that resides in ``lewis`` can be modified, while it is still treated like a +normal package that has been installed via ``pip``. + +Alternatively, Lewis can be run from source. For this it is necessary to install the requirements first: + +``` +(lewis-dev)$ cd lewis +(lewis-dev)$ python -m pip install -r requirements-dev.txt +``` + +Either way, to make sure that everything is working as +intended, run the unit tests and check for pep8 errors, as well as build the documentation: + +``` +(lewis-dev)$ pytest tests +(lewis-dev)$ flake8 setup.py lewis scripts system-tests tests +(lewis-dev)$ sphinx-build -W -b html docs/ docs/_build/html +``` + +There are also system-tests that (partially) test Lewis from the application/run-time level. These tests are based on +the `Approval Tests Framework `__ which works by comparing a program's standard output +against a "golden master" - if the output doesn't match then the tests fail. +For ``lewis`` and ``lewis-control`` the tests check that the programs work together correctly. For example: if a value +on a simulated device in ``lewis`` is changed via ``lewis-control``` then by querying the status of the device the +values can be compared against the expected status (the "golden master"). The tests can be run like so: + +``` +(lewis-dev)$ pytest system_tests/lewis_tests.py +``` + +It is good practice to run these tests regularly during development and, also, look for opportunities to add +more tests. The tests will also be run via the CI system. + +A more comprehensive way of running all tests is to use ``tox``, which creates fresh virtual +environments for all of these tasks: + +``` +(lewis-dev)$ tox +``` + +The advantage of tox is that it generates a source package from the source tree and installs +it in the virtual environments that it creates, testing closer to the thing that is actually +installed in the end. Running all the verification steps this way takes a bit longer, so during +development it might be more desirable to just run the components that are necessary. + +Before starting development it is important to install the pre-commit hooks, so that formatting and ``flake8`` checks +are performed before code is committed: + +``` +(lewis-dev)$ pre-commit install +``` + +To test that the hooks are installed correctly and to run them manually use the following command: + +``` +(lewis-dev)$ pre-commit run --all-files +``` + +Development should happen in a separate branch. If the work is related to a specific issue, +it is good practice to include the issue number in the branch name, along with a short +summary of a few words, for example: + +``` +(lewis-dev)$ git checkout -b 123_enhance_logic_flow +``` + +It's also good practice to push the branch back to github from time to time, so that other +members of the development team can see what's going on (even before a pull request is opened): + +``` +(lewis-dev)$ git push origin 123_enhance_logic_flow +``` + +During development it is good practice to regularly test that changes do not break existing +or new tests. Before opening a pull request on github (which will run all the tests again +under different Python environments via the CI system), it is recommended to run tox one last time +locally, as that resembles the conditions in the CI environment quite closely. diff --git a/_sources/developer_guide/framework_details.md.txt b/_sources/developer_guide/framework_details.md.txt new file mode 100644 index 00000000..01dbe786 --- /dev/null +++ b/_sources/developer_guide/framework_details.md.txt @@ -0,0 +1,74 @@ +# Framework Details + +The Lewis framework is built around a cycle-driven core which in turn drives +the device simulation, including an optional StateMachine, and shared protocol +adapters that separate the communication layer from the simulated device. + +![The simulation cycle diagram.](../resources/diagrams/SimulationCycles.png) + +## Cycle-driven + +All processing in the framework occurs during "heartbeat" simulation ticks +which propagate calls to `process` methods throughout the simulation, +along with a Δt parameter that contains the time that has +passed since the last tick. The device simulation is then responsible for +updating its state based on how much time has passed and what input has +been received during that time. + +The benefits of this approach include: + +- This closely models real device behaviour, since processing in + electronic devices naturally occurs on a cycle basis. +- As a side-effect of the above, certain quirks of real devices are + often captured by the simulated device naturally, without additional + effort. +- The simulation becomes deterministic: The same amount of process + cycles, with the same Δt parameters along the way, and + the same input via the device protocol, will always result in exactly + the same device state. +- Simulation speed can be controlled by increasing (fast-forward) or + decreasing (slow-motion) the Δt parameter by a given factor. +- Simulation fidelity can be controlled independently from speed by + increasing or decreasing the number of cycles per second while + adjusting the Δt parameter to compensate. + +The above traits are very desirable both for running automated tests +against the simulation, and for debugging any issues that are +identified. + +## Statemachine + +A class designed for a cycle-driven approach is provided to allow modeling complex +device behaviour in an event-driven fashion. + +A device may initialize a statemachine on construction, telling it what +states the device can be in and what conditions should cause it to +transition between them. The statemachine will automatically check +eligible (exiting current state) transition conditions every cycle and +perform transitions as necessary, triggering callbacks for any event +that occurs. The following events are available for every state: + + - `on_exit` is triggered once just before exiting the state + - `on_entry` is triggered once when entering the state + - `in_state` is triggered every cycle that ends in the state + +Every cycle will trigger exactly one `in_state` event. This will +always be the last event of the cycle. When no transition occurs, this +is the only event. On the very first cycle of a simulation run, +`on_entry` is raised against the initial state before raising an +`in_state` against it. Any other cycles that involve a transition +first raise `on_exit` against the current state, and then raise +`on_entry` and `in_state` against the new state. Only one transition +may occur per cycle. + +There are three ways to specify event handlers when initializing the +statemachine: + +- Object-Oriented: Implement one class per state, derived from + `lewis.core.statemachine.State`, which optionally contains up to + one of each event handler +- Function-Driven: Bind individual functions to individual events that + need handling +- Implicit: Implement handlers in the device class, with standard names + like `on_entry_init` for a state called "init", and call + `bindHandlersByName()` diff --git a/_sources/developer_guide/release_checklist.md.txt b/_sources/developer_guide/release_checklist.md.txt new file mode 100644 index 00000000..ffe36df2 --- /dev/null +++ b/_sources/developer_guide/release_checklist.md.txt @@ -0,0 +1,96 @@ +# Release Checklist + +This document provides a check list of steps to take and things to watch out +for when preparing a new release of Lewis. It is organized roughly in the order +that these things need to be done or checked. + +If any issues are found, it is best to start again at the top once they are +resolved and the fix is merged. + +## Preparing for Release +These steps are to prepare for a release on Git, and to commit as a pull +request named "Prepare release x.y.z". This pull request should be merged +prior to proceeding to the next section. + +### Git Milestones + + - Go to https://github.com/ISISComputingGroup/lewis/milestones + - Ensure all issues and PRs included in this release are tagged correctly + - Create milestone for next release + - Ensure any open issues or PRs not included are tagged for next release + +### Release Notes + + - Ensure release notes are up to date against all included changes + - If changes to existing devices may be required to update Lewis, include an + Update Guide section in the release notes + - Include new release notes in `docs/release_notes/` + - Remove orphan tag from release notes for this release + +### Update Version + - Update `__version__` in `lewis/__init__.py` + - Update `release` in `docs/conf.py` + - Update `version` in `setup.py` + +### GitHub Release + - Draft release blurb at https://github.com/ISISComputingGroup/lewis/releases + +### Merge Changes + - Merge any changes made in this section into the main branch + - Ensure this pull request is also tagged for the current version + +## Build and Finalize Release +These steps should be taken once the ones in the previous section have been +completed. + +### Build PyPI Package +This should be done in a clean directory. + +``` +$ python -m venv build +$ . build/bin/activate +(build) $ git clone https://github.com/ISISComputingGroup/lewis.git +(build) $ cd lewis +(build) $ pip install twine wheel +(build) $ python setup.py sdist bdist_wheel +(build) $ twine check dist/* +(build) $ deactivate +``` + +### Test PyPI Package +Ideally, `.tar.gz` and `.whl` produced in previous step should also be shared +with and tested by another developer. + +Make sure tests are run in a fresh virtual environment: +``` +$ python -m venv targz +$ . targz/bin/activate +(targz) $ pip install lewis/dist/lewis-X.Y.Z.tar.gz +(targz) $ lewis linkam_t95 +... +(targz) $ deactivate + +$ python -m venv whl +$ . whl/bin/activate +(whl) $ pip install lewis/dist/lewis-X.Y.Z-py3-none-any.whl +(whl) $ lewis linkam_t95 +... +(whl) $ deactivate +``` + +Since these are release packages, unit tests aren't available. Run a few manual +tests against the packaged version of Lewis to double check that things still +work as expected. + +### Git Release + - Finalize and submit release blurb at: + https://github.com/ISISComputingGroup/lewis/releases + - Close the current milestone at: + https://github.com/ISISComputingGroup/lewis/milestones + +### Upload PyPI Package +The ``twine`` utility can be used to upload the packages to PyPI: +``` +$ twine upload dist/* +``` +Note: requires a PyPi account and lewis permissions. diff --git a/_sources/developer_guide/writing_devices.md.txt b/_sources/developer_guide/writing_devices.md.txt new file mode 100644 index 00000000..6b59edc8 --- /dev/null +++ b/_sources/developer_guide/writing_devices.md.txt @@ -0,0 +1,422 @@ +# Writing Device Simulators +The following section describes how to write a new device simulator, what to +consider, and how to get the changes upstream. + +## Preparations +The Lewis framework provides all the infrastructure to run device +simulations so that developing a new simulation requires little more +than writing code for the actual device. + +The process of writing a new device simulator is best explained using +the example of a stateful device. + +All that is required to develop a new device is to install Lewis, preferably +in a fresh virtual environment: +``` +$ pip install lewis +``` + +## Device analysis +The hypothetical device that is to be simulated is a simple controller +that controls one motor and can be communicated with via a +`TCP `__ +connection. The user can connect to the device using telnet and submit +commands followed by `\r\n` (automatically added by +`telnet `__). Responses are followed +by `\r\n` as well. The following commands and responses are available: + +- `S?`: Returns the status of the motor connected to the controller. + Can be either `idle` or `moving`, is initially `idle`. +- `P?`: Returns the current position of the motor in mm. Is initially 0. +- `T=10.0`: Sets the target position to `10.0` (accepts any + floating point number) and starts a movement if the position is + within the limits [0, 250] and returns `T=10.0`. If the motor is + not in idle state, it returns `err: not idle`. If the value + violates the limits, it returns `err: not 0<=T<=250`. +- `T?` Returns the current target of the motor in mm. Is initially 0. +- `H`: Stops the movement by setting the target to the current + position and returns `T=6.555,P=6.555`. If the motor is idle, + nothing happens, but the values are returned anyway. + +In the simplest approach, the parameters that can describe the device +are: + +- position: Read only. +- target: Can be read and written by the user, but with certain + restrictions. + +Additionally, the device is stateful in the sense that it can be in one +of three states. + +- `idle`: The motor is powered on and ready to receive commands. +- `moving`: The motor is moving towards the user supplied target. + +Between those three states, different transitions exist: + +- `idle` -> `moving`: The target position is different from the + current position, the motor starts moving. +- `moving` -> `idle`: The motor has reached the target position or + the user has supplied a stop command, which sets the target position + to the current position, causing the motor to stop. + +The states and transitions described above form a finite state machine +with two states and two transitions. This state machine forms the heart +of the simulated device, so it should be implemented using Lewis' +cycle based [finite state +machine](https://en.wikipedia.org/wiki/Finite-state_machine), which +will be explained below. + +## Implementing the device simulation +In many cases there may eventually be more than one device simulation, so the directory +structure should be something like this, assuming your directory is `/some/path`: + +``` +/some/path + | + +- my_devices + | + +- device_1 + | + +- device_2 + | + +- __init__.py (Empty file) +``` + +Each device resides in the sub-package `my_devices` in the. The first step is to create a +new directory in the `my_devices` directory called `example_motor`, +which should contain a single file, `__init__.py`. For simple devices +like this it's acceptable to put everything into one file, but for more +complex simulators it's recommended to follow the structure of the +devices that are already part of the Lewis distribution. + +Conceptually, in Lewis, devices are split in two Parts: a device +model, which contains internal device state, as well as potentially a +state machine, and an interface that exposes the device to the outside +world via a communication protocol that is provided by an "adapter". The +adapter specifies the communication protocol (for example +[EPICS](http://www.aps.anl.gov/epics/) or TCP/IP), whereas the +interface specifies the syntax and semantics of the actual command +language of the device. + +For the actual device simulation there are two classes to choose between +for sub-classing. The class `lewis.devices.Device` can be used for very simple +devices that do not require a state machine to represent their +operation. On each simulation cycle, the method `doProcess` is +executed if it is implemented. This can be used to implement +time-dependent behavior. For the majority of cases, such as in the +example, it is more convenient to inherit from `lewis.devices.StateMachineDevice`. +It provides an internal state machine and options to override +characteristics of the state machine on initialization. + +`lewis.devices.StateMachineDevice` has three methods that must be implemented by +sub-classes: `lewis.devices.StateMachineDevice._get_state_handlers`, +`lewis.devices.StateMachineDevice._get_initial_state` and +`lewis.devices.StateMachineDevice._get_transition_handlers`. They are used to define +the state machine. A fourth, optional method can be used to initialize internal device +state, it's calld `lewis.devices.StateMachineDevice._initialize_data`. In this case +the device implementation should also go into `__init__.py`: + +``` +from lewis.devices import StateMachineDevice + +from lewis.core.statemachine import State +from lewis.core import approaches + +from collections import OrderedDict + +class DefaultMovingState(State): + def in_state(self, dt): + old_position = self._context.position + self._context.position = approaches.linear(old_position, self._context.target, + self._context.speed, dt) + self.log.info('Moved position (%s -> %s), target=%s, speed=%s', old_position, + self._context.position, self._context.target, self._context.speed) + +class SimulatedExampleMotor(StateMachineDevice): + def _initialize_data(self): + self.position = 0.0 + self._target = 0.0 + self.speed = 2.0 + + def _get_state_handlers(self): + return { + 'idle': State(), + 'moving': DefaultMovingState() + } + + def _get_initial_state(self): + return 'idle' + + def _get_transition_handlers(self): + return OrderedDict([ + (('idle', 'moving'), lambda: self.position != self.target), + (('moving', 'idle'), lambda: self.position == self.target)]) + + @property + def state(self): + return self._csm.state + + @property + def target(self): + return self._target + + @target.setter + def target(self, new_target): + if self.state == 'moving': + raise RuntimeError('Can not set new target while moving.') + + if not (0 <= new_target <= 250): + raise ValueError('Target is out of range [0, 250]') + + self._target = new_target + + def stop(self): + self._target = self.position + + self.log.info('Stopping movement after user request.') + + return self.target, self.position +``` + +This defines the state machine according to the description at the top +of the page and some internal state variables, for example `target`, +which has some limits on when and to what values it can be set. + +Both states of the motor are described by a state handler. In case of +the `idle`-state it is enough to use `lewis.core.statemachine.State`, +which simply does nothing. `lewis.core.statemachine.State` has three methods that +can be overridden: + + - `lewis.core.statemachine.State.on_entry` + - `lewis.core.statemachine.State.in_state` + - `lewis.core.statemachine.State.on_exit`. + +For other ways to specify those state handlers, please consult the documentation of +`lewis.core.statemachine.StateMachine`, where this is described in detail. +The advantage of using the `lewis.core.statemachine.State`-class is that it +has a so called context, which is stored in the `_context`-member. In case of +`lewis.devices.StateMachineDevice`, this context is the device object. +This means that device data can be modified in a state handler. + +This is the case for the `moving`-state, where a state handler has +been defined by sub-classing `lewis.core.statemachine.State`. +In its `in_state`-method it modifies the `position` member of the device until it has reached +`target` with a rate that is stored in the `speed`-member. This +linear change behavior is implemented in the `~lewis.core.approaches.linear`-function from +`lewis.core.approaches`. It automatically makes sure that the target is +always obtained even for very coarse `dt`-values. + +The transitions between states are defined using lambda-functions in +this case, which simply check whether the current position is identical +with the target or not. + +The device also provides a read-only property `state`, which forwards +the state machine's (in the device as member `_csm`) state. The speed +of the motor is not part of the device specification, but it is added as +a member so that it can be changed via the `lewis-control` script to test +how the motor behaves at different speeds. The device is now fully +functional, but it's not possible to interact with it yet, because the +interface is not specified yet. + +### Implementing the device interface +Device interfaces are implemented by sub-classing an appropriate +pre-written, protocol specific interface base class from the framework's +`lewis.adapters`-package and overriding a few members. In this case this +base class is called `lewis.adapters.stream.StreamInterface`. The first step +is to specify the available commands in terms of a collection of +`lewis.adapters.stream.Cmd`-objects. These objects effectively bind +commands specified in terms of regular expressions to the interface's methods. +According to the specifications above, the commands are defined like this: + +``` +from lewis.adapters.stream import StreamInterface, Cmd, scanf + +class ExampleMotorStreamInterface(StreamInterface): + commands = { + Cmd('get_status', r'^S\?$'), + Cmd('get_position', r'^P\?$'), + Cmd('get_target', r'^T\?$'), + Cmd('set_target', scanf('T=%f'), argument_mappings=(float,)), + Cmd('stop', r'^H$', + return_mapping=lambda x: 'T={},P={}'.format(x[0], x[1])), + } + + in_terminator = '\r\n' + out_terminator = '\r\n' + + def get_status(self): + return self.device.state + + def get_position(self): + return self.device.position + + def get_target(self): + return self.device.target + + def set_target(self, new_target): + try: + self.device.target = new_target + return 'T={}'.format(new_target) + except RuntimeError: + return 'err: not idle' + except ValueError: + return 'err: not 0<=T<=250' +``` + +The first argument to `lewis.adapters.stream.Cmd` specifies the method +name the command is bound to, whereas the second argument is a pattern that a +request coming in over the TCP stream must match. If the pattern is specified as a string, +it is treated as a regular expression. In the above example, `lewis.adapters.stream.scanf` +is used for one of the functions, it allows for `scanf`-like format specifiers. If a method has +arguments (such as `set_target`), these need to be defined as capture +groups in the regular expression. These groups are passed as strings to +the bound method. If any sort of conversion is required for these +arguments, the `argument_mapping`-parameter can be a tuple of +conversion functions with the same lengths as the number of capture +groups in the regular expression. In the case of `set_target` it's +enough to convert the string to float, but `lewis.adapters.stream.scanf` does that +automatically, so it is not strictly required here. Return values (except `None`) +are converted to strings automatically, but this conversion can be +overridden by supplying a callable object to `return_mapping`, as it +is the case for the `stop`-command. + +You may have noticed that `stop` is not a method of the interface. +`lewis.adapters.stream.StreamInterface` tries to resolve the supplied method +names in multiple ways. First it checks its own members, then it checks the members of the +device it owns (accessible in the interface via the `device`-member) +and binds to the appropriate method. If the method name can not be +found in either the device or the interface, an error is produced, which +minimizes the likelihood of typos. The definitions in the interface +always have precedence, this is intentionally done so that device +behavior can be overridden later on with minimal changes to the code. + +In case of the `stop`-method, which returns two floating point numbers +(target and position), the `return_mapping` is used to format the +device's position and target as specified in the protocol definition at +the top of the page. + +Finally, in- and out-terminators need to be specified. These are +stripped from and appended to requests and replies respectively. + +This entire device can also be found in the `lewis.examples` module. It can be +started using the `-a` and `-k` parameters of `lewis.py`: + +``` +$ lewis -a /some/path -k my_devices example_motor -p "stream: {bind_address: 127.0.0.1, port: 9999}" +``` + +All functionality described in the +`user_guide`, such as accessing the device and the simulation via the +`lewis-control.py`-script are automatically available. + +## Logging +Both device and interface support logging, they supply a `log` member which is +a logger configured with the right name. The adapter already logs all important actions +that influence the device, so in the interface it should not be necessary to do too much +logging, but it might be interesting for debugging purposes. + +Note that the simulation already produces one debug log message per simulation cycle logging +the elapsed (real-)time, so it is not necessary to log the `dt` parameters in addition. +`lewis.core.statemachine.StateMachine` also logs on each cycle which state it is in and +which transitions are triggered (if any). In the `lewis.core.statemachine.State`-handlers +that are device specific, any logging should focus on the behavior in that concrete state, as +for example demonstrated in the example above. + +It is also important to consider the log level. Log messages that occur on each cycle must be +strictly limited to the `debug`-level, because they potentially produce a lot of data. +The `info`-level and above should be used for information that is relevant to anyone running +the simulation, such as failures or other "virtual problems" that might otherwise go unnoticed. +A good example would be a device that ignores faulty commands - a `warning` could be logged +with details about the command and that it was ignored. + +## User facing documentation + +The `lewis.adapters.stream.StreamAdapter`-class has a property +`documentation`, which generates user facing documentation from the +`lewis.adapters.stream.Cmd`-objects (it can be displayed via the `-i`-flag of +`lewis` from the `interface` object via `lewis-control.py`). The regular expression of +each command is listed, along with a documentation string. If the `doc`-parameter is provided +to Cmd, it is used,otherwise the docstring of the wrapped method is used (it does not matter +whether the method is part of the device or the interface for feature to work). The latter is the +recommended way, because it avoids duplication. But in some cases, the user- and the +developer facing documentation may be so different that it's useful to override the docstring. + +This is also combined with the docstring of the interface (in this case +`ExampleMotorStreamInterface`), and some information about the configured host/port, +as well as terminators. The documentation has been left out from the above code samples for +brevity, but in the `examples`-directory, the docs are present. + +All adapters offer similar functionality, the purpose is that the devices are documented in +a way that makes them easy to use by non-developers. This is especially important if the +protocol is non-obvious. + +## Unit tests +Unit tests should be added to the `test`-directory. While it would be +best to have unit tests for device and interface separately, it is most +important that the tests capture overall device behavior, so that it's +immediately noticed when a change to Lewis' core parts breaks the +simulation. It also makes it easier later on to refactor and change the +device. + +## Adding setups +In order to test certain failure scenarios of a device, setups can be +added to a device. The easiest way is to define a dictionary called +`setups` in the `__init__.py` file. A setup consists of a device +type and initialization parameters: + +``` +setups = dict( + moving=dict( + device_type=SimulatedExampleMotor, + parameters=dict( + override_initial_state='moving', + override_initial_data=dict( + _target=120.0, position=20.0 + ) + ) + ) +) +``` + +In this case a `moving`-scenario is defined where the motor is already +moving to a target when the simulation is started. + +## Compatibility with framework versions +To make sure that users have a good experience using the newly added device, +it should specify what version of Lewis it works with. This is achieved by +adding another variable to the top level of the device module which contains +a version specification: +``` +framework_version = '1.0.1' +``` + +This will make sure that older or newer versions of Lewis do not present odd exceptions +or error messages to users trying to start the device. If Lewis detects a mismatch +between the required version and the existing version, an error message is logged +so that users know where the problem comes from. In the ideal case this variable +would be updated with each release of Lewis after it has been made sure that the +device is compatible. + +## Further steps +Once a device is developed far enough, it's time to submit a pull +request. As an external contributor, this happens via a fork on github. +Members of the development team will review the code and may make +suggestions for changes. Once the code is acceptable, it will be merged +into Lewis' main branch and become a part of the distribution. + +If a second interface is added to a device, either using a different +interface type or the same but with different commands, the interface +definitions should be moved out of the `__init__.py` file. Lewis +will continue to work if the interfaces are moved to a sub-folder of the +device called `interfaces`. This needs to have its own +`__init__.py`, where interface-classes can be imported from other +files in that module. It's best to look at the chopper and linkam\_t95 +devices that are already in Lewis. + +The same is true for setups. For complex setups, these should be moved +to a sub-module of the device called `setups`, where each setup can +live in its own file. Please see the documentation of +`lewis.devices.import_device` for reference. + +## More Examples +More example devices and interfaces are provided in the `lewis.examples` directory. diff --git a/_sources/generated/lewis.adapters.epics.rst.txt b/_sources/generated/lewis.adapters.epics.rst.txt new file mode 100644 index 00000000..8fec2074 --- /dev/null +++ b/_sources/generated/lewis.adapters.epics.rst.txt @@ -0,0 +1,32 @@ + + +``lewis.adapters.epics`` +======================== + + + + + + +.. automodule:: lewis.adapters.epics + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + BoundPV + EpicsAdapter + EpicsInterface + PV + PropertyExposingDriver + + \ No newline at end of file diff --git a/_sources/generated/lewis.adapters.modbus.rst.txt b/_sources/generated/lewis.adapters.modbus.rst.txt new file mode 100644 index 00000000..f2394560 --- /dev/null +++ b/_sources/generated/lewis.adapters.modbus.rst.txt @@ -0,0 +1,42 @@ + + +``lewis.adapters.modbus`` +========================= + + + + + + + + + + + +.. automodule:: lewis.adapters.modbus + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + MBEX + ModbusAdapter + ModbusBasicDataBank + ModbusDataBank + ModbusDataStore + ModbusHandler + ModbusInterface + ModbusProtocol + ModbusServer + ModbusTCPFrame + + \ No newline at end of file diff --git a/_sources/generated/lewis.adapters.rst.txt b/_sources/generated/lewis.adapters.rst.txt new file mode 100644 index 00000000..4487230f --- /dev/null +++ b/_sources/generated/lewis.adapters.rst.txt @@ -0,0 +1,27 @@ + + +``lewis.adapters`` +================== + +.. automodule:: lewis.adapters + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + epics + modbus + stream + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.adapters.stream.rst.txt b/_sources/generated/lewis.adapters.stream.rst.txt new file mode 100644 index 00000000..4686f6cd --- /dev/null +++ b/_sources/generated/lewis.adapters.stream.rst.txt @@ -0,0 +1,44 @@ + + +``lewis.adapters.stream`` +========================= + + + + + + + + + + + + +.. automodule:: lewis.adapters.stream + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + Cmd + CommandBase + Func + PatternMatcher + StreamAdapter + StreamHandler + StreamInterface + StreamServer + Var + regex + scanf + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.adapters.rst.txt b/_sources/generated/lewis.core.adapters.rst.txt new file mode 100644 index 00000000..22317b9f --- /dev/null +++ b/_sources/generated/lewis.core.adapters.rst.txt @@ -0,0 +1,28 @@ + + +``lewis.core.adapters`` +======================= + + + + +.. automodule:: lewis.core.adapters + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + Adapter + AdapterCollection + NoLock + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.approaches.rst.txt b/_sources/generated/lewis.core.approaches.rst.txt new file mode 100644 index 00000000..ecee078f --- /dev/null +++ b/_sources/generated/lewis.core.approaches.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.core.approaches`` +========================= + + +.. automodule:: lewis.core.approaches + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + linear + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.control_client.rst.txt b/_sources/generated/lewis.core.control_client.rst.txt new file mode 100644 index 00000000..4f530fb1 --- /dev/null +++ b/_sources/generated/lewis.core.control_client.rst.txt @@ -0,0 +1,30 @@ + + +``lewis.core.control_client`` +============================= + + + + + +.. automodule:: lewis.core.control_client + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + ControlClient + ObjectProxy + ProtocolException + RemoteException + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.control_server.rst.txt b/_sources/generated/lewis.core.control_server.rst.txt new file mode 100644 index 00000000..50ea9c4b --- /dev/null +++ b/_sources/generated/lewis.core.control_server.rst.txt @@ -0,0 +1,28 @@ + + +``lewis.core.control_server`` +============================= + + + + +.. automodule:: lewis.core.control_server + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + ControlServer + ExposedObject + ExposedObjectCollection + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.devices.rst.txt b/_sources/generated/lewis.core.devices.rst.txt new file mode 100644 index 00000000..33941c43 --- /dev/null +++ b/_sources/generated/lewis.core.devices.rst.txt @@ -0,0 +1,34 @@ + + +``lewis.core.devices`` +====================== + + + + + + + +.. automodule:: lewis.core.devices + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + DeviceBase + DeviceBuilder + DeviceRegistry + InterfaceBase + is_device + is_interface + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.exceptions.rst.txt b/_sources/generated/lewis.core.exceptions.rst.txt new file mode 100644 index 00000000..23059784 --- /dev/null +++ b/_sources/generated/lewis.core.exceptions.rst.txt @@ -0,0 +1,28 @@ + + +``lewis.core.exceptions`` +========================= + + + + +.. automodule:: lewis.core.exceptions + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + AccessViolationException + LewisException + LimitViolationException + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.logging.rst.txt b/_sources/generated/lewis.core.logging.rst.txt new file mode 100644 index 00000000..3e7b0d91 --- /dev/null +++ b/_sources/generated/lewis.core.logging.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.core.logging`` +====================== + + + +.. automodule:: lewis.core.logging + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + HasLog + has_log + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.processor.rst.txt b/_sources/generated/lewis.core.processor.rst.txt new file mode 100644 index 00000000..d290b70d --- /dev/null +++ b/_sources/generated/lewis.core.processor.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.core.processor`` +======================== + + + +.. automodule:: lewis.core.processor + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + CanProcess + CanProcessComposite + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.rst.txt b/_sources/generated/lewis.core.rst.txt new file mode 100644 index 00000000..91177afb --- /dev/null +++ b/_sources/generated/lewis.core.rst.txt @@ -0,0 +1,35 @@ + + +``lewis.core`` +============== + +.. automodule:: lewis.core + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + adapters + approaches + control_client + control_server + devices + exceptions + logging + processor + simulation + statemachine + utils + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.simulation.rst.txt b/_sources/generated/lewis.core.simulation.rst.txt new file mode 100644 index 00000000..3b99bd4b --- /dev/null +++ b/_sources/generated/lewis.core.simulation.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.core.simulation`` +========================= + + + +.. automodule:: lewis.core.simulation + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + Simulation + SimulationFactory + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.statemachine.rst.txt b/_sources/generated/lewis.core.statemachine.rst.txt new file mode 100644 index 00000000..35e5051b --- /dev/null +++ b/_sources/generated/lewis.core.statemachine.rst.txt @@ -0,0 +1,32 @@ + + +``lewis.core.statemachine`` +=========================== + + + + + + +.. automodule:: lewis.core.statemachine + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + HasContext + State + StateMachine + StateMachineException + Transition + + \ No newline at end of file diff --git a/_sources/generated/lewis.core.utils.rst.txt b/_sources/generated/lewis.core.utils.rst.txt new file mode 100644 index 00000000..639720d8 --- /dev/null +++ b/_sources/generated/lewis.core.utils.rst.txt @@ -0,0 +1,38 @@ + + +``lewis.core.utils`` +==================== + + + + + + + + + +.. automodule:: lewis.core.utils + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + FromOptionalDependency + check_limits + dict_strict_update + extract_module_name + format_doc_text + get_members + get_submodules + seconds_since + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.chopper.devices.bearings.rst.txt b/_sources/generated/lewis.devices.chopper.devices.bearings.rst.txt new file mode 100644 index 00000000..6e08342a --- /dev/null +++ b/_sources/generated/lewis.devices.chopper.devices.bearings.rst.txt @@ -0,0 +1,28 @@ + + +``lewis.devices.chopper.devices.bearings`` +========================================== + + + + +.. automodule:: lewis.devices.chopper.devices.bearings + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + Bearings + MagneticBearings + MechanicalBearings + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.chopper.devices.device.rst.txt b/_sources/generated/lewis.devices.chopper.devices.device.rst.txt new file mode 100644 index 00000000..4f7419a9 --- /dev/null +++ b/_sources/generated/lewis.devices.chopper.devices.device.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.chopper.devices.device`` +======================================== + + + +.. automodule:: lewis.devices.chopper.devices.device + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + SimulatedBearings + SimulatedChopper + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.chopper.devices.rst.txt b/_sources/generated/lewis.devices.chopper.devices.rst.txt new file mode 100644 index 00000000..05f8f9eb --- /dev/null +++ b/_sources/generated/lewis.devices.chopper.devices.rst.txt @@ -0,0 +1,27 @@ + + +``lewis.devices.chopper.devices`` +================================= + +.. automodule:: lewis.devices.chopper.devices + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + bearings + device + states + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.chopper.devices.states.rst.txt b/_sources/generated/lewis.devices.chopper.devices.states.rst.txt new file mode 100644 index 00000000..7b314a3f --- /dev/null +++ b/_sources/generated/lewis.devices.chopper.devices.states.rst.txt @@ -0,0 +1,42 @@ + + +``lewis.devices.chopper.devices.states`` +======================================== + + + + + + + + + + + +.. automodule:: lewis.devices.chopper.devices.states + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + DefaultAcceleratingState + DefaultIdleState + DefaultInitState + DefaultParkedState + DefaultParkingState + DefaultPhaseLockedState + DefaultPhaseLockingState + DefaultStoppedState + DefaultStoppingState + on_entry + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.chopper.interfaces.epics_interface.rst.txt b/_sources/generated/lewis.devices.chopper.interfaces.epics_interface.rst.txt new file mode 100644 index 00000000..d1691da4 --- /dev/null +++ b/_sources/generated/lewis.devices.chopper.interfaces.epics_interface.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.devices.chopper.interfaces.epics_interface`` +==================================================== + + +.. automodule:: lewis.devices.chopper.interfaces.epics_interface + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + ChopperEpicsInterface + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.chopper.interfaces.rst.txt b/_sources/generated/lewis.devices.chopper.interfaces.rst.txt new file mode 100644 index 00000000..02e9d4a7 --- /dev/null +++ b/_sources/generated/lewis.devices.chopper.interfaces.rst.txt @@ -0,0 +1,25 @@ + + +``lewis.devices.chopper.interfaces`` +==================================== + +.. automodule:: lewis.devices.chopper.interfaces + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + epics_interface + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.chopper.rst.txt b/_sources/generated/lewis.devices.chopper.rst.txt new file mode 100644 index 00000000..88ed45cf --- /dev/null +++ b/_sources/generated/lewis.devices.chopper.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.chopper`` +========================= + +.. automodule:: lewis.devices.chopper + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + devices + interfaces + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.julabo.devices.device.rst.txt b/_sources/generated/lewis.devices.julabo.devices.device.rst.txt new file mode 100644 index 00000000..1a6c3e3e --- /dev/null +++ b/_sources/generated/lewis.devices.julabo.devices.device.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.devices.julabo.devices.device`` +======================================= + + +.. automodule:: lewis.devices.julabo.devices.device + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + SimulatedJulabo + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.julabo.devices.rst.txt b/_sources/generated/lewis.devices.julabo.devices.rst.txt new file mode 100644 index 00000000..8996d3ef --- /dev/null +++ b/_sources/generated/lewis.devices.julabo.devices.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.julabo.devices`` +================================ + +.. automodule:: lewis.devices.julabo.devices + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + device + states + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.julabo.devices.states.rst.txt b/_sources/generated/lewis.devices.julabo.devices.states.rst.txt new file mode 100644 index 00000000..f9855781 --- /dev/null +++ b/_sources/generated/lewis.devices.julabo.devices.states.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.julabo.devices.states`` +======================================= + + + +.. automodule:: lewis.devices.julabo.devices.states + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + DefaultCirculatingState + DefaultNotCirculatingState + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.rst.txt b/_sources/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.rst.txt new file mode 100644 index 00000000..7e0b5afe --- /dev/null +++ b/_sources/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.devices.julabo.interfaces.julabo_stream_interface_1`` +============================================================= + + +.. automodule:: lewis.devices.julabo.interfaces.julabo_stream_interface_1 + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + JulaboStreamInterfaceV1 + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.rst.txt b/_sources/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.rst.txt new file mode 100644 index 00000000..093728a9 --- /dev/null +++ b/_sources/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.devices.julabo.interfaces.julabo_stream_interface_2`` +============================================================= + + +.. automodule:: lewis.devices.julabo.interfaces.julabo_stream_interface_2 + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + JulaboStreamInterfaceV2 + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.julabo.interfaces.rst.txt b/_sources/generated/lewis.devices.julabo.interfaces.rst.txt new file mode 100644 index 00000000..ffbdd96a --- /dev/null +++ b/_sources/generated/lewis.devices.julabo.interfaces.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.julabo.interfaces`` +=================================== + +.. automodule:: lewis.devices.julabo.interfaces + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + julabo_stream_interface_1 + julabo_stream_interface_2 + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.julabo.rst.txt b/_sources/generated/lewis.devices.julabo.rst.txt new file mode 100644 index 00000000..e16a216d --- /dev/null +++ b/_sources/generated/lewis.devices.julabo.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.julabo`` +======================== + +.. automodule:: lewis.devices.julabo + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + devices + interfaces + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.linkam_t95.devices.device.rst.txt b/_sources/generated/lewis.devices.linkam_t95.devices.device.rst.txt new file mode 100644 index 00000000..6ae701c2 --- /dev/null +++ b/_sources/generated/lewis.devices.linkam_t95.devices.device.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.devices.linkam_t95.devices.device`` +=========================================== + + +.. automodule:: lewis.devices.linkam_t95.devices.device + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + SimulatedLinkamT95 + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.linkam_t95.devices.rst.txt b/_sources/generated/lewis.devices.linkam_t95.devices.rst.txt new file mode 100644 index 00000000..cd7ecd8e --- /dev/null +++ b/_sources/generated/lewis.devices.linkam_t95.devices.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.linkam_t95.devices`` +==================================== + +.. automodule:: lewis.devices.linkam_t95.devices + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + device + states + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.linkam_t95.devices.states.rst.txt b/_sources/generated/lewis.devices.linkam_t95.devices.states.rst.txt new file mode 100644 index 00000000..5217fff1 --- /dev/null +++ b/_sources/generated/lewis.devices.linkam_t95.devices.states.rst.txt @@ -0,0 +1,34 @@ + + +``lewis.devices.linkam_t95.devices.states`` +=========================================== + + + + + + + +.. automodule:: lewis.devices.linkam_t95.devices.states + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + DefaultCoolState + DefaultHeatState + DefaultHoldState + DefaultInitState + DefaultStartedState + DefaultStoppedState + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.linkam_t95.interfaces.rst.txt b/_sources/generated/lewis.devices.linkam_t95.interfaces.rst.txt new file mode 100644 index 00000000..3bc16a8f --- /dev/null +++ b/_sources/generated/lewis.devices.linkam_t95.interfaces.rst.txt @@ -0,0 +1,25 @@ + + +``lewis.devices.linkam_t95.interfaces`` +======================================= + +.. automodule:: lewis.devices.linkam_t95.interfaces + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + stream_interface + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.linkam_t95.interfaces.stream_interface.rst.txt b/_sources/generated/lewis.devices.linkam_t95.interfaces.stream_interface.rst.txt new file mode 100644 index 00000000..8045b0f7 --- /dev/null +++ b/_sources/generated/lewis.devices.linkam_t95.interfaces.stream_interface.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.devices.linkam_t95.interfaces.stream_interface`` +======================================================== + + +.. automodule:: lewis.devices.linkam_t95.interfaces.stream_interface + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + LinkamT95StreamInterface + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.linkam_t95.rst.txt b/_sources/generated/lewis.devices.linkam_t95.rst.txt new file mode 100644 index 00000000..8abecebb --- /dev/null +++ b/_sources/generated/lewis.devices.linkam_t95.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.devices.linkam_t95`` +============================ + +.. automodule:: lewis.devices.linkam_t95 + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + devices + interfaces + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.devices.rst.txt b/_sources/generated/lewis.devices.rst.txt new file mode 100644 index 00000000..ebf50d35 --- /dev/null +++ b/_sources/generated/lewis.devices.rst.txt @@ -0,0 +1,37 @@ + + +``lewis.devices`` +================= + + + +.. automodule:: lewis.devices + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + chopper + julabo + linkam_t95 + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + Device + StateMachineDevice + + \ No newline at end of file diff --git a/_sources/generated/lewis.examples.dual_device.rst.txt b/_sources/generated/lewis.examples.dual_device.rst.txt new file mode 100644 index 00000000..eea8488b --- /dev/null +++ b/_sources/generated/lewis.examples.dual_device.rst.txt @@ -0,0 +1,28 @@ + + +``lewis.examples.dual_device`` +============================== + + + + +.. automodule:: lewis.examples.dual_device + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + VerySimpleDevice + VerySimpleInterface + VerySimpleStreamInterface + + \ No newline at end of file diff --git a/_sources/generated/lewis.examples.example_motor.rst.txt b/_sources/generated/lewis.examples.example_motor.rst.txt new file mode 100644 index 00000000..c007cd1c --- /dev/null +++ b/_sources/generated/lewis.examples.example_motor.rst.txt @@ -0,0 +1,28 @@ + + +``lewis.examples.example_motor`` +================================ + + + + +.. automodule:: lewis.examples.example_motor + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + DefaultMovingState + ExampleMotorStreamInterface + SimulatedExampleMotor + + \ No newline at end of file diff --git a/_sources/generated/lewis.examples.modbus_device.rst.txt b/_sources/generated/lewis.examples.modbus_device.rst.txt new file mode 100644 index 00000000..cb2f9557 --- /dev/null +++ b/_sources/generated/lewis.examples.modbus_device.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.examples.modbus_device`` +================================ + + + +.. automodule:: lewis.examples.modbus_device + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + ExampleModbusInterface + ModbusDevice + + \ No newline at end of file diff --git a/_sources/generated/lewis.examples.rst.txt b/_sources/generated/lewis.examples.rst.txt new file mode 100644 index 00000000..226fee59 --- /dev/null +++ b/_sources/generated/lewis.examples.rst.txt @@ -0,0 +1,29 @@ + + +``lewis.examples`` +================== + +.. automodule:: lewis.examples + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + dual_device + example_motor + modbus_device + simple_device + timeout_device + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.examples.simple_device.rst.txt b/_sources/generated/lewis.examples.simple_device.rst.txt new file mode 100644 index 00000000..67aa34e0 --- /dev/null +++ b/_sources/generated/lewis.examples.simple_device.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.examples.simple_device`` +================================ + + + +.. automodule:: lewis.examples.simple_device + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + VerySimpleDevice + VerySimpleInterface + + \ No newline at end of file diff --git a/_sources/generated/lewis.examples.timeout_device.rst.txt b/_sources/generated/lewis.examples.timeout_device.rst.txt new file mode 100644 index 00000000..9a6245cf --- /dev/null +++ b/_sources/generated/lewis.examples.timeout_device.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.examples.timeout_device`` +================================= + + + +.. automodule:: lewis.examples.timeout_device + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + TimeTerminatedDevice + TimeTerminatedInterface + + \ No newline at end of file diff --git a/_sources/generated/lewis.rst.txt b/_sources/generated/lewis.rst.txt new file mode 100644 index 00000000..8874dbb8 --- /dev/null +++ b/_sources/generated/lewis.rst.txt @@ -0,0 +1,30 @@ + + +``lewis`` +========= + +.. automodule:: lewis + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + adapters + core + devices + examples + scripts + utils + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.scripts.control.rst.txt b/_sources/generated/lewis.scripts.control.rst.txt new file mode 100644 index 00000000..649e6250 --- /dev/null +++ b/_sources/generated/lewis.scripts.control.rst.txt @@ -0,0 +1,34 @@ + + +``lewis.scripts.control`` +========================= + + + + + + + +.. automodule:: lewis.scripts.control + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + call_method + control_simulation + convert_type + is_remote_method + list_objects + show_api + + \ No newline at end of file diff --git a/_sources/generated/lewis.scripts.rst.txt b/_sources/generated/lewis.scripts.rst.txt new file mode 100644 index 00000000..dc3900ca --- /dev/null +++ b/_sources/generated/lewis.scripts.rst.txt @@ -0,0 +1,34 @@ + + +``lewis.scripts`` +================= + + +.. automodule:: lewis.scripts + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + control + run + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + get_usage_text + + \ No newline at end of file diff --git a/_sources/generated/lewis.scripts.run.rst.txt b/_sources/generated/lewis.scripts.run.rst.txt new file mode 100644 index 00000000..fad1ce52 --- /dev/null +++ b/_sources/generated/lewis.scripts.run.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.scripts.run`` +===================== + + + +.. automodule:: lewis.scripts.run + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + parse_adapter_options + run_simulation + + \ No newline at end of file diff --git a/_sources/generated/lewis.utils.byte_conversions.rst.txt b/_sources/generated/lewis.utils.byte_conversions.rst.txt new file mode 100644 index 00000000..a7fb02e3 --- /dev/null +++ b/_sources/generated/lewis.utils.byte_conversions.rst.txt @@ -0,0 +1,30 @@ + + +``lewis.utils.byte_conversions`` +================================ + + + + + +.. automodule:: lewis.utils.byte_conversions + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + float_to_raw_bytes + int_to_raw_bytes + raw_bytes_to_float + raw_bytes_to_int + + \ No newline at end of file diff --git a/_sources/generated/lewis.utils.command_builder.rst.txt b/_sources/generated/lewis.utils.command_builder.rst.txt new file mode 100644 index 00000000..3d7dc514 --- /dev/null +++ b/_sources/generated/lewis.utils.command_builder.rst.txt @@ -0,0 +1,24 @@ + + +``lewis.utils.command_builder`` +=============================== + + +.. automodule:: lewis.utils.command_builder + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + CmdBuilder + + \ No newline at end of file diff --git a/_sources/generated/lewis.utils.constants.rst.txt b/_sources/generated/lewis.utils.constants.rst.txt new file mode 100644 index 00000000..b53269d0 --- /dev/null +++ b/_sources/generated/lewis.utils.constants.rst.txt @@ -0,0 +1,16 @@ + + +``lewis.utils.constants`` +========================= + +.. automodule:: lewis.utils.constants + :members: + :show-inheritance: + + + + + + + + \ No newline at end of file diff --git a/_sources/generated/lewis.utils.replies.rst.txt b/_sources/generated/lewis.utils.replies.rst.txt new file mode 100644 index 00000000..c0d895a7 --- /dev/null +++ b/_sources/generated/lewis.utils.replies.rst.txt @@ -0,0 +1,26 @@ + + +``lewis.utils.replies`` +======================= + + + +.. automodule:: lewis.utils.replies + :members: + :show-inheritance: + + + + + + + + .. rubric:: Members + + .. autosummary:: + :nosignatures: + + conditional_reply + timed_reply + + \ No newline at end of file diff --git a/_sources/generated/lewis.utils.rst.txt b/_sources/generated/lewis.utils.rst.txt new file mode 100644 index 00000000..476a7c27 --- /dev/null +++ b/_sources/generated/lewis.utils.rst.txt @@ -0,0 +1,28 @@ + + +``lewis.utils`` +=============== + +.. automodule:: lewis.utils + :members: + :show-inheritance: + + + + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + + byte_conversions + command_builder + constants + replies + + + + + + \ No newline at end of file diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..40808451 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,48 @@ +Welcome to the Lewis documentation! +=================================== +Lewis is a Python package that makes it easy to develop complex stateful device simulations. It +is licensed under GPL version 3 and the source is available on github_, where you are welcome to +open new issues so the package can improve. + +Documentation contents: + +Quickstart +========== +.. toctree:: + :maxdepth: 2 + :glob: + :caption: Quickstart + + quickstart + + +.. toctree:: + :maxdepth: 2 + :glob: + :caption: User guide + + user_guide/* + + +.. toctree:: + :maxdepth: 1 + :glob: + :caption: Developer guide + + developer_guide/* + +.. toctree:: + :maxdepth: 1 + :caption: Release notes + :glob: + + release_notes/release_notes* + +.. toctree:: + :titlesonly: + :caption: API reference + + _api + + +.. _github: https://github.com/ISISComputingGroup/lewis diff --git a/_sources/quickstart.md.txt b/_sources/quickstart.md.txt new file mode 100644 index 00000000..98b54e1c --- /dev/null +++ b/_sources/quickstart.md.txt @@ -0,0 +1,199 @@ +# Quickstart Guide +This section aims to get you started with Lewis as quickly as possible. It is meant as a basic starting point for becoming familiar with Lewis, and to give you a broad overview of what it can do. As such, many features are skimmed over or skipped entirely. See the detailed documentation sections for a more complete overview of features. + +This guide is presented as a step-by-step tutorial, so skipping sections may mean you will miss steps that are required for the examples to work. + +## Install Lewis +The recommended way to install Lewis is via PyPI and using a virtual environment. This guide assumes you have Python and Pip installed and in your PATH. + +- Create a virtual environment (optional) +- Install Lewis from PyPI, `$ pip install lewis ` + +## Run the Motor Example +Once Lewis is installed, you can use it to start some of the example devices it ships with. + +You can see which devices are available by just executing Lewis without parameters: + +``` +$ lewis +Please specify a device to simulate. The following devices are available: + julabo + chopper + linkam_t95 +``` + +Some additional, simpler examples are located in the `lewis.examples` module. You can tell Lewis which module to scan for devices using the `-k` parameter: +``` +$ lewis -k lewis.examples +Please specify a device to simulate. The following devices are available: + dual_device + simple_device + timeout_device + modbus_device + example_motor +``` + +For this guide, we will launch the example_motor: +``` +$ lewis -k lewis.examples example_motor +INFO lewis.DeviceBase: Creating device, setting up state machine +INFO lewis.Simulation: Changed cycle delay to 0.1 +INFO lewis.Simulation: Changed speed to 1.0 +INFO lewis.Simulation: Starting simulation +INFO lewis.AdapterCollection: Connecting device interface for protocol 'stream' +INFO lewis.ExampleMotorStreamInterface.StreamServer: Listening on 0.0.0.0:9999 +``` + +The example motor is a TCP Stream device and listens on port 9999 on all adapters by default. + +## Connect to Motor via Telnet +With the last command from the previous section still running, open another terminal window. + +Since the example motor conveniently uses CRLF line terminators, we can use telnet to talk to it: +``` +$ telnet localhost 9999 +Trying 127.0.0.1... +Connected to localhost. +Escape character is '^]'. +``` + +You're now connected to the TCP Stream interface of the example motor device. It understands the following commands: + +| Command | Meaning | +|---------|---------------| +| S? | get status | +| P? | get position | +| T? | get target | +| T=%f | set target | +| H | stop movement | + +You can get more details, and details on the interface of any device, by using the `-i` or `--show-interface` argument: + +``` +$ lewis -k lewis.examples example_motor -i +``` + +Note that the commands are case sensitive. Try entering a few commands in the Telnet session: + +``` +S? +idle +P? +0.0 +T=20.0 +T=20.0 +S? +moving +P? +9.106584 +``` + +See [the source code](https://github.com/ISISComputingGroup/lewis/blob/main/lewis/examples/example_motor/__init__.py) of the example motor if you want to see what makes it tick. + +## Connect to Motor via Control Client +In addition to the simulated TCP Stream interface, Lewis provides a so-called Control Server interface, which allows you to bypass the normal device protocol and access both device and simulation parameters directly while the simulation is running. This can be very useful for debugging and diagnostics, without having to modify the main device interface. + +Remote access is disabled by default and enabled only if you provide the `-r` argument when starting Lewis. Stop the previously launched instance of Lewis by pressing `Ctrl-C` and run Lewis again with the `-r` parameter to enable remote access like this: + +``` +$ lewis -r localhost:10000 -k lewis.examples example_motor +``` + +Lewis ships with a Control Client commandline tool that allows you to connect to it. It also has an `-r` argument but for the client it defaults to `localhost:10000`, which is why it is recommended to use the same value above. + +Open another terminal session. If you installed Lewis in a virtual environment, make sure to activate it in the new terminal session so that Lewis is available. + +Running `lewis-control` without any parameter displays the objects available to interact with: + +``` +$ lewis-control +device +interface +simulation +``` + +You can think of these as root nodes in a tree that `lewis-control` allows you to traverse. Passing one of them as an argument shows you what is available below that level: +``` +$ lewis-control device +Type: SimulatedExampleMotor +Properties (current values): + position (20.0) + speed (2.0) + state (idle) + target (20.0) +Methods: + stop +``` + +Going down one more level retrieves the value of a single property, or calls a method (without passing arguments): + +``` +$ lewis-control device target +0.0 +``` + +And by specifying additional argument(s) we can set properties (or pass arguments to methods): + +``` +$ lewis-control device target 100.0 +$ lewis-control device +Type: SimulatedExampleMotor +Properties (current values): + position (29.159932) + speed (2.0) + state (moving) + target (100.0) +Methods: + stop +$ lewis-control device stop +[78.64038600000002, 78.64038600000002] +$ lewis-control device +Type: SimulatedExampleMotor +Properties (current values): + position (78.640386) + speed (2.0) + state (idle) + target (78.640386) +Methods: + stop +``` +Note that, as you go along, you can also use a Telnet session in another terminal to issue commands or request information, and that the state of the device will be consistent between the two connections. + +Aside from the simulated device itself, you can also access and modify parameters of the simulation and network interface(s): + +``` +$ lewis-control simulation +$ lewis-control interface +``` +See the respective sections of documentation for more details. + +## Control Motor via Control API +While the command line client is convenient for manual diagnostics and debugging, you may find the Control API more useful for automated testing. It exposes all the same functionality available on the CLI via a Python library (In fact, that is how the CLI client is implemented). + +If you installed Lewis in a virtual environment, make sure you activate it: + +``` +$ . myenv/bin/activate +``` +Usually, you would use this API to write a Python script, but for demo purposes we will just use the interactive Python client: + +``` +$ python +>>> from lewis.core.control_client import ControlClient +>>> +>>> client = ControlClient(host='localhost', port='10000') +>>> motor = client.get_object('device') +>>> +>>> motor.target +78.64038600000002 +>>> motor.target = 20.0 +>>> motor.state +u'moving' +>>> motor.stop() +[45.142721999999964, 45.142721999999964] +>>> motor.state +u'idle' +>>> motor.position +45.142721999999964 +``` +As with the previous sections, you can also interact with the motor using any of the other interfaces as you are doing this and the state will always be consistent between them. diff --git a/_sources/release_notes/release_1_0_0.md.txt b/_sources/release_notes/release_1_0_0.md.txt new file mode 100644 index 00000000..8c276ed0 --- /dev/null +++ b/_sources/release_notes/release_1_0_0.md.txt @@ -0,0 +1,14 @@ +# Release 1.0 + +The initial release of Lewis (at that point still plankton). These release notes have been +compiled after the release as we were not keeping any release notes at that time. + +## Features + - Cycle-based, deterministic device simulations based on finite state machines + - Control over the simulation's time granularity and speed (slow motion, fast forward) + - Simulation and device control via command line and via optional network service + - Two ready to use simulated devices using different protocols: + - ESS chopper abstraction (CHIC) using EPICS Channel Access + - Linkam T95 temperature controller using TCP Stream protocol + - Documentation in Markdown format for viewing in Github, both for users and developers + - Examples for implementing Stream protocol based devices diff --git a/_sources/release_notes/release_1_0_1.md.txt b/_sources/release_notes/release_1_0_1.md.txt new file mode 100644 index 00000000..dfbb0786 --- /dev/null +++ b/_sources/release_notes/release_1_0_1.md.txt @@ -0,0 +1,25 @@ +# Release 1.0.1 + +This release is the first one under the name "Lewis". Its main purpose is to make a PyPI package +available as well as online documentation under the new name. + +Nevertheless version 1.0.1 fixes some bugs and introduces a few new features that were originally +scheduled for release 1.1 but had already been finished at the time of the release. + +## New features + - It is now possible to obtain device interface documentation via the command line + and the control server, making it easier to communicate with unfamiliar devices. + For command line invocation there is a new flag: ``lewis -i linkam_t95``. + Thanks to `David Michel` for requesting this feature. + - Lewis is now available as a `PyPI`-package and can be installed via ``pip install lewis``. + - Documentation is now generated via Sphinx and has been made available online on `RTD`_. + +## Bug fixes and other improvements + - The control server can now be bound to a hostname instead of an IP-address (very useful + for ``localhost`` in particular). + - pcaspy is now an optional requirement that has to be enabled explicitly in the requirements.txt + file or installation via ``pip install lewis[epics]``. + - Error messages displayed on the command line have been improved. + - A flake8 job has been added to the continuous integration pipeline to enforce Python + style guidelines in the codebase. + diff --git a/_sources/release_notes/release_1_0_2.md.txt b/_sources/release_notes/release_1_0_2.md.txt new file mode 100644 index 00000000..83cdd46d --- /dev/null +++ b/_sources/release_notes/release_1_0_2.md.txt @@ -0,0 +1,72 @@ +# Release 1.0.2 +This version of Lewis was released on January 26th, 2017. A few bugs have been fixed and a lot +of functionality has been added. The most notable changes are preliminary Modbus protocol support +and logging capabilities which make debugging easier. + +## New features +- A preliminary Modbus Adapter has been added. The current version is mainly aimed at what is + currently required by the IBEX team for the ``nanodac``. Since all that is needed is writing + and reading back from memory via the Modbus protocol, bindings to ``Device`` attributes or + functions have not been implemented yet. We will add these in a future version. + + The current version supports: + + - Eight common Function Codes (0x01 through 0x06, 0x0F and 0x10) + - Overlaid memory segments (using the same databank for ``di`` and ``co`` for example) + - Modbus Exceptions for invalid Function Codes, bad memory addresses, invalid data, etc + - Request frames may arrive in arbitrary chunks of multiple or partial frames + + For a usage example, see ``examples/modbus_device``. + +- Logging capabilities have been added to the framework through the standard Python `logging`_ + module. The ``lewis``-script logs messages to stderr, the level can be set using a new flag + ``-o/--output-level``. + + All devices have a new member ``log``, which can be used like this: +``` + class SomeDevice(Device): + def some_method(self, param): + self.log.debug('some_method called with param=%s', param) +``` + + This new behavior is also supported by `lewis.core.statemachine.State`, + so that changes in device state can be logged as well. + +- A simulation for a Julabo FP50 waterbath was kindly contributed by Matt Clarke. It is + communicating through TCP Stream and offers two different protocol versions. The new device + can be started like the other available devices: + ``` + $ lewis -p julabo-version-1 julabo + ``` + +- Exposing devices via TCP Stream has been made easier. It is now possible to define commands + that expose lambda-functions, named functions and data attributes (with separate read/write + patterns). See the updated documentation of :mod:`lewis.adapters.stream`. + +- TCP Stream based devices are now easier to test with telnet due to a new adapter argument. + The new ``-t``-flag makes the device interface "telnet compatible": + + ``` + $ lewis linkam_t95 -- -t + ``` + + Instead of the native in- and out-terminator of the device, the interface now looks for ``\r\n``. + +- The `lewis.adapters.epics.PV`-class has been extended to allow for meta data updates + at runtime. A second property can now be specified that returns a dictionary to update the + PV's metadata such as limits or alarm states. +- It is now possible to change multiple device parameters through lewis-control: + ``` + $ lewis-control simulation set_device_parameters "{'target_speed': 1, 'target_phase': 20}" + ``` + Thanks to the IBEX team for requesting this. + +## Bug fixes and other improvements +- Virtually disconnecting devices via the control server now actually closes all network + connections and shuts down any running servers, making it impossible to re-connect to the + device in that state. Virtually re-connecting the device returns the behavior back to normal. +- If a device contained members that are not JSON serializable, displaying the device's API + using the lewis-control script failed. This has been fixed, instead a message is now printed + that informs the user about why fetching the attribute value failed. Thanks to Adrian Potter + for reporting this issue. + diff --git a/_sources/release_notes/release_1_0_3.md.txt b/_sources/release_notes/release_1_0_3.md.txt new file mode 100644 index 00000000..31bd3280 --- /dev/null +++ b/_sources/release_notes/release_1_0_3.md.txt @@ -0,0 +1,170 @@ +# Release 1.0.3 +This version was released on March 24th, 2017. In this release, the `lewis.adapters.epics`- +module has received some updates. Some important groundwork for future improvements has been +laid as well, which resulted in the ability to switch device setups at runtime via the control +server and a new command line syntax for configuring communications. The control server and client +have been improved as well. + +## Command line interface change +The way options are passed to the adapters has changed completely, the functionality has been +merged into the ``-p``-argument, which has a new long version now, ``--adapter-options``. + +For the default adapter options, it is still possible to use the ``lewis``-command with ``-p`` +in the same way as before: + +``` +$ lewis -p stream linkam_t95 +``` + +To supply options, such as the address and port to bind to, the argument accepts an extended +dictionary syntax now: + +``` +$ lewis linkam_t95 -p "stream: {bind_address: localhost, port: 9998}" +``` + +The space after each colon is significant, it can not be left out. For strings containing +special characters, such as colons, it is necessary to quote them: + +``` +$ lewis chopper -p "epics: {prefix: 'PREF:'}" +``` + +To see what options can be specified, use the new ``-L/--list-adapter-options`` flag: + +``` +$ lewis chopper -p epics -L +``` + +## New features +- Writing devices with an EPICS interface has been made more convenient for cases where the device + does not have properties, but does have getter and setter methods. + `lewis.adapters.epics.PV` has been extended to accept a wider range of values for + ``target_property`` and ``meta_data_property``, for example method names: +``` + class FooDevice(Device): + _foo = 3 + + def get_foo(self): + return self._foo * 3 + + class FooDeviceInterface(EpicsAdapter): + pvs = { + 'Foo': PV('get_foo') + } +``` + + For read/write cases, a tuple of names can be supplied. Instead of method names it is also + allowed to specify callables, for example functions or lambda expressions. In that case, the + signature of the function is checked. See also the new example in + ``lewis.examples.epics_device``. + +- The device setup (specified in the ``setups``-dict or module inside the device module) + can be changed at runtime through the control server. It is not possible to switch to + another device, only setups of the same device can be used. To query available setups: + +``` + $ lewis-control simulation setups +``` + Then, to actually activate the new setup, assuming it is called ``new_setup``: +``` + $ lewis-control simulation switch_setup new_setup +``` + +- It has been made easier to deposit devices in an external module while maintaining control over + compatibility with the rest of the Lewis-framework. Lewis now checks for a version specification + in each device module against the framework version before obtaining devices, adapters and + setups from it. Please add such a version specification to your devices. + + This way using devices from different sources becomes more reliable for users with different + versions of Lewis, or hint them to update. By default, Lewis won't start if a device specifies + another framework version, but this behavior can be overridden by using the new flag + ``-R/--relaxed-versions``: + + In this case the simulation will start, but a warning will still be logged so that this can be + identified as a potential source of errors later on. + +- A new flag ``-V/--verify`` has been added to the ``lewis``-script. When activated, it sets + the output level to ``debug`` and exits before actually starting the simulation. This can + help diagnose problems with device modules or input parameters. + +## Bug fixes and other improvements +- The functionality for disconnecting and reconnecting a device's communication interfaces that + used to be accessible via ``lewis-control`` through the ``simulation`` has been moved into a + separate channel called ``interface``. To disconnect a device use: + + In general, more fine-grained control over the device's communication is now possible. + +- Both `lewis.core.control_server.ControlServer` and + `lewis.core.control_client.ControlClient` were subject to some improvements, most + notably a settable timeout for requests was added so that incomplete requests do not cause the + client to hang anymore. In ``lewis-control`` script, a new ``-t/--timeout`` argument was added + to make use of that new functionality. + + - Only members defined as part of the device class are listed when using ``lewis-control device``. + ``lewis-control`` generally no longer lists inherited framework functions such as ``log``, + ``add_processor``, etc. + +## Upgrade Guide +The following changes have to be made to upgrade code working with Lewis `1.0.2` to work with +Lewis `1.0.3`: + +- Any scripts or code starting Lewis with the old style adapter parameters need to be updated to + the new style adapter options. + + For EPICS adapters: +``` + Old style: + $ lewis chopper + $ lewis chopper -p epics + $ lewis chopper -p epics -- -p SIM: + $ lewis chopper -- --prefix SIM: + New style: + $ lewis chopper + $ lewis chopper -p epics + $ lewis chopper -p "epics: {prefix: 'SIM:'}" +``` + + For TCP Stream adapters: +``` + Old style: + $ lewis linkam_t95 + $ lewis linkam_t95 -p stream + $ lewis linkam_t95 -p stream -- -b 127.0.0.1 -p 9999 -t + $ lewis linkam_t95 -- --bind_address 127.0.0.1 --port 9999 --telnet_mode + New style: + $ lewis linkam_t95 + $ lewis linkam_t95 -p stream + $ lewis linkam_t95 -p "stream: {bind_address: 127.0.0.1, port: 9999, telnet_mode: True}" +``` + + For Modbus adapters: +``` + Old style: + $ lewis -k lewis.examples modbus_device + $ lewis -k lewis.examples modbus_device -p modbus + $ lewis -k lewis.examples modbus_device -p modbus -- -b 127.0.0.1 -p 5020 + $ lewis -k lewis.examples modbus_device -- --bind_address 127.0.0.1 --port 5020 + New style: + $ lewis -k lewis.examples modbus_device + $ lewis -k lewis.examples modbus_device -p modbus + $ lewis -k lewis.examples modbus_device -p "modbus: {bind_address: 127.0.0.1, port: 5020}" +``` + - Devices must now specify a ``framework_version`` in the global namespace of their top-level + ``__init__.py``, like this: + +``` + framework_version = '1.0.3' +``` + + This will need to be updated with every release. If this version is missing or does not match + the current Lewis framework version, attempting to run the device simulation will fail with a + message informing the user of the mismatch. This can be bypassed by starting Lewis with the + following parameter: + +``` + $ lewis linkam_t95 -R + $ lewis linkam_t95 --relaxed-versions +``` + Warning: in the next release, specifying ``framework_version`` becomes optional and + ``--relaxed-versions`` is renamed to ``--ignore-versions``. diff --git a/_sources/release_notes/release_1_1_0.md.txt b/_sources/release_notes/release_1_1_0.md.txt new file mode 100644 index 00000000..9ce0bb28 --- /dev/null +++ b/_sources/release_notes/release_1_1_0.md.txt @@ -0,0 +1,121 @@ +# Release 1.1 +In this release, some key changes to the core framework have been implemented. It is now possible +to have more than one communication interface for a device, which enables some interesting use +cases like partial interfaces, or multiple communication protocols accessing the same device. One +prerequisite for this feature was running the network services in different threads than the +device simulation. + +Another key change, one that requires some minor changes to existing devices (see upgrade guide), +was that the communication interface definition has been completely separate from the network +services handling the network communication. + +Besides these major improvements, there have been a number of smaller improvements and new +features, and Lewis now also has a logo (see below). + +## New features +- It is now possible to have devices with more than one communication interface. The `-p`-option + can be supplied multiple times: +``` + $ lewis some_device -p protocol1 -p protocol2 +``` + + When no ``-p`` option is specified, the script behaves as before (use default protocol if + possible or produce an error message). To start a simulation without any device communication, + use the new ``-n``/``--no-interface`` option: +``` + $ lewis some_device -n +``` + + It is not possible to use both ``-p`` and ``-n`` at the same time, this results in an error + message. + + The ``epics_device`` example has been renamed to ``dual_device`` and extended to include a + second interface definition, so it exposes the device state via two different protocols: + +``` + $ lewis -k lewis.examples dual_device -p epics -p stream +``` +- `lewis.adapters.stream` has been extended. Besides regular expressions, it is now + possible to use `scanf` format specifications to define commands. This makes handling + of for example floating point numbers much more convenient: + +``` + from lewis.adapters import StreamInterface, Cmd, scanf + + class SomeInterface(StreamInterface): + commands = { + Cmd(lambda x: x**2, scanf('SQ %f')) + } +``` + + `lewis.adapters.stream.scanf` provides argument mappings for the matched arguments + automatically, so it is optional to pass them to ``Cmd``. In the case outlined above, the + argument is automatically converted to ``float``. + + If a string is specified directly (instead of ``scanf(...)``), it is treated as a regular + expression like in earlier versions. + + Internally, the scanf package is used for handling these patterns, please check the package + documentation for all available format specifiers. Thanks to @joshburnett for accepting + a small patch to the package that made the package easier to integrate into Lewis. + +- The control client, lewis-control, now provides a version argument via ``--version`` or ``-v``. + +``` + $ lewis-control -v +``` + +## Bug fixes and other improvements +- Lewis now has a logo. It is based on a state machine with one state that is entered and + repeated infinitely - like the simulation cycles in Lewis. + + For low-resolution images or settings with little space, there is also a simplified version. + + The logo was made using inkscape, the font used in the logo is Rubik (in the SVG itself, + the text was converted into a path, so that the font does not need to be installed for the logo + to render correctly). The two PNGs and also the SVGs are in the source repository, feel + free to include them in presentations or posters. + +- Adapters now run in a different thread than the simulation itself. The consequence of this is + that slow network communication or expensive computations in the device do not influence + one another anymore. Otherwise, communication still works exactly like in previous versions. + +- The behavior of the ``framework_version``-variable for devices that was introduced in version + 1.0.3 has been modified to make it easier to convert from older versions of Lewis. + + With the default options of the ``lewis``-command, devices that do not specify the variable + will be loaded after logging a warning. An error message is only displayed when strict + version checking is enabled through the new ``-S/--strict-versions``-flag. + + The option to ignore version mismatches has been renamed to ``-I/--ignore-versions``. When + that flag is specified, any device regardless of the contents of ``framework_version`` is + loaded, but a warning is still logged. + + Specifying the ``framework_version`` variable is still encouraged as it can contribute to + more certainty on the user side as to whether a device can function with a certain function + of Lewis. + +## Upgrade guide + +- Due to a change to how Adapters and Devices work together, device interfaces are not + inheriting from Adapter-classes anymore. Instead, there are dedicated Interface classes. + They are located in the same modules as the Adapters, so only small changes are necessary: + + Old: +``` + from lewis.adapters.stream import StreamAdapter, Cmd + + class DeviceInterface(StreamAdapter): + pass +``` + + New: +``` + from lewis.adapters.stream import StreamInterface, Cmd + + class DeviceInterface(StreamInterface): + pass +``` + + The same goes for ``EpicsAdapter`` and ``ModbusAdapter``, which must be modified to + ``EpicsInterface`` and ``ModbusInterface`` respectively. diff --git a/_sources/release_notes/release_1_1_1.md.txt b/_sources/release_notes/release_1_1_1.md.txt new file mode 100644 index 00000000..b9e80a12 --- /dev/null +++ b/_sources/release_notes/release_1_1_1.md.txt @@ -0,0 +1,9 @@ +# Release 1.1.1 +This is a pure bug fix release that removes three problems that were overlooked in the 1.1 release. + +## Bug fixes +- Version strings in ``framework_version`` are now coerced, so that for example ``1.1`` becomes +``1.1.0`` automatically. +- Lewis does no longer hang forever when starting a network service fails. +- Switching setups at runtime works again as in release 1.0.3, in 1.1. it had been disabled due +to an oversight. diff --git a/_sources/release_notes/release_1_2_0.md.txt b/_sources/release_notes/release_1_2_0.md.txt new file mode 100644 index 00000000..d79c45ec --- /dev/null +++ b/_sources/release_notes/release_1_2_0.md.txt @@ -0,0 +1,55 @@ +# Release 1.2 +After releasing 1.1.0 and 1.1.1, we decided to move to a more reproducible testing workflow that +is operating closer to the packages that are released in the end. This only affects developers +who work on the Lewis code base. In addition, `lewis.adapters.epics` was improved a bit +with better error messages and more reasonable PV update frequencies. The ``lewis-control`` +server now runs in its own thread, which has made it more responsive. + +## New Features +- `StreamInterface` has been improved to support a ``readtimeout`` attribute which is analogous + to the ReadTimeout system variable in Protocol files. The value of ``readtimeout`` determines how + many milliseconds to wait for more input, once we have started receiving data for a command. Under + normal circumstances, this timeout being triggered is an error and causes the incoming buffer to be + flushed and a ``handle_error`` call in the device interface. However, if the ``in_terminator`` + attribute is empty, this timeout is treated as the command terminator instead. + + ``readtimeout`` defaults to 100 (ms). + ``readtimeout = 0`` disables this feature entirely. + + The effective resolution is currently limited 10 ms increments due to the fixed adapter cycle rate. + +- The `lewis.core.control_server.ControlServer` is now running in its own thread, separate + from the simulation. As a result, ``lewis-control`` and the Python Control API are now much more + responsive. This is because requests are processed asynchronously and, therefore, multiple + requests can be processed per simulation cycle. + +## Bugfixes and other improvements +- Error messages in the binding step of :class:`PV` have been improved. It is now easier to find + the source of common problems (missing properties, spelling errors). + +- PVs are only updated if the underlying value has actually changed. Changes to metadata are processed + and logged separately. This leads to cleaner logs even at small values for ``poll_interval``. + +- Using ``yaml.safe_load`` instead of ``yaml.load`` as a security precaution. + + +## Changes for developers +- The ``lewis.py`` and ``lewis-control.py`` files have been removed, because especially the former + created some problems with the new package structure by interfering with the tests and docs- + generation. + + For using Lewis when it's installed through pip, this does not change anything, but for + development of the Lewis framework (not of devices), it is now strongly recommended to do so + in a separate virtual environment, installing Lewis from source as an editable package. Details + on this can be found in the updated `developer_guide`. + +- Tests are now run with pytest instead of nose. In addition, a tox configuration has been + added for more reproducible tests with different interpreters. + + The first run may take a bit longer, since each step is run in a fresh virtual environment that tox + creates automatically. + + To run specific tests, for example to verify that building the docs works, use the ``-e`` flag + of tox + + To see all tests that are available, including a short description, use ``tox -l -v``. diff --git a/_sources/release_notes/release_1_2_1.md.txt b/_sources/release_notes/release_1_2_1.md.txt new file mode 100644 index 00000000..e981e04a --- /dev/null +++ b/_sources/release_notes/release_1_2_1.md.txt @@ -0,0 +1,9 @@ +# Release 1.2.1 +This is a minor release that fixes one bug and adds documentation. + +## Documentation +- Added quickstart guide. +- Removed references to plankton. + +## Bug fixes +- Improved exception handling for module imports diff --git a/_sources/release_notes/release_1_2_2.md.txt b/_sources/release_notes/release_1_2_2.md.txt new file mode 100644 index 00000000..9abb0472 --- /dev/null +++ b/_sources/release_notes/release_1_2_2.md.txt @@ -0,0 +1,5 @@ +# Release 1.2.2 +This is a minor release that adds a feature for sending unsolicited messages. + +## New Features + - Added function for sending unsolicited messages to a device. See [here](https://github.com/ISISComputingGroup/lewis/commit/08a335a8b11661478c97fd88e3e7d6bb9fcea866). diff --git a/_sources/release_notes/release_1_3_0.md.txt b/_sources/release_notes/release_1_3_0.md.txt new file mode 100644 index 00000000..58c0ce60 --- /dev/null +++ b/_sources/release_notes/release_1_3_0.md.txt @@ -0,0 +1,8 @@ +# Release 1.3.0 +This is a major release because it removes Python 2 support. Any other changes are minor. + +## Changes for developers +- Added pre-commit checking for formatting, flake8 and isort +- Uses Black for formatting +- Added a system test for checking that the overall system is functioning +- Added scripts for running lewis and lewis-control without installing diff --git a/_sources/release_notes/release_1_3_1.md.txt b/_sources/release_notes/release_1_3_1.md.txt new file mode 100644 index 00000000..eca2ab8e --- /dev/null +++ b/_sources/release_notes/release_1_3_1.md.txt @@ -0,0 +1,7 @@ +# Release 1.3.1 +This is a minor release that adds some utilities and fixes some issues with Python 3 bytes/str. + +## Changes for developers +- Added utilities developed at ISIS. +- Use bytes in unsolicited reply (Python 3 fix) +- Add ability to send bytes to lewis diff --git a/_sources/release_notes/release_1_3_2.md.txt b/_sources/release_notes/release_1_3_2.md.txt new file mode 100644 index 00000000..71697567 --- /dev/null +++ b/_sources/release_notes/release_1_3_2.md.txt @@ -0,0 +1,5 @@ +# Release 1.3.2 +This is a minor release that adds functionality that tries to handle mixed bytes and strings gracefully. + +## Changes for developers +- Tries to handle mixed bytes and strings gracefully. diff --git a/_sources/release_notes/release_1_3_3.md.txt b/_sources/release_notes/release_1_3_3.md.txt new file mode 100644 index 00000000..25b10744 --- /dev/null +++ b/_sources/release_notes/release_1_3_3.md.txt @@ -0,0 +1,2 @@ +# Release 1.3.3 +This is a minor release that updates the supported versions list. diff --git a/_sources/release_notes/release_notes.rst.txt b/_sources/release_notes/release_notes.rst.txt new file mode 100644 index 00000000..37aa9027 --- /dev/null +++ b/_sources/release_notes/release_notes.rst.txt @@ -0,0 +1,9 @@ +.. _release_notes: + +Release notes +============= +.. toctree:: + :maxdepth: 1 + :glob: + + * \ No newline at end of file diff --git a/_sources/user_guide/adapter_specifics.md.txt b/_sources/user_guide/adapter_specifics.md.txt new file mode 100644 index 00000000..3c69664b --- /dev/null +++ b/_sources/user_guide/adapter_specifics.md.txt @@ -0,0 +1,35 @@ +# Adapter Specifics + +## EPICS Adapter Specifics +The EPICS adapter takes only one optional argument: + +- ``prefix``: This string is prefixed to all PV names. Defaults to empty / no prefix. + +Arguments meant for the adapter can be specified with the adapter options. +For example: + +``` +$ python lewis.py chopper --adapter-options "epics: {prefix: 'SIM2:'}" +``` + +On Linux, this means that ``EPICS_CA_ADDR_LIST`` must include this +networks broadcast address: +``` +$ export EPICS_CA_AUTO_ADDR_LIST=NO +$ export EPICS_CA_ADDR_LIST=172.17.255.255 +$ export EPICS_CAS_INTF_ADDR_LIST=localhost +``` + +## Stream Adapter Specifics +The TCP Stream adapter has the following optional arguments: +- ``bind_address``: Address of network adapter to listen on. + Defaults to "0.0.0.0" (all network adapters). +- ``port``: Port to listen for connections on. Defaults to 9999. +- ``telnet_mode``: When True, overrides both in and out terminators + to CRNL for telnet compatibility. Defaults to False. + +Arguments meant for the adapter can be specified with the adapter options. +For example: +``` +$ python lewis.py linkam_t95 -p "stream: {bind_address: localhost, port: 1234}" +``` diff --git a/_sources/user_guide/command_line_tools.md.txt b/_sources/user_guide/command_line_tools.md.txt new file mode 100644 index 00000000..7eea2230 --- /dev/null +++ b/_sources/user_guide/command_line_tools.md.txt @@ -0,0 +1,9 @@ +# Command line tools +This page documents the program usage for ``lewis`` and ``lewis-control``, the two command line +tools provided as part of a Lewis installation. + +## lewis +See {py:meth}`lewis.scripts.run` + +## lewis-control +See {py:meth}`lewis.scripts.control` diff --git a/_sources/user_guide/remote_access_devices.md.txt b/_sources/user_guide/remote_access_devices.md.txt new file mode 100644 index 00000000..7b8f094e --- /dev/null +++ b/_sources/user_guide/remote_access_devices.md.txt @@ -0,0 +1,175 @@ +# Remote Access to Devices +*Please note that this functionality should only be used on a trusted +network.* + +Besides the device specific protocols, the device can be made accessible +from the outside via JSON-RPC over ZMQ. This can be achieved by passing +the ``-r`` option with a ``host:port`` string to the simulation: + +``` +$ lewis chopper -r 127.0.0.1:10000 -p "epics: {prefix: 'SIM:'}" +``` + +Now the device can be controlled via the ``lewis-control.py``-script +in a different terminal window. The service can be queried to show the +available objects by not supplying an object name: + +``` +$ lewis-control -r 127.0.0.1:10000 +``` + +The ``-r`` (or ``--rpc-host``) option defaults to the value shown here, +so it will be omitted in the following examples. To get information on +the API of an object, supplying an object name without a property or +method will list the object's API: + +``` +$ lewis-control device +``` + +This will output a list of properties and methods which is available for +remote access. This may not comprise the full interface of the object +depending on the server side configuration. Obtaining the value of a +property is done like this: + +``` +$ lewis-control device state +``` + +The same syntax is used to call methods without parameters: +``` +$ lewis-control device initialize +``` + +To set a property to a new value, the value has to be supplied on the +command line: +``` +$ lewis-control device target_speed 100 +$ lewis-control device start +``` + +It is possible to set multiple device parameters at once, but this goes through the simulation +itself, so that it is generic to all devices: +``` +$ lewis-control simulation set_device_parameters "{'target_speed': 1, 'target_phase': 20}" +``` + +Another case of device-related access to the simulation is switching the setup. To obtain a +list of available setups, the following command is available: +``` +$ lewis-control simulation setups +``` + +It is then possible to switch the setup to one from the list, assuming it is called ``new_setup``: +``` +$ lewis-control simulation switch_setup new_setup +``` + +The setup switching process is logged. + +## Accessing the Device Communication Interface +Just as device model and communication interface are separate concepts in Lewis, these interfaces +can be controlled separately as well. + +To query the available communication protocols, the following command is available: +``` +$ lewis-control interface protocols +``` + +This will list all communication protocols that are currently exposing device behavior. +The following methods are available for interacting with the communication interfaces: +``` +$ lewis-control interface disconnect +$ lewis-control interface connect +$ lewis-control interface is_connected +$ lewis-control interface documentation +``` + +Without any arguments, these methods will affect all of the device's interfaces, but specifying +any number of valid protocol names will limit the method to those protocols. Assuming a device +has two interfaces, one for the ``stream`` protocol and one for ``epics``, the following sequence +would disconnect both, but then only reconnect the ``stream``-adapter: +``` +$ lewis-control interface disconnect +$ lewis-control interface connect stream +$ lewis-control interface is_connected stream +True +$ lewis-control interface is_connected +{'stream': True, 'epics': False} +``` + +Disconnecting is essentially the equivalent of "cutting the cable", no new connections +will be accepted and existing ones will be terminated. + +To find out how to interact with any device via its usual communication channels a way would be: +``` +$ lewis-control interface protocol +['stream', 'epics'] +$ lewis-control interface documentation epics +[ ... long description of protocol ... ] +``` + +## Value Interpretation and Syntax + +``lewis_control`` interprets values as built-in Python literals or containers using +`ast.literal_eval`. This means any +syntax for literal evaluation supported by Python works here as well. The following are all valid +values which are interpreted as you might expect: +``` +$ lewis-control device float_value 12.0 +$ lewis-control device float_value .5 +$ lewis-control device float_value 1.23e10 +$ lewis-control device int_value 123 +$ lewis-control device int_value 0xDEADBEEF +$ lewis-control device int_value 010 # Value of 8 in base 8 (octal) +$ lewis-control device str_value hello_world +$ lewis-control device method_call_with_two_string_args hello world +$ lewis-control device str_value "hello world" +$ lewis-control device unicode_value "u'hello_world'" +$ lewis-control device list_value "[1,2,3]" +$ lewis-control device list_value "['a', 'b', 'c']" +$ lewis-control device dict_value "{'a': 1, 'b': 2}" +``` + +WARNING: Any value that cannot be successfully evaluated is silently converted into a +string literal instead! The following attempts turn into strings because the letters +are not quoted: +``` +$ lewis-control device str_value_looks_like_dict "{a: 1, b: 2}" +$ lewis-control device str_value_looks_like_list "[a, b, c]" +``` + +This is done for convenience, to avoid having to double quote and/or escape quote trivial string +values to match Python syntax while also taking shell quotation and escapes into account. But it +can lead to unexpected results at times. + +## Control Client Python API +For use cases that require more flexibility and control, it is advised to write a Python script +using the API provided in ``lewis.core.control_client`` instead of using the command line utility. +This makes it possible to use the remote objects in a fairly transparent fashion. + +Here is a brief example using the ``chopper`` device: +``` +from time import sleep +from lewis.core.control_client import ControlClient + +client = ControlClient(host='127.0.0.1', port='10000') +chopper = client.get_object('device') + +chopper.target_speed = 100 +chopper.initialize() + +while chopper.state != 'stopped': + sleep(0.1) + +chopper.start() +``` + +All calls, reads and assignments are synchronous and blocking in terms of the methods and +attributes they access on the server. However, much like with real devices, the behaviour of the +simulated device is asynchronous from its interface. Consequently, depending on the specific +device, some effects of calling a method may take place long after the method is called (and +returns). + +This is why, in the above example, a loop is used to wait for ``chopper.state`` to change in +response to the ``chopper.initialize()`` call. diff --git a/_sources/user_guide/remote_access_simulation.md.txt b/_sources/user_guide/remote_access_simulation.md.txt new file mode 100644 index 00000000..121799e6 --- /dev/null +++ b/_sources/user_guide/remote_access_simulation.md.txt @@ -0,0 +1,45 @@ +# Remote Access to Simulation Parameters +*Please note that this functionality should only be used on a trusted +network.* + +Certain control over the simulation is also exposed in the shape of an +object named ``simulation`` if Lewis is started with ``-r``. The +simulation can be paused and resumed using the control script: +``` +$ lewis-control simulation pause +$ lewis-control simulation resume +``` + +With these commands, the simulation is paused, while the communication +with the device remains responsive. The communication channel (for +example TCP stream server) would still respond to queries and commands, +but they would not be processed by the device. + +The speed of the simulation can be adjusted as well, along with the +number of cycles that are processed per second (via the ``cycle_delay`` +parameter). +``` +$ lewis-control simulation speed 10 +$ lewis-control simulation cycle_delay 0.05 +``` + +This will cause the twice as many cycles per second to be computed +compared to the default, and the simulation runs ten times faster than +actual time. + +It's also possible to obtain some information about the simulation, for +example how long it has been running and how much simulated time has +passed: +``` +$ lewis-control simulation uptime +$ lewis-control simulation runtime +``` + +Finally, the simulation can also be stopped: +``` +$ lewis-control simulation stop +``` + +It is not possible to recover from that, as the processing of remote +commands stops as well. The only way to restart the simulation is to run +Lewis again with the same parameters. diff --git a/_sources/user_guide/usage_with_python.md.txt b/_sources/user_guide/usage_with_python.md.txt new file mode 100644 index 00000000..00ffd32e --- /dev/null +++ b/_sources/user_guide/usage_with_python.md.txt @@ -0,0 +1,112 @@ +# Usage with Python +To use Lewis directly via Python you must first install its dependencies: + +- Python 3.6+ +- pip (latest) + +On most linux systems these can be installed via the distribution's package manager. + +## Virtual environments +We recommend using a virtual environment. + +## Installation via pip +Lewis is available on the `Python Package Index `__. That means +it can be installed using pip: +``` +$ pip install lewis +``` + +This will install lewis along with its dependencies. If you would like to use EPICS based devices +and have a working EPICS environment on your machine, you can install it like this to get the +additional required dependencies: +``` +$ pip install lewis[epics] +``` + +This will install two scripts in the path, ``lewis`` and ``lewis-control``. Both scripts provide +command line help: +``` +$ lewis --help +$ lewis-control --help +``` + +To list available devices, just type ``lewis`` in the command line, a list of devices that are +available for simulation will be printed. + +All following sections of this user manual assume that Lewis has been installed via pip and that +the ``lewis`` command is available. + +## Installation from source +Clone the repository in a location of your choice, we recommend that you do it inside a virtual +environment so that you can keep track of the dependencies: +``` +$ git clone https://github.com/ISISComputingGroup/lewis.git +``` + +If you do not have git available, you can +also download this repository as an archive and unpack it somewhere. A +few additional dependencies must be installed. This can be done through +pip in the top level directory of Lewis, which contains the ``setup.py``-file: +``` +$ pip install . +``` + +Note: There are a few optional dependencies for certain adapter types. Currently the only +optional dependency is ``pcaspy`` for using devices with an EPICS interface, it requires a +working installation of EPICS base. Please refer to the `installation instructions +`__ of the module. +To include ``pcaspy`` in the installation of dependencies, use: +``` +$ pip install ".[epics]" +``` + +If you also want to develop Lewis, the workflow is a bit different. Please refer to the +`developer_guide` for details. + +If you want to use the EPICS adapter, you will also need to configure a few more +EPICS environment variables correctly. If you only want to communicate +using EPICS locally via the loopback device, you can configure it like +this: +``` +$ export EPICS_CA_AUTO_ADDR_LIST=NO +$ export EPICS_CA_ADDR_LIST=localhost +$ export EPICS_CAS_INTF_ADDR_LIST=localhost +``` + +Once all dependencies and requirements are satisfied, Lewis can be +run using the following general format (from inside the Lewis +directory): +``` +$ python -m lewis device_name [arguments] +``` + +You can then run Lewis as follows (from within the lewis +directory): +``` +$ python -m lewis chopper -p epics +``` + +Details about parameters for the various adapters, and differences +between OSes are covered in the "Adapter Specifics" sections. + +If you decided to install Lewis this way, please be aware that the ``lewis`` and ``lewis-control`` +calls in the other parts of the guide have to be replaced with ``python lewis.py``. + +## Running from source +Lewis can be run directly from source. First it is necessary to install the basic requirements: +``` +$ pip install -r requirements.txt +``` + +If you would like to use EPICS based devices +and have a working EPICS environment on your machine then it is necessary to install ``pcaspy`` like so: +``` +$ pip install pcaspy +``` + +There are Python scripts for running both ``lewis`` and ``lewis-control`` in the top-level scripts directory. +These scripts work exactly the same as when Lewis is installed via pip (see above). For example: +``` +$ python scripts/lewis.py --help +$ python scripts/lewis-control.py --help +``` diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..81415803 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..7ebbd6d0 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,914 @@ +/* + * Sphinx stylesheet -- basic theme. + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin-top: 10px; +} + +ul.search li { + padding: 5px 0; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 00000000..88ba55b9 --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 00000000..0f14f106 --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 00000000..0398ebb9 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,149 @@ +/* + * Base JavaScript utilities for all Sphinx HTML documentation. + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 00000000..7e4c114f --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/fonts/Lato/lato-bold.eot b/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 00000000..3361183a Binary files /dev/null and b/_static/fonts/Lato/lato-bold.eot differ diff --git a/_static/fonts/Lato/lato-bold.ttf b/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 00000000..29f691d5 Binary files /dev/null and b/_static/fonts/Lato/lato-bold.ttf differ diff --git a/_static/fonts/Lato/lato-bold.woff b/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 00000000..c6dff51f Binary files /dev/null and b/_static/fonts/Lato/lato-bold.woff differ diff --git a/_static/fonts/Lato/lato-bold.woff2 b/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 00000000..bb195043 Binary files /dev/null and b/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/_static/fonts/Lato/lato-bolditalic.eot b/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 00000000..3d415493 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/_static/fonts/Lato/lato-bolditalic.ttf b/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 00000000..f402040b Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/_static/fonts/Lato/lato-bolditalic.woff b/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 00000000..88ad05b9 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/_static/fonts/Lato/lato-bolditalic.woff2 b/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 00000000..c4e3d804 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/_static/fonts/Lato/lato-italic.eot b/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 00000000..3f826421 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.eot differ diff --git a/_static/fonts/Lato/lato-italic.ttf b/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 00000000..b4bfc9b2 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.ttf differ diff --git a/_static/fonts/Lato/lato-italic.woff b/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 00000000..76114bc0 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.woff differ diff --git a/_static/fonts/Lato/lato-italic.woff2 b/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 00000000..3404f37e Binary files /dev/null and b/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/_static/fonts/Lato/lato-regular.eot b/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 00000000..11e3f2a5 Binary files /dev/null and b/_static/fonts/Lato/lato-regular.eot differ diff --git a/_static/fonts/Lato/lato-regular.ttf b/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/_static/fonts/Lato/lato-regular.ttf differ diff --git a/_static/fonts/Lato/lato-regular.woff b/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/_static/fonts/Lato/lato-regular.woff differ diff --git a/_static/fonts/Lato/lato-regular.woff2 b/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 00000000..79dc8efe Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 00000000..df5d1df2 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 00000000..6cb60000 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 00000000..7059e231 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 00000000..2f7ca78a Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 00000000..eb52a790 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 00000000..f815f63f Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 00000000..f2c76e5b Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 00000000..c4c6022f --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); + + const languagesHTML = ` +
+
Languages
+ ${languages + .map( + (translation) => ` +
+ ${translation.language.code} +
+ `, + ) + .join("\n")} +
+ `; + return languagesHTML; + } + + function renderVersions(config) { + if (!config.versions.active.length) { + return ""; + } + const versionsHTML = ` +
+
Versions
+ ${config.versions.active + .map( + (version) => ` +
+ ${version.slug} +
+ `, + ) + .join("\n")} +
+ `; + return versionsHTML; + } + + function renderDownloads(config) { + if (!Object.keys(config.versions.current.downloads).length) { + return ""; + } + const downloadsNameDisplay = { + pdf: "PDF", + epub: "Epub", + htmlzip: "HTML", + }; + + const downloadsHTML = ` +
+
Downloads
+ ${Object.entries(config.versions.current.downloads) + .map( + ([name, url]) => ` +
+ ${downloadsNameDisplay[name]} +
+ `, + ) + .join("\n")} +
+ `; + return downloadsHTML; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const flyout = ` +
+ + Read the Docs + v: ${config.versions.current.slug} + + +
+
+ ${renderLanguages(config)} + ${renderVersions(config)} + ${renderDownloads(config)} +
+
On Read the Docs
+
+ Project Home +
+
+ Builds +
+
+ Downloads +
+
+
+
Search
+
+
+ +
+
+
+
+ + Hosted by Read the Docs + +
+
+ `; + + // Inject the generated flyout into the body HTML element. + document.body.insertAdjacentHTML("beforeend", flyout); + + // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. + document + .querySelector("#flyout-search-form") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); + }) +} + +if (themeLanguageSelector || themeVersionSelector) { + function onSelectorSwitch(event) { + const option = event.target.selectedIndex; + const item = event.target.options[option]; + window.location.href = item.dataset.url; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const versionSwitch = document.querySelector( + "div.switch-menus > div.version-switch", + ); + if (themeVersionSelector) { + let versions = config.versions.active; + if (config.versions.current.hidden || config.versions.current.type === "external") { + versions.unshift(config.versions.current); + } + const versionSelect = ` + + `; + + versionSwitch.innerHTML = versionSelect; + versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + + const languageSwitch = document.querySelector( + "div.switch-menus > div.language-switch", + ); + + if (themeLanguageSelector) { + if (config.projects.translations.length) { + // Add the current language to the options on the selector + let languages = config.projects.translations.concat( + config.projects.current, + ); + languages = languages.sort((a, b) => + a.language.name.localeCompare(b.language.name), + ); + + const languageSelect = ` + + `; + + languageSwitch.innerHTML = languageSelect; + languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + else { + languageSwitch.remove(); + } + } + }); +} + +document.addEventListener("readthedocs-addons-data-ready", function (event) { + // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. + document + .querySelector("[role='search'] input") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); +}); \ No newline at end of file diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 00000000..c7fe6c6f --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,192 @@ +/* + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/lewis-logo.png b/_static/lewis-logo.png new file mode 100644 index 00000000..da6e4003 Binary files /dev/null and b/_static/lewis-logo.png differ diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 00000000..d96755fd Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 00000000..7107cec9 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 00000000..84ab3030 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 00000000..2c774d17 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,632 @@ +/* + * Sphinx JavaScript utilities for the full-text search. + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename, kind] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename, kind] = item; + + let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + SearchResultKind.title, + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + SearchResultKind.object, + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + SearchResultKind.text, + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 00000000..8a96c69a --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/developer_guide/developing_lewis.html b/developer_guide/developing_lewis.html new file mode 100644 index 00000000..fedd462e --- /dev/null +++ b/developer_guide/developing_lewis.html @@ -0,0 +1,202 @@ + + + + + + + + + Developing Lewis — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Developing Lewis

+

Begin by checking-out the source from GitHub:

+
(lewis-dev)$ git clone https://github.com/ISISComputingGroup/lewis.git
+
+
+

To develop Lewis, it is strongly recommended to work in a dedicated virtual environment, otherwise +it is not possible to have another version of Lewis installed system wide in parallel.

+

With the virtual environment activated, Lewis can be installed as an editable package:

+
(lewis-dev)$ cd lewis
+(lewis-dev)$ python -m pip install ".[dev]"
+
+
+

Now the Lewis package that resides in lewis can be modified, while it is still treated like a +normal package that has been installed via pip.

+

Alternatively, Lewis can be run from source. For this it is necessary to install the requirements first:

+
(lewis-dev)$ cd lewis
+(lewis-dev)$ python -m pip install -r requirements-dev.txt
+
+
+

Either way, to make sure that everything is working as +intended, run the unit tests and check for pep8 errors, as well as build the documentation:

+
(lewis-dev)$ pytest tests
+(lewis-dev)$ flake8 setup.py lewis scripts system-tests tests
+(lewis-dev)$ sphinx-build -W -b html docs/ docs/_build/html
+
+
+

There are also system-tests that (partially) test Lewis from the application/run-time level. These tests are based on +the Approval Tests Framework <https://approvaltests.com/>__ which works by comparing a program’s standard output +against a “golden master” - if the output doesn’t match then the tests fail. +For lewis and lewis-control the tests check that the programs work together correctly. For example: if a value +on a simulated device in lewis is changed via ``lewis-control``` then by querying the status of the device the +values can be compared against the expected status (the “golden master”). The tests can be run like so:

+
(lewis-dev)$ pytest system_tests/lewis_tests.py
+
+
+

It is good practice to run these tests regularly during development and, also, look for opportunities to add +more tests. The tests will also be run via the CI system.

+

A more comprehensive way of running all tests is to use tox, which creates fresh virtual +environments for all of these tasks:

+
(lewis-dev)$ tox
+
+
+

The advantage of tox is that it generates a source package from the source tree and installs +it in the virtual environments that it creates, testing closer to the thing that is actually +installed in the end. Running all the verification steps this way takes a bit longer, so during +development it might be more desirable to just run the components that are necessary.

+

Before starting development it is important to install the pre-commit hooks, so that formatting and flake8 checks +are performed before code is committed:

+
(lewis-dev)$ pre-commit install
+
+
+

To test that the hooks are installed correctly and to run them manually use the following command:

+
(lewis-dev)$ pre-commit run --all-files
+
+
+

Development should happen in a separate branch. If the work is related to a specific issue, +it is good practice to include the issue number in the branch name, along with a short +summary of a few words, for example:

+
(lewis-dev)$ git checkout -b 123_enhance_logic_flow
+
+
+

It’s also good practice to push the branch back to github from time to time, so that other +members of the development team can see what’s going on (even before a pull request is opened):

+
(lewis-dev)$ git push origin 123_enhance_logic_flow
+
+
+

During development it is good practice to regularly test that changes do not break existing +or new tests. Before opening a pull request on github (which will run all the tests again +under different Python environments via the CI system), it is recommended to run tox one last time +locally, as that resembles the conditions in the CI environment quite closely.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/developer_guide/framework_details.html b/developer_guide/framework_details.html new file mode 100644 index 00000000..63a8c2c3 --- /dev/null +++ b/developer_guide/framework_details.html @@ -0,0 +1,206 @@ + + + + + + + + + Framework Details — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Framework Details

+

The Lewis framework is built around a cycle-driven core which in turn drives +the device simulation, including an optional StateMachine, and shared protocol +adapters that separate the communication layer from the simulated device.

+

The simulation cycle diagram.

+
+

Cycle-driven

+

All processing in the framework occurs during “heartbeat” simulation ticks +which propagate calls to process methods throughout the simulation, +along with a Δt parameter that contains the time that has +passed since the last tick. The device simulation is then responsible for +updating its state based on how much time has passed and what input has +been received during that time.

+

The benefits of this approach include:

+
    +
  • This closely models real device behaviour, since processing in +electronic devices naturally occurs on a cycle basis.

  • +
  • As a side-effect of the above, certain quirks of real devices are +often captured by the simulated device naturally, without additional +effort.

  • +
  • The simulation becomes deterministic: The same amount of process +cycles, with the same Δt parameters along the way, and +the same input via the device protocol, will always result in exactly +the same device state.

  • +
  • Simulation speed can be controlled by increasing (fast-forward) or +decreasing (slow-motion) the Δt parameter by a given factor.

  • +
  • Simulation fidelity can be controlled independently from speed by +increasing or decreasing the number of cycles per second while +adjusting the Δt parameter to compensate.

  • +
+

The above traits are very desirable both for running automated tests +against the simulation, and for debugging any issues that are +identified.

+
+
+

Statemachine

+

A class designed for a cycle-driven approach is provided to allow modeling complex +device behaviour in an event-driven fashion.

+

A device may initialize a statemachine on construction, telling it what +states the device can be in and what conditions should cause it to +transition between them. The statemachine will automatically check +eligible (exiting current state) transition conditions every cycle and +perform transitions as necessary, triggering callbacks for any event +that occurs. The following events are available for every state:

+
    +
  • on_exit is triggered once just before exiting the state

  • +
  • on_entry is triggered once when entering the state

  • +
  • in_state is triggered every cycle that ends in the state

  • +
+

Every cycle will trigger exactly one in_state event. This will +always be the last event of the cycle. When no transition occurs, this +is the only event. On the very first cycle of a simulation run, +on_entry is raised against the initial state before raising an +in_state against it. Any other cycles that involve a transition +first raise on_exit against the current state, and then raise +on_entry and in_state against the new state. Only one transition +may occur per cycle.

+

There are three ways to specify event handlers when initializing the +statemachine:

+
    +
  • Object-Oriented: Implement one class per state, derived from +lewis.core.statemachine.State, which optionally contains up to +one of each event handler

  • +
  • Function-Driven: Bind individual functions to individual events that +need handling

  • +
  • Implicit: Implement handlers in the device class, with standard names +like on_entry_init for a state called “init”, and call +bindHandlersByName()

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/developer_guide/release_checklist.html b/developer_guide/release_checklist.html new file mode 100644 index 00000000..7c71cdc6 --- /dev/null +++ b/developer_guide/release_checklist.html @@ -0,0 +1,261 @@ + + + + + + + + + Release Checklist — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release Checklist

+

This document provides a check list of steps to take and things to watch out +for when preparing a new release of Lewis. It is organized roughly in the order +that these things need to be done or checked.

+

If any issues are found, it is best to start again at the top once they are +resolved and the fix is merged.

+
+

Preparing for Release

+

These steps are to prepare for a release on Git, and to commit as a pull +request named “Prepare release x.y.z”. This pull request should be merged +prior to proceeding to the next section.

+
+

Git Milestones

+
    +
  • Go to https://github.com/ISISComputingGroup/lewis/milestones

  • +
  • Ensure all issues and PRs included in this release are tagged correctly

  • +
  • Create milestone for next release

  • +
  • Ensure any open issues or PRs not included are tagged for next release

  • +
+
+
+

Release Notes

+
    +
  • Ensure release notes are up to date against all included changes

  • +
  • If changes to existing devices may be required to update Lewis, include an +Update Guide section in the release notes

  • +
  • Include new release notes in docs/release_notes/

  • +
  • Remove orphan tag from release notes for this release

  • +
+
+
+

Update Version

+
    +
  • Update __version__ in lewis/__init__.py

  • +
  • Update release in docs/conf.py

  • +
  • Update version in setup.py

  • +
+
+
+

GitHub Release

+
    +
  • Draft release blurb at https://github.com/ISISComputingGroup/lewis/releases

  • +
+
+
+

Merge Changes

+
    +
  • Merge any changes made in this section into the main branch

  • +
  • Ensure this pull request is also tagged for the current version

  • +
+
+
+
+

Build and Finalize Release

+

These steps should be taken once the ones in the previous section have been +completed.

+
+

Build PyPI Package

+

This should be done in a clean directory.

+
$ python -m venv build
+$ . build/bin/activate
+(build) $ git clone https://github.com/ISISComputingGroup/lewis.git
+(build) $ cd lewis
+(build) $ pip install twine wheel
+(build) $ python setup.py sdist bdist_wheel
+(build) $ twine check dist/*
+(build) $ deactivate
+
+
+
+
+

Test PyPI Package

+

Ideally, .tar.gz and .whl produced in previous step should also be shared +with and tested by another developer.

+

Make sure tests are run in a fresh virtual environment:

+
$ python -m venv targz
+$ . targz/bin/activate
+(targz) $ pip install lewis/dist/lewis-X.Y.Z.tar.gz
+(targz) $ lewis linkam_t95
+...
+(targz) $ deactivate
+
+$ python -m venv whl
+$ . whl/bin/activate
+(whl) $ pip install lewis/dist/lewis-X.Y.Z-py3-none-any.whl
+(whl) $ lewis linkam_t95
+...
+(whl) $ deactivate
+
+
+

Since these are release packages, unit tests aren’t available. Run a few manual +tests against the packaged version of Lewis to double check that things still +work as expected.

+
+
+

Git Release

+
    +
  • Finalize and submit release blurb at: +https://github.com/ISISComputingGroup/lewis/releases

  • +
  • Close the current milestone at: +https://github.com/ISISComputingGroup/lewis/milestones

  • +
+
+
+

Upload PyPI Package

+

The twine utility can be used to upload the packages to PyPI:

+
$ twine upload dist/*
+
+
+

Note: requires a PyPi account and lewis permissions.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/developer_guide/writing_devices.html b/developer_guide/writing_devices.html new file mode 100644 index 00000000..ae95ac30 --- /dev/null +++ b/developer_guide/writing_devices.html @@ -0,0 +1,549 @@ + + + + + + + + + Writing Device Simulators — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Writing Device Simulators

+

The following section describes how to write a new device simulator, what to +consider, and how to get the changes upstream.

+
+

Preparations

+

The Lewis framework provides all the infrastructure to run device +simulations so that developing a new simulation requires little more +than writing code for the actual device.

+

The process of writing a new device simulator is best explained using +the example of a stateful device.

+

All that is required to develop a new device is to install Lewis, preferably +in a fresh virtual environment:

+
$ pip install lewis
+
+
+
+
+

Device analysis

+

The hypothetical device that is to be simulated is a simple controller +that controls one motor and can be communicated with via a +TCP <https://en.wikipedia.org/wiki/Transmission_Control_Protocol>__ +connection. The user can connect to the device using telnet and submit +commands followed by \r\n (automatically added by +telnet <https://linux.die.net/man/1/telnet>__). Responses are followed +by \r\n as well. The following commands and responses are available:

+
    +
  • S?: Returns the status of the motor connected to the controller. +Can be either idle or moving, is initially idle.

  • +
  • P?: Returns the current position of the motor in mm. Is initially 0.

  • +
  • T=10.0: Sets the target position to 10.0 (accepts any +floating point number) and starts a movement if the position is +within the limits [0, 250] and returns T=10.0. If the motor is +not in idle state, it returns err: not idle. If the value +violates the limits, it returns err: not 0<=T<=250.

  • +
  • T? Returns the current target of the motor in mm. Is initially 0.

  • +
  • H: Stops the movement by setting the target to the current +position and returns T=6.555,P=6.555. If the motor is idle, +nothing happens, but the values are returned anyway.

  • +
+

In the simplest approach, the parameters that can describe the device +are:

+
    +
  • position: Read only.

  • +
  • target: Can be read and written by the user, but with certain +restrictions.

  • +
+

Additionally, the device is stateful in the sense that it can be in one +of three states.

+
    +
  • idle: The motor is powered on and ready to receive commands.

  • +
  • moving: The motor is moving towards the user supplied target.

  • +
+

Between those three states, different transitions exist:

+
    +
  • idle -> moving: The target position is different from the +current position, the motor starts moving.

  • +
  • moving -> idle: The motor has reached the target position or +the user has supplied a stop command, which sets the target position +to the current position, causing the motor to stop.

  • +
+

The states and transitions described above form a finite state machine +with two states and two transitions. This state machine forms the heart +of the simulated device, so it should be implemented using Lewis’ +cycle based finite state +machine, which +will be explained below.

+
+
+

Implementing the device simulation

+

In many cases there may eventually be more than one device simulation, so the directory +structure should be something like this, assuming your directory is /some/path:

+
/some/path
+    |
+    +- my_devices
+        |
+        +- device_1
+        |
+        +- device_2
+        |
+        +- __init__.py (Empty file)
+
+
+

Each device resides in the sub-package my_devices in the. The first step is to create a +new directory in the my_devices directory called example_motor, +which should contain a single file, __init__.py. For simple devices +like this it’s acceptable to put everything into one file, but for more +complex simulators it’s recommended to follow the structure of the +devices that are already part of the Lewis distribution.

+

Conceptually, in Lewis, devices are split in two Parts: a device +model, which contains internal device state, as well as potentially a +state machine, and an interface that exposes the device to the outside +world via a communication protocol that is provided by an “adapter”. The +adapter specifies the communication protocol (for example +EPICS or TCP/IP), whereas the +interface specifies the syntax and semantics of the actual command +language of the device.

+

For the actual device simulation there are two classes to choose between +for sub-classing. The class lewis.devices.Device can be used for very simple +devices that do not require a state machine to represent their +operation. On each simulation cycle, the method doProcess is +executed if it is implemented. This can be used to implement +time-dependent behavior. For the majority of cases, such as in the +example, it is more convenient to inherit from lewis.devices.StateMachineDevice. +It provides an internal state machine and options to override +characteristics of the state machine on initialization.

+

lewis.devices.StateMachineDevice has three methods that must be implemented by +sub-classes: lewis.devices.StateMachineDevice._get_state_handlers, +lewis.devices.StateMachineDevice._get_initial_state and +lewis.devices.StateMachineDevice._get_transition_handlers. They are used to define +the state machine. A fourth, optional method can be used to initialize internal device +state, it’s calld lewis.devices.StateMachineDevice._initialize_data. In this case +the device implementation should also go into __init__.py:

+
from lewis.devices import StateMachineDevice
+
+from lewis.core.statemachine import State
+from lewis.core import approaches
+
+from collections import OrderedDict
+
+class DefaultMovingState(State):
+    def in_state(self, dt):
+        old_position = self._context.position
+        self._context.position = approaches.linear(old_position, self._context.target,
+                                                   self._context.speed, dt)
+        self.log.info('Moved position (%s -> %s), target=%s, speed=%s', old_position,
+                      self._context.position, self._context.target, self._context.speed)
+
+class SimulatedExampleMotor(StateMachineDevice):
+    def _initialize_data(self):
+        self.position = 0.0
+        self._target = 0.0
+        self.speed = 2.0
+
+    def _get_state_handlers(self):
+        return {
+            'idle': State(),
+            'moving': DefaultMovingState()
+        }
+
+    def _get_initial_state(self):
+        return 'idle'
+
+    def _get_transition_handlers(self):
+        return OrderedDict([
+            (('idle', 'moving'), lambda: self.position != self.target),
+            (('moving', 'idle'), lambda: self.position == self.target)])
+
+    @property
+    def state(self):
+        return self._csm.state
+
+    @property
+    def target(self):
+        return self._target
+
+    @target.setter
+    def target(self, new_target):
+        if self.state == 'moving':
+            raise RuntimeError('Can not set new target while moving.')
+
+        if not (0 <= new_target <= 250):
+            raise ValueError('Target is out of range [0, 250]')
+
+        self._target = new_target
+
+    def stop(self):
+        self._target = self.position
+
+        self.log.info('Stopping movement after user request.')
+
+        return self.target, self.position
+
+
+

This defines the state machine according to the description at the top +of the page and some internal state variables, for example target, +which has some limits on when and to what values it can be set.

+

Both states of the motor are described by a state handler. In case of +the idle-state it is enough to use lewis.core.statemachine.State, +which simply does nothing. lewis.core.statemachine.State has three methods that +can be overridden:

+
    +
  • lewis.core.statemachine.State.on_entry

  • +
  • lewis.core.statemachine.State.in_state

  • +
  • lewis.core.statemachine.State.on_exit.

  • +
+

For other ways to specify those state handlers, please consult the documentation of +lewis.core.statemachine.StateMachine, where this is described in detail. +The advantage of using the lewis.core.statemachine.State-class is that it +has a so called context, which is stored in the _context-member. In case of +lewis.devices.StateMachineDevice, this context is the device object. +This means that device data can be modified in a state handler.

+

This is the case for the moving-state, where a state handler has +been defined by sub-classing lewis.core.statemachine.State. +In its in_state-method it modifies the position member of the device until it has reached +target with a rate that is stored in the speed-member. This +linear change behavior is implemented in the ~lewis.core.approaches.linear-function from +lewis.core.approaches. It automatically makes sure that the target is +always obtained even for very coarse dt-values.

+

The transitions between states are defined using lambda-functions in +this case, which simply check whether the current position is identical +with the target or not.

+

The device also provides a read-only property state, which forwards +the state machine’s (in the device as member _csm) state. The speed +of the motor is not part of the device specification, but it is added as +a member so that it can be changed via the lewis-control script to test +how the motor behaves at different speeds. The device is now fully +functional, but it’s not possible to interact with it yet, because the +interface is not specified yet.

+
+

Implementing the device interface

+

Device interfaces are implemented by sub-classing an appropriate +pre-written, protocol specific interface base class from the framework’s +lewis.adapters-package and overriding a few members. In this case this +base class is called lewis.adapters.stream.StreamInterface. The first step +is to specify the available commands in terms of a collection of +lewis.adapters.stream.Cmd-objects. These objects effectively bind +commands specified in terms of regular expressions to the interface’s methods. +According to the specifications above, the commands are defined like this:

+
from lewis.adapters.stream import StreamInterface, Cmd, scanf
+
+class ExampleMotorStreamInterface(StreamInterface):
+    commands = {
+        Cmd('get_status', r'^S\?$'),
+        Cmd('get_position', r'^P\?$'),
+        Cmd('get_target', r'^T\?$'),
+        Cmd('set_target', scanf('T=%f'), argument_mappings=(float,)),
+        Cmd('stop', r'^H$',
+            return_mapping=lambda x: 'T={},P={}'.format(x[0], x[1])),
+    }
+
+    in_terminator = '\r\n'
+    out_terminator = '\r\n'
+
+    def get_status(self):
+        return self.device.state
+
+    def get_position(self):
+        return self.device.position
+
+    def get_target(self):
+        return self.device.target
+
+    def set_target(self, new_target):
+        try:
+            self.device.target = new_target
+            return 'T={}'.format(new_target)
+        except RuntimeError:
+            return 'err: not idle'
+        except ValueError:
+            return 'err: not 0<=T<=250'
+
+
+

The first argument to lewis.adapters.stream.Cmd specifies the method +name the command is bound to, whereas the second argument is a pattern that a +request coming in over the TCP stream must match. If the pattern is specified as a string, +it is treated as a regular expression. In the above example, lewis.adapters.stream.scanf +is used for one of the functions, it allows for scanf-like format specifiers. If a method has +arguments (such as set_target), these need to be defined as capture +groups in the regular expression. These groups are passed as strings to +the bound method. If any sort of conversion is required for these +arguments, the argument_mapping-parameter can be a tuple of +conversion functions with the same lengths as the number of capture +groups in the regular expression. In the case of set_target it’s +enough to convert the string to float, but lewis.adapters.stream.scanf does that +automatically, so it is not strictly required here. Return values (except None) +are converted to strings automatically, but this conversion can be +overridden by supplying a callable object to return_mapping, as it +is the case for the stop-command.

+

You may have noticed that stop is not a method of the interface. +lewis.adapters.stream.StreamInterface tries to resolve the supplied method +names in multiple ways. First it checks its own members, then it checks the members of the +device it owns (accessible in the interface via the device-member) +and binds to the appropriate method. If the method name can not be +found in either the device or the interface, an error is produced, which +minimizes the likelihood of typos. The definitions in the interface +always have precedence, this is intentionally done so that device +behavior can be overridden later on with minimal changes to the code.

+

In case of the stop-method, which returns two floating point numbers +(target and position), the return_mapping is used to format the +device’s position and target as specified in the protocol definition at +the top of the page.

+

Finally, in- and out-terminators need to be specified. These are +stripped from and appended to requests and replies respectively.

+

This entire device can also be found in the lewis.examples module. It can be +started using the -a and -k parameters of lewis.py:

+
$ lewis -a /some/path -k my_devices example_motor -p "stream: {bind_address: 127.0.0.1, port: 9999}"
+
+
+

All functionality described in the +user_guide, such as accessing the device and the simulation via the +lewis-control.py-script are automatically available.

+
+
+
+

Logging

+

Both device and interface support logging, they supply a log member which is +a logger configured with the right name. The adapter already logs all important actions +that influence the device, so in the interface it should not be necessary to do too much +logging, but it might be interesting for debugging purposes.

+

Note that the simulation already produces one debug log message per simulation cycle logging +the elapsed (real-)time, so it is not necessary to log the dt parameters in addition. +lewis.core.statemachine.StateMachine also logs on each cycle which state it is in and +which transitions are triggered (if any). In the lewis.core.statemachine.State-handlers +that are device specific, any logging should focus on the behavior in that concrete state, as +for example demonstrated in the example above.

+

It is also important to consider the log level. Log messages that occur on each cycle must be +strictly limited to the debug-level, because they potentially produce a lot of data. +The info-level and above should be used for information that is relevant to anyone running +the simulation, such as failures or other “virtual problems” that might otherwise go unnoticed. +A good example would be a device that ignores faulty commands - a warning could be logged +with details about the command and that it was ignored.

+
+
+

User facing documentation

+

The lewis.adapters.stream.StreamAdapter-class has a property +documentation, which generates user facing documentation from the +lewis.adapters.stream.Cmd-objects (it can be displayed via the -i-flag of +lewis from the interface object via lewis-control.py). The regular expression of +each command is listed, along with a documentation string. If the doc-parameter is provided +to Cmd, it is used,otherwise the docstring of the wrapped method is used (it does not matter +whether the method is part of the device or the interface for feature to work). The latter is the +recommended way, because it avoids duplication. But in some cases, the user- and the +developer facing documentation may be so different that it’s useful to override the docstring.

+

This is also combined with the docstring of the interface (in this case +ExampleMotorStreamInterface), and some information about the configured host/port, +as well as terminators. The documentation has been left out from the above code samples for +brevity, but in the examples-directory, the docs are present.

+

All adapters offer similar functionality, the purpose is that the devices are documented in +a way that makes them easy to use by non-developers. This is especially important if the +protocol is non-obvious.

+
+
+

Unit tests

+

Unit tests should be added to the test-directory. While it would be +best to have unit tests for device and interface separately, it is most +important that the tests capture overall device behavior, so that it’s +immediately noticed when a change to Lewis’ core parts breaks the +simulation. It also makes it easier later on to refactor and change the +device.

+
+
+

Adding setups

+

In order to test certain failure scenarios of a device, setups can be +added to a device. The easiest way is to define a dictionary called +setups in the __init__.py file. A setup consists of a device +type and initialization parameters:

+
setups = dict(
+    moving=dict(
+        device_type=SimulatedExampleMotor,
+        parameters=dict(
+            override_initial_state='moving',
+            override_initial_data=dict(
+                _target=120.0, position=20.0
+            )
+        )
+    )
+)
+
+
+

In this case a moving-scenario is defined where the motor is already +moving to a target when the simulation is started.

+
+
+

Compatibility with framework versions

+

To make sure that users have a good experience using the newly added device, +it should specify what version of Lewis it works with. This is achieved by +adding another variable to the top level of the device module which contains +a version specification:

+
framework_version = '1.0.1'
+
+
+

This will make sure that older or newer versions of Lewis do not present odd exceptions +or error messages to users trying to start the device. If Lewis detects a mismatch +between the required version and the existing version, an error message is logged +so that users know where the problem comes from. In the ideal case this variable +would be updated with each release of Lewis after it has been made sure that the +device is compatible.

+
+
+

Further steps

+

Once a device is developed far enough, it’s time to submit a pull +request. As an external contributor, this happens via a fork on github. +Members of the development team will review the code and may make +suggestions for changes. Once the code is acceptable, it will be merged +into Lewis’ main branch and become a part of the distribution.

+

If a second interface is added to a device, either using a different +interface type or the same but with different commands, the interface +definitions should be moved out of the __init__.py file. Lewis +will continue to work if the interfaces are moved to a sub-folder of the +device called interfaces. This needs to have its own +__init__.py, where interface-classes can be imported from other +files in that module. It’s best to look at the chopper and linkam_t95 +devices that are already in Lewis.

+

The same is true for setups. For complex setups, these should be moved +to a sub-module of the device called setups, where each setup can +live in its own file. Please see the documentation of +lewis.devices.import_device for reference.

+
+
+

More Examples

+

More example devices and interfaces are provided in the lewis.examples directory.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.adapters.epics.html b/generated/lewis.adapters.epics.html new file mode 100644 index 00000000..25c7e21f --- /dev/null +++ b/generated/lewis.adapters.epics.html @@ -0,0 +1,452 @@ + + + + + + + + + lewis.adapters.epics — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.adapters.epics

+

Members

+ + + + + + + + + + + + + + + + + + +

BoundPV

Class to represent PVs that are bound to an adapter

EpicsAdapter

This adapter provides ChannelAccess server functionality through the pcaspy module.

EpicsInterface

Inheriting from this class provides an EPICS-interface to a device for use with EpicsAdapter.

PV

The PV-class is used to declare the EPICS-interface exposed by a sub-class of EpicsAdapter.

PropertyExposingDriver

+
+
+class lewis.adapters.epics.BoundPV(pv, target, meta_target=None)[source]
+

Bases: object

+

Class to represent PVs that are bound to an adapter

+

This class is very similar to Func, in that +it is the result of a binding operation between a user-specified PV-object +and a Device and/or Adapter object. Also, it should rarely be used directly. objects +are generated automatically by EpicsAdapter.

+

The binding happens by supplying a target-object which has an attribute or a property +named according to the property-name stored in the PV-object, and a meta_target-object +which has an attribute named according to the meta_data_property in PV.

+

The properties read_only, config, and poll_interval simply forward the +data of PV, while doc uses the target object to potentially obtain the property’s +docstring.

+

To get and set the value of the property on the target, the value-property of +this class can be used, to get the meta data dict, there’s a meta-property.

+
+
Parameters:
+
    +
  • pv – PV object to bind to target and meta_target.

  • +
  • target – Object that has an attribute named pv.property.

  • +
  • meta_target – Object that has an attribute named pv.meta_data_property.

  • +
+
+
+
+
+property config
+

Config dict passed on to pcaspy-machinery.

+
+ +
+
+property doc
+

Docstring of property on target or override specified on PV-object.

+
+ +
+
+property meta
+

Value of the bound meta-property on the target.

+
+ +
+
+property poll_interval
+

Interval at which to update PV in pcaspy.

+
+ +
+
+property read_only
+

True if the PV is read-only.

+
+ +
+
+property value
+

Value of the bound property on the target.

+
+ +
+ +
+
+class lewis.adapters.epics.EpicsAdapter(options=None)[source]
+

Bases: Adapter

+

This adapter provides ChannelAccess server functionality through the pcaspy module.

+

It’s possible to configure the prefix for the PVs provided by this adapter. The +corresponding key in the options dictionary is called prefix:

+
options = {"prefix": "PVPREFIX:"}
+
+
+
+
Parameters:
+

options – Dictionary with options.

+
+
+
+
+property documentation
+

This property can be overridden in a sub-class to provide protocol documentation to users +at runtime. By default it returns the indentation cleaned-up docstring of the class.

+
+ +
+
+handle(cycle_delay=0.1) None[source]
+

Call this method to spend about cycle_delay seconds processing +requests in the pcaspy server. Under load, for example when running caget at a +high frequency, the actual time spent in the method may be much shorter. This effect +is not corrected for.

+
+
Parameters:
+

cycle_delay – Approximate time to be spent processing requests in pcaspy server.

+
+
+
+ +
+
+property is_running
+

This property indicates whether the Adapter’s server is running and listening. The result +of calls to start_server() and stop_server() should be reflected as expected.

+
+ +
+
+start_server() None[source]
+

Creates a pcaspy-server.

+
+

Note

+

The server does not process requests unless handle() is called regularly.

+
+
+ +
+
+stop_server() None[source]
+

This method must be re-implemented to stop and tear down anything that has been setup +in start_server(). This method should close all connections to clients that have +been established since the adapter has been started.

+
+

Note

+

This method may be called multiple times over the lifetime of the Adapter, so it is +important to make sure that this does not cause problems.

+
+
+ +
+ +
+
+class lewis.adapters.epics.EpicsInterface[source]
+

Bases: InterfaceBase

+

Inheriting from this class provides an EPICS-interface to a device for use with +EpicsAdapter. In the simplest case all that is required is to inherit +from this class and override the pvs-member. It should be a dictionary +that contains PV-names (without prefix) as keys and instances of PV as +values. The prefix is handled by EpicsAdapter.

+

For a simple device with two properties, speed and position, the first of which +should be read-only, it’s enough to define the following:

+
class SimpleDeviceEpicsInterface(EpicsInterface):
+    pvs = {"VELO": PV("speed", read_only=True), "POS": PV("position", lolo=0, hihi=100)}
+
+
+

For more complex behavior, the interface could contain properties that do not +exist in the device itself. If the device should also have a PV called STOP +that “stops the device”, the interface could look like this:

+
class SimpleDeviceEpicsInterface(EpicsInterface):
+    pvs = {
+        "VELO": PV("speed", read_only=True),
+        "POS": PV("position", lolo=0, hihi=100),
+        "STOP": PV("stop", type="int"),
+    }
+
+    @property
+    def stop(self):
+        return 0
+
+    @stop.setter
+    def stop(self, value):
+        if value == 1:
+            self.device.halt()
+
+
+

Even though the device does not have a property called stop (but a method called +halt), issuing the command

+
$ caput STOP 1
+
+
+

will achieve the desired behavior, because EpicsInterface merges the properties +of the device into SimpleDeviceEpicsInterface itself, so that it is does not +matter whether the specified property in PV exists in the device or the adapter.

+

The intention of this design is to keep device classes small and free of +protocol specific stuff, such as in the case above where stopping a device +via EPICS might involve writing a value to a PV, whereas other protocols may +offer an RPC-way of achieving the same thing.

+
+
+property adapter
+

Adapter type that is required to process and expose interfaces of this type. Must be +implemented in subclasses.

+
+ +
+ +
+
+class lewis.adapters.epics.PV(target_property, poll_interval=1.0, read_only=False, meta_data_property=None, doc=None, **kwargs)[source]
+

Bases: object

+

The PV-class is used to declare the EPICS-interface exposed by a sub-class of +EpicsAdapter. The target_property argument specifies which property of the adapter +the PV maps to. To make development easier it can also be a part of the exposed +device. If the property exists on both the Adapter-subclass and the device, the former +has precedence. This is useful for overriding behavior for protocol specific “quirks”.

+

If the PV should be read only, this needs to be specified via +the corresponding parameter. The information about the poll interval is used +py EpicsAdapter to update the PV in regular intervals. All other named arguments +are forwarded to the pcaspy server’s pvdb, so it’s possible to pass on +limits, types, enum-values and so on.

+

In case those arguments change at runtime, it’s possible to provide meta_data_property, +which should contain the name of a property that returns a dict containing these values. +For example if limits change:

+
class Interface(EpicsInterface):
+    pvs = {"example": PV("example", meta_data_property="example_meta")}
+
+    @property
+    def example_meta(self):
+        return {
+            "lolim": self.device._example_low_limit,
+            "hilim": self.device._example_high_limit,
+        }
+
+
+

The PV infos are then updated together with the value, determined by poll_interval.

+

In cases where the device is accessed via properties alone, this class provides the possibility +to expose methods as PVs. A common use case would be to model a getter:

+
class SomeDevice(Device):
+    def get_example(self):
+        return 42
+
+
+class Interface(EpicsInterface):
+    pvs = {"example": PV("get_example")}
+
+
+

It is also possible to model a getter/setter pair, in this case a tuple has to be provided:

+
class SomeDevice(Device):
+    _ex = 40
+
+    def get_example(self):
+        return self._ex + 2
+
+    def set_example(self, new_example):
+        self._ex = new_example - 2
+
+
+class Interface(EpicsInterface):
+    pvs = {"example": PV(("get_example", "set_example"))}
+
+
+

Any of the two members in the tuple can be substituted with None in case it does not apply. +Besides method names it is also allowed to provide callables. Valid callables are for example +bound methods and free functions, but also lambda expressions and partials.

+

There are however restrictions for the supplied functions (be it as method names or directly +as callables) with respect to their signature. Getter functions must be callable without any +arguments, setter functions must be callable with exactly one argument. The self of +methods does not count towards this.

+
+
Parameters:
+
    +
  • target_property – Property or method name, getter function, tuple of getter/setter.

  • +
  • poll_interval – Update interval of the PV.

  • +
  • read_only – Should be True if the PV is read only. If not specified, the PV is +read_only if only a getter is supplied.

  • +
  • meta_data_property – Property or method name, getter function, tuple of getter/setter.

  • +
  • doc – Description of the PV. If not supplied, docstring of mapped property is used.

  • +
  • kwargs – Arguments forwarded into pcaspy pvdb-dict.

  • +
+
+
+
+
+bind(*targets)[source]
+

Tries to bind the PV to one of the supplied targets. Targets are inspected according to +the order in which they are supplied.

+
+
Parameters:
+

targets – Objects to inspect from.

+
+
Returns:
+

BoundPV instance with the PV bound to the target property.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.adapters.html b/generated/lewis.adapters.html new file mode 100644 index 00000000..02a86692 --- /dev/null +++ b/generated/lewis.adapters.html @@ -0,0 +1,168 @@ + + + + + + + + + lewis.adapters — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.adapters

+

The core Adapter API is located in lewis.core.adapters. This package contains concrete +implementations of Adapter in its submodules, along with +specific tools for each adapter.

+

Submodules

+ + + + + + + + + + + + +

epics

modbus

This module provides components to expose a Device via a Modbus-interface.

stream

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.adapters.modbus.html b/generated/lewis.adapters.modbus.html new file mode 100644 index 00000000..1b3aabc8 --- /dev/null +++ b/generated/lewis.adapters.modbus.html @@ -0,0 +1,488 @@ + + + + + + + + + lewis.adapters.modbus — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.adapters.modbus

+

This module provides components to expose a Device via a Modbus-interface. The following resources +were used as guidelines and references for implementing the protocol:

+
+
+
+

Note

+

For an example how Modbus can be used in the current implementation, please look +at lewis/examples/modbus_device.

+
+

Members

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

MBEX

Modbus standard exception codes

ModbusAdapter

ModbusBasicDataBank

A basic ModbusDataBank instance.

ModbusDataBank

Preliminary DataBank implementation for Modbus.

ModbusDataStore

Convenience struct to hold the four types of DataBanks in Modbus

ModbusHandler

ModbusInterface

ModbusProtocol

This class implements the Modbus TCP Protocol.

ModbusServer

ModbusTCPFrame

This class models a frame of the Modbus TCP protocol.

+
+
+class lewis.adapters.modbus.MBEX[source]
+

Bases: object

+

Modbus standard exception codes

+
+ +
+
+class lewis.adapters.modbus.ModbusAdapter(options=None)[source]
+

Bases: Adapter

+
+
+handle(cycle_delay=0.1) None[source]
+

This function is called on each cycle of a simulation. It should process requests that are +made via the protocol that exposes the device. The time spent processing should be +approximately cycle_delay seconds, during which the adapter may block the current +process. It is desirable to stick to the provided time, but deviations are permissible if +necessary due to the way the protocol works.

+
+
Parameters:
+

cycle_delay – Approximate time spent processing requests.

+
+
+
+ +
+
+property is_running
+

This property indicates whether the Adapter’s server is running and listening. The result +of calls to start_server() and stop_server() should be reflected as expected.

+
+ +
+
+start_server() None[source]
+

This method must be re-implemented to start the infrastructure required for the +protocol in question. These startup operations are not supposed to be carried out on +construction of the adapter in order to preserve control over when services are +started during a run of a simulation.

+
+

Note

+

This method may be called multiple times over the lifetime of the Adapter, so it is +important to make sure that this does not cause problems.

+
+
+

See also

+

See stop_server() for shutting down the adapter.

+
+
+ +
+
+stop_server() None[source]
+

This method must be re-implemented to stop and tear down anything that has been setup +in start_server(). This method should close all connections to clients that have +been established since the adapter has been started.

+
+

Note

+

This method may be called multiple times over the lifetime of the Adapter, so it is +important to make sure that this does not cause problems.

+
+
+ +
+ +
+
+class lewis.adapters.modbus.ModbusBasicDataBank(default_value=0, start_addr=0, last_addr=65535)[source]
+

Bases: ModbusDataBank

+

A basic ModbusDataBank instance.

+

This type of DataBank simply serves as a memory space for Modbus requests to read from and +write to. It does not support binding addresses to attributes or functions of the device +or interface. Example usage:

+
di = ModbusBasicDataBank(False, 0x1000, 0x1FFF)
+
+
+
+
Parameters:
+
    +
  • default_value – Value to initialize memory with

  • +
  • start_addr – First valid address

  • +
  • last_addr – Last valid address

  • +
+
+
+
+ +
+
+class lewis.adapters.modbus.ModbusDataBank(**kwargs)[source]
+

Bases: object

+

Preliminary DataBank implementation for Modbus.

+

This is a very generic implementation of a databank for Modbus. It’s meant to set the +groundwork for future implementations. Only derived classes should be instantiated, not +this class directly. The signature of this __init__ method is subject to change.

+
+
Parameters:
+

kwargs – Configuration

+
+
+
+
+get(addr, count)[source]
+

Read list of count values at addr memory location in DataBank.

+
+
Parameters:
+
    +
  • addr – Address to read from

  • +
  • count – Number of entries to retrieve

  • +
+
+
Returns:
+

list of entry values

+
+
Raises:
+

IndexError – Raised if address range falls outside valid range

+
+
+
+ +
+
+set(addr, values) None[source]
+

Write list values to addr memory location in DataBank.

+
+
Parameters:
+
    +
  • addr – Address to write to

  • +
  • values – list of values to write

  • +
+
+
Raises:
+

IndexError – Raised if address range falls outside valid range

+
+
+
+ +
+ +
+
+class lewis.adapters.modbus.ModbusDataStore(di=None, co=None, ir=None, hr=None)[source]
+

Bases: object

+

Convenience struct to hold the four types of DataBanks in Modbus

+
+ +
+
+class lewis.adapters.modbus.ModbusInterface[source]
+

Bases: InterfaceBase

+
+
+property adapter
+

Adapter type that is required to process and expose interfaces of this type. Must be +implemented in subclasses.

+
+ +
+ +
+
+class lewis.adapters.modbus.ModbusProtocol(sender, datastore)[source]
+

Bases: object

+

This class implements the Modbus TCP Protocol.

+

The user of this class should provide a ModbusDataStore instance that will be used to +fulfill read and write requests, and a callable sender which accepts one bytearray +parameter. The sender will be called whenever a response frame is generated, with a +bytearray containing the response frame as the parameter.

+

Processing occurs when the user calls ModbusProtocol.process(), passing in the raw frame +data to process as a bytearray. The data may include multiple frames and partial frame +fragments. Any data that could not be processed (due to incomplete frames) is buffered for +the next call to process.

+
+
Parameters:
+
    +
  • sender – callable that accepts one bytearray parameter, called to send responses.

  • +
  • datastore – ModbusDataStore instance to reference when processing requests

  • +
+
+
+
+
+process(data, device_lock) None[source]
+

Process as much of given data as possible.

+

Any remainder, in case there is an incomplete frame at the end, is stored so that +processing may continue where it left off when more data is provided.

+
+
Parameters:
+
    +
  • data – Incoming byte data. Must be compatible with bytearray.

  • +
  • device_lock – threading.Lock instance that is acquired for device interaction.

  • +
+
+
+
+ +
+ +
+
+class lewis.adapters.modbus.ModbusTCPFrame(stream=None)[source]
+

Bases: object

+

This class models a frame of the Modbus TCP protocol.

+

It may be a request, a response or an exception. Typically, requests are constructed using the +init method, while responses and exceptions are constructed by called create_request or +create_exception on an instance that is a request.

+

Note that data from the passed in bytearray stream is consumed. That is, bytes will be removed +from the front of the bytearray if construction is successful.

+
+
Parameters:
+

stream – bytearray to consume data from to construct this frame.

+
+
Raises:
+

EOFError – Not enough data for complete frame; no data consumed.

+
+
+
+
+create_exception(code)[source]
+

Create an exception frame based on this frame.

+
+
Parameters:
+

code – Modbus exception code to use for this exception

+
+
Returns:
+

ModbusTCPFrame instance that represents an exception

+
+
+
+ +
+
+create_response(data=None)[source]
+

Create a response frame based on this frame.

+
+
Parameters:
+

data – Data section of response as bytearray. If None, request data section is kept.

+
+
Returns:
+

ModbusTCPFrame instance that represents a response

+
+
+
+ +
+
+from_bytearray(stream) None[source]
+

Constructs this frame from input data stream, consuming as many bytes as necessary from +the beginning of the stream.

+

If stream does not contain enough data to construct a complete modbus frame, an EOFError +is raised and no data is consumed.

+
+
Parameters:
+

stream – bytearray to consume data from to construct this frame.

+
+
Raises:
+

EOFError – Not enough data for complete frame; no data consumed.

+
+
+
+ +
+
+is_valid()[source]
+

Check integrity and validity of this frame.

+
+
Returns:
+

bool True if this frame is structurally valid.

+
+
+
+ +
+
+to_bytearray()[source]
+

Convert this frame into its bytearray representation.

+
+
Returns:
+

bytearray representation of this frame.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.adapters.stream.html b/generated/lewis.adapters.stream.html new file mode 100644 index 00000000..705faf96 --- /dev/null +++ b/generated/lewis.adapters.stream.html @@ -0,0 +1,670 @@ + + + + + + + + + lewis.adapters.stream — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.adapters.stream

+

Members

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Cmd

This class is an implementation of CommandBase that can expose a callable object or a named method of the device/interface controlled by StreamAdapter.

CommandBase

This is the common base class of Cmd and Var.

Func

Objects of this type connect a callable object to a pattern matcher (PatternMatcher), which currently comprises regex and scanf.

PatternMatcher

This class defines an interface for general command-matchers that use any kind of technique to match a certain request in string form.

StreamAdapter

The StreamAdapter is the bridge between the Device Interface and the TCP Stream networking backend implementation.

StreamHandler

StreamInterface

This class is used to provide a TCP-stream based interface to a device.

StreamServer

Var

With this implementation of CommandBase it's possible to expose plain data attributes or properties of device or interface.

regex

Implementation of PatternMatcher that compiles the specified pattern into a regular expression.

scanf

Interprets the specified pattern as a scanf format.

+
+
+class lewis.adapters.stream.Cmd(func, pattern, argument_mappings=None, return_mapping=<function Cmd.<lambda>>, doc=None)[source]
+

Bases: CommandBase

+

This class is an implementation of CommandBase that can expose a callable object +or a named method of the device/interface controlled by StreamAdapter.

+
def random():
+    return 6
+
+SomeInterface(StreamInterface):
+    commands = {
+        Cmd(lambda: 4, pattern='^R$', doc='Returns a random number.'),
+        Cmd('random', pattern='^RR$', doc='Better random number.'),
+        Cmd(random, pattern='^RRR$', doc='The best random number.'),
+    }
+
+    def random(self):
+        return 5
+
+
+

The interface defined by the above example has three commands, R which calls a lambda +function that always returns 4, RR, which calls SomeInterface.random and returns 5 and +lastly RRR which calls the free function defined above and returns the best random number.

+

For a detailed explanation of requirements to the constructor arguments, please refer to the +documentation of Func, to which the arguments are forwarded.

+
+

See also

+

Var exposes attributes and properties of a device object. The documentation +of Func provides more information about the common constructor arguments.

+
+
+
Parameters:
+
    +
  • func – Function to be called when pattern matches or member of device/interface.

  • +
  • pattern – Pattern to match (PatternMatcher or string).

  • +
  • argument_mappings – Iterable with mapping functions from string to some type.

  • +
  • return_mapping – Mapping function for return value of method.

  • +
  • doc – Description of the command. If not supplied, the docstring is used.

  • +
+
+
+
+ +
+
+class lewis.adapters.stream.CommandBase(func, pattern, argument_mappings=None, return_mapping=None, doc=None)[source]
+

Bases: object

+

This is the common base class of Cmd and Var. The concept of commands for +the stream adapter is based on connecting a callable object to a pattern that matches an +inbound request.

+

The type of pattern can be either an implementation of PatternMatcher +(regex or scanf format specification) or a plain string (which is treated as a regular +expression).

+

For free function and lambda expressions this is straightforward: the function object can +simply be stored together with the pattern. Most often however, the callable +is a method of the device or interface object - these do not exist when the commands are +defined.

+

This problem is solved by introducing a “bind”-step in StreamAdapter. So instead +of a function object, both Cmd and Var store the name of a member of device +or interface. At “bind-time”, this is translated into the correct callable.

+

So instead of using Cmd or Var directly, both classes’ bind()-methods +return an iterable of Func-objects which can be used for processing requests. +StreamAdapter performs this bind-step when it’s constructed. For details regarding +the implementations, please see the corresponding classes.

+
+

See also

+

Please take a look at Cmd for exposing callable objects or methods of +device/interface and Var for exposing attributes and properties.

+

To see how argument_mappings, return_mapping and doc are applied, please look at +Func.

+
+
+
Parameters:
+
    +
  • func – Function to be called when pattern matches or member of device/interface.

  • +
  • pattern – Pattern to match (PatternMatcher or string).

  • +
  • argument_mappings – Iterable with mapping functions from string to some type.

  • +
  • return_mapping – Mapping function for return value of method.

  • +
  • doc – Description of the command. If not supplied, the docstring is used.

  • +
+
+
+
+ +
+
+class lewis.adapters.stream.Func(func, pattern, argument_mappings=None, return_mapping=None, doc=None)[source]
+

Bases: object

+

Objects of this type connect a callable object to a pattern matcher (PatternMatcher), +which currently comprises regex and scanf. Strings are also +accepted, they are treated like a regular expression internally. This preserves default +behavior from older versions of Lewis.

+

In general, Func-objects should not be created directly, instead they are created by one of +the sub-classes of CommandBase using bind().

+

Function arguments are indicated by groups in the regular expression. The number of +groups has to match the number of arguments of the function. In earlier versions of Lewis it +was possible to pass flags to re.compile, this has been removed for consistency issues +in Var. It is however still possible to use the exact same flags as part of the +regular expression. In the documentation of re, this is outlined, simply add a group to the +expression that contains the flags, for example (?i) to make the expression case +insensitive. This special group does not count towards the matching groups used for argument +capture.

+

The optional argument_mappings can be an iterable of callables with one parameter of the +same length as the number of arguments of the function. The first parameter will be +transformed using the first function, the second using the second function and so on. +This can be useful to automatically transform strings provided by the adapter into a proper +data type such as int or float before they are passed to the function. In case the +pattern is of type scanf, this is optional (but will override the mappings +provided by the matcher).

+

The return_mapping argument is similar, it should map the return value of the function +to a string. The default map function only does that when the supplied value +is not None. It can also be set to a numeric value or a string constant so that the +command always returns the same value. If it is None, the return value is not +modified at all.

+

Finally, documentation can be provided by passing the doc-argument. If it is omitted, +the docstring of the bound function is used and if that is not present, left empty.

+
+
Parameters:
+
    +
  • func – Function to be called when pattern matches or member of device/interface.

  • +
  • patternregex, scanf object or string.

  • +
  • argument_mappings – Iterable with mapping functions from string to some type.

  • +
  • return_mapping – Mapping function for return value of method.

  • +
  • doc – Description of the command. If not supplied, the docstring is used.

  • +
+
+
Raises:
+

RuntimeError: If the function cannot be mapped for any reason.

+
+
+
+
+map_arguments(arguments)[source]
+

Returns the mapped function arguments. If no mapping functions are defined, the arguments +are returned as they were supplied.

+
+
Parameters:
+

arguments – List of arguments for bound function as strings.

+
+
Returns:
+

Mapped arguments.

+
+
+
+ +
+
+map_return_value(return_value)[source]
+

Returns the mapped return_value of a processed request. If no return_mapping has been +defined, the value is returned as is. If return_mapping is a static value, that value +is returned, ignoring return_value completely.

+
+
Parameters:
+

return_value – Value to map.

+
+
Returns:
+

Mapped return value.

+
+
+
+ +
+ +
+
+class lewis.adapters.stream.PatternMatcher(pattern)[source]
+

Bases: object

+

This class defines an interface for general command-matchers that use any kind of +technique to match a certain request in string form. It is used by Func to check +whether a request can be processed using a function and to extract any function arguments.

+

Sub-classes must implement all defined abstract methods/properties.

+
+

See also

+

regex, scanf are concrete implementations of this class.

+
+
+
+property arg_count: NoReturn
+

Number of arguments that are matched in a request.

+
+ +
+
+property argument_mappings: NoReturn
+

Mapping functions that can be applied to the arguments returned by match().

+
+ +
+
+match(request) NoReturn[source]
+

Tries to match the request against the internally stored pattern. Returns any matched +function arguments.

+
+
Parameters:
+

request – Request to attempt matching.

+
+
Returns:
+

List of matched argument values (possibly empty) or None if not matching.

+
+
+
+ +
+
+property pattern
+

The pattern definition used for matching a request.

+
+ +
+ +
+
+class lewis.adapters.stream.StreamAdapter(options=None)[source]
+

Bases: Adapter

+

The StreamAdapter is the bridge between the Device Interface and the TCP Stream networking +backend implementation.

+

Available adapter options are:

+
+
    +
  • bind_address: IP of network adapter to bind on (defaults to 0.0.0.0, or all adapters)

  • +
  • port: Port to listen on (defaults to 9999)

  • +
  • telnet_mode: When True, overrides in- and out-terminator for CRNL (defaults to False)

  • +
+
+
+
Parameters:
+

options – Dictionary with options.

+
+
+
+
+property documentation
+

This property can be overridden in a sub-class to provide protocol documentation to users +at runtime. By default it returns the indentation cleaned-up docstring of the class.

+
+ +
+
+handle(cycle_delay=0.1) None[source]
+

Spend approximately cycle_delay seconds to process requests to the server.

+
+
Parameters:
+

cycle_delay – S

+
+
+
+ +
+
+property is_running
+

This property indicates whether the Adapter’s server is running and listening. The result +of calls to start_server() and stop_server() should be reflected as expected.

+
+ +
+
+start_server() None[source]
+

Starts the TCP stream server, binding to the configured host and port. +Host and port are configured via the command line arguments.

+
+

Note

+

The server does not process requests unless +handle() is called in regular intervals.

+
+
+ +
+
+stop_server() None[source]
+

This method must be re-implemented to stop and tear down anything that has been setup +in start_server(). This method should close all connections to clients that have +been established since the adapter has been started.

+
+

Note

+

This method may be called multiple times over the lifetime of the Adapter, so it is +important to make sure that this does not cause problems.

+
+
+ +
+ +
+
+class lewis.adapters.stream.StreamHandler(sock, target, stream_server)[source]
+

Bases: async_chat

+
+ +
+
+class lewis.adapters.stream.StreamInterface[source]
+

Bases: InterfaceBase

+

This class is used to provide a TCP-stream based interface to a device.

+

Many hardware devices use a protocol that is based on exchanging text with a client via +a TCP stream. Sometimes RS232-based devices are also exposed this way via an adapter-box. +This adapter makes it easy to mimic such a protocol.

+

This class has the following attributes which may be overridden by subclasses:

+
+
    +
  • protocol: What this interface is called for purposes of the -p commandline option. +Defaults to “stream”.

  • +
  • in_terminator, out_terminator: These define how lines are terminated when transferred +to and from the device respectively. They are stripped/added automatically. +Inverse of protocol file InTerminator and OutTerminator. The default is \\r.

  • +
  • readtimeout: How many msec to wait for additional data between packets, once transmission +of an incoming command has begun. Inverse of ReadTimeout in protocol files. +Defaults to 100 (ms). Set to 0 to disable timeout completely.

  • +
  • commands: A list of CommandBase-objects that define mappings between protocol +and device/interface methods/attributes.

  • +
+
+

By default, commands are expressed as regular expressions, a simple example may look like this:

+
class SimpleDeviceStreamInterface(StreamInterface):
+    commands = [
+        Cmd('set_speed', r'^S=([0-9]+)$', argument_mappings=[int]),
+        Cmd('get_speed', r'^S\?$')
+        Var('speed', read_pattern=r'^V\?$', write_pattern=r'^V=([0-9]+)$')
+    ]
+
+    def set_speed(self, new_speed):
+        self.device.speed = new_speed
+
+    def get_speed(self):
+        return self.device.speed
+
+
+

The interface has two commands, S? to return the speed and S=10 to set the speed +to an integer value. It also exposes the same speed attribute as a variable, using auto- +generated V? and V=10 commands.

+

As in the lewis.adapters.epics.EpicsInterface, it does not matter whether the +wrapped method is a part of the device or of the interface, this is handled automatically when +a new device is assigned to the device-property.

+

In addition, the handle_error()-method can be overridden. It is called when an exception +is raised while handling commands.

+
+
+property adapter
+

Adapter type that is required to process and expose interfaces of this type. Must be +implemented in subclasses.

+
+ +
+
+handle_error(request, error) None[source]
+

Override this method to handle exceptions that are raised during command processing. +The default implementation does nothing, so that any errors are silently ignored.

+
+
Parameters:
+
    +
  • request – The request that resulted in the error.

  • +
  • error – The exception that was raised.

  • +
+
+
+
+ +
+ +
+
+class lewis.adapters.stream.Var(target_member, read_pattern=None, write_pattern=None, argument_mappings=None, return_mapping=<function Var.<lambda>>, doc=None)[source]
+

Bases: CommandBase

+

With this implementation of CommandBase it’s possible to expose plain data attributes +or properties of device or interface. Getting and setting a value are separate procedures +which both have their own pattern, read_pattern and write_pattern to match a command each. +Please note that write_pattern has to have exactly one group defined to match a parameter.

+

Due to this separation, parameters can be made read-only, write-only or read-write in the +interface:

+
class SomeInterface(StreamInterface):
+    commands = {
+        Var('foo', read_pattern='^F$', write_pattern=r'^F=(\d+)$',
+            argument_mappings=(int,), doc='An integer attribute.'),
+        Var('bar' read_pattern='^B$')
+    }
+
+    foo = 10
+
+    @property
+    def bar(self):
+        return self.foo + 5
+
+    @bar.setter
+    def bar(self, new_bar):
+        self.foo = new_bar - 5
+
+
+

In the above example, the foo attribute can be read and written, it’s automatically converted +to an integer, while bar is a property that can only be read via the stream protocol.

+
+

See also

+

For exposing methods and free functions, there’s the Cmd-class.

+
+
+
Parameters:
+
    +
  • target_member – Attribute or property of device/interface to expose.

  • +
  • read_pattern – Pattern to match for getter (PatternMatcher or string).

  • +
  • write_pattern – Pattern to match for setter (PatternMatcher or string).

  • +
  • argument_mappings – Iterable with mapping functions from string to some type, +only applied to setter.

  • +
  • return_mapping – Mapping function for return value of method, +applied to getter and setter.

  • +
  • doc – Description of the command. If not supplied, the docstring is used. For plain data +attributes the only way to get docs is to supply this argument.

  • +
+
+
+
+ +
+
+class lewis.adapters.stream.regex(pattern)[source]
+

Bases: PatternMatcher

+

Implementation of PatternMatcher that compiles the specified pattern into a regular +expression.

+
+
+property arg_count
+

Number of arguments that are matched in a request.

+
+ +
+
+property argument_mappings: None
+

Mapping functions that can be applied to the arguments returned by match().

+
+ +
+
+match(request)[source]
+

Tries to match the request against the internally stored pattern. Returns any matched +function arguments.

+
+
Parameters:
+

request – Request to attempt matching.

+
+
Returns:
+

List of matched argument values (possibly empty) or None if not matching.

+
+
+
+ +
+ +
+
+class lewis.adapters.stream.scanf(pattern, exact_match=True)[source]
+

Bases: regex

+

Interprets the specified pattern as a scanf format. Internally, the scanf package is used +to transform the format into a regular expression. Please consult the documentation of scanf +for valid pattern specifications.

+

By default, the resulting regular expression matches exactly. Consider this example:

+
exact = scanf("T=%f")
+not_exact = scanf("T=%f", exact_match=False)
+
+
+

The first pattern only matches the string T=4.0, whereas the second would also match +T=4.0garbage. Please note that the specifiers like %f are automatically turned into +groups in the generated regular expression.

+
+
Parameters:
+
    +
  • pattern – Scanf format specification.

  • +
  • exact_match – Match only if the entire string matches.

  • +
+
+
+
+
+property argument_mappings
+

Mapping functions that can be applied to the arguments returned by match().

+
+ +
+
+property pattern
+

The pattern definition used for matching a request.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.adapters.html b/generated/lewis.core.adapters.html new file mode 100644 index 00000000..14df6e74 --- /dev/null +++ b/generated/lewis.core.adapters.html @@ -0,0 +1,430 @@ + + + + + + + + + lewis.core.adapters — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.adapters

+

This module contains Adapter, which serves as a base class for concrete adapter +implementations in lewis.adapters. It also contains AdapterCollection which can +be used to store multiple adapters and manage them together.

+

Members

+ + + + + + + + + + + + +

Adapter

Base class for adapters

AdapterCollection

A container to manage the adapters of a device

NoLock

A dummy context manager that raises a RuntimeError when it's used.

+
+
+class lewis.core.adapters.Adapter(options: dict[str, Any] | None = None)[source]
+

Bases: object

+

Base class for adapters

+

This class serves as a base class for concrete adapter implementations that expose a device via +a certain communication protocol. It defines the minimal interface that an adapter must provide +in order to fit seamlessly into other parts of the framework +(most importantly Simulation).

+

Sub-classes should re-define the protocol-member to something appropriate. While it is +explicitly supported to modify it in concrete device interface implementations, it is good +to have a default (for example epics or stream).

+

An adapter should provide everything that is needed for the communication via the protocol it +defines. This might involve constructing a server-object, configuring it and starting the +service (this should happen in start_server()). Due to the large differences between +protocols it is very hard to provide general guidelines here. Please take a look at the +implementations of existing adapters (EpicsAdapter, +StreamAdapter),to get some examples.

+

In principle, an adapter can exist on its own, but it only really becomes useful when a device +is bound to it. To do this, assign an object derived from +lewis.core.devices.DeviceBase to the device-property. Sub-classes have to +implement _bind_device() to achieve actual binding behavior.

+

It is possible to pass a dictionary with configuration options to Adapter. The keys of +the dictionary are accessible as properties of the _options-member. Only keys that are +in the default_options member of the class are accepted. Inheriting classes must override +default_options to be a dictionary with the possible options for the adapter.

+

Each adapter has a lock member, which contains a NoLock by default. To make +device access thread-safe, any adapter should acquire this lock before interacting with +the device (or interface). This means that before starting the server component of an Adapter, +a proper Lock-object needs to be assigned to lock.

+
+
Parameters:
+

options – Configuration options for the adapter.

+
+
+
+
+property documentation: str
+

This property can be overridden in a sub-class to provide protocol documentation to users +at runtime. By default it returns the indentation cleaned-up docstring of the class.

+
+ +
+
+handle(cycle_delay: float = 0.1) None[source]
+

This function is called on each cycle of a simulation. It should process requests that are +made via the protocol that exposes the device. The time spent processing should be +approximately cycle_delay seconds, during which the adapter may block the current +process. It is desirable to stick to the provided time, but deviations are permissible if +necessary due to the way the protocol works.

+
+
Parameters:
+

cycle_delay – Approximate time spent processing requests.

+
+
+
+ +
+
+property interface: InterfaceBase | None
+

The device property contains the device-object exposed by the adapter.

+

The property can be set from the outside, at that point the adapter will +call _bind_device() (which is implemented in each adapter sub-class) +and thus re-bind its commands etc. to call the new device.

+
+ +
+
+property is_running: bool
+

This property indicates whether the Adapter’s server is running and listening. The result +of calls to start_server() and stop_server() should be reflected as expected.

+
+ +
+
+start_server() None[source]
+

This method must be re-implemented to start the infrastructure required for the +protocol in question. These startup operations are not supposed to be carried out on +construction of the adapter in order to preserve control over when services are +started during a run of a simulation.

+
+

Note

+

This method may be called multiple times over the lifetime of the Adapter, so it is +important to make sure that this does not cause problems.

+
+
+

See also

+

See stop_server() for shutting down the adapter.

+
+
+ +
+
+stop_server() None[source]
+

This method must be re-implemented to stop and tear down anything that has been setup +in start_server(). This method should close all connections to clients that have +been established since the adapter has been started.

+
+

Note

+

This method may be called multiple times over the lifetime of the Adapter, so it is +important to make sure that this does not cause problems.

+
+
+ +
+ +
+
+class lewis.core.adapters.AdapterCollection(*args: Adapter)[source]
+

Bases: object

+

A container to manage the adapters of a device

+

This container is designed to keep all adapters that expose a device in one place and interact +with them in a uniform way.

+

Adapters can be passed as arguments upon construction or added later on using +add_adapter() (and removed using remove_adapter()). The available protocols can be +queried using the protocols() property.

+

Each adapter can be started and stopped separately by supplying protocol names to +connect() and disconnect(), both methods accept an arbitrary number of arguments, +so that any subset of the stored protocols can be handled at any time. Supplying no protocol +names at all will start/stop all adapters. These semantics also apply for is_connected() +and documentation.

+

This class also makes sure that all adapters use the same Lock for device interaction.

+
+
Parameters:
+

args – List of adapters to add to the container

+
+
+
+
+add_adapter(adapter: Adapter) None[source]
+

Adds the supplied adapter to the container but raises a RuntimeError if there’s +already an adapter registered for the same protocol.

+
+
Parameters:
+

adapter – Adapter to add to the container

+
+
+
+ +
+
+configuration(*args: str) dict[str | None, dict[str, Any]][source]
+

Returns a dictionary that contains the options for the specified adapter. The dictionary +keys are the adapter protocols.

+
+
Parameters:
+

args – List of protocols for which to list options, empty for all adapters.

+
+
Returns:
+

Dict of protocol: option-dict pairs.

+
+
+
+ +
+
+connect(*args: str) None[source]
+

This method starts an adapter for each specified protocol in a separate thread, if the +adapter is not already running.

+
+
Parameters:
+

args – List of protocols for which to start adapters or empty for all.

+
+
+
+ +
+
+property device_lock: allocate_lock
+

This lock is passed to each adapter when it’s started. It’s supposed to be used to ensure +that the device is only accessed from one thread at a time, for example during network IO. +Simulation uses this lock to block the device during the +simulation cycle calculations.

+
+ +
+
+disconnect(*args: str) None[source]
+

Stops all adapters for the specified protocols. The method waits for each adapter thread +to join, so it might hang if the thread is not terminating correctly.

+
+
Parameters:
+

args – List of protocols for which to stop adapters or empty for all.

+
+
+
+ +
+
+documentation(*args: str) str[source]
+

Returns the concatenated documentation for the adapters specified by the supplied +protocols or all of them if no arguments are provided.

+
+
Parameters:
+

args – List of protocols for which to get documentation or empty for all.

+
+
Returns:
+

Documentation for all selected adapters.

+
+
+
+ +
+
+is_connected(*args: str) bool | dict[str | None, bool][source]
+

If only one protocol is supplied, a single bool is returned with the connection status. +Otherwise, this method returns a dictionary of adapter connection statuses for the supplied +protocols. If no protocols are supplied, all adapter statuses are returned.

+
+
Parameters:
+

args – List of protocols for which to start adapters or empty for all.

+
+
Returns:
+

Boolean for single adapter or dict of statuses for multiple.

+
+
+
+ +
+
+property protocols: list[str]
+

List of protocols for which adapters are registered.

+
+ +
+
+remove_adapter(protocol: str) None[source]
+

Tries to remove the adapter for the specified protocol, raises a RuntimeError if there +is no adapter registered for that particular protocol.

+
+
Parameters:
+

protocol – Protocol to remove from container

+
+
+
+ +
+
+set_device(new_device: DeviceBase) None[source]
+

Bind the new device to all interfaces managed by the adapters in the collection.

+
+ +
+ +
+
+class lewis.core.adapters.NoLock[source]
+

Bases: object

+

A dummy context manager that raises a RuntimeError when it’s used. This makes it easier to +detect cases where an Adapter has not received the proper lock-object to make sure +that device/interface access is synchronous.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.approaches.html b/generated/lewis.core.approaches.html new file mode 100644 index 00000000..0b5a78f0 --- /dev/null +++ b/generated/lewis.core.approaches.html @@ -0,0 +1,200 @@ + + + + + + + + + lewis.core.approaches — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.approaches

+

Defines functions that model typical behavior, such as a value approaching a target linearly at +a certain rate.

+

Members

+ + + + + + +

linear

This function returns the new value after moving towards target at the given speed constantly for the time dt.

+
+
+lewis.core.approaches.linear(current: float, target: float, rate: float, dt: float)[source]
+

This function returns the new value after moving towards +target at the given speed constantly for the time dt.

+

If for example the current position is 10 and the target is -20, +the returned value will be less than 10 if rate and dt are greater +than 0:

+
new_pos = linear(10, -20, 10, 0.1)  # new_pos = 9
+
+
+

The function makes sure that the returned value never overshoots:

+
new_pos = linear(10, -20, 10, 100)  # new_pos = -20
+
+
+
+
Parameters:
+
    +
  • current – The current value of the variable to be changed.

  • +
  • target – The target value to approach.

  • +
  • rate – The rate at which the parameter should move towards target.

  • +
  • dt – The time for which to calculate the change.

  • +
+
+
Returns:
+

The new variable value.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.control_client.html b/generated/lewis.core.control_client.html new file mode 100644 index 00000000..746db44c --- /dev/null +++ b/generated/lewis.core.control_client.html @@ -0,0 +1,299 @@ + + + + + + + + + lewis.core.control_client — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.control_client

+

This module provides client code for objects exposed via JSON-RPC over ZMQ.

+
+

See also

+

The server-part for these client classes is defined +in the module control_server.

+
+

Members

+ + + + + + + + + + + + + + + +

ControlClient

This class provides an interface to a ControlServer instance on the server side.

ObjectProxy

This class serves as a base class for dynamically created classes on the client side that represent server-side objects.

ProtocolException

An exception type for exceptions related to the transport protocol, i.e. malformed requests etc.

RemoteException

This exception type replaces exceptions that are raised on the server, but unknown (i.e. not in the exceptions-module) on the client side.

+
+
+class lewis.core.control_client.ControlClient(host='127.0.0.1', port='10000', timeout=3000)[source]
+

Bases: object

+

This class provides an interface to a ControlServer instance on +the server side. Proxies to exposed objects can be obtained either +directly via get_object or, in case the server exposes a collection +of objects at the top level, a dictionary of named objects can be +obtained via get_object_collection.

+

If a timeout is supplied, all underlying network operations time out +after the specified time (in milliseconds), for no timeout specify None.

+
+
Parameters:
+
    +
  • host – Host the control server is running on.

  • +
  • port – Port on which the control server is listening.

  • +
  • timeout – Timeout in milliseconds for ZMQ operations.

  • +
+
+
+
+
+get_object_collection(object_name='')[source]
+

If the remote end exposes a collection of objects under the supplied object name (empty +for top level), this method returns a dictionary of these objects stored under their +names on the server.

+

This function performs n + 1 calls to the server, where n is the number of objects.

+
+
Parameters:
+

object_name – Object name on the server. This is required if the object collection +is not the top level object.

+
+
+
+ +
+
+json_rpc(method, *args)[source]
+

This method takes a ZMQ REQ-socket and submits a JSON-object containing +the RPC (JSON-RPC 2.0 format) to the supplied method with the supplied arguments. +Then it waits for a reply from the server and blocks until it has received +a JSON-response. The method returns the response and the id it used to tag +the original request, which is a random UUID (uuid.uuid4).

+
+
Parameters:
+
    +
  • method – Method to call on remote.

  • +
  • args – Arguments to method call.

  • +
+
+
Returns:
+

JSON result and request id.

+
+
+
+ +
+ +
+
+class lewis.core.control_client.ObjectProxy(connection, members, prefix='')[source]
+

Bases: object

+

This class serves as a base class for dynamically created classes on the +client side that represent server-side objects. Upon initialization, +this class takes the supplied methods and installs appropriate proxy methods +or properties into the object and class respectively. Because of that +class manipulation, this class must never be used directly. +Instead, it should be used as a base-class for dynamically created types +that mirror types on the server, like this:

+
proxy = type("SomeClassName", (ObjectProxy,), {})(connection, methods, prefix)
+
+
+

There is however, the class ControlClient, which automates all that +and provides objects that are ready to use.

+

Exceptions on the server are propagated to the client. If the exception is not part +of the exceptions-module (builtins for Python 3), a RemoteException is raised instead +which contains information about the server side exception.

+

All RPC method names are prefixed with the supplied prefix, which is usually the +object name on the server plus a dot.

+
+
Parameters:
+
    +
  • connection – ControlClient-object for remote calls.

  • +
  • members – List of strings to generate methods and properties.

  • +
  • prefix – Usually object name on the server plus dot.

  • +
+
+
+
+ +
+
+exception lewis.core.control_client.ProtocolException[source]
+

Bases: Exception

+

An exception type for exceptions related to the transport protocol, i.e. +malformed requests etc.

+
+ +
+
+exception lewis.core.control_client.RemoteException(exception_type, message)[source]
+

Bases: Exception

+

This exception type replaces exceptions that are raised on the server, +but unknown (i.e. not in the exceptions-module) on the client side. +To retain as much information as possible, the exception type on the server and +the message are stored.

+
+
Parameters:
+
    +
  • exception_type – Type of the exception on the server side.

  • +
  • message – Exception message on the server side.

  • +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.control_server.html b/generated/lewis.core.control_server.html new file mode 100644 index 00000000..04cb3497 --- /dev/null +++ b/generated/lewis.core.control_server.html @@ -0,0 +1,355 @@ + + + + + + + + + lewis.core.control_server — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.control_server

+

This module contains classes to expose objects via a JSON-RPC over ZMQ server. Lewis uses +this infrastructure in Simulation.

+
+

See also

+

Client classes for the service defined in this module can be found in +control_client.

+
+

Members

+ + + + + + + + + + + + +

ControlServer

This server opens a ZMQ REP-socket at the given host and port when start_server is called.

ExposedObject

ExposedObject is a class that makes it easy to expose an object via the JSONRPCResponseManager from the json-rpc package, where it can serve as a dispatcher.

ExposedObjectCollection

This class helps expose a number of objects (plain or RPCObject) by exposing the methods of each object as

+
+
+class lewis.core.control_server.ControlServer(object_map, connection_string)[source]
+

Bases: object

+

This server opens a ZMQ REP-socket at the given host and port when start_server +is called.

+

The server constructs an ExposedObjectCollection from the supplied +name: object-dictionary and uses that as a handler for JSON-RPC requests. If it is an +instance of ExposedObject, that is used directly.

+

Each time process is called, the server tries to get request data and responds to that. +If there is no data, the method does nothing.

+

Please note that this RPC-service comes without any security, authentication, etc. +Only use it to expose objects on a trusted network and be aware that anyone on that +network can access the exposed objects without any restrictions.

+
+
Parameters:
+
    +
  • object_map – Dictionary with name: object-pairs to construct an +ExposedObjectCollection or ExposedObject

  • +
  • connection_string – String with host:port pair for binding control server.

  • +
+
+
+
+
+property exposed_object
+

The exposed object. This is a read only property.

+
+ +
+
+property is_running
+

This property is True if the server is running.

+
+ +
+
+process(blocking=False) None[source]
+

Each time this method is called, the socket tries to retrieve data and passes +it to the JSONRPCResponseManager, which in turn passes the RPC to the +ExposedObjectCollection.

+

In case no data are available, the method does nothing. This behavior is required for +Lewis where everything is running in one thread. The central loop can call process +at some point to process remote calls, so the RPC-server does not introduce its own +infinite processing loop.

+

If the server has not been started yet (via start_server()), a RuntimeError +is raised.

+
+
Parameters:
+

blocking – If True, this function will block until it has received data or a timeout +is triggered. Default is False to preserve behavior of prior versions.

+
+
+
+ +
+
+start_server() None[source]
+

Binds the server to the configured host and port and starts listening.

+
+ +
+ +
+
+class lewis.core.control_server.ExposedObject(obj, members=None, exclude=None, exclude_inherited=False, lock=None)[source]
+

Bases: object

+

ExposedObject is a class that makes it easy to expose an object via the +JSONRPCResponseManager from the json-rpc package, where it can serve as a dispatcher. +For this purpose it exposes a read-only dict-like interface.

+

The basic problem solved by this wrapper is that plain data members of an object are not +really captured well by the RPC-approach, where a client performs function calls on a +remote machine and gets the result back.

+

The supplied object is inspected using dir(object) and all entries that do not start +with a _ are exposed in a way that depends on whether the corresponding member +is a method or a property (either in the Python-sense or the general OO-sense). Methods +are stored directly, and stored in an internal dict where the method name is the key and +the callable method object is the value. For properties, a getter- and a setter function +are generated, which are then stored in the same dict. The names of these methods for +a property called a are a:get and a:set. The separator has been chosen to be +colon because it can’t be part of a valid Python identifier.

+

If the second argument is not empty, it is interpreted to be the list of members +to expose and only those are actually exposed. This can be used to explicitly expose +members of an object that start with an underscore. If all but one or two members +should be exposed, it’s also possible to use the exclude-argument to explicitly +exclude a few members. Both parameters can be used in combination, the exclude-list +takes precedence.

+

In certain situations it is desirable to acquire a lock before accessing the exposed object, +for example when multiple threads are accessing it on the server side. For this purpose, +the lock-parameter can be used. If it is not None, the exposed methods are wrapped +in a function that acquires the lock before accessing obj, and releases it afterwards.

+
+
Parameters:
+
    +
  • obj – The object to expose.

  • +
  • members – This list of methods will be exposed. (defaults to all public members)

  • +
  • exclude – Members in this list will not be exposed.

  • +
  • exclude_inherited – Should inherited members be excluded? (defaults to False)

  • +
  • lockthreading.Lock that is used when accessing obj.

  • +
+
+
+
+
+get_api()[source]
+

This method returns the class name and a list of exposed methods. +It is exposed to RPC-clients by an instance of ExposedObjectCollection.

+
+
Returns:
+

A dictionary describing the exposed API (consisting of a class name and methods).

+
+
+
+ +
+ +
+
+class lewis.core.control_server.ExposedObjectCollection(named_objects)[source]
+

Bases: ExposedObject

+

This class helps expose a number of objects (plain or RPCObject) by +exposing the methods of each object as

+
name.method
+
+
+

Furthermore it exposes each object’s API as a method with the following name:

+
name: api
+
+
+

A list of exposed objects can be obtained by calling the following method from the client:

+
:objects
+
+
+
+
Parameters:
+

named_objects – Dictionary of of name: object pairs.

+
+
+
+
+add_object(obj, name) None[source]
+

Adds the supplied object to the collection under the supplied name. If the name is already +in use, a RuntimeError is raised. If the object is not an instance of +ExposedObject, the method automatically constructs one.

+
+
Parameters:
+
    +
  • obj – Object to add to the collection.

  • +
  • name – Name of the exposed object.

  • +
+
+
+
+ +
+
+get_objects()[source]
+

Returns the names of the exposed objects.

+
+ +
+
+remove_object(name) None[source]
+

Remove the object exposed under that name. If no object is registered under the supplied +name, a RuntimeError is raised.

+
+
Parameters:
+

name – Name of object to be removed.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.devices.html b/generated/lewis.core.devices.html new file mode 100644 index 00000000..6a9dfb39 --- /dev/null +++ b/generated/lewis.core.devices.html @@ -0,0 +1,436 @@ + + + + + + + + + lewis.core.devices — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.devices

+

This module contains DeviceBase as a base class for other device classes and +infrastructure that can import devices from a module (DeviceRegistry). The latter also +produces factory-like objects that create device instances and interfaces based on setups +(DeviceBuilder).

+

Members

+ + + + + + + + + + + + + + + + + + + + + +

DeviceBase

This class is a common base for Device and StateMachineDevice.

DeviceBuilder

This class takes a module object (for example imported via importlib.import_module or via the DeviceRegistry) and inspects it so that it's possible to construct devices and interfaces.

DeviceRegistry

This class takes the name of a module and constructs a DeviceBuilder from each sub-module.

InterfaceBase

This class is a common base for protocol specific interfaces that are exposed by a subclass of Adapter.

is_device

Returns True if obj is a device type (derived from DeviceBase), but not defined in lewis.core.devices or lewis.devices.

is_interface

Returns True if obj is an interface (derived from InterfaceBase), but not defined in lewis.adapters, where concrete interfaces for protocols are defined.

+
+
+class lewis.core.devices.DeviceBase[source]
+

Bases: object

+

This class is a common base for Device and +StateMachineDevice. It is mainly used in the device +discovery process.

+
+ +
+
+class lewis.core.devices.DeviceBuilder(module)[source]
+

Bases: object

+

This class takes a module object (for example imported via importlib.import_module or via the +DeviceRegistry) and inspects it so that it’s possible to construct devices and +interfaces.

+

In order for the class to work properly, the device module has to adhere to a few rules. +Device types, which means classes inheriting from DeviceBase, are imported directly +from the device module, equivalent to the following:

+
from device_name import SimulatedDeviceType
+
+
+

If SimulatedDeviceType is defined in the __init__.py, there’s nothing else to do. If +the device class is defined elsewhere, it must be imported in the __init__.py file as +written above. If there is only one device type (which is probably the most common case), it is +assumed to be default device type.

+

Setups are discovered in two locations, the first one is a dict called setups in the device +module, which must contain setup names as keys and as values again a dict. This inner dict has +one mandatory key called device_type and one optional key parameters containing the +constructor arguments for the specified device type:

+
setups = dict(
+    broken=dict(
+        device_type=SimulatedDeviceType,
+        parameters=dict(
+            override_initial_state="error",
+            override_initial_data=dict(target=-10, position=-20.0),
+        ),
+    )
+)
+
+
+

The other location is a sub-package called setups, which should in turn contain modules. Each +module must contain a variable device_type and a variable parameters which are +analogous to the keys in the dict described above. This allows for more complex setups which +define additional classes and so on.

+

The default setup is special, it is used when no setup is supplied to +create_device(). If the setup default is not defined, one is created with the default +device type. This has two consequences, no setups need to be defined for very simple devices, +but if multiple device types are defined, a default setup must be defined.

+

A setup can be supplied to the create_device().

+

Lastly, the builder tries to discover device interfaces, which are currently classes based on +lewis.adapters.InterfaceBase. These are looked for in the module and in a sub-package +called interfaces (which should contain modules with adapters like the setups package).

+

Each interface has a protocol, if a protocol occurs more than once in a device module, +a RuntimeError is raised.

+
+
+create_device(setup=None)[source]
+

Creates a device object according to the provided setup. If no setup is provided, +the default setup is used. If the setup can’t be found, a LewisException is raised. +This can also happen if the device type specified in the setup is invalid.

+
+
Parameters:
+

setup – Name of the setup from which to create device.

+
+
Returns:
+

Device object initialized according to the provided setup.

+
+
+
+ +
+
+create_interface(protocol=None, *args, **kwargs)[source]
+

Returns an interface that implements the provided protocol. If the protocol is not +known, a LewisException is raised. All additional arguments are forwarded +to the interface constructor (see Adapter for details).

+
+
Parameters:
+
    +
  • protocol – Protocol which the interface must implement.

  • +
  • args – Positional arguments that are passed on to the interface.

  • +
  • kwargs – Keyword arguments that are passed on to the interface.

  • +
+
+
Returns:
+

Instance of the interface type.

+
+
+
+ +
+
+property default_device_type
+

If the module only defines one device type, it is the default device type. It is used +whenever a setup does not provide a device_type.

+
+ +
+
+property default_protocol
+

In case only one protocol exists for the device, this is the default protocol.

+
+ +
+
+property device_types
+

This property contains a dict of all device types in the device module. The keys are +type names, the values are the types themselves.

+
+ +
+
+property interfaces
+

This property contains a map with protocols as keys and interface types as values. +The types are imported from the interfaces sub-module and from the device module +itself. If two interfaces with the same protocol are discovered, a RuntimeError is raiesed.

+
+ +
+
+property name
+

The name of the device, which is also the name of the device module.

+
+ +
+
+property protocols
+

All available protocols for this device.

+
+ +
+
+property setups
+

A map with all available setups. Setups are imported from the setups dictionary +in a device module and from the setups sub-module. If no default-setup exists, +one is created using the default_device_type. If there are several device types in +the module, the default setup must be provided explicitly.

+
+ +
+ +
+
+class lewis.core.devices.DeviceRegistry(device_module)[source]
+

Bases: object

+

This class takes the name of a module and constructs a DeviceBuilder from +each sub-module. The available devices can be queried and a DeviceBuilder can be +obtained for each device:

+
from lewis.core.devices import DeviceRegistry
+
+registry = DeviceRegistry("lewis.devices")
+chopper_builder = registry.device_builder("chopper")
+
+# construct device, interface, ...
+
+
+

If the module can not be imported, a LewisException is raised.

+
+
Parameters:
+

device_module – Name of device module from which devices are loaded.

+
+
+
+
+device_builder(name)[source]
+

Returns a DeviceBuilder instance that can be used to create device objects +based on setups, as well as device interfaces. If the device name is not stored +in the internal map, a LewisException is raised.

+

Each DeviceBuilder has a framework_version-member, which specifies the version +of Lewis the device has been written for. If the version does not match the current +framework version, it is only possible to obtain those device builders calling the +method with strict_versions set to False, otherwise a +LewisException is raised. A warning message is logged +in all cases. If framework_version is None (e.g. not specified at all), it +is accepted unless strict_versions is set to True.

+
+
Parameters:
+

name – Name of the device.

+
+
Returns:
+

DeviceBuilder-object for requested device.

+
+
+
+ +
+
+property devices
+

All available device names.

+
+ +
+ +
+
+class lewis.core.devices.InterfaceBase[source]
+

Bases: object

+

This class is a common base for protocol specific interfaces that are exposed by a subclass of +Adapter. This base class is not meant to be used directly in +a device package - this is what the interfaces in lewis.adapters are for.

+

There is a 1:1 correspondence between device and interface, where the interface holds a +reference to the device. It can be changed through the device-property.

+
+
+property adapter: NoReturn
+

Adapter type that is required to process and expose interfaces of this type. Must be +implemented in subclasses.

+
+ +
+
+property device
+

The device this interface is bound to. When a new device is set, _bind_device() is +called, where the interface can react to the device change if necessary.

+
+ +
+ +
+
+lewis.core.devices.is_device(obj)[source]
+

Returns True if obj is a device type (derived from DeviceBase), but not defined in +lewis.core.devices or lewis.devices.

+
+
Parameters:
+

obj – Object to test.

+
+
Returns:
+

True if obj is a device type.

+
+
+
+ +
+
+lewis.core.devices.is_interface(obj)[source]
+

Returns True if obj is an interface (derived from InterfaceBase), but not defined in +lewis.adapters, where concrete interfaces for protocols are defined.

+
+
Parameters:
+

obj – Object to test.

+
+
Returns:
+

True if obj is an interface type.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.exceptions.html b/generated/lewis.core.exceptions.html new file mode 100644 index 00000000..b0337597 --- /dev/null +++ b/generated/lewis.core.exceptions.html @@ -0,0 +1,202 @@ + + + + + + + + + lewis.core.exceptions — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.exceptions

+

Defines exception types specific to lewis. The main intention of these exception types is +that they can be caught and meaningful messages can be displayed to the user.

+

Members

+ + + + + + + + + + + + +

AccessViolationException

This exception can be raised in situation where the performed action (accessing a property or similar) is not allowed.

LewisException

This exception type is used to distinguish exceptions that are expected from unexpected ones.

LimitViolationException

An exception that can be raised in a device to indicate a limit violation.

+
+
+exception lewis.core.exceptions.AccessViolationException[source]
+

Bases: Exception

+

This exception can be raised in situation where the performed action (accessing a property or +similar) is not allowed. An example is BoundPV for enforcing +read-only PVs.

+
+ +
+
+exception lewis.core.exceptions.LewisException[source]
+

Bases: Exception

+

This exception type is used to distinguish exceptions that are expected +from unexpected ones. This enables better error handling and more importantly +better presentation of errors to the users.

+
+ +
+
+exception lewis.core.exceptions.LimitViolationException[source]
+

Bases: Exception

+

An exception that can be raised in a device to indicate a limit violation. It is for example +raised by the check_limits.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.html b/generated/lewis.core.html new file mode 100644 index 00000000..12843742 --- /dev/null +++ b/generated/lewis.core.html @@ -0,0 +1,197 @@ + + + + + + + + + lewis.core — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core

+

Submodules

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

adapters

This module contains Adapter, which serves as a base class for concrete adapter implementations in lewis.adapters.

approaches

Defines functions that model typical behavior, such as a value approaching a target linearly at a certain rate.

control_client

This module provides client code for objects exposed via JSON-RPC over ZMQ.

control_server

This module contains classes to expose objects via a JSON-RPC over ZMQ server.

devices

This module contains DeviceBase as a base class for other device classes and infrastructure that can import devices from a module (DeviceRegistry).

exceptions

Defines exception types specific to lewis.

logging

This module contains everything logging-related in Lewis.

processor

This module defines two classes related to one of lewis' essential concepts, namely the cycle-based approach.

simulation

A Simulation combines a Device and its interface (derived from an Adapter).

statemachine

The statemachine module contains one of lewis' central parts, the cycle-based StateMachine.

utils

This module contains some useful helper classes and functions that are not specific to a certain module contained in the Core API.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.logging.html b/generated/lewis.core.logging.html new file mode 100644 index 00000000..55a14faf --- /dev/null +++ b/generated/lewis.core.logging.html @@ -0,0 +1,243 @@ + + + + + + + + + lewis.core.logging — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.logging

+

This module contains everything logging-related in Lewis. There is one relevant +module level variable that defines the default log format, default_log_format.

+

All places that use logging in Lewis prefix their logger names with lewis so +that you can easily control the logs caused by Lewis if you use it as a library. +Lewis uses the default settings of the logging module, so if you use Lewis as a +library and do not have any logging enabled, messages that are more severe than WARNING +are printed to stdout. For details on how to disable that behavior, change levels +for certain loggers and so on, please refer to the documentation +of the standard logging library.

+

Members

+ + + + + + + + + +

HasLog

has_log

This is a decorator to add logging functionality to a class or function.

+
+
+class lewis.core.logging.HasLog(*args, **kwargs)[source]
+

Bases: Protocol

+
+ +
+
+lewis.core.logging.has_log(target: Type[T]) Type[T][source]
+
+lewis.core.logging.has_log(target: Callable[[P], T]) Callable[[P], T]
+

This is a decorator to add logging functionality to a class or function.

+

Applying this decorator to a class or function will add two new members:

+
+
    +
  • log is an instance of logging.Logger. The name of the logger is +set to lewis.Foo for a class named Foo.

  • +
  • _set_logging_context is a method that modifies the name of the logger +when the class is used in a certain context.

  • +
+
+

If context is a string, that string is directly inserted between lewis +and Foo, so that the logger name would be lewis.bar.Foo if context +was 'bar'. The more common case is probably context being an object of +some class, in which case the class name is inserted. If context is an object +of type Bar, the logger name of Foo would be lewis.Bar.Foo.

+

To provide a more concrete example in terms of Lewis, this is used for the state +machine logger in a device. So the logs of the state machine belonging to a certain +device appear in the log as originating from lewis.DeviceName.StateMachine, which +makes it possible to distinguish between messages from different state machines.

+

Example for how to use logging in a class:

+
from lewis.core.logging import has_log
+
+
+@has_log
+class Foo(Base):
+    def __init__(self):
+        super(Foo, self).__init__()
+
+    def bar(self, baz):
+        self.log.debug("Called bar with parameter baz=%s", baz)
+        return baz is not None
+
+
+

It works similarly for free functions, although the actual logging calls are a bit different:

+
from lewis.core.logging import has_log
+
+
+@has_log
+def foo(bar):
+    foo.log.info("Called with argument bar=%s", bar)
+    return bar
+
+
+

The name of the logger is lewis.foo, the context could also be modified by calling +foo._set_logging_context.

+
+
Parameters:
+

target – Target to decorate with logging functionality.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.processor.html b/generated/lewis.core.processor.html new file mode 100644 index 00000000..c03f4e92 --- /dev/null +++ b/generated/lewis.core.processor.html @@ -0,0 +1,211 @@ + + + + + + + + + lewis.core.processor — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.processor

+

This module defines two classes related to one of lewis’ essential concepts, namely +the cycle-based approach. CanProcess and CanProcessComposite implement the +composite design pattern so that it’s possible to form a tree of objects which can perform +calculations based on an elapsed time Δt.

+

Members

+ + + + + + + + + +

CanProcess

The CanProcess class is meant as a base for all things that are able to process on the basis of a time delta (dt).

CanProcessComposite

This subclass of CanProcess is a convenient way of collecting multiple items that implement the CanProcess interface.

+
+
+class lewis.core.processor.CanProcess[source]
+

Bases: object

+

The CanProcess class is meant as a base for all things that +are able to process on the basis of a time delta (dt).

+

The base implementation does nothing.

+

There are three methods that can be implemented by sub-classes and are called in the +process-method in this order:

+
+
    +
  1. doBeforeProcess

  2. +
  3. doProcess

  4. +
  5. doAfterProcess

  6. +
+
+

The doBefore- and doAfterProcess methods are only called if a doProcess-method exists.

+
+ +
+
+class lewis.core.processor.CanProcessComposite(iterable=())[source]
+

Bases: CanProcess

+

This subclass of CanProcess is a convenient way of collecting +multiple items that implement the CanProcess interface.

+

Items can be added to the composite like this:

+
composite = CanProcessComposite()
+composite.add_processor(item_that_implements_CanProcess)
+
+
+

The process-method calls the process-method of each contained +item. Specific things that have to be done before or after the +containing items are processed can be implemented in the doBefore- +and doAfterProcess methods.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.simulation.html b/generated/lewis.core.simulation.html new file mode 100644 index 00000000..bc81f090 --- /dev/null +++ b/generated/lewis.core.simulation.html @@ -0,0 +1,392 @@ + + + + + + + + + lewis.core.simulation — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.simulation

+

A Simulation combines a Device and its interface (derived from +an Adapter).

+

Members

+ + + + + + + + + +

Simulation

The Simulation class controls certain aspects of a device simulation, the most important one being time.

SimulationFactory

This class is used to create Simulation-objects according to a certain set of parameters, such as device, setup and protocol.

+
+
+class lewis.core.simulation.Simulation(device, adapters=(), device_builder=None, control_server=None)[source]
+

Bases: object

+

The Simulation class controls certain aspects of a device simulation, +the most important one being time.

+

Once start() is called, the process-method of the device +is called in regular intervals. The time between these calls is +influenced by the cycle_delay property. Because of the way some +network protocols work, the actual processing time can be +longer or shorter, so cycle_delay should be seen as a guideline +rather than a guaranteed parameter.

+

In the simplest case, the actual time-delta between two cycles +is passed to the simulated device so that it can update its internal +state according to the elapsed time. It is however possible to set +a simulation speed, which serves as a multiplier for this time. +If the speed is set to 2 and 0.1 seconds pass between two cycles, +the simulation is asked to simulate 0.2 seconds, and so on. Speed 0 +effectively stops all time dependent calculations in the +simulated device.

+

Another possibility to pause the simulation is the pause-method. After +calling it, all processing in the device is suspended, while the communication +adapters continue to work. This can be used to simulate that a device is “hanging”. +The simulation can be continued using the resume-method.

+

A number of status properties provide information about the simulation. +The total uptime (in actually elapsed time) can be obtained through the +uptime-property, whereas the runtime-property contains the simulated time. +The cycles-property indicates the total number of simulation cycles, which +does not increase when the simulation is paused.

+

Finally, the simulation can be stopped entirely with the stop-method.

+

All functionality except for the start-method can be made available to remote +computers via a ControlServer-instance. The way to expose device and simulation +is to pass a ‘host:port’-string as the control_server argument, +which will construct the control server. Simulation will try to start the +control server using the start_server method.

+
+
Parameters:
+
    +
  • device – The simulated device.

  • +
  • adapters – Adapters which expose the simulated device.

  • +
  • device_builderDeviceBuilder instance to enable setup- +switching at runtime.

  • +
  • control_server – ‘host:port’-string to construct control server or None.

  • +
+
+
+
+
+property control_server
+

ControlServer-instance that exposes the object to remote machines. Can only +be set before start has been called or on a running simulation if no +control server was previously present. If the server is not running, it will be started +after it has been set.

+
+ +
+
+property cycle_delay
+

Desired time between simulation cycles, this can not be negative. +Use 0 for highest possible processing rate.

+
+ +
+
+property cycles
+

Simulation cycles processed since start has been called.

+
+ +
+
+property is_paused
+

True if the simulation is paused (implies that the simulation has been started).

+
+ +
+
+property is_started
+

This property is true if the simulation has been started.

+
+ +
+
+pause() None[source]
+

Pause the simulation. Can only be called after start has been called.

+
+ +
+
+resume() None[source]
+

Resume a paused simulation. Can only be called after start +and pause have been called.

+
+ +
+
+property runtime
+

The accumulated simulation time. Whenever speed is different from 1, this +progresses at a different rate than uptime.

+
+ +
+
+set_device_parameters(parameters) None[source]
+

Set multiple parameters of the simulated device “simultaneously”. The passed +parameter is assumed to be device parameter/value dict. +The method only allows to set existing attributes. If there are invalid +attribute names, the attributes are not updated, instead a RuntimeError +is raised. The same happens if any of the parameters are methods, which +can not be updated with this mechanisms.

+
+
Parameters:
+

parameters – Dict of device attribute/values to update the device.

+
+
+
+ +
+
+property setups
+

A list of setups that are available. Use switch_setup() to +change the setup.

+
+ +
+
+property speed
+

Simulation speed. Actual elapsed time is multiplied with this property +to determine simulated time. Values greater than 1 increase the simulation +speed, values between 1 and 0 decrease it. A speed of 0 effectively pauses +the simulation.

+
+ +
+
+start() None[source]
+

Starts the simulation.

+
+ +
+
+stop() None[source]
+

Stops the simulation entirely.

+
+ +
+
+switch_setup(new_setup) None[source]
+

This method switches the setup, which means that it replaces the currently +simulated device with a new device, as defined by the setup.

+

If any error occurs during setup switching it is logged and re-raised.

+
+
Parameters:
+

new_setup – Name of the new setup to load.

+
+
+
+ +
+
+property uptime
+

Elapsed time in seconds since the simulation has been started.

+
+ +
+ +
+
+class lewis.core.simulation.SimulationFactory(devices_package)[source]
+

Bases: object

+

This class is used to create Simulation-objects according to a certain +set of parameters, such as device, setup and protocol. To create a simulation, it needs to +know where devices are stored:

+
factory = SimulationFactory("lewis.devices")
+
+
+

The actual creation happens via the create()-method:

+
simulation = factory.create("device_name", protocol="protocol")
+
+
+

The simulation can then be started and stopped as desired.

+
+

Warning

+

This class is meant for internal use at the moment and may change frequently.

+
+
+
+create(device, setup=None, protocols=None, control_server=None)[source]
+

Creates a Simulation according to the supplied parameters.

+
+
Parameters:
+
    +
  • device – Name of device.

  • +
  • setup – Name of the setup for device creation.

  • +
  • protocols – Dictionary where each key is assigned a dictionary with options for the +corresponding Adapter. For available +protocols, see get_protocols().

  • +
  • control_server – String to construct a control server (host:port).

  • +
+
+
Returns:
+

Simulation object according to input parameters.

+
+
+
+ +
+
+property devices
+

Names of available devices.

+
+ +
+
+get_protocols(device)[source]
+

Returns a list of available protocols for the specified device.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.statemachine.html b/generated/lewis.core.statemachine.html new file mode 100644 index 00000000..1f2e2d4f --- /dev/null +++ b/generated/lewis.core.statemachine.html @@ -0,0 +1,410 @@ + + + + + + + + + lewis.core.statemachine — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.statemachine

+

The statemachine module contains one of lewis’ central parts, the cycle-based +StateMachine. The module also contains classes that make it easier to define the +state machine (State, Transition). Despite its central nature, it’s unlikely +to be used directly in client code for device simulations - these should be based on +StateMachineDevice, which provides a more convenient interface for that purpose.

+

Members

+ + + + + + + + + + + + + + + + + + +

HasContext

Mixin to provide a Context.

State

StateMachine state handler base class.

StateMachine

Cycle based state machine.

StateMachineException

Classes in this module should only raise this type of Exception.

Transition

StateMachine transition condition base class.

+
+
+class lewis.core.statemachine.HasContext[source]
+

Bases: object

+

Mixin to provide a Context.

+

Creates a _context member variable that can be assigned with set_context().

+

Any state handler or transition callable that derives from this mixin will +receive a context from its StateMachine upon initialization (assuming the +StateMachine was provided with a context itself).

+
+
+set_context(new_context) None[source]
+

Assigns the new context to the member variable _context.

+
+ +
+ +
+
+class lewis.core.statemachine.State[source]
+

Bases: HasContext

+

StateMachine state handler base class.

+

Provides a way to implement StateMachine event handling behaviour using an +object-oriented interface. Once the StateMachine is configured to do so, it +will automatically invoke the events in this class when appropriate.

+

To use this class, create a derived class and override any events that need +custom behaviour. Device context is provided via HasContext mixin.

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+
+on_exit(dt) None[source]
+

Handle exit event. Raised once, when this state is exited.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.core.statemachine.StateMachine(cfg, context=None)[source]
+

Bases: CanProcess

+

Cycle based state machine.

+
+
Parameters:
+
    +
  • cfg – dict which contains state machine configuration.

  • +
  • context – object which is assigned to State and Transition objects as their _context.

  • +
+
+
+

The configuration dict may contain the following keys:

+
+
    +
  • initial: Name of the initial state of this machine

  • +
  • states: [optional] Dict of custom state handlers

  • +
  • transitions: [optional] Dict of transitions in this state machine.

  • +
+
+

State handlers may be given as a dict, list or State class:

+
+
    +
  • dict: May contain keys ‘on_entry’, ‘in_state’ and ‘on_exit’.

  • +
  • list: May contain up to 3 entries, above events in that order.

  • +
  • class: Should be an instance of a class that derives from State.

  • +
+
+

In case of handlers being provided as a dict or a list, values should be callable +and may take a single parameter: the Delta T since the last cycle.

+

Transitions should be provided as a dict where:

+
+
    +
  • Each key is a tuple of two values, the FROM and TO states respectively.

  • +
  • Each value is a callable transition condition that return True or False.

  • +
+
+

Transition conditions are called once per cycle when in the FROM state. If one of +the transition conditions returns True, the transition is executed that cycle. The +remaining conditions aren’t called.

+

Consider using an OrderedDict if order matters.

+

Only one transition may occur per cycle. Every cycle will, at the very least, +trigger an in_state event against the current state.

+
+

See also

+

See doProcess() for details.

+
+
+
+bind_handlers_by_name(instance, override=False, prefix=None) None[source]
+

Auto-bind state handlers based on naming convention.

+
+
Parameters:
+
    +
  • instance – Target object instance to search for handlers and bind events to.

  • +
  • override – If set to True, matching handlers will replace +previously registered handlers.

  • +
  • prefix – Dict or list of prefixes to override defaults +(keys: on_entry, in_state, on_exit)

  • +
+
+
+

This function enables automatically binding state handlers to events without having to +specify them in the constructor. When called, this function searches instance for +member functions that match the following patterns for all known states +(states mentioned in ‘states’ or ‘transitions’ dicts of cfg):

+
+
    +
  • instance._on_entry_[state]

  • +
  • instance._in_state_[state]

  • +
  • instance._on_exit_[state]

  • +
+
+

The default prefixes may be overridden using the prefix parameter. Supported keys are +‘on_entry’, ‘in_state’, and ‘on_exit’. Values should include any and +all desired underscores.

+

Matching functions are assigned as handlers to the corresponding state events, +iff no handler was previously assigned to that event.

+

If a state event already had a handler assigned (during construction or previous call +to this function), no changes are made even if a matching function is found. To force +previously assigned handlers to be overwritten, set the third parameter to True. +This may be useful to implement inheritance-like specialization using multiple +implementation classes but only one StateMachine instance.

+
+ +
+
+can(state)[source]
+

Returns true if the transition to ‘state’ is allowed from the current state.

+
+
Parameters:
+

state – State to check transition to

+
+
Returns:
+

True if state is reachable from current

+
+
+
+ +
+
+doProcess(dt) None[source]
+

Process a cycle of this state machine.

+
+
Parameters:
+

dt – Delta T. “Time” passed since last cycle, passed on to event handlers.

+
+
+

A cycle will perform at most one transition and exactly one in_state event.

+

A transition will only occur if one of the transition condition functions leaving +the current state returns True.

+
+
When a transition occurs, the following events are raised:
    +
  • on_exit_old_state()

  • +
  • on_entry_new_state()

  • +
  • in_state_new_state()

  • +
+
+
+

The first cycle after init or reset will never call transition checks and, instead, +always performs on_entry and in_state on the initial state.

+

Whether a transition occurs or not, and regardless of any other circumstances, a +cycle always ends by raising an in_state event on the current (potentially new) +state.

+
+ +
+
+reset() None[source]
+

Reset the state machine to before the first cycle. The next process() will +enter the initial state.

+
+ +
+
+property state
+

Name of the current state.

+
+ +
+ +
+
+exception lewis.core.statemachine.StateMachineException[source]
+

Bases: Exception

+

Classes in this module should only raise this type of Exception.

+
+ +
+
+class lewis.core.statemachine.Transition[source]
+

Bases: HasContext

+

StateMachine transition condition base class.

+

Provides a way to implement a transition that requires access to the device +context. The device context is provided via HasContext mixin, and can be +accessed as self._context.

+

To use this class, create a derived class and override the __call__() attribute.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.core.utils.html b/generated/lewis.core.utils.html new file mode 100644 index 00000000..de835a1c --- /dev/null +++ b/generated/lewis.core.utils.html @@ -0,0 +1,424 @@ + + + + + + + + + lewis.core.utils — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.core.utils

+

This module contains some useful helper classes and functions that are not specific to a certain +module contained in the Core API.

+

Members

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

FromOptionalDependency

This is a utility class for importing classes from a module or replacing them with dummy types if the module can not be loaded.

check_limits

This decorator helps to make sure that the parameter of a property setter (or any other method with one argument) is within certain numerical limits.

dict_strict_update

This function updates base_dict with update_dict if and only if update_dict does not contain keys that are not already in base_dict.

extract_module_name

This function tries to extract a valid module name from the basename of the supplied path.

format_doc_text

A very thin wrapper around textwrap.fill to consistently wrap documentation text for display in a command line environment.

get_members

Returns all members of an object for which the supplied predicate is true and that do not begin with __.

get_submodules

This function imports all sub-modules of the supplied module and returns a dictionary with module names as keys and the sub-module objects as values.

seconds_since

This is a small helper function that returns the elapsed seconds since start using datetime.datetime.now().

+
+
+class lewis.core.utils.FromOptionalDependency(module, exception=None)[source]
+

Bases: object

+

This is a utility class for importing classes from a module or +replacing them with dummy types if the module can not be loaded.

+

Assume module ‘a’ that does:

+
from b import C, D
+
+
+

and module ‘e’ which does:

+
from a import F
+
+
+

where ‘b’ is a hard to install dependency which is thus optional. +To still be able to do:

+
import e
+
+
+

without raising an error, for example for inspection purposes, +this class can be used as a workaround in module ‘a’:

+
C, D = FromOptionalDependency("b").do_import("C", "D")
+
+
+

which is not as pretty as the actual syntax, but at least it +can be read in a similar way. If the module ‘b’ can not be imported, +stub-types are created that are called ‘C’ and ‘D’. Everything depending +on these types will work until any of those are instantiated - in that +case an exception is raised.

+

The exception can be controlled via the exception-parameter. If it is a +string, a LewisException is constructed from it. Alternatively it can +be an instance of an exception-type. If not provided, a LewisException +with a standard message is constructed. If it is anything else, a RuntimeError +is raised.

+

Essentially, this class helps deferring ImportErrors until anything from +the module that was attempted to load is actually used.

+
+
Parameters:
+
    +
  • module – Module from that symbols should be imported.

  • +
  • exception – Text for LewisException or custom exception object.

  • +
+
+
+
+
+do_import(*names)[source]
+

Tries to import names from the module specified on initialization +of the FromOptionalDependency-object. In case an ImportError occurs, +the requested names are replaced with stub objects.

+
+
Parameters:
+

names – List of strings that are used as type names.

+
+
Returns:
+

Tuple of actual symbols or stub types with provided names. If there is only one +element in the tuple, that element is returned.

+
+
+
+ +
+ +
+
+class lewis.core.utils.check_limits(lower=None, upper=None, silent=False)[source]
+

Bases: object

+

This decorator helps to make sure that the parameter of a property setter (or any other +method with one argument) is within certain numerical limits.

+

It’s possible to set static limits using floats or ints:

+
class Foo:
+    _bar = 0
+
+    @property
+    def bar(self):
+        return self._bar
+
+    @bar.setter
+    @check_limits(0, 15)
+    def bar(self, new_value):
+        self._bar = new_value
+
+
+

But sometimes this is not flexible enough, so it’s also possible to supply strings, which +are the names of attributes of the object the decorated method belongs with:

+
class Foo:
+    _bar = 0
+
+    bar_min = 0
+    bar_max = 24
+
+    @property
+    def bar(self):
+        return self._bar
+
+    @bar.setter
+    @check_limits("bar_min", "bar_max")
+    def bar(self, new_value):
+        self._bar = new_value
+
+
+

This will make sure that the new value is always between bar_min and bar_max, even +if they change at runtime. If the limit is None (default), the value will not be limited +in that direction.

+

Upper and lower limit can also be used exclusively, for example for a property that has a lower +bound but not an upper, say a temperature:

+
class Foo:
+    _temp = 273.15
+
+    @check_limits(lower=0)
+    def set_temperature(self, t_in_kelvin):
+        self._temp = t_in_kelvin
+
+
+

If the value is outside the specified limits, the decorated function is not called and a +LimitViolationException is raised if the silent- +parameter is False (default). If that option is active, the call is simply silently +ignored.

+
+
Parameters:
+
    +
  • lower – Numerical lower limit or name of attribute that contains limit.

  • +
  • upper – Numerical upper limit or name of attribute that contains limit.

  • +
  • silent – A limit violation will not raise an exception if this option is True.

  • +
+
+
+
+ +
+
+lewis.core.utils.dict_strict_update(base_dict, update_dict) None[source]
+

This function updates base_dict with update_dict if and only if update_dict does not contain +keys that are not already in base_dict. It is essentially a more strict interpretation of the +term “updating” the dict.

+

If update_dict contains keys that are not in base_dict, a RuntimeError is raised.

+
+
Parameters:
+
    +
  • base_dict – The dict that is to be updated. This dict is modified.

  • +
  • update_dict – The dict containing the new values.

  • +
+
+
+
+ +
+
+lewis.core.utils.extract_module_name(absolute_path)[source]
+

This function tries to extract a valid module name from the basename of the supplied path. +If it’s a directory, the directory name is returned, if it’s a file, the file name +without extension is returned. If the basename starts with _ or . or it’s a file with an +ending different from .py, the function returns None

+
+
Parameters:
+

absolute_path – Absolute path of something that might be a module.

+
+
Returns:
+

Module name or None.

+
+
+
+ +
+
+lewis.core.utils.format_doc_text(text)[source]
+

A very thin wrapper around textwrap.fill to consistently wrap documentation text +for display in a command line environment. The text is wrapped to 99 characters with an +indentation depth of 4 spaces. Each line is wrapped independently in order to preserve +manually added line breaks.

+
+
Parameters:
+

text – The text to format, it is cleaned by inspect.cleandoc.

+
+
Returns:
+

The formatted doc text.

+
+
+
+ +
+
+lewis.core.utils.get_members(obj, predicate=None)[source]
+

Returns all members of an object for which the supplied predicate is true and that do not +begin with __. Keep in mind that the supplied function must accept a potentially very broad +range of inputs, because the members of an object can be of any type. The function puts +those members into a dict with the member names as keys and returns it. If no predicate is +supplied, all members are put into the dict.

+
+
Parameters:
+
    +
  • obj – Object from which to get the members.

  • +
  • predicate – Filter function for the members, only members for which True is returned are +part of the resulting dict.

  • +
+
+
Returns:
+

Dict with name-object pairs of members of obj for which predicate returns true.

+
+
+
+ +
+
+lewis.core.utils.get_submodules(module) dict[str, ModuleType][source]
+

This function imports all sub-modules of the supplied module and returns a dictionary +with module names as keys and the sub-module objects as values. If the supplied parameter +is not a module object, a RuntimeError is raised.

+
+
Parameters:
+

module – Module object from which to import sub-modules.

+
+
Returns:
+

Dict with name-module pairs.

+
+
+
+ +
+
+lewis.core.utils.seconds_since(start)[source]
+

This is a small helper function that returns the elapsed seconds +since start using datetime.datetime.now().

+
+
Parameters:
+

start – Start time.

+
+
Returns:
+

Elapsed seconds since start time.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.chopper.devices.bearings.html b/generated/lewis.devices.chopper.devices.bearings.html new file mode 100644 index 00000000..9a6834fa --- /dev/null +++ b/generated/lewis.devices.chopper.devices.bearings.html @@ -0,0 +1,168 @@ + + + + + + + + + lewis.devices.chopper.devices.bearings — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.chopper.devices.bearings

+

Members

+ + + + + + + + + + + + +

Bearings

MagneticBearings

MechanicalBearings

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.chopper.devices.device.html b/generated/lewis.devices.chopper.devices.device.html new file mode 100644 index 00000000..6bf0145b --- /dev/null +++ b/generated/lewis.devices.chopper.devices.device.html @@ -0,0 +1,184 @@ + + + + + + + + + lewis.devices.chopper.devices.device — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.chopper.devices.device

+

Members

+ + + + + + + + + +

SimulatedBearings

SimulatedChopper

+
+
+class lewis.devices.chopper.devices.device.SimulatedBearings[source]
+

Bases: CanProcess, MagneticBearings

+
+ +
+
+class lewis.devices.chopper.devices.device.SimulatedChopper(override_states: dict[str, State] | None = None, override_transitions: dict[tuple[State, State], Callable[[], bool]] | None = None, override_initial_state: State | None = None, override_initial_data: dict[str, float] | None = None)[source]
+

Bases: StateMachineDevice

+
+
+property state
+

The current state of the chopper. This parameter is read-only, it is +determined by the internal state machine of the device.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.chopper.devices.html b/generated/lewis.devices.chopper.devices.html new file mode 100644 index 00000000..34742a89 --- /dev/null +++ b/generated/lewis.devices.chopper.devices.html @@ -0,0 +1,167 @@ + + + + + + + + + lewis.devices.chopper.devices — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.chopper.devices

+

Submodules

+ + + + + + + + + + + + +

bearings

device

states

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.chopper.devices.states.html b/generated/lewis.devices.chopper.devices.states.html new file mode 100644 index 00000000..5043c7cd --- /dev/null +++ b/generated/lewis.devices.chopper.devices.states.html @@ -0,0 +1,379 @@ + + + + + + + + + lewis.devices.chopper.devices.states — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.chopper.devices.states

+

Members

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

DefaultAcceleratingState

DefaultIdleState

DefaultInitState

DefaultParkedState

DefaultParkingState

DefaultPhaseLockedState

DefaultPhaseLockingState

DefaultStoppedState

DefaultStoppingState

on_entry

+
+
+class lewis.devices.chopper.devices.states.DefaultAcceleratingState(acceleration=5.0)[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.chopper.devices.states.DefaultIdleState(acceleration=0.05)[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.chopper.devices.states.DefaultInitState[source]
+

Bases: State

+
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.chopper.devices.states.DefaultParkedState[source]
+

Bases: State

+
+ +
+
+class lewis.devices.chopper.devices.states.DefaultParkingState(parking_speed=5.0)[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.chopper.devices.states.DefaultPhaseLockedState[source]
+

Bases: State

+
+ +
+
+class lewis.devices.chopper.devices.states.DefaultPhaseLockingState(phase_locking_speed=5.0)[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.chopper.devices.states.DefaultStoppedState[source]
+

Bases: State

+
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.chopper.devices.states.DefaultStoppingState(acceleration=5.0)[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.chopper.html b/generated/lewis.devices.chopper.html new file mode 100644 index 00000000..ce9392a2 --- /dev/null +++ b/generated/lewis.devices.chopper.html @@ -0,0 +1,163 @@ + + + + + + + + + lewis.devices.chopper — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.chopper

+

Submodules

+ + + + + + + + + +

devices

interfaces

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.chopper.interfaces.epics_interface.html b/generated/lewis.devices.chopper.interfaces.epics_interface.html new file mode 100644 index 00000000..acd09e69 --- /dev/null +++ b/generated/lewis.devices.chopper.interfaces.epics_interface.html @@ -0,0 +1,210 @@ + + + + + + + + + lewis.devices.chopper.interfaces.epics_interface — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.chopper.interfaces.epics_interface

+

Members

+ + + + + + +

ChopperEpicsInterface

ESS chopper EPICS interface

+
+
+class lewis.devices.chopper.interfaces.epics_interface.ChopperEpicsInterface[source]
+

Bases: EpicsInterface

+

ESS chopper EPICS interface

+

Interaction with this interface should happen via ChannelAccess (CA). The PV-names +usually carry a prefix which depends on the concrete device and environment, so +it is omitted in this description. The dynamically generated description of the PVs +does however contain the prefix so that the names can be copy-pasted easily.

+

The first step is to initialize the chopper, for example via caput on the command line:

+
+

$ caput CmdS init

+
+

After this, the chopper is in a state where it can be started:

+
+

$ caget State +State stopped

+
+

To set a specific speed and phase, the setpoints have to be configured via caput:

+
+

$ caput Spd 100 +$ caput Phs 34.5

+
+

Then the chopper can be commanded to move towards those values:

+
+

$ caput CmdS start

+
+

Now the disc accelerates to the setpoints, the state should now be different:

+
+

$ caget State +State accelerating

+
+

The possible commands are part of the PV-specific documentation.

+
+
+property execute_command: str
+

Command to execute. Possible commands are start, stop, set_phase, +unlock, park, init, deinit.

+
+ +
+
+property last_command
+

The last command that was executed successfully.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.chopper.interfaces.html b/generated/lewis.devices.chopper.interfaces.html new file mode 100644 index 00000000..e65d4850 --- /dev/null +++ b/generated/lewis.devices.chopper.interfaces.html @@ -0,0 +1,161 @@ + + + + + + + + + lewis.devices.chopper.interfaces — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.chopper.interfaces

+

Submodules

+ + + + + + +

epics_interface

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.html b/generated/lewis.devices.html new file mode 100644 index 00000000..b61abe4b --- /dev/null +++ b/generated/lewis.devices.html @@ -0,0 +1,244 @@ + + + + + + + + + lewis.devices — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices

+

This module contains base classes for devices. Inherit from Device for simple devices +or from StateMachineDevice for devices that are more complex and can be described +using a state machine.

+

Submodules

+ + + + + + + + + + + + +

chopper

julabo

linkam_t95

+

Members

+ + + + + + + + + +

Device

This class exists mainly for consistency.

StateMachineDevice

This class is intended to be sub-classed to implement devices using a finite state machine internally.

+
+
+class lewis.devices.Device[source]
+

Bases: DeviceBase, CanProcess

+

This class exists mainly for consistency. It is meant to implement very simple devices that +do not require a state machine for their simulation. For such devices, all that is required +is subclassing from Device and possibly implementing doProcess, but this is optional.

+

StateMachineDevice offers more functionality and is more likely to be useful for implementing +simulations of real devices.

+
+ +
+
+class lewis.devices.StateMachineDevice(override_states: dict[str, State] | None = None, override_transitions: dict[tuple[State, State], Callable[[], bool]] | None = None, override_initial_state: State | None = None, override_initial_data: dict[str, float] | None = None)[source]
+

Bases: DeviceBase, CanProcessComposite

+

This class is intended to be sub-classed to implement devices using a finite state machine +internally.

+

Implementing such a device is straightforward, there are three methods +that must be overridden:

+
+
    +
  • _get_state_handlers()

  • +
  • _get_initial_state()

  • +
  • _get_transition_handlers()

  • +
+
+

The first method is supposed to return a dictionary with state handlers for each state +of the state machine, the second method must return the name of the initial state. +The third method must return a dict-like object (often an OrderedDict from collections) +that defines the conditions for transitions between the states of the state machine.

+

They are implemented as methods and not as plain class member variables, because often +they use the self-variable, which does not exist at the class level.

+

From these three methods, a StateMachine-instance is +constructed, it’s available as the device’s _csm-member. CSM is short for +“cycle-based state machine”.

+

Most device implementation will also want to override this method:

+
+
    +
  • _initialize_data()

  • +
+
+

This method should initialise device state variables (such as temperature, speed, etc.). +Having this in a separate method from __init__ has the advantage that it can be used +to reset those variables at a later stage, without having to write the same code again.

+

Following this scheme, inheriting from StateMachineDevice also provides the possibility +for users of the class to override the states, the transitions, the initial state and +even the data. For states, transitions and data, dicts need to be passed to the +constructor, for the initial state that should be a string.

+

All these overrides can be used to define device setups to describe certain scenarios +more easily.

+
+
Parameters:
+
    +
  • override_states – Dict with one entry per state. Only states defined in the state +machine are allowed.

  • +
  • override_transitions – Dict with (state, state) tuples as keys and +callables as values.

  • +
  • override_initial_state – The initial state.

  • +
  • override_initial_data – A dict that contains data members +that should be overwritten on construction.

  • +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.julabo.devices.device.html b/generated/lewis.devices.julabo.devices.device.html new file mode 100644 index 00000000..053f7ea4 --- /dev/null +++ b/generated/lewis.devices.julabo.devices.device.html @@ -0,0 +1,286 @@ + + + + + + + + + lewis.devices.julabo.devices.device — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.julabo.devices.device

+

Members

+ + + + + + +

SimulatedJulabo

+
+
+class lewis.devices.julabo.devices.device.SimulatedJulabo(override_states: dict[str, State] | None = None, override_transitions: dict[tuple[State, State], Callable[[], bool]] | None = None, override_initial_state: State | None = None, override_initial_data: dict[str, float] | None = None)[source]
+

Bases: StateMachineDevice

+
+
+set_circulating(param) str[source]
+

Sets whether to circulate - in effect whether the heater is on.

+
+
Parameters:
+

param – The mode to set, must be 0 or 1.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_external_d(param) str[source]
+

Sets the external derivative. +Tv in Julabo speak.

+
+
Parameters:
+

param – The value to set, must be an integer between 0 and 999

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_external_i(param) str[source]
+

Sets the external integral. +Tn in Julabo speak.

+
+
Parameters:
+

param – The value to set, must be an integer between 3 and 9999

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_external_p(param) str[source]
+

Sets the external proportional. +Xp in Julabo speak.

+
+
Parameters:
+

param – The value to set, must be between 0.1 and 99.9

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_internal_d(param) str[source]
+

Sets the internal derivative. +Tv in Julabo speak.

+
+
Parameters:
+

param – The value to set, must be an integer between 0 and 999

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_internal_i(param) str[source]
+

Sets the internal integral. +Tn in Julabo speak.

+
+
Parameters:
+

param – The value to set, must be an integer between 3 and 9999

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_internal_p(param) str[source]
+

Sets the internal proportional. +Xp in Julabo speak.

+
+
Parameters:
+

param – The value to set, must be between 0.1 and 99.9

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_set_point(param) str[source]
+

Sets the target temperature.

+
+
Parameters:
+

param – The new temperature in C. Must be positive.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.julabo.devices.html b/generated/lewis.devices.julabo.devices.html new file mode 100644 index 00000000..3a024a60 --- /dev/null +++ b/generated/lewis.devices.julabo.devices.html @@ -0,0 +1,164 @@ + + + + + + + + + lewis.devices.julabo.devices — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.julabo.devices

+

Submodules

+ + + + + + + + + +

device

states

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.julabo.devices.states.html b/generated/lewis.devices.julabo.devices.states.html new file mode 100644 index 00000000..0e4e05ce --- /dev/null +++ b/generated/lewis.devices.julabo.devices.states.html @@ -0,0 +1,191 @@ + + + + + + + + + lewis.devices.julabo.devices.states — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.julabo.devices.states

+

Members

+ + + + + + + + + +

DefaultCirculatingState

DefaultNotCirculatingState

+
+
+class lewis.devices.julabo.devices.states.DefaultCirculatingState[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.julabo.devices.states.DefaultNotCirculatingState[source]
+

Bases: State

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.julabo.html b/generated/lewis.devices.julabo.html new file mode 100644 index 00000000..0e7328d4 --- /dev/null +++ b/generated/lewis.devices.julabo.html @@ -0,0 +1,163 @@ + + + + + + + + + lewis.devices.julabo — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.julabo

+

Submodules

+ + + + + + + + + +

devices

interfaces

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.julabo.interfaces.html b/generated/lewis.devices.julabo.interfaces.html new file mode 100644 index 00000000..4500b31c --- /dev/null +++ b/generated/lewis.devices.julabo.interfaces.html @@ -0,0 +1,164 @@ + + + + + + + + + lewis.devices.julabo.interfaces — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.julabo.interfaces

+

Submodules

+ + + + + + + + + +

julabo_stream_interface_1

julabo_stream_interface_2

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.html b/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.html new file mode 100644 index 00000000..87ab8f5c --- /dev/null +++ b/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.html @@ -0,0 +1,170 @@ + + + + + + + + + lewis.devices.julabo.interfaces.julabo_stream_interface_1 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.julabo.interfaces.julabo_stream_interface_1

+

Members

+ + + + + + +

JulaboStreamInterfaceV1

Julabos can have different commands sets depending on the version number of the hardware.

+
+
+class lewis.devices.julabo.interfaces.julabo_stream_interface_1.JulaboStreamInterfaceV1[source]
+

Bases: StreamInterface

+

Julabos can have different commands sets depending on the version number of the hardware.

+

This protocol matches that for: FP50_MH (confirmed).

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.html b/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.html new file mode 100644 index 00000000..9ba5c562 --- /dev/null +++ b/generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.html @@ -0,0 +1,170 @@ + + + + + + + + + lewis.devices.julabo.interfaces.julabo_stream_interface_2 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.julabo.interfaces.julabo_stream_interface_2

+

Members

+ + + + + + +

JulaboStreamInterfaceV2

Julabos can have different commands sets depending on the version number of the hardware.

+
+
+class lewis.devices.julabo.interfaces.julabo_stream_interface_2.JulaboStreamInterfaceV2[source]
+

Bases: StreamInterface

+

Julabos can have different commands sets depending on the version number of the hardware.

+

This protocol matches that for: FP50-HE (unconfirmed).

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.linkam_t95.devices.device.html b/generated/lewis.devices.linkam_t95.devices.device.html new file mode 100644 index 00000000..642994a5 --- /dev/null +++ b/generated/lewis.devices.linkam_t95.devices.device.html @@ -0,0 +1,168 @@ + + + + + + + + + lewis.devices.linkam_t95.devices.device — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.linkam_t95.devices.device

+

Members

+ + + + + + +

SimulatedLinkamT95

+
+
+class lewis.devices.linkam_t95.devices.device.SimulatedLinkamT95(override_states: dict[str, State] | None = None, override_transitions: dict[tuple[State, State], Callable[[], bool]] | None = None, override_initial_state: State | None = None, override_initial_data: dict[str, float] | None = None)[source]
+

Bases: StateMachineDevice

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.linkam_t95.devices.html b/generated/lewis.devices.linkam_t95.devices.html new file mode 100644 index 00000000..7f978a2b --- /dev/null +++ b/generated/lewis.devices.linkam_t95.devices.html @@ -0,0 +1,164 @@ + + + + + + + + + lewis.devices.linkam_t95.devices — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.linkam_t95.devices

+

Submodules

+ + + + + + + + + +

device

states

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.linkam_t95.devices.states.html b/generated/lewis.devices.linkam_t95.devices.states.html new file mode 100644 index 00000000..63ee6433 --- /dev/null +++ b/generated/lewis.devices.linkam_t95.devices.states.html @@ -0,0 +1,274 @@ + + + + + + + + + lewis.devices.linkam_t95.devices.states — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.linkam_t95.devices.states

+

Members

+ + + + + + + + + + + + + + + + + + + + + +

DefaultCoolState

DefaultHeatState

DefaultHoldState

DefaultInitState

DefaultStartedState

DefaultStoppedState

+
+
+class lewis.devices.linkam_t95.devices.states.DefaultCoolState[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+
+on_exit(dt) None[source]
+

Handle exit event. Raised once, when this state is exited.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.linkam_t95.devices.states.DefaultHeatState[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.linkam_t95.devices.states.DefaultHoldState[source]
+

Bases: State

+
+ +
+
+class lewis.devices.linkam_t95.devices.states.DefaultInitState[source]
+

Bases: State

+
+ +
+
+class lewis.devices.linkam_t95.devices.states.DefaultStartedState[source]
+

Bases: State

+
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.devices.linkam_t95.devices.states.DefaultStoppedState[source]
+

Bases: State

+
+
+on_entry(dt) None[source]
+

Handle entry event. Raised once, when this state is entered.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.linkam_t95.html b/generated/lewis.devices.linkam_t95.html new file mode 100644 index 00000000..e4bf7ed0 --- /dev/null +++ b/generated/lewis.devices.linkam_t95.html @@ -0,0 +1,163 @@ + + + + + + + + + lewis.devices.linkam_t95 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.linkam_t95

+

Submodules

+ + + + + + + + + +

devices

interfaces

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.linkam_t95.interfaces.html b/generated/lewis.devices.linkam_t95.interfaces.html new file mode 100644 index 00000000..693124cb --- /dev/null +++ b/generated/lewis.devices.linkam_t95.interfaces.html @@ -0,0 +1,161 @@ + + + + + + + + + lewis.devices.linkam_t95.interfaces — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.linkam_t95.interfaces

+

Submodules

+ + + + + + +

stream_interface

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.devices.linkam_t95.interfaces.stream_interface.html b/generated/lewis.devices.linkam_t95.interfaces.stream_interface.html new file mode 100644 index 00000000..bf7f0930 --- /dev/null +++ b/generated/lewis.devices.linkam_t95.interfaces.stream_interface.html @@ -0,0 +1,304 @@ + + + + + + + + + lewis.devices.linkam_t95.interfaces.stream_interface — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.devices.linkam_t95.interfaces.stream_interface

+

Members

+ + + + + + +

LinkamT95StreamInterface

Linkam T95 TCP stream interface.

+
+
+class lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface[source]
+

Bases: StreamInterface

+

Linkam T95 TCP stream interface.

+

This is the interface of a simulated Linkam T95 device. The device listens on a configured +host:port-combination, one option to connect to it is via telnet:

+
+

$ telnet host port

+
+

Once connected, it’s possible to send the specified commands, described in the dynamically +generated documentation. Information about host, port and line terminators in the concrete +device instance are also generated dynamically.

+
+
+cool() bytes[source]
+

Models “Cool Command” functionality of device.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+get_status()[source]
+

Models “T Command” functionality of device.

+

Returns all available status information about the device as single byte array.

+
+
Returns:
+

Byte array consisting of 10 status bytes.

+
+
+
+ +
+
+handle_error(request, error) None[source]
+

If command is not recognised print and error

+
+
Args:

request: requested string +error: problem

+
+
+
+ +
+
+heat() bytes[source]
+

Models “Heat Command” functionality of device.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+hold() bytes[source]
+

Models “Hold Command” functionality of device.

+

Device will hold current temperature until a heat or cool command is issued.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+pump_command(param) bytes[source]
+

Models “LNP Pump Commands” functionality of device.

+

Switches between automatic or manual pump mode, and adjusts speed when in manual mode.

+
+
Parameters:
+

param – ‘a0’ for auto, ‘m0’ for manual, [0-N] for speed.

+
+
Returns:
+

+
+
+
+ +
+
+set_limit(param) bytes[source]
+

Models “Limit Command” functionality of device.

+

Sets the target temperate to be reached.

+
+
Parameters:
+

param – Target temperature in C, multiplied by 10, as a string. Can be negative.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+set_rate(param) bytes[source]
+

Models “Rate Command” functionality of device.

+

Sets the target rate of temperature change.

+
+
Parameters:
+

param – Rate of temperature change in C/min, multiplied by 100, as a string. Must be positive.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+start() bytes[source]
+

Models “Start Command” functionality of device.

+

Tells the T95 unit to start heating or cooling at the rate specified by setRate and to a +limit set by setLimit.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+
+stop() bytes[source]
+

Models “Stop Command” functionality of device.

+

Tells the T95 unit to stop heating or cooling.

+
+
Returns:
+

Empty string.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.examples.dual_device.html b/generated/lewis.examples.dual_device.html new file mode 100644 index 00000000..10d56273 --- /dev/null +++ b/generated/lewis.examples.dual_device.html @@ -0,0 +1,207 @@ + + + + + + + + + lewis.examples.dual_device — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.examples.dual_device

+

Members

+ + + + + + + + + + + + +

VerySimpleDevice

VerySimpleInterface

This is the EPICS interface to a quite simple device.

VerySimpleStreamInterface

This is a TCP stream interface to the epics device, which only exposes param.

+
+
+class lewis.examples.dual_device.VerySimpleDevice[source]
+

Bases: Device

+
+
+get_param()[source]
+

The parameter multiplied by 2.

+
+ +
+
+property second
+

A second (floating point) parameter.

+
+ +
+ +
+
+class lewis.examples.dual_device.VerySimpleInterface[source]
+

Bases: EpicsInterface

+

This is the EPICS interface to a quite simple device. It offers 5 PVs that expose +different things that are part of the device, the interface or neither.

+
+
+property second_int
+

The second parameter as an integer.

+
+ +
+ +
+
+class lewis.examples.dual_device.VerySimpleStreamInterface[source]
+

Bases: StreamInterface

+

This is a TCP stream interface to the epics device, which only exposes param.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.examples.example_motor.html b/generated/lewis.examples.example_motor.html new file mode 100644 index 00000000..3964a88d --- /dev/null +++ b/generated/lewis.examples.example_motor.html @@ -0,0 +1,241 @@ + + + + + + + + + lewis.examples.example_motor — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.examples.example_motor

+

Members

+ + + + + + + + + + + + +

DefaultMovingState

ExampleMotorStreamInterface

TCP-stream based example motor interface

SimulatedExampleMotor

+
+
+class lewis.examples.example_motor.DefaultMovingState[source]
+

Bases: State

+
+
+in_state(dt) None[source]
+

Handle in-state event.

+

Raised repeatedly, once per cycle, while idling in this state. Exactly one +in-state event occurs per cycle for every StateMachine. This is always the +last event of the cycle.

+
+
Parameters:
+

dt – Delta T since last cycle.

+
+
+
+ +
+ +
+
+class lewis.examples.example_motor.ExampleMotorStreamInterface[source]
+

Bases: StreamInterface

+

TCP-stream based example motor interface

+

This motor simulation can be controlled via telnet:

+
+

$ telnet host port

+
+

Where the host and port-parameter are part of the dynamically created documentation for +a concrete device instance.

+

The motor starts moving immediately when a new target position is set. Once it’s moving, +it has to be stopped to receive a new target, otherwise an error is generated.

+
+
+get_position()[source]
+

Returns the current position in mm.

+
+ +
+
+get_status()[source]
+

Returns the status of the device, which is one of ‘idle’ or ‘moving’.

+
+ +
+
+get_target()[source]
+

Returns the current target in mm.

+
+ +
+
+set_target(new_target)[source]
+

Sets the new target in mm, the movement starts immediately. If the value is outside +the interval [0, 250] or the motor is already moving, an error is returned, otherwise +the new target is returned.

+
+ +
+ +
+
+class lewis.examples.example_motor.SimulatedExampleMotor(override_states: dict[str, State] | None = None, override_transitions: dict[tuple[State, State], Callable[[], bool]] | None = None, override_initial_state: State | None = None, override_initial_data: dict[str, float] | None = None)[source]
+

Bases: StateMachineDevice

+
+
+stop()[source]
+

Stops the motor and returns the new target and position, which are equal

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.examples.html b/generated/lewis.examples.html new file mode 100644 index 00000000..ff22d236 --- /dev/null +++ b/generated/lewis.examples.html @@ -0,0 +1,173 @@ + + + + + + + + + lewis.examples — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.examples

+

Submodules

+ + + + + + + + + + + + + + + + + + +

dual_device

example_motor

modbus_device

simple_device

timeout_device

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.examples.modbus_device.html b/generated/lewis.examples.modbus_device.html new file mode 100644 index 00000000..2ebc8c48 --- /dev/null +++ b/generated/lewis.examples.modbus_device.html @@ -0,0 +1,184 @@ + + + + + + + + + lewis.examples.modbus_device — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.examples.modbus_device

+

Members

+ + + + + + + + + +

ExampleModbusInterface

The class attributes di, co, ir and hr represent Discrete Inputs, Coils, Input Registers and Holding Registers, respectively.

ModbusDevice

+
+
+class lewis.examples.modbus_device.ExampleModbusInterface[source]
+

Bases: ModbusInterface

+

The class attributes di, co, ir and hr represent Discrete Inputs, Coils, Input Registers and +Holding Registers, respectively. Each attribute should be assigned a ModbusDataBank instance +by the Interface implementation.

+

Here, two basic ModbusDataBanks are created and initialized to a default value across the full +range of valid addresses. One DataBank is shared by di and co, and the other by ir and hr to +demonstrate overlaid memory segments. If you want each segment to have its own memory, just +create separate instances for all four.

+
+ +
+
+class lewis.examples.modbus_device.ModbusDevice[source]
+

Bases: Device

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.examples.simple_device.html b/generated/lewis.examples.simple_device.html new file mode 100644 index 00000000..8377303f --- /dev/null +++ b/generated/lewis.examples.simple_device.html @@ -0,0 +1,222 @@ + + + + + + + + + lewis.examples.simple_device — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.examples.simple_device

+

Members

+ + + + + + + + + +

VerySimpleDevice

VerySimpleInterface

A very simple device with TCP-stream interface

+
+
+class lewis.examples.simple_device.VerySimpleDevice[source]
+

Bases: Device

+
+ +
+
+class lewis.examples.simple_device.VerySimpleInterface[source]
+

Bases: StreamInterface

+

A very simple device with TCP-stream interface

+

The device has only one parameter, which can be set to an arbitrary +value. The interface consists of five commands which can be invoked via telnet. +To connect:

+
+

$ telnet host port

+
+

After that, typing either of the commands and pressing enter sends them to the server.

+

The commands are:

+
+
    +
  • V: Returns the parameter as part of a verbose message.

  • +
  • V=something: Sets the parameter to something.

  • +
  • P: Returns the device parameter unmodified.

  • +
  • P=something: Exactly the same as V=something.

  • +
  • R or r: Returns the number 4.

  • +
+
+
+
+get_param()[source]
+

Returns the device parameter.

+
+ +
+
+handle_error(request, error)[source]
+

Override this method to handle exceptions that are raised during command processing. +The default implementation does nothing, so that any errors are silently ignored.

+
+
Parameters:
+
    +
  • request – The request that resulted in the error.

  • +
  • error – The exception that was raised.

  • +
+
+
+
+ +
+
+set_param(new_param) None[source]
+

Set the device parameter, does not return anything.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.examples.timeout_device.html b/generated/lewis.examples.timeout_device.html new file mode 100644 index 00000000..6fbd93a8 --- /dev/null +++ b/generated/lewis.examples.timeout_device.html @@ -0,0 +1,216 @@ + + + + + + + + + lewis.examples.timeout_device — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.examples.timeout_device

+

Members

+ + + + + + + + + +

TimeTerminatedDevice

TimeTerminatedInterface

A simple device where commands are terminated by a timeout.

+
+
+class lewis.examples.timeout_device.TimeTerminatedDevice[source]
+

Bases: Device

+
+ +
+
+class lewis.examples.timeout_device.TimeTerminatedInterface[source]
+

Bases: StreamInterface

+

A simple device where commands are terminated by a timeout.

+

This demonstrates how to implement devices that do not have standard +terminators and where a command is considered terminated after a certain +time delay of not receiving more data.

+

To interact with this device, you must switch telnet into char mode, or use +netcat with special tty settings:

+
+

$ telnet host port +^] +telnet> mode char +[type command and wait]

+

$ stty -icanon && nc host port +hello world! +foobar!

+
+

The following commands are available:

+
+
    +
  • ``hello ``: Reply with “world!”

  • +
  • foo: Replay with “bar!”

  • +
  • P: Returns the device parameter

  • +
  • P=something: Set parameter to specified value

  • +
+
+
+
+handle_error(request, error)[source]
+

Override this method to handle exceptions that are raised during command processing. +The default implementation does nothing, so that any errors are silently ignored.

+
+
Parameters:
+
    +
  • request – The request that resulted in the error.

  • +
  • error – The exception that was raised.

  • +
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.html b/generated/lewis.html new file mode 100644 index 00000000..7635b9e8 --- /dev/null +++ b/generated/lewis.html @@ -0,0 +1,169 @@ + + + + + + + + + lewis — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis

+

Lewis - a library for creating hardware device simulators

+

Submodules

+ + + + + + + + + + + + + + + + + + + + + +

adapters

The core Adapter API is located in lewis.core.adapters.

core

devices

This module contains base classes for devices.

examples

scripts

utils

This package contains helpful utilities for people that are building emulators.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.scripts.control.html b/generated/lewis.scripts.control.html new file mode 100644 index 00000000..99f82352 --- /dev/null +++ b/generated/lewis.scripts.control.html @@ -0,0 +1,204 @@ + + + + + + + + + lewis.scripts.control — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.scripts.control

+

To interact with the control server of a running simulation, use this script. Usage:

+
usage: lewis-control [-r RPC_HOST] [-t TIMEOUT] [-n] [-v] [-h]
+                     [object] [member] [arguments ...]
+
+A client to manipulate the simulated device remotely through a separate
+channel. For this tool to be of any use, lewis must be invoked with the
+-r/--rpc-host option.
+
+Positional arguments:
+  object                Object to control. If left out, all objects are
+                        listed.
+  member                Object-member to access. If omitted, API of the object
+                        is listed.
+  arguments             Arguments to method call. For setting a property,
+                        supply the property value.
+
+Optional arguments:
+  -r RPC_HOST, --rpc-host RPC_HOST
+                        HOST:PORT string specifying control server to connect
+                        to.
+  -t TIMEOUT, --timeout TIMEOUT
+                        Timeout after which the control client exits. Must be
+                        at least as long as one simulation cycle.
+  -n, --print-none      By default, no output is generated if the remote
+                        function returns None. Specifying this flag will force
+                        the client to print those None-values.
+  -v, --version         Prints the version and exits.
+  -h, --h               Shows this help message and exits.
+
+
+

Members

+ + + + + + + + + + + + + + + + + + + + + +

call_method

control_simulation

convert_type

is_remote_method

list_objects

show_api

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.scripts.html b/generated/lewis.scripts.html new file mode 100644 index 00000000..5db7f16a --- /dev/null +++ b/generated/lewis.scripts.html @@ -0,0 +1,187 @@ + + + + + + + + + lewis.scripts — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.scripts

+

Submodules

+ + + + + + + + + +

control

To interact with the control server of a running simulation, use this script.

run

This script is the main interaction point of the user with Lewis.

+

Members

+ + + + + + +

get_usage_text

This small helper function extracts the help information from an ArgumentParser instance and indents the text by the number of spaces supplied in the indent-argument.

+
+
+lewis.scripts.get_usage_text(parser, indent=None)[source]
+

This small helper function extracts the help information from an ArgumentParser instance +and indents the text by the number of spaces supplied in the indent-argument.

+
+
Parameters:
+
    +
  • parser – ArgumentParser object.

  • +
  • indent – Number of spaces to put before each line or None.

  • +
+
+
Returns:
+

Formatted help string of the supplied parser.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.scripts.run.html b/generated/lewis.scripts.run.html new file mode 100644 index 00000000..9a5a7326 --- /dev/null +++ b/generated/lewis.scripts.run.html @@ -0,0 +1,252 @@ + + + + + + + + + lewis.scripts.run — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.scripts.run

+

This script is the main interaction point of the user with Lewis. The usage is as follows:

+
usage: lewis [-s SETUP] [-n | -p ADAPTER_OPTIONS] [-l] [-L] [-i]
+             [-k DEVICE_PACKAGE] [-a ADD_PATH] [-c CYCLE_DELAY] [-e SPEED]
+             [-r RPC_HOST] [-o {none,critical,error,warning,info,debug}] [-V]
+             [-I] [-v] [-h] [-R]
+             [device]
+
+This script starts a simulated device that is exposed via the specified
+communication protocol. Complete documentation of Lewis is available in the
+online documentation on GitHub https://github.com/ess-dmsc/lewis/
+
+Positional arguments:
+  device                Name of the device to simulate, omitting this argument
+                        prints out a list of available devices.
+
+Device related parameters:
+  Parameters that influence the selected device, such as setup or protocol.
+
+  -s SETUP, --setup SETUP
+                        Name of the setup to load. If not provided, the
+                        default setup is selected. If thereis no default, a
+                        list of setups is printed.
+  -n, --no-interface    If supplied, the device simulation will not have any
+                        communication interface.
+  -p ADAPTER_OPTIONS, --adapter-options ADAPTER_OPTIONS
+                        Supply the protocol name and adapter options in the
+                        format "name:{opt1: val, opt2: val}". Use the -l flag
+                        to see which protocols are available for the selected
+                        device. Can be supplied multiple times for multiple
+                        protocols.
+  -l, --list-protocols  List available protocols for selected device.
+  -L, --list-adapter-options
+                        List available configuration options and their value.
+                        Values that have not been modified in the -p argument
+                        are default values.
+  -i, --show-interface  Show command interface of device interface.
+  -k DEVICE_PACKAGE, --device-package DEVICE_PACKAGE
+                        Name of packages where devices are found.
+  -a ADD_PATH, --add-path ADD_PATH
+                        Path where the device package exists. Is added to the
+                        path.
+
+Simulation related parameters:
+  Parameters that influence the simulation itself, such as timing and speed.
+
+  -c CYCLE_DELAY, --cycle-delay CYCLE_DELAY
+                        Approximate time to spend in each cycle of the
+                        simulation. 0 for maximum simulation rate.
+  -e SPEED, --speed SPEED
+                        Simulation speed. The actually elapsed time between
+                        two cycles is multiplied with this speed to determine
+                        the simulated time.
+  -r RPC_HOST, --rpc-host RPC_HOST
+                        HOST:PORT format string for exposing the device and
+                        the simulation via JSON-RPC over ZMQ. Use lewis-
+                        control to access this service from the command line.
+
+Other arguments:
+  -o {none,critical,error,warning,info,debug}, --output-level {none,critical,error,warning,info,debug}
+                        Level of detail for logging to stderr.
+  -V, --verify          Sets the output level to 'debug' and aborts before
+                        starting the device simulation. This is intended to
+                        help with diagnosing problems with devices or input
+                        arguments.
+  -I, --ignore-versions
+                        Ignore version mismatches between device and
+                        framework. A warning will still be logged.
+  -v, --version         Prints the version and exits.
+  -h, --help            Shows this help message and exits.
+
+Deprecated arguments:
+  -R, --relaxed-versions
+                        Renamed to --I/--ignore-versions. Using this old
+                        option produces an error and it will be removed in a
+                        future release.
+
+
+

Members

+ + + + + + + + + +

parse_adapter_options

run_simulation

This is effectively the main function of a typical simulation run.

+
+
+lewis.scripts.run.run_simulation(argument_list=None) None[source]
+

This is effectively the main function of a typical simulation run. Arguments passed in are +parsed and used to construct and run the simulation.

+

This function only exits when the program has completed or is interrupted.

+
+
Parameters:
+

argument_list – Argument list to pass to the argument parser declared in this module.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.utils.byte_conversions.html b/generated/lewis.utils.byte_conversions.html new file mode 100644 index 00000000..32924210 --- /dev/null +++ b/generated/lewis.utils.byte_conversions.html @@ -0,0 +1,238 @@ + + + + + + + + + lewis.utils.byte_conversions — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.utils.byte_conversions

+

Members

+ + + + + + + + + + + + + + + +

float_to_raw_bytes

Converts an floating point number to an unsigned set of bytes.

int_to_raw_bytes

Converts an integer to an unsigned set of bytes with the specified length (represented as a string).

raw_bytes_to_float

Convert a set of bytes to a floating point number

raw_bytes_to_int

Converts an unsigned set of bytes to an integer.

+
+
+lewis.utils.byte_conversions.float_to_raw_bytes(real_number: float, low_byte_first: bool = True) bytes[source]
+

Converts an floating point number to an unsigned set of bytes.

+
+
Parameters:
+
    +
  • real_number – The float to convert.

  • +
  • low_byte_first – Whether to put the least significant byte first. True by default.

  • +
+
+
Returns:
+

A string representation of the bytes.

+
+
+
+ +
+
+lewis.utils.byte_conversions.int_to_raw_bytes(integer: int, length: int, low_byte_first: bool) bytes[source]
+

Converts an integer to an unsigned set of bytes with the specified length (represented as a string). Unless the +integer is negative in which case it converts to a signed integer.

+

If low byte first is True, the least significant byte comes first, otherwise the most significant byte comes first.

+
+
Parameters:
+
    +
  • integer – The integer to convert.

  • +
  • length – The length of the result.

  • +
  • low_byte_first – Whether to put the least significant byte first.

  • +
+
+
Returns:
+

string representation of the bytes.

+
+
+
+ +
+
+lewis.utils.byte_conversions.raw_bytes_to_float(raw_bytes: bytes) float[source]
+

Convert a set of bytes to a floating point number

+
+
Parameters:
+

raw_bytes – A string representation of the raw bytes.

+
+
Returns:
+

float: The floating point number represented by the given bytes.

+
+
+
+ +
+
+lewis.utils.byte_conversions.raw_bytes_to_int(raw_bytes: bytes, low_bytes_first: bool = True) int[source]
+

Converts an unsigned set of bytes to an integer.

+
+
Parameters:
+
    +
  • raw_bytes – A string representation of the raw bytes.

  • +
  • low_bytes_first – Whether the given raw bytes are in little endian or not. True by default.

  • +
+
+
Returns:
+

The integer represented by the raw bytes passed in.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.utils.command_builder.html b/generated/lewis.utils.command_builder.html new file mode 100644 index 00000000..aceb1794 --- /dev/null +++ b/generated/lewis.utils.command_builder.html @@ -0,0 +1,495 @@ + + + + + + + + + lewis.utils.command_builder — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.utils.command_builder

+

A fluent command builder for lewis.

+

Members

+ + + + + + +

CmdBuilder

Build a command for the stream adapter.

+
+
+class lewis.utils.command_builder.CmdBuilder(target_method, arg_sep='', ignore='', ignore_case=False)[source]
+

Bases: object

+

Build a command for the stream adapter.

+

Do this by creating this object, adding the values and then building it (this uses a fluent interface).

+

For example to read a pressure the ioc might send “pres?” and when that happens this should call get_pres +command would be: +>>> CmdBuilder(“get_pres”).escape(“pres?”).build() +This will generate the regex needed by Lewis. The escape is just making sure none of the characters are special +reg ex characters. +If you wanted to set a pressure the ioc might send “pres <pressure>” where <pressure> is a floating point number, +the interface should call set_pres with that number. Now use: +>>> CmdBuilder(“set_pres”).escape(“pres “).float().build() +this add float as a regularly expression capture group for your argument. It is equivalent to: +>>> Cmd(“set_pres”, “pres ([+-]?d+.?d*)”) +There are various arguments like int and digit. Finally some special characters are included so if your protocol +uses enquirey character ascii 5 you can match is using +>>> CmdBuilder(“set_pres”).escape(“pres?”).enq().build()

+

Create a builder. Use build to create the final object

+
+
Parameters:
+
    +
  • target_method – name of the method target to call when the reg ex matches

  • +
  • arg_sep – separators between arguments which are next to each other

  • +
  • ignore – set of characters to ignore between text and arguments

  • +
  • ignore_case – ignore the case when matching command

  • +
+
+
+
+
+ack() CmdBuilder[source]
+

Add the ACK character (0x6) to the string.

+
+
Returns:
+

builder

+
+
+
+ +
+
+add_ascii_character(char_number: int) CmdBuilder[source]
+

Add a single character based on its integer value, e.g. 49 is ‘a’.

+
+
Parameters:
+

char_number – character number

+
+
Returns:
+

self

+
+
+
+ +
+
+any() CmdBuilder[source]
+

Add an argument that matches anything.

+
+
Returns:
+

builder

+
+
+
+ +
+
+any_except(char: str) CmdBuilder[source]
+

Adds an argument that matches anything other than a specified character (useful for commands containing +delimiters)

+
+
Parameters:
+

char – the character not to match

+
+
Returns:
+

builder

+
+
+
+ +
+
+arg(arg_regex, argument_mapping: ~functools.partial = functools.partial(<class 'str'>, encoding='utf-8')) CmdBuilder[source]
+

Add an argument to the command.

+
+
Parameters:
+
    +
  • arg_regex – regex for the argument (capture group will be added)

  • +
  • argument_mapping – the type mapping for the argument (default is str)

  • +
+
+
Returns:
+

builder

+
+
+
+ +
+
+build(*args, **kwargs) Cmd[source]
+

Builds the CMd object based on the target and regular expression.

+
+
Parameters:
+
    +
  • args – arguments to pass to Cmd constructor

  • +
  • kwargs – key word arguments to pass to Cmd constructor

  • +
+
+
Returns:
+

Cmd object

+
+
+
+ +
+
+char(not_chars: None | list[str] | str = None, ignore=False) CmdBuilder[source]
+

Add a single character argument.

+
+
Parameters:
+
    +
  • not_chars – characters that the character can not be; None for can be anything

  • +
  • ignore – True to match with a char but ignore the returned value (default: False)

  • +
+
+
Returns:
+

builder

+
+
+
+ +
+
+digit(mapping: type = <class 'int'>, ignore: bool = False) CmdBuilder[source]
+

Add a single digit argument.

+
+
Parameters:
+
    +
  • mapping – The type to cast the response to (default: int)

  • +
  • ignore – True to match with a digit but ignore the returned value (default: False)

  • +
+
+
Returns:
+

builder

+
+
+
+ +
+
+enq() CmdBuilder[source]
+

Add the ENQ character (0x5) to the string.

+
+
Returns:
+

builder

+
+
+
+ +
+
+enum(*allowed_values: AnyStr) CmdBuilder[source]
+

Matches one of a set of specified strings.

+
+
Parameters:
+

allowed_values – the values this function is allowed to match

+
+
Returns:
+

builder

+
+
+
+ +
+
+eos() CmdBuilder[source]
+

Adds the regex end-of-string character to a command.

+
+
Returns:
+

builder

+
+
+
+ +
+
+eot() CmdBuilder[source]
+

Add the EOT character (0x4) to the string.

+
+
Returns:
+

builder

+
+
+
+ +
+
+escape(text) CmdBuilder[source]
+

Add some text to the regex which is escaped.

+
+
Parameters:
+

text – text to add

+
+
Returns:
+

builder

+
+
+
+ +
+
+etx() CmdBuilder[source]
+

Add the ETX character (0x3) to the string.

+
+
Returns:
+

builder

+
+
+
+ +
+
+float(mapping: type = <class 'float'>, ignore: bool = False) CmdBuilder[source]
+

Add a float argument.

+
+
Parameters:
+
    +
  • mapping – The type to cast the response to (default: float)

  • +
  • ignore – True to match with a float but ignore the returned value (default: False)

  • +
+
+
Returns:
+

builder

+
+
+
+ +
+
+get_multicommands(command_separator: AnyStr) CmdBuilder[source]
+

Allows emulator to split multiple commands separated by a defined command separator, e.g. “;”. +Must be accompanied by stream device methods. See Keithley 2700 for examples

+
+
Parameters:
+

command_separator – Character(s) that separate commands

+
+
Returns:
+

builder

+
+
+
+ +
+
+int(mapping: type = <class 'int'>, ignore: bool = False) CmdBuilder[source]
+

Add an integer argument.

+
+
Parameters:
+
    +
  • mapping – The type to cast the response to (default: int)

  • +
  • ignore – True to match with a int but ignore the returned value (default: False)

  • +
+
+
Returns:
+

builder

+
+
+
+ +
+
+optional(text) CmdBuilder[source]
+

Add some escaped text which does not necessarily need to be there. For commands with optional parameters +:param text: Text to add +:return: builder

+
+ +
+
+regex(new_regex: str) CmdBuilder[source]
+

Add a regex to match but not as an argument.

+
+
Parameters:
+

new_regex – regex to add

+
+
Returns:
+

builder

+
+
+
+ +
+
+spaces(at_least_one: bool = False) CmdBuilder[source]
+

Add a regex for any number of spaces

+
+
Parameters:
+

at_least_one – true there must be at least one space; false there can be any number including zero

+
+
Returns:
+

builder

+
+
+
+ +
+
+string(length: None | int = None) CmdBuilder[source]
+

Add an argument which is a string of a given length (if blank string is any length)

+
+
Parameters:
+

length – length of string; None for any length

+
+
Returns:
+

builder

+
+
+
+ +
+
+stx() CmdBuilder[source]
+

Add the STX character (0x2) to the string.

+
+
Returns:
+

builder

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.utils.constants.html b/generated/lewis.utils.constants.html new file mode 100644 index 00000000..064a7a88 --- /dev/null +++ b/generated/lewis.utils.constants.html @@ -0,0 +1,154 @@ + + + + + + + + + lewis.utils.constants — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.utils.constants

+

List of constants which are useful in communications

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.utils.html b/generated/lewis.utils.html new file mode 100644 index 00000000..97ffc435 --- /dev/null +++ b/generated/lewis.utils.html @@ -0,0 +1,170 @@ + + + + + + + + + lewis.utils — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.utils

+

This package contains helpful utilities for people that are building emulators.

+

Submodules

+ + + + + + + + + + + + + + + +

byte_conversions

command_builder

A fluent command builder for lewis.

constants

List of constants which are useful in communications

replies

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/generated/lewis.utils.replies.html b/generated/lewis.utils.replies.html new file mode 100644 index 00000000..5ab87b14 --- /dev/null +++ b/generated/lewis.utils.replies.html @@ -0,0 +1,221 @@ + + + + + + + + + lewis.utils.replies — lewis documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

lewis.utils.replies

+

Members

+ + + + + + + + + +

conditional_reply

Decorator that executes the command and replies if the device has a member called 'property name' and it is True in a boolean context.

timed_reply

Decorator that inhibits a command and performs an action if call time is less than some minimum time delay between the current and last input.

+
+
+lewis.utils.replies.conditional_reply(property_name: str, reply: str | None = None) Callable[[P], T][source]
+

Decorator that executes the command and replies if the device has a member called +‘property name’ and it is True in a boolean context.

+

Example usage:

+
@conditional_reply("connected")
+def acknowledge_pressure(channel):
+    return ACK
+
+
+
+
Parameters:
+
    +
  • property_name – The name of the property to look for on the device

  • +
  • reply – Desired output reply string when condition is false

  • +
+
+
Returns:
+

The function returns as normal if property is true. +The command is not executed and there is no reply if property is false

+
+
+

:except AttributeError if the first argument of the decorated function (self) +does not contain .device or ._device +:except AttributeError if the device does not contain a property called property_name

+
+ +
+
+lewis.utils.replies.timed_reply(action: str, reply: str | None = None, minimum_time_delay: float = 0) Callable[[P], T][source]
+

Decorator that inhibits a command and performs an action if call time is less than +some minimum time delay between the current and last input.

+

Example usage:

+
@timed_reply(action="crash_pump", reply="WARNING: Input too quick", minimum_time_delay=150)
+def acknowledge_pressure(channel):
+    return ACK
+
+
+
+
Parameters:
+
    +
  • action – The name of the method to execute for on the device

  • +
  • reply – Desired output reply string when input time delay is less than the minimum

  • +
  • minimum_time_delay – The minimum time (ms) between commands sent to the device

  • +
+
+
Returns:
+

The function returns as normal if minimum delay exceeded. +The command is not executed and the action method is called on the device instead

+
+
+
+
:except AttributeError if the first argument of the decorated function (self)

does not contain .device or ._device

+
+
+

:except AttributeError if the device does not contain a property called action

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 00000000..1bc78eec --- /dev/null +++ b/genindex.html @@ -0,0 +1,1418 @@ + + + + + + + + Index — lewis documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

J

+ + + +
+ +

L

+ + + +
    +
  • last_command (lewis.devices.chopper.interfaces.epics_interface.ChopperEpicsInterface property) +
  • +
  • + lewis + +
  • +
  • + lewis.adapters + +
  • +
  • + lewis.adapters.epics + +
  • +
  • + lewis.adapters.modbus + +
  • +
  • + lewis.adapters.stream + +
  • +
  • + lewis.core + +
  • +
  • + lewis.core.adapters + +
  • +
  • + lewis.core.approaches + +
  • +
  • + lewis.core.control_client + +
  • +
  • + lewis.core.control_server + +
  • +
  • + lewis.core.devices + +
  • +
  • + lewis.core.exceptions + +
  • +
  • + lewis.core.logging + +
  • +
  • + lewis.core.processor + +
  • +
  • + lewis.core.simulation + +
  • +
  • + lewis.core.statemachine + +
  • +
  • + lewis.core.utils + +
  • +
  • + lewis.devices + +
  • +
  • + lewis.devices.chopper + +
  • +
  • + lewis.devices.chopper.devices + +
  • +
  • + lewis.devices.chopper.devices.bearings + +
  • +
  • + lewis.devices.chopper.devices.device + +
  • +
  • + lewis.devices.chopper.devices.states + +
  • +
  • + lewis.devices.chopper.interfaces + +
  • +
  • + lewis.devices.chopper.interfaces.epics_interface + +
  • +
  • + lewis.devices.julabo + +
  • +
  • + lewis.devices.julabo.devices + +
  • +
+ +

M

+ + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + +
+ +

V

+ + + +
+ + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..5747c202 --- /dev/null +++ b/index.html @@ -0,0 +1,287 @@ + + + + + + + + + Welcome to the Lewis documentation! — lewis documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Welcome to the Lewis documentation!

+

Lewis is a Python package that makes it easy to develop complex stateful device simulations. It +is licensed under GPL version 3 and the source is available on github, where you are welcome to +open new issues so the package can improve.

+

Documentation contents:

+
+
+

Quickstart

+ + + +
+

Release notes

+ +
+
+

API reference

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 00000000..d8e11a94 Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 00000000..b618e02b --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,399 @@ + + + + + + + + Python Module Index — lewis documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ l +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ l
+ lewis +
    + lewis.adapters +
    + lewis.adapters.epics +
    + lewis.adapters.modbus +
    + lewis.adapters.stream +
    + lewis.core +
    + lewis.core.adapters +
    + lewis.core.approaches +
    + lewis.core.control_client +
    + lewis.core.control_server +
    + lewis.core.devices +
    + lewis.core.exceptions +
    + lewis.core.logging +
    + lewis.core.processor +
    + lewis.core.simulation +
    + lewis.core.statemachine +
    + lewis.core.utils +
    + lewis.devices +
    + lewis.devices.chopper +
    + lewis.devices.chopper.devices +
    + lewis.devices.chopper.devices.bearings +
    + lewis.devices.chopper.devices.device +
    + lewis.devices.chopper.devices.states +
    + lewis.devices.chopper.interfaces +
    + lewis.devices.chopper.interfaces.epics_interface +
    + lewis.devices.julabo +
    + lewis.devices.julabo.devices +
    + lewis.devices.julabo.devices.device +
    + lewis.devices.julabo.devices.states +
    + lewis.devices.julabo.interfaces +
    + lewis.devices.julabo.interfaces.julabo_stream_interface_1 +
    + lewis.devices.julabo.interfaces.julabo_stream_interface_2 +
    + lewis.devices.linkam_t95 +
    + lewis.devices.linkam_t95.devices +
    + lewis.devices.linkam_t95.devices.device +
    + lewis.devices.linkam_t95.devices.states +
    + lewis.devices.linkam_t95.interfaces +
    + lewis.devices.linkam_t95.interfaces.stream_interface +
    + lewis.examples +
    + lewis.examples.dual_device +
    + lewis.examples.example_motor +
    + lewis.examples.modbus_device +
    + lewis.examples.simple_device +
    + lewis.examples.timeout_device +
    + lewis.scripts +
    + lewis.scripts.control +
    + lewis.scripts.run +
    + lewis.utils +
    + lewis.utils.byte_conversions +
    + lewis.utils.command_builder +
    + lewis.utils.constants +
    + lewis.utils.replies +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/quickstart.html b/quickstart.html new file mode 100644 index 00000000..e5d345ad --- /dev/null +++ b/quickstart.html @@ -0,0 +1,331 @@ + + + + + + + + + Quickstart Guide — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Quickstart Guide

+

This section aims to get you started with Lewis as quickly as possible. It is meant as a basic starting point for becoming familiar with Lewis, and to give you a broad overview of what it can do. As such, many features are skimmed over or skipped entirely. See the detailed documentation sections for a more complete overview of features.

+

This guide is presented as a step-by-step tutorial, so skipping sections may mean you will miss steps that are required for the examples to work.

+
+

Install Lewis

+

The recommended way to install Lewis is via PyPI and using a virtual environment. This guide assumes you have Python and Pip installed and in your PATH.

+
    +
  • Create a virtual environment (optional)

  • +
  • Install Lewis from PyPI, $ pip install lewis

  • +
+
+
+

Run the Motor Example

+

Once Lewis is installed, you can use it to start some of the example devices it ships with.

+

You can see which devices are available by just executing Lewis without parameters:

+
$ lewis
+Please specify a device to simulate. The following devices are available:
+    julabo
+    chopper
+    linkam_t95
+
+
+

Some additional, simpler examples are located in the lewis.examples module. You can tell Lewis which module to scan for devices using the -k parameter:

+
$ lewis -k lewis.examples
+Please specify a device to simulate. The following devices are available:
+    dual_device
+    simple_device
+    timeout_device
+    modbus_device
+    example_motor
+
+
+

For this guide, we will launch the example_motor:

+
$ lewis -k lewis.examples example_motor
+INFO lewis.DeviceBase: Creating device, setting up state machine
+INFO lewis.Simulation: Changed cycle delay to 0.1
+INFO lewis.Simulation: Changed speed to 1.0
+INFO lewis.Simulation: Starting simulation
+INFO lewis.AdapterCollection: Connecting device interface for protocol 'stream'
+INFO lewis.ExampleMotorStreamInterface.StreamServer: Listening on 0.0.0.0:9999
+
+
+

The example motor is a TCP Stream device and listens on port 9999 on all adapters by default.

+
+
+

Connect to Motor via Telnet

+

With the last command from the previous section still running, open another terminal window.

+

Since the example motor conveniently uses CRLF line terminators, we can use telnet to talk to it:

+
$ telnet localhost 9999
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+
+
+

You’re now connected to the TCP Stream interface of the example motor device. It understands the following commands:

+ + + + + + + + + + + + + + + + + + + + + + + +

Command

Meaning

S?

get status

P?

get position

T?

get target

T=%f

set target

H

stop movement

+

You can get more details, and details on the interface of any device, by using the -i or --show-interface argument:

+
$ lewis -k lewis.examples example_motor -i
+
+
+

Note that the commands are case sensitive. Try entering a few commands in the Telnet session:

+
S?
+idle
+P?
+0.0
+T=20.0
+T=20.0
+S?
+moving
+P?
+9.106584
+
+
+

See the source code of the example motor if you want to see what makes it tick.

+
+
+

Connect to Motor via Control Client

+

In addition to the simulated TCP Stream interface, Lewis provides a so-called Control Server interface, which allows you to bypass the normal device protocol and access both device and simulation parameters directly while the simulation is running. This can be very useful for debugging and diagnostics, without having to modify the main device interface.

+

Remote access is disabled by default and enabled only if you provide the -r argument when starting Lewis. Stop the previously launched instance of Lewis by pressing Ctrl-C and run Lewis again with the -r parameter to enable remote access like this:

+
$ lewis -r localhost:10000 -k lewis.examples example_motor
+
+
+

Lewis ships with a Control Client commandline tool that allows you to connect to it. It also has an -r argument but for the client it defaults to localhost:10000, which is why it is recommended to use the same value above.

+

Open another terminal session. If you installed Lewis in a virtual environment, make sure to activate it in the new terminal session so that Lewis is available.

+

Running lewis-control without any parameter displays the objects available to interact with:

+
$ lewis-control
+device
+interface
+simulation
+
+
+

You can think of these as root nodes in a tree that lewis-control allows you to traverse. Passing one of them as an argument shows you what is available below that level:

+
$ lewis-control device
+Type: SimulatedExampleMotor
+Properties (current values):
+    position    (20.0)
+    speed       (2.0)
+    state       (idle)
+    target      (20.0)
+Methods:
+    stop
+
+
+

Going down one more level retrieves the value of a single property, or calls a method (without passing arguments):

+
$ lewis-control device target
+0.0
+
+
+

And by specifying additional argument(s) we can set properties (or pass arguments to methods):

+
$ lewis-control device target 100.0
+$ lewis-control device
+Type: SimulatedExampleMotor
+Properties (current values):
+    position    (29.159932)
+    speed       (2.0)
+    state       (moving)
+    target      (100.0)
+Methods:
+    stop
+$ lewis-control device stop
+[78.64038600000002, 78.64038600000002]
+$ lewis-control device
+Type: SimulatedExampleMotor
+Properties (current values):
+    position    (78.640386)
+    speed       (2.0)
+    state       (idle)
+    target      (78.640386)
+Methods:
+    stop
+
+
+

Note that, as you go along, you can also use a Telnet session in another terminal to issue commands or request information, and that the state of the device will be consistent between the two connections.

+

Aside from the simulated device itself, you can also access and modify parameters of the simulation and network interface(s):

+
$ lewis-control simulation
+$ lewis-control interface
+
+
+

See the respective sections of documentation for more details.

+
+
+

Control Motor via Control API

+

While the command line client is convenient for manual diagnostics and debugging, you may find the Control API more useful for automated testing. It exposes all the same functionality available on the CLI via a Python library (In fact, that is how the CLI client is implemented).

+

If you installed Lewis in a virtual environment, make sure you activate it:

+
$ . myenv/bin/activate
+
+
+

Usually, you would use this API to write a Python script, but for demo purposes we will just use the interactive Python client:

+
$ python
+>>> from lewis.core.control_client import ControlClient
+>>>
+>>> client = ControlClient(host='localhost', port='10000')
+>>> motor = client.get_object('device')
+>>>
+>>> motor.target
+78.64038600000002
+>>> motor.target = 20.0
+>>> motor.state
+u'moving'
+>>> motor.stop()
+[45.142721999999964, 45.142721999999964]
+>>> motor.state
+u'idle'
+>>> motor.position
+45.142721999999964
+
+
+

As with the previous sections, you can also interact with the motor using any of the other interfaces as you are doing this and the state will always be consistent between them.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_0_0.html b/release_notes/release_1_0_0.html new file mode 100644 index 00000000..cc32a8c7 --- /dev/null +++ b/release_notes/release_1_0_0.html @@ -0,0 +1,170 @@ + + + + + + + + + Release 1.0 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.0

+

The initial release of Lewis (at that point still plankton). These release notes have been +compiled after the release as we were not keeping any release notes at that time.

+
+

Features

+
    +
  • Cycle-based, deterministic device simulations based on finite state machines

  • +
  • Control over the simulation’s time granularity and speed (slow motion, fast forward)

  • +
  • Simulation and device control via command line and via optional network service

  • +
  • Two ready to use simulated devices using different protocols:

    +
      +
    • ESS chopper abstraction (CHIC) using EPICS Channel Access

    • +
    • Linkam T95 temperature controller using TCP Stream protocol

    • +
    +
  • +
  • Documentation in Markdown format for viewing in Github, both for users and developers

  • +
  • Examples for implementing Stream protocol based devices

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_0_1.html b/release_notes/release_1_0_1.html new file mode 100644 index 00000000..0c9e36b7 --- /dev/null +++ b/release_notes/release_1_0_1.html @@ -0,0 +1,180 @@ + + + + + + + + + Release 1.0.1 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.0.1

+

This release is the first one under the name “Lewis”. Its main purpose is to make a PyPI package +available as well as online documentation under the new name.

+

Nevertheless version 1.0.1 fixes some bugs and introduces a few new features that were originally +scheduled for release 1.1 but had already been finished at the time of the release.

+
+

New features

+
    +
  • It is now possible to obtain device interface documentation via the command line +and the control server, making it easier to communicate with unfamiliar devices. +For command line invocation there is a new flag: lewis -i linkam_t95. +Thanks to David Michel for requesting this feature.

  • +
  • Lewis is now available as a PyPI-package and can be installed via pip install lewis.

  • +
  • Documentation is now generated via Sphinx and has been made available online on RTD_.

  • +
+
+
+

Bug fixes and other improvements

+
    +
  • The control server can now be bound to a hostname instead of an IP-address (very useful +for localhost in particular).

  • +
  • pcaspy is now an optional requirement that has to be enabled explicitly in the requirements.txt +file or installation via pip install lewis[epics].

  • +
  • Error messages displayed on the command line have been improved.

  • +
  • A flake8 job has been added to the continuous integration pipeline to enforce Python +style guidelines in the codebase.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_0_2.html b/release_notes/release_1_0_2.html new file mode 100644 index 00000000..e122e676 --- /dev/null +++ b/release_notes/release_1_0_2.html @@ -0,0 +1,225 @@ + + + + + + + + + Release 1.0.2 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.0.2

+

This version of Lewis was released on January 26th, 2017. A few bugs have been fixed and a lot +of functionality has been added. The most notable changes are preliminary Modbus protocol support +and logging capabilities which make debugging easier.

+
+

New features

+
    +
  • A preliminary Modbus Adapter has been added. The current version is mainly aimed at what is +currently required by the IBEX team for the nanodac. Since all that is needed is writing +and reading back from memory via the Modbus protocol, bindings to Device attributes or +functions have not been implemented yet. We will add these in a future version.

    +

    The current version supports:

    +
      +
    • Eight common Function Codes (0x01 through 0x06, 0x0F and 0x10)

    • +
    • Overlaid memory segments (using the same databank for di and co for example)

    • +
    • Modbus Exceptions for invalid Function Codes, bad memory addresses, invalid data, etc

    • +
    • Request frames may arrive in arbitrary chunks of multiple or partial frames

    • +
    +

    For a usage example, see examples/modbus_device.

    +
  • +
  • Logging capabilities have been added to the framework through the standard Python logging_ +module. The lewis-script logs messages to stderr, the level can be set using a new flag +-o/--output-level.

    +

    All devices have a new member log, which can be used like this:

    +
  • +
+
    class SomeDevice(Device):
+        def some_method(self, param):
+            self.log.debug('some_method called with param=%s', param)
+
+
+

This new behavior is also supported by lewis.core.statemachine.State, +so that changes in device state can be logged as well.

+
    +
  • A simulation for a Julabo FP50 waterbath was kindly contributed by Matt Clarke. It is +communicating through TCP Stream and offers two different protocol versions. The new device +can be started like the other available devices:

    +
    $ lewis -p julabo-version-1 julabo
    +
    +
    +
  • +
  • Exposing devices via TCP Stream has been made easier. It is now possible to define commands +that expose lambda-functions, named functions and data attributes (with separate read/write +patterns). See the updated documentation of :mod:lewis.adapters.stream.

  • +
  • TCP Stream based devices are now easier to test with telnet due to a new adapter argument. +The new -t-flag makes the device interface “telnet compatible”:

    +
    $ lewis linkam_t95 -- -t
    +
    +
    +

    Instead of the native in- and out-terminator of the device, the interface now looks for \r\n.

    +
  • +
  • The lewis.adapters.epics.PV-class has been extended to allow for meta data updates +at runtime. A second property can now be specified that returns a dictionary to update the +PV’s metadata such as limits or alarm states.

  • +
  • It is now possible to change multiple device parameters through lewis-control:

  • +
+
$ lewis-control simulation set_device_parameters "{'target_speed': 1, 'target_phase': 20}"
+
+
+

Thanks to the IBEX team for requesting this.

+
+
+

Bug fixes and other improvements

+
    +
  • Virtually disconnecting devices via the control server now actually closes all network +connections and shuts down any running servers, making it impossible to re-connect to the +device in that state. Virtually re-connecting the device returns the behavior back to normal.

  • +
  • If a device contained members that are not JSON serializable, displaying the device’s API +using the lewis-control script failed. This has been fixed, instead a message is now printed +that informs the user about why fetching the attribute value failed. Thanks to Adrian Potter +for reporting this issue.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_0_3.html b/release_notes/release_1_0_3.html new file mode 100644 index 00000000..ee6eb258 --- /dev/null +++ b/release_notes/release_1_0_3.html @@ -0,0 +1,318 @@ + + + + + + + + + Release 1.0.3 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.0.3

+

This version was released on March 24th, 2017. In this release, the lewis.adapters.epics- +module has received some updates. Some important groundwork for future improvements has been +laid as well, which resulted in the ability to switch device setups at runtime via the control +server and a new command line syntax for configuring communications. The control server and client +have been improved as well.

+
+

Command line interface change

+

The way options are passed to the adapters has changed completely, the functionality has been +merged into the -p-argument, which has a new long version now, --adapter-options.

+

For the default adapter options, it is still possible to use the lewis-command with -p +in the same way as before:

+
$ lewis -p stream linkam_t95
+
+
+

To supply options, such as the address and port to bind to, the argument accepts an extended +dictionary syntax now:

+
$ lewis linkam_t95 -p "stream: {bind_address: localhost, port: 9998}"
+
+
+

The space after each colon is significant, it can not be left out. For strings containing +special characters, such as colons, it is necessary to quote them:

+
$ lewis chopper -p "epics: {prefix: 'PREF:'}"
+
+
+

To see what options can be specified, use the new -L/--list-adapter-options flag:

+
$ lewis chopper -p epics -L
+
+
+
+
+

New features

+
    +
  • Writing devices with an EPICS interface has been made more convenient for cases where the device +does not have properties, but does have getter and setter methods. +lewis.adapters.epics.PV has been extended to accept a wider range of values for +target_property and meta_data_property, for example method names:

  • +
+
     class FooDevice(Device):
+         _foo = 3
+
+         def get_foo(self):
+             return self._foo * 3
+
+     class FooDeviceInterface(EpicsAdapter):
+         pvs = {
+             'Foo': PV('get_foo')
+         }
+
+
+

For read/write cases, a tuple of names can be supplied. Instead of method names it is also +allowed to specify callables, for example functions or lambda expressions. In that case, the +signature of the function is checked. See also the new example in +lewis.examples.epics_device.

+
    +
  • The device setup (specified in the setups-dict or module inside the device module) +can be changed at runtime through the control server. It is not possible to switch to +another device, only setups of the same device can be used. To query available setups:

  • +
+
   $ lewis-control simulation setups
+
+
+

Then, to actually activate the new setup, assuming it is called new_setup:

+
   $ lewis-control simulation switch_setup new_setup
+
+
+
    +
  • It has been made easier to deposit devices in an external module while maintaining control over +compatibility with the rest of the Lewis-framework. Lewis now checks for a version specification +in each device module against the framework version before obtaining devices, adapters and +setups from it. Please add such a version specification to your devices.

    +

    This way using devices from different sources becomes more reliable for users with different +versions of Lewis, or hint them to update. By default, Lewis won’t start if a device specifies +another framework version, but this behavior can be overridden by using the new flag +-R/--relaxed-versions:

    +

    In this case the simulation will start, but a warning will still be logged so that this can be +identified as a potential source of errors later on.

    +
  • +
  • A new flag -V/--verify has been added to the lewis-script. When activated, it sets +the output level to debug and exits before actually starting the simulation. This can +help diagnose problems with device modules or input parameters.

  • +
+
+
+

Bug fixes and other improvements

+
    +
  • The functionality for disconnecting and reconnecting a device’s communication interfaces that +used to be accessible via lewis-control through the simulation has been moved into a +separate channel called interface. To disconnect a device use:

    +

    In general, more fine-grained control over the device’s communication is now possible.

    +
  • +
  • Both lewis.core.control_server.ControlServer and +lewis.core.control_client.ControlClient were subject to some improvements, most +notably a settable timeout for requests was added so that incomplete requests do not cause the +client to hang anymore. In lewis-control script, a new -t/--timeout argument was added +to make use of that new functionality.

  • +
  • Only members defined as part of the device class are listed when using lewis-control device. +lewis-control generally no longer lists inherited framework functions such as log, +add_processor, etc.

  • +
+
+
+

Upgrade Guide

+

The following changes have to be made to upgrade code working with Lewis 1.0.2 to work with +Lewis 1.0.3:

+
    +
  • Any scripts or code starting Lewis with the old style adapter parameters need to be updated to +the new style adapter options.

    +

    For EPICS adapters:

    +
  • +
+
      Old style:
+      $ lewis chopper
+      $ lewis chopper -p epics
+      $ lewis chopper -p epics -- -p SIM:
+      $ lewis chopper -- --prefix SIM:
+      New style:
+      $ lewis chopper
+      $ lewis chopper -p epics
+      $ lewis chopper -p "epics: {prefix: 'SIM:'}"
+
+
+

For TCP Stream adapters:

+
       Old style:
+       $ lewis linkam_t95
+       $ lewis linkam_t95 -p stream
+       $ lewis linkam_t95 -p stream -- -b 127.0.0.1 -p 9999 -t
+       $ lewis linkam_t95 -- --bind_address 127.0.0.1 --port 9999 --telnet_mode
+       New style:
+       $ lewis linkam_t95
+       $ lewis linkam_t95 -p stream
+       $ lewis linkam_t95 -p "stream: {bind_address: 127.0.0.1, port: 9999, telnet_mode: True}"
+
+
+

For Modbus adapters:

+
      Old style:
+      $ lewis -k lewis.examples modbus_device
+      $ lewis -k lewis.examples modbus_device -p modbus
+      $ lewis -k lewis.examples modbus_device -p modbus -- -b 127.0.0.1 -p 5020
+      $ lewis -k lewis.examples modbus_device -- --bind_address 127.0.0.1 --port 5020
+      New style:
+      $ lewis -k lewis.examples modbus_device
+      $ lewis -k lewis.examples modbus_device -p modbus
+      $ lewis -k lewis.examples modbus_device -p "modbus: {bind_address: 127.0.0.1, port: 5020}"
+
+
+
    +
  • Devices must now specify a framework_version in the global namespace of their top-level +__init__.py, like this:

  • +
+
   framework_version = '1.0.3'
+
+
+

This will need to be updated with every release. If this version is missing or does not match +the current Lewis framework version, attempting to run the device simulation will fail with a +message informing the user of the mismatch. This can be bypassed by starting Lewis with the +following parameter:

+
   $ lewis linkam_t95 -R
+   $ lewis linkam_t95 --relaxed-versions
+
+
+

Warning: in the next release, specifying framework_version becomes optional and +--relaxed-versions is renamed to --ignore-versions.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_1_0.html b/release_notes/release_1_1_0.html new file mode 100644 index 00000000..00098c91 --- /dev/null +++ b/release_notes/release_1_1_0.html @@ -0,0 +1,267 @@ + + + + + + + + + Release 1.1 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.1

+

In this release, some key changes to the core framework have been implemented. It is now possible +to have more than one communication interface for a device, which enables some interesting use +cases like partial interfaces, or multiple communication protocols accessing the same device. One +prerequisite for this feature was running the network services in different threads than the +device simulation.

+

Another key change, one that requires some minor changes to existing devices (see upgrade guide), +was that the communication interface definition has been completely separate from the network +services handling the network communication.

+

Besides these major improvements, there have been a number of smaller improvements and new +features, and Lewis now also has a logo (see below).

+
+

New features

+
    +
  • It is now possible to have devices with more than one communication interface. The -p-option +can be supplied multiple times:

  • +
+
   $ lewis some_device -p protocol1 -p protocol2
+
+
+

When no -p option is specified, the script behaves as before (use default protocol if +possible or produce an error message). To start a simulation without any device communication, +use the new -n/--no-interface option:

+
   $ lewis some_device -n
+
+
+

It is not possible to use both -p and -n at the same time, this results in an error +message.

+

The epics_device example has been renamed to dual_device and extended to include a +second interface definition, so it exposes the device state via two different protocols:

+
   $ lewis -k lewis.examples dual_device -p epics -p stream
+
+
+
    +
  • lewis.adapters.stream has been extended. Besides regular expressions, it is now +possible to use scanf format specifications to define commands. This makes handling +of for example floating point numbers much more convenient:

  • +
+
   from lewis.adapters import StreamInterface, Cmd, scanf
+
+   class SomeInterface(StreamInterface):
+      commands = {
+         Cmd(lambda x: x**2, scanf('SQ %f'))
+      }
+
+
+

lewis.adapters.stream.scanf provides argument mappings for the matched arguments +automatically, so it is optional to pass them to Cmd. In the case outlined above, the +argument is automatically converted to float.

+

If a string is specified directly (instead of scanf(...)), it is treated as a regular +expression like in earlier versions.

+

Internally, the scanf package is used for handling these patterns, please check the package +documentation for all available format specifiers. Thanks to @joshburnett for accepting +a small patch to the package that made the package easier to integrate into Lewis.

+
    +
  • The control client, lewis-control, now provides a version argument via --version or -v.

  • +
+
   $ lewis-control -v
+
+
+
+
+

Bug fixes and other improvements

+
    +
  • Lewis now has a logo. It is based on a state machine with one state that is entered and +repeated infinitely - like the simulation cycles in Lewis.

    +

    For low-resolution images or settings with little space, there is also a simplified version.

    +

    The logo was made using inkscape, the font used in the logo is Rubik (in the SVG itself, +the text was converted into a path, so that the font does not need to be installed for the logo +to render correctly). The two PNGs and also the SVGs are in the source repository, feel +free to include them in presentations or posters.

    +
  • +
  • Adapters now run in a different thread than the simulation itself. The consequence of this is +that slow network communication or expensive computations in the device do not influence +one another anymore. Otherwise, communication still works exactly like in previous versions.

  • +
  • The behavior of the framework_version-variable for devices that was introduced in version +1.0.3 has been modified to make it easier to convert from older versions of Lewis.

    +

    With the default options of the lewis-command, devices that do not specify the variable +will be loaded after logging a warning. An error message is only displayed when strict +version checking is enabled through the new -S/--strict-versions-flag.

    +

    The option to ignore version mismatches has been renamed to -I/--ignore-versions. When +that flag is specified, any device regardless of the contents of framework_version is +loaded, but a warning is still logged.

    +

    Specifying the framework_version variable is still encouraged as it can contribute to +more certainty on the user side as to whether a device can function with a certain function +of Lewis.

    +
  • +
+
+
+

Upgrade guide

+
    +
  • Due to a change to how Adapters and Devices work together, device interfaces are not +inheriting from Adapter-classes anymore. Instead, there are dedicated Interface classes. +They are located in the same modules as the Adapters, so only small changes are necessary:

    +

    Old:

    +
  • +
+
    from lewis.adapters.stream import StreamAdapter, Cmd
+
+    class DeviceInterface(StreamAdapter):
+        pass
+
+
+

New:

+
    from lewis.adapters.stream import StreamInterface, Cmd
+
+    class DeviceInterface(StreamInterface):
+        pass
+
+
+

The same goes for EpicsAdapter and ModbusAdapter, which must be modified to +EpicsInterface and ModbusInterface respectively.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_1_1.html b/release_notes/release_1_1_1.html new file mode 100644 index 00000000..ccb558cc --- /dev/null +++ b/release_notes/release_1_1_1.html @@ -0,0 +1,163 @@ + + + + + + + + + Release 1.1.1 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.1.1

+

This is a pure bug fix release that removes three problems that were overlooked in the 1.1 release.

+
+

Bug fixes

+
    +
  • Version strings in framework_version are now coerced, so that for example 1.1 becomes +1.1.0 automatically.

  • +
  • Lewis does no longer hang forever when starting a network service fails.

  • +
  • Switching setups at runtime works again as in release 1.0.3, in 1.1. it had been disabled due +to an oversight.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_2_0.html b/release_notes/release_1_2_0.html new file mode 100644 index 00000000..8b736cb4 --- /dev/null +++ b/release_notes/release_1_2_0.html @@ -0,0 +1,209 @@ + + + + + + + + + Release 1.2 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.2

+

After releasing 1.1.0 and 1.1.1, we decided to move to a more reproducible testing workflow that +is operating closer to the packages that are released in the end. This only affects developers +who work on the Lewis code base. In addition, lewis.adapters.epics was improved a bit +with better error messages and more reasonable PV update frequencies. The lewis-control +server now runs in its own thread, which has made it more responsive.

+
+

New Features

+
    +
  • StreamInterface has been improved to support a readtimeout attribute which is analogous +to the ReadTimeout system variable in Protocol files. The value of readtimeout determines how +many milliseconds to wait for more input, once we have started receiving data for a command. Under +normal circumstances, this timeout being triggered is an error and causes the incoming buffer to be +flushed and a handle_error call in the device interface. However, if the in_terminator +attribute is empty, this timeout is treated as the command terminator instead.

    +

    readtimeout defaults to 100 (ms). +readtimeout = 0 disables this feature entirely.

    +

    The effective resolution is currently limited 10 ms increments due to the fixed adapter cycle rate.

    +
  • +
  • The lewis.core.control_server.ControlServer is now running in its own thread, separate +from the simulation. As a result, lewis-control and the Python Control API are now much more +responsive. This is because requests are processed asynchronously and, therefore, multiple +requests can be processed per simulation cycle.

  • +
+
+
+

Bugfixes and other improvements

+
    +
  • Error messages in the binding step of :class:PV have been improved. It is now easier to find +the source of common problems (missing properties, spelling errors).

  • +
  • PVs are only updated if the underlying value has actually changed. Changes to metadata are processed +and logged separately. This leads to cleaner logs even at small values for poll_interval.

  • +
  • Using yaml.safe_load instead of yaml.load as a security precaution.

  • +
+
+
+

Changes for developers

+
    +
  • The lewis.py and lewis-control.py files have been removed, because especially the former +created some problems with the new package structure by interfering with the tests and docs- +generation.

    +

    For using Lewis when it’s installed through pip, this does not change anything, but for +development of the Lewis framework (not of devices), it is now strongly recommended to do so +in a separate virtual environment, installing Lewis from source as an editable package. Details +on this can be found in the updated developer_guide.

    +
  • +
  • Tests are now run with pytest instead of nose. In addition, a tox configuration has been +added for more reproducible tests with different interpreters.

    +

    The first run may take a bit longer, since each step is run in a fresh virtual environment that tox +creates automatically.

    +

    To run specific tests, for example to verify that building the docs works, use the -e flag +of tox

    +

    To see all tests that are available, including a short description, use tox -l -v.

    +
  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_2_1.html b/release_notes/release_1_2_1.html new file mode 100644 index 00000000..79da7d88 --- /dev/null +++ b/release_notes/release_1_2_1.html @@ -0,0 +1,167 @@ + + + + + + + + + Release 1.2.1 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.2.1

+

This is a minor release that fixes one bug and adds documentation.

+
+

Documentation

+
    +
  • Added quickstart guide.

  • +
  • Removed references to plankton.

  • +
+
+
+

Bug fixes

+
    +
  • Improved exception handling for module imports

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_2_2.html b/release_notes/release_1_2_2.html new file mode 100644 index 00000000..a6c6f980 --- /dev/null +++ b/release_notes/release_1_2_2.html @@ -0,0 +1,159 @@ + + + + + + + + + Release 1.2.2 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.2.2

+

This is a minor release that adds a feature for sending unsolicited messages.

+
+

New Features

+
    +
  • Added function for sending unsolicited messages to a device. See here.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_3_0.html b/release_notes/release_1_3_0.html new file mode 100644 index 00000000..a225d766 --- /dev/null +++ b/release_notes/release_1_3_0.html @@ -0,0 +1,162 @@ + + + + + + + + + Release 1.3.0 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.3.0

+

This is a major release because it removes Python 2 support. Any other changes are minor.

+
+

Changes for developers

+
    +
  • Added pre-commit checking for formatting, flake8 and isort

  • +
  • Uses Black for formatting

  • +
  • Added a system test for checking that the overall system is functioning

  • +
  • Added scripts for running lewis and lewis-control without installing

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_3_1.html b/release_notes/release_1_3_1.html new file mode 100644 index 00000000..b9f9efab --- /dev/null +++ b/release_notes/release_1_3_1.html @@ -0,0 +1,161 @@ + + + + + + + + + Release 1.3.1 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.3.1

+

This is a minor release that adds some utilities and fixes some issues with Python 3 bytes/str.

+
+

Changes for developers

+
    +
  • Added utilities developed at ISIS.

  • +
  • Use bytes in unsolicited reply (Python 3 fix)

  • +
  • Add ability to send bytes to lewis

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_3_2.html b/release_notes/release_1_3_2.html new file mode 100644 index 00000000..fd5ceaae --- /dev/null +++ b/release_notes/release_1_3_2.html @@ -0,0 +1,159 @@ + + + + + + + + + Release 1.3.2 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.3.2

+

This is a minor release that adds functionality that tries to handle mixed bytes and strings gracefully.

+
+

Changes for developers

+
    +
  • Tries to handle mixed bytes and strings gracefully.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_1_3_3.html b/release_notes/release_1_3_3.html new file mode 100644 index 00000000..7ee75a2e --- /dev/null +++ b/release_notes/release_1_3_3.html @@ -0,0 +1,150 @@ + + + + + + + + + Release 1.3.3 — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release 1.3.3

+

This is a minor release that updates the supported versions list.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/release_notes/release_notes.html b/release_notes/release_notes.html new file mode 100644 index 00000000..aaaa703e --- /dev/null +++ b/release_notes/release_notes.html @@ -0,0 +1,165 @@ + + + + + + + + + Release notes — lewis documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 00000000..a9b69dd3 --- /dev/null +++ b/search.html @@ -0,0 +1,144 @@ + + + + + + + + Search — lewis documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 00000000..e46e12f5 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"API": [[0, null]], "API reference": [[57, null]], "Accessing the Device Communication Interface": [[75, "accessing-the-device-communication-interface"]], "Adapter Specifics": [[73, null]], "Adding setups": [[4, "adding-setups"]], "Bug fixes": [[64, "bug-fixes"], [66, "bug-fixes"]], "Bug fixes and other improvements": [[60, "bug-fixes-and-other-improvements"], [61, "bug-fixes-and-other-improvements"], [62, "bug-fixes-and-other-improvements"], [63, "bug-fixes-and-other-improvements"]], "Bugfixes and other improvements": [[65, "bugfixes-and-other-improvements"]], "Build PyPI Package": [[3, "build-pypi-package"]], "Build and Finalize Release": [[3, "build-and-finalize-release"]], "Changes for developers": [[65, "changes-for-developers"], [68, "changes-for-developers"], [69, "changes-for-developers"], [70, "changes-for-developers"]], "Command line interface change": [[62, "command-line-interface-change"]], "Command line tools": [[74, null]], "Compatibility with framework versions": [[4, "compatibility-with-framework-versions"]], "Connect to Motor via Control Client": [[58, "connect-to-motor-via-control-client"]], "Connect to Motor via Telnet": [[58, "connect-to-motor-via-telnet"]], "Control Client Python API": [[75, "control-client-python-api"]], "Control Motor via Control API": [[58, "control-motor-via-control-api"]], "Cycle-driven": [[2, "cycle-driven"]], "Developer guide": [[57, null]], "Developing Lewis": [[1, null]], "Device analysis": [[4, "device-analysis"]], "Documentation": [[66, "documentation"]], "EPICS Adapter Specifics": [[73, "epics-adapter-specifics"]], "Features": [[59, "features"]], "Framework Details": [[2, null]], "Further steps": [[4, "further-steps"]], "Git Milestones": [[3, "git-milestones"]], "Git Release": [[3, "git-release"]], "GitHub Release": [[3, "github-release"]], "Implementing the device interface": [[4, "implementing-the-device-interface"]], "Implementing the device simulation": [[4, "implementing-the-device-simulation"]], "Install Lewis": [[58, "install-lewis"]], "Installation from source": [[77, "installation-from-source"]], "Installation via pip": [[77, "installation-via-pip"]], "Logging": [[4, "logging"]], "Merge Changes": [[3, "merge-changes"]], "More Examples": [[4, "more-examples"]], "New Features": [[65, "new-features"], [67, "new-features"]], "New features": [[60, "new-features"], [61, "new-features"], [62, "new-features"], [63, "new-features"]], "Preparations": [[4, "preparations"]], "Preparing for Release": [[3, "preparing-for-release"]], "Quickstart": [[57, "quickstart"], [57, null]], "Quickstart Guide": [[58, null]], "Release 1.0": [[59, null]], "Release 1.0.1": [[60, null]], "Release 1.0.2": [[61, null]], "Release 1.0.3": [[62, null]], "Release 1.1": [[63, null]], "Release 1.1.1": [[64, null]], "Release 1.2": [[65, null]], "Release 1.2.1": [[66, null]], "Release 1.2.2": [[67, null]], "Release 1.3.0": [[68, null]], "Release 1.3.1": [[69, null]], "Release 1.3.2": [[70, null]], "Release 1.3.3": [[71, null]], "Release Checklist": [[3, null]], "Release Notes": [[3, "release-notes"]], "Release notes": [[57, null], [72, null]], "Remote Access to Devices": [[75, null]], "Remote Access to Simulation Parameters": [[76, null]], "Run the Motor Example": [[58, "run-the-motor-example"]], "Running from source": [[77, "running-from-source"]], "Statemachine": [[2, "statemachine"]], "Stream Adapter Specifics": [[73, "stream-adapter-specifics"]], "Test PyPI Package": [[3, "test-pypi-package"]], "Unit tests": [[4, "unit-tests"]], "Update Version": [[3, "update-version"]], "Upgrade Guide": [[62, "upgrade-guide"]], "Upgrade guide": [[63, "upgrade-guide"]], "Upload PyPI Package": [[3, "upload-pypi-package"]], "Usage with Python": [[77, null]], "User facing documentation": [[4, "user-facing-documentation"]], "User guide": [[57, null]], "Value Interpretation and Syntax": [[75, "value-interpretation-and-syntax"]], "Virtual environments": [[77, "virtual-environments"]], "Welcome to the Lewis documentation!": [[57, null]], "Writing Device Simulators": [[4, null]], "lewis": [[5, null], [74, "lewis"]], "lewis-control": [[74, "lewis-control"]], "lewis.adapters": [[6, null]], "lewis.adapters.epics": [[7, null]], "lewis.adapters.modbus": [[8, null]], "lewis.adapters.stream": [[9, null]], "lewis.core": [[10, null]], "lewis.core.adapters": [[11, null]], "lewis.core.approaches": [[12, null]], "lewis.core.control_client": [[13, null]], "lewis.core.control_server": [[14, null]], "lewis.core.devices": [[15, null]], "lewis.core.exceptions": [[16, null]], "lewis.core.logging": [[17, null]], "lewis.core.processor": [[18, null]], "lewis.core.simulation": [[19, null]], "lewis.core.statemachine": [[20, null]], "lewis.core.utils": [[21, null]], "lewis.devices": [[22, null]], "lewis.devices.chopper": [[23, null]], "lewis.devices.chopper.devices": [[24, null]], "lewis.devices.chopper.devices.bearings": [[25, null]], "lewis.devices.chopper.devices.device": [[26, null]], "lewis.devices.chopper.devices.states": [[27, null]], "lewis.devices.chopper.interfaces": [[28, null]], "lewis.devices.chopper.interfaces.epics_interface": [[29, null]], "lewis.devices.julabo": [[30, null]], "lewis.devices.julabo.devices": [[31, null]], "lewis.devices.julabo.devices.device": [[32, null]], "lewis.devices.julabo.devices.states": [[33, null]], "lewis.devices.julabo.interfaces": [[34, null]], "lewis.devices.julabo.interfaces.julabo_stream_interface_1": [[35, null]], "lewis.devices.julabo.interfaces.julabo_stream_interface_2": [[36, null]], "lewis.devices.linkam_t95": [[37, null]], "lewis.devices.linkam_t95.devices": [[38, null]], "lewis.devices.linkam_t95.devices.device": [[39, null]], "lewis.devices.linkam_t95.devices.states": [[40, null]], "lewis.devices.linkam_t95.interfaces": [[41, null]], "lewis.devices.linkam_t95.interfaces.stream_interface": [[42, null]], "lewis.examples": [[43, null]], "lewis.examples.dual_device": [[44, null]], "lewis.examples.example_motor": [[45, null]], "lewis.examples.modbus_device": [[46, null]], "lewis.examples.simple_device": [[47, null]], "lewis.examples.timeout_device": [[48, null]], "lewis.scripts": [[49, null]], "lewis.scripts.control": [[50, null]], "lewis.scripts.run": [[51, null]], "lewis.utils": [[52, null]], "lewis.utils.byte_conversions": [[53, null]], "lewis.utils.command_builder": [[54, null]], "lewis.utils.constants": [[55, null]], "lewis.utils.replies": [[56, null]]}, "docnames": ["_api", "developer_guide/developing_lewis", "developer_guide/framework_details", "developer_guide/release_checklist", "developer_guide/writing_devices", "generated/lewis", "generated/lewis.adapters", "generated/lewis.adapters.epics", "generated/lewis.adapters.modbus", "generated/lewis.adapters.stream", "generated/lewis.core", "generated/lewis.core.adapters", "generated/lewis.core.approaches", "generated/lewis.core.control_client", "generated/lewis.core.control_server", "generated/lewis.core.devices", "generated/lewis.core.exceptions", "generated/lewis.core.logging", "generated/lewis.core.processor", "generated/lewis.core.simulation", "generated/lewis.core.statemachine", "generated/lewis.core.utils", "generated/lewis.devices", "generated/lewis.devices.chopper", "generated/lewis.devices.chopper.devices", "generated/lewis.devices.chopper.devices.bearings", "generated/lewis.devices.chopper.devices.device", "generated/lewis.devices.chopper.devices.states", "generated/lewis.devices.chopper.interfaces", "generated/lewis.devices.chopper.interfaces.epics_interface", "generated/lewis.devices.julabo", "generated/lewis.devices.julabo.devices", "generated/lewis.devices.julabo.devices.device", "generated/lewis.devices.julabo.devices.states", "generated/lewis.devices.julabo.interfaces", "generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1", "generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2", "generated/lewis.devices.linkam_t95", "generated/lewis.devices.linkam_t95.devices", "generated/lewis.devices.linkam_t95.devices.device", "generated/lewis.devices.linkam_t95.devices.states", "generated/lewis.devices.linkam_t95.interfaces", "generated/lewis.devices.linkam_t95.interfaces.stream_interface", "generated/lewis.examples", "generated/lewis.examples.dual_device", "generated/lewis.examples.example_motor", "generated/lewis.examples.modbus_device", "generated/lewis.examples.simple_device", "generated/lewis.examples.timeout_device", "generated/lewis.scripts", "generated/lewis.scripts.control", "generated/lewis.scripts.run", "generated/lewis.utils", "generated/lewis.utils.byte_conversions", "generated/lewis.utils.command_builder", "generated/lewis.utils.constants", "generated/lewis.utils.replies", "index", "quickstart", "release_notes/release_1_0_0", "release_notes/release_1_0_1", "release_notes/release_1_0_2", "release_notes/release_1_0_3", "release_notes/release_1_1_0", "release_notes/release_1_1_1", "release_notes/release_1_2_0", "release_notes/release_1_2_1", "release_notes/release_1_2_2", "release_notes/release_1_3_0", "release_notes/release_1_3_1", "release_notes/release_1_3_2", "release_notes/release_1_3_3", "release_notes/release_notes", "user_guide/adapter_specifics", "user_guide/command_line_tools", "user_guide/remote_access_devices", "user_guide/remote_access_simulation", "user_guide/usage_with_python"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["_api.rst", "developer_guide/developing_lewis.md", "developer_guide/framework_details.md", "developer_guide/release_checklist.md", "developer_guide/writing_devices.md", "generated/lewis.rst", "generated/lewis.adapters.rst", "generated/lewis.adapters.epics.rst", "generated/lewis.adapters.modbus.rst", "generated/lewis.adapters.stream.rst", "generated/lewis.core.rst", "generated/lewis.core.adapters.rst", "generated/lewis.core.approaches.rst", "generated/lewis.core.control_client.rst", "generated/lewis.core.control_server.rst", "generated/lewis.core.devices.rst", "generated/lewis.core.exceptions.rst", "generated/lewis.core.logging.rst", "generated/lewis.core.processor.rst", "generated/lewis.core.simulation.rst", "generated/lewis.core.statemachine.rst", "generated/lewis.core.utils.rst", "generated/lewis.devices.rst", "generated/lewis.devices.chopper.rst", "generated/lewis.devices.chopper.devices.rst", "generated/lewis.devices.chopper.devices.bearings.rst", "generated/lewis.devices.chopper.devices.device.rst", "generated/lewis.devices.chopper.devices.states.rst", "generated/lewis.devices.chopper.interfaces.rst", "generated/lewis.devices.chopper.interfaces.epics_interface.rst", "generated/lewis.devices.julabo.rst", "generated/lewis.devices.julabo.devices.rst", "generated/lewis.devices.julabo.devices.device.rst", "generated/lewis.devices.julabo.devices.states.rst", "generated/lewis.devices.julabo.interfaces.rst", "generated/lewis.devices.julabo.interfaces.julabo_stream_interface_1.rst", "generated/lewis.devices.julabo.interfaces.julabo_stream_interface_2.rst", "generated/lewis.devices.linkam_t95.rst", "generated/lewis.devices.linkam_t95.devices.rst", "generated/lewis.devices.linkam_t95.devices.device.rst", "generated/lewis.devices.linkam_t95.devices.states.rst", "generated/lewis.devices.linkam_t95.interfaces.rst", "generated/lewis.devices.linkam_t95.interfaces.stream_interface.rst", "generated/lewis.examples.rst", "generated/lewis.examples.dual_device.rst", "generated/lewis.examples.example_motor.rst", "generated/lewis.examples.modbus_device.rst", "generated/lewis.examples.simple_device.rst", "generated/lewis.examples.timeout_device.rst", "generated/lewis.scripts.rst", "generated/lewis.scripts.control.rst", "generated/lewis.scripts.run.rst", "generated/lewis.utils.rst", "generated/lewis.utils.byte_conversions.rst", "generated/lewis.utils.command_builder.rst", "generated/lewis.utils.constants.rst", "generated/lewis.utils.replies.rst", "index.rst", "quickstart.md", "release_notes/release_1_0_0.md", "release_notes/release_1_0_1.md", "release_notes/release_1_0_2.md", "release_notes/release_1_0_3.md", "release_notes/release_1_1_0.md", "release_notes/release_1_1_1.md", "release_notes/release_1_2_0.md", "release_notes/release_1_2_1.md", "release_notes/release_1_2_2.md", "release_notes/release_1_3_0.md", "release_notes/release_1_3_1.md", "release_notes/release_1_3_2.md", "release_notes/release_1_3_3.md", "release_notes/release_notes.rst", "user_guide/adapter_specifics.md", "user_guide/command_line_tools.md", "user_guide/remote_access_devices.md", "user_guide/remote_access_simulation.md", "user_guide/usage_with_python.md"], "indexentries": {"accessviolationexception": [[16, "lewis.core.exceptions.AccessViolationException", false]], "ack() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.ack", false]], "adapter (class in lewis.core.adapters)": [[11, "lewis.core.adapters.Adapter", false]], "adapter (lewis.adapters.epics.epicsinterface property)": [[7, "lewis.adapters.epics.EpicsInterface.adapter", false]], "adapter (lewis.adapters.modbus.modbusinterface property)": [[8, "lewis.adapters.modbus.ModbusInterface.adapter", false]], "adapter (lewis.adapters.stream.streaminterface property)": [[9, "lewis.adapters.stream.StreamInterface.adapter", false]], "adapter (lewis.core.devices.interfacebase property)": [[15, "lewis.core.devices.InterfaceBase.adapter", false]], "adaptercollection (class in lewis.core.adapters)": [[11, "lewis.core.adapters.AdapterCollection", false]], "add_adapter() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.add_adapter", false]], "add_ascii_character() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.add_ascii_character", false]], "add_object() (lewis.core.control_server.exposedobjectcollection method)": [[14, "lewis.core.control_server.ExposedObjectCollection.add_object", false]], "any() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.any", false]], "any_except() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.any_except", false]], "arg() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.arg", false]], "arg_count (lewis.adapters.stream.patternmatcher property)": [[9, "lewis.adapters.stream.PatternMatcher.arg_count", false]], "arg_count (lewis.adapters.stream.regex property)": [[9, "lewis.adapters.stream.regex.arg_count", false]], "argument_mappings (lewis.adapters.stream.patternmatcher property)": [[9, "lewis.adapters.stream.PatternMatcher.argument_mappings", false]], "argument_mappings (lewis.adapters.stream.regex property)": [[9, "lewis.adapters.stream.regex.argument_mappings", false]], "argument_mappings (lewis.adapters.stream.scanf property)": [[9, "lewis.adapters.stream.scanf.argument_mappings", false]], "bind() (lewis.adapters.epics.pv method)": [[7, "lewis.adapters.epics.PV.bind", false]], "bind_handlers_by_name() (lewis.core.statemachine.statemachine method)": [[20, "lewis.core.statemachine.StateMachine.bind_handlers_by_name", false]], "boundpv (class in lewis.adapters.epics)": [[7, "lewis.adapters.epics.BoundPV", false]], "build() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.build", false]], "can() (lewis.core.statemachine.statemachine method)": [[20, "lewis.core.statemachine.StateMachine.can", false]], "canprocess (class in lewis.core.processor)": [[18, "lewis.core.processor.CanProcess", false]], "canprocesscomposite (class in lewis.core.processor)": [[18, "lewis.core.processor.CanProcessComposite", false]], "char() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.char", false]], "check_limits (class in lewis.core.utils)": [[21, "lewis.core.utils.check_limits", false]], "chopperepicsinterface (class in lewis.devices.chopper.interfaces.epics_interface)": [[29, "lewis.devices.chopper.interfaces.epics_interface.ChopperEpicsInterface", false]], "cmd (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.Cmd", false]], "cmdbuilder (class in lewis.utils.command_builder)": [[54, "lewis.utils.command_builder.CmdBuilder", false]], "commandbase (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.CommandBase", false]], "conditional_reply() (in module lewis.utils.replies)": [[56, "lewis.utils.replies.conditional_reply", false]], "config (lewis.adapters.epics.boundpv property)": [[7, "lewis.adapters.epics.BoundPV.config", false]], "configuration() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.configuration", false]], "connect() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.connect", false]], "control_server (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.control_server", false]], "controlclient (class in lewis.core.control_client)": [[13, "lewis.core.control_client.ControlClient", false]], "controlserver (class in lewis.core.control_server)": [[14, "lewis.core.control_server.ControlServer", false]], "cool() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.cool", false]], "create() (lewis.core.simulation.simulationfactory method)": [[19, "lewis.core.simulation.SimulationFactory.create", false]], "create_device() (lewis.core.devices.devicebuilder method)": [[15, "lewis.core.devices.DeviceBuilder.create_device", false]], "create_exception() (lewis.adapters.modbus.modbustcpframe method)": [[8, "lewis.adapters.modbus.ModbusTCPFrame.create_exception", false]], "create_interface() (lewis.core.devices.devicebuilder method)": [[15, "lewis.core.devices.DeviceBuilder.create_interface", false]], "create_response() (lewis.adapters.modbus.modbustcpframe method)": [[8, "lewis.adapters.modbus.ModbusTCPFrame.create_response", false]], "cycle_delay (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.cycle_delay", false]], "cycles (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.cycles", false]], "default_device_type (lewis.core.devices.devicebuilder property)": [[15, "lewis.core.devices.DeviceBuilder.default_device_type", false]], "default_protocol (lewis.core.devices.devicebuilder property)": [[15, "lewis.core.devices.DeviceBuilder.default_protocol", false]], "defaultacceleratingstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultAcceleratingState", false]], "defaultcirculatingstate (class in lewis.devices.julabo.devices.states)": [[33, "lewis.devices.julabo.devices.states.DefaultCirculatingState", false]], "defaultcoolstate (class in lewis.devices.linkam_t95.devices.states)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultCoolState", false]], "defaultheatstate (class in lewis.devices.linkam_t95.devices.states)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultHeatState", false]], "defaultholdstate (class in lewis.devices.linkam_t95.devices.states)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultHoldState", false]], "defaultidlestate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultIdleState", false]], "defaultinitstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultInitState", false]], "defaultinitstate (class in lewis.devices.linkam_t95.devices.states)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultInitState", false]], "defaultmovingstate (class in lewis.examples.example_motor)": [[45, "lewis.examples.example_motor.DefaultMovingState", false]], "defaultnotcirculatingstate (class in lewis.devices.julabo.devices.states)": [[33, "lewis.devices.julabo.devices.states.DefaultNotCirculatingState", false]], "defaultparkedstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultParkedState", false]], "defaultparkingstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultParkingState", false]], "defaultphaselockedstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultPhaseLockedState", false]], "defaultphaselockingstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultPhaseLockingState", false]], "defaultstartedstate (class in lewis.devices.linkam_t95.devices.states)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultStartedState", false]], "defaultstoppedstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultStoppedState", false]], "defaultstoppedstate (class in lewis.devices.linkam_t95.devices.states)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultStoppedState", false]], "defaultstoppingstate (class in lewis.devices.chopper.devices.states)": [[27, "lewis.devices.chopper.devices.states.DefaultStoppingState", false]], "device (class in lewis.devices)": [[22, "lewis.devices.Device", false]], "device (lewis.core.devices.interfacebase property)": [[15, "lewis.core.devices.InterfaceBase.device", false]], "device_builder() (lewis.core.devices.deviceregistry method)": [[15, "lewis.core.devices.DeviceRegistry.device_builder", false]], "device_lock (lewis.core.adapters.adaptercollection property)": [[11, "lewis.core.adapters.AdapterCollection.device_lock", false]], "device_types (lewis.core.devices.devicebuilder property)": [[15, "lewis.core.devices.DeviceBuilder.device_types", false]], "devicebase (class in lewis.core.devices)": [[15, "lewis.core.devices.DeviceBase", false]], "devicebuilder (class in lewis.core.devices)": [[15, "lewis.core.devices.DeviceBuilder", false]], "deviceregistry (class in lewis.core.devices)": [[15, "lewis.core.devices.DeviceRegistry", false]], "devices (lewis.core.devices.deviceregistry property)": [[15, "lewis.core.devices.DeviceRegistry.devices", false]], "devices (lewis.core.simulation.simulationfactory property)": [[19, "lewis.core.simulation.SimulationFactory.devices", false]], "dict_strict_update() (in module lewis.core.utils)": [[21, "lewis.core.utils.dict_strict_update", false]], "digit() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.digit", false]], "disconnect() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.disconnect", false]], "do_import() (lewis.core.utils.fromoptionaldependency method)": [[21, "lewis.core.utils.FromOptionalDependency.do_import", false]], "doc (lewis.adapters.epics.boundpv property)": [[7, "lewis.adapters.epics.BoundPV.doc", false]], "documentation (lewis.adapters.epics.epicsadapter property)": [[7, "lewis.adapters.epics.EpicsAdapter.documentation", false]], "documentation (lewis.adapters.stream.streamadapter property)": [[9, "lewis.adapters.stream.StreamAdapter.documentation", false]], "documentation (lewis.core.adapters.adapter property)": [[11, "lewis.core.adapters.Adapter.documentation", false]], "documentation() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.documentation", false]], "doprocess() (lewis.core.statemachine.statemachine method)": [[20, "lewis.core.statemachine.StateMachine.doProcess", false]], "enq() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.enq", false]], "enum() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.enum", false]], "eos() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.eos", false]], "eot() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.eot", false]], "epicsadapter (class in lewis.adapters.epics)": [[7, "lewis.adapters.epics.EpicsAdapter", false]], "epicsinterface (class in lewis.adapters.epics)": [[7, "lewis.adapters.epics.EpicsInterface", false]], "escape() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.escape", false]], "etx() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.etx", false]], "examplemodbusinterface (class in lewis.examples.modbus_device)": [[46, "lewis.examples.modbus_device.ExampleModbusInterface", false]], "examplemotorstreaminterface (class in lewis.examples.example_motor)": [[45, "lewis.examples.example_motor.ExampleMotorStreamInterface", false]], "execute_command (lewis.devices.chopper.interfaces.epics_interface.chopperepicsinterface property)": [[29, "lewis.devices.chopper.interfaces.epics_interface.ChopperEpicsInterface.execute_command", false]], "exposed_object (lewis.core.control_server.controlserver property)": [[14, "lewis.core.control_server.ControlServer.exposed_object", false]], "exposedobject (class in lewis.core.control_server)": [[14, "lewis.core.control_server.ExposedObject", false]], "exposedobjectcollection (class in lewis.core.control_server)": [[14, "lewis.core.control_server.ExposedObjectCollection", false]], "extract_module_name() (in module lewis.core.utils)": [[21, "lewis.core.utils.extract_module_name", false]], "float() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.float", false]], "float_to_raw_bytes() (in module lewis.utils.byte_conversions)": [[53, "lewis.utils.byte_conversions.float_to_raw_bytes", false]], "format_doc_text() (in module lewis.core.utils)": [[21, "lewis.core.utils.format_doc_text", false]], "from_bytearray() (lewis.adapters.modbus.modbustcpframe method)": [[8, "lewis.adapters.modbus.ModbusTCPFrame.from_bytearray", false]], "fromoptionaldependency (class in lewis.core.utils)": [[21, "lewis.core.utils.FromOptionalDependency", false]], "func (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.Func", false]], "get() (lewis.adapters.modbus.modbusdatabank method)": [[8, "lewis.adapters.modbus.ModbusDataBank.get", false]], "get_api() (lewis.core.control_server.exposedobject method)": [[14, "lewis.core.control_server.ExposedObject.get_api", false]], "get_members() (in module lewis.core.utils)": [[21, "lewis.core.utils.get_members", false]], "get_multicommands() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.get_multicommands", false]], "get_object_collection() (lewis.core.control_client.controlclient method)": [[13, "lewis.core.control_client.ControlClient.get_object_collection", false]], "get_objects() (lewis.core.control_server.exposedobjectcollection method)": [[14, "lewis.core.control_server.ExposedObjectCollection.get_objects", false]], "get_param() (lewis.examples.dual_device.verysimpledevice method)": [[44, "lewis.examples.dual_device.VerySimpleDevice.get_param", false]], "get_param() (lewis.examples.simple_device.verysimpleinterface method)": [[47, "lewis.examples.simple_device.VerySimpleInterface.get_param", false]], "get_position() (lewis.examples.example_motor.examplemotorstreaminterface method)": [[45, "lewis.examples.example_motor.ExampleMotorStreamInterface.get_position", false]], "get_protocols() (lewis.core.simulation.simulationfactory method)": [[19, "lewis.core.simulation.SimulationFactory.get_protocols", false]], "get_status() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.get_status", false]], "get_status() (lewis.examples.example_motor.examplemotorstreaminterface method)": [[45, "lewis.examples.example_motor.ExampleMotorStreamInterface.get_status", false]], "get_submodules() (in module lewis.core.utils)": [[21, "lewis.core.utils.get_submodules", false]], "get_target() (lewis.examples.example_motor.examplemotorstreaminterface method)": [[45, "lewis.examples.example_motor.ExampleMotorStreamInterface.get_target", false]], "get_usage_text() (in module lewis.scripts)": [[49, "lewis.scripts.get_usage_text", false]], "handle() (lewis.adapters.epics.epicsadapter method)": [[7, "lewis.adapters.epics.EpicsAdapter.handle", false]], "handle() (lewis.adapters.modbus.modbusadapter method)": [[8, "lewis.adapters.modbus.ModbusAdapter.handle", false]], "handle() (lewis.adapters.stream.streamadapter method)": [[9, "lewis.adapters.stream.StreamAdapter.handle", false]], "handle() (lewis.core.adapters.adapter method)": [[11, "lewis.core.adapters.Adapter.handle", false]], "handle_error() (lewis.adapters.stream.streaminterface method)": [[9, "lewis.adapters.stream.StreamInterface.handle_error", false]], "handle_error() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.handle_error", false]], "handle_error() (lewis.examples.simple_device.verysimpleinterface method)": [[47, "lewis.examples.simple_device.VerySimpleInterface.handle_error", false]], "handle_error() (lewis.examples.timeout_device.timeterminatedinterface method)": [[48, "lewis.examples.timeout_device.TimeTerminatedInterface.handle_error", false]], "has_log() (in module lewis.core.logging)": [[17, "lewis.core.logging.has_log", false]], "hascontext (class in lewis.core.statemachine)": [[20, "lewis.core.statemachine.HasContext", false]], "haslog (class in lewis.core.logging)": [[17, "lewis.core.logging.HasLog", false]], "heat() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.heat", false]], "hold() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.hold", false]], "in_state() (lewis.core.statemachine.state method)": [[20, "lewis.core.statemachine.State.in_state", false]], "in_state() (lewis.devices.chopper.devices.states.defaultacceleratingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultAcceleratingState.in_state", false]], "in_state() (lewis.devices.chopper.devices.states.defaultidlestate method)": [[27, "lewis.devices.chopper.devices.states.DefaultIdleState.in_state", false]], "in_state() (lewis.devices.chopper.devices.states.defaultparkingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultParkingState.in_state", false]], "in_state() (lewis.devices.chopper.devices.states.defaultphaselockingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultPhaseLockingState.in_state", false]], "in_state() (lewis.devices.chopper.devices.states.defaultstoppingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultStoppingState.in_state", false]], "in_state() (lewis.devices.julabo.devices.states.defaultcirculatingstate method)": [[33, "lewis.devices.julabo.devices.states.DefaultCirculatingState.in_state", false]], "in_state() (lewis.devices.linkam_t95.devices.states.defaultcoolstate method)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultCoolState.in_state", false]], "in_state() (lewis.devices.linkam_t95.devices.states.defaultheatstate method)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultHeatState.in_state", false]], "in_state() (lewis.examples.example_motor.defaultmovingstate method)": [[45, "lewis.examples.example_motor.DefaultMovingState.in_state", false]], "int() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.int", false]], "int_to_raw_bytes() (in module lewis.utils.byte_conversions)": [[53, "lewis.utils.byte_conversions.int_to_raw_bytes", false]], "interface (lewis.core.adapters.adapter property)": [[11, "lewis.core.adapters.Adapter.interface", false]], "interfacebase (class in lewis.core.devices)": [[15, "lewis.core.devices.InterfaceBase", false]], "interfaces (lewis.core.devices.devicebuilder property)": [[15, "lewis.core.devices.DeviceBuilder.interfaces", false]], "is_connected() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.is_connected", false]], "is_device() (in module lewis.core.devices)": [[15, "lewis.core.devices.is_device", false]], "is_interface() (in module lewis.core.devices)": [[15, "lewis.core.devices.is_interface", false]], "is_paused (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.is_paused", false]], "is_running (lewis.adapters.epics.epicsadapter property)": [[7, "lewis.adapters.epics.EpicsAdapter.is_running", false]], "is_running (lewis.adapters.modbus.modbusadapter property)": [[8, "lewis.adapters.modbus.ModbusAdapter.is_running", false]], "is_running (lewis.adapters.stream.streamadapter property)": [[9, "lewis.adapters.stream.StreamAdapter.is_running", false]], "is_running (lewis.core.adapters.adapter property)": [[11, "lewis.core.adapters.Adapter.is_running", false]], "is_running (lewis.core.control_server.controlserver property)": [[14, "lewis.core.control_server.ControlServer.is_running", false]], "is_started (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.is_started", false]], "is_valid() (lewis.adapters.modbus.modbustcpframe method)": [[8, "lewis.adapters.modbus.ModbusTCPFrame.is_valid", false]], "json_rpc() (lewis.core.control_client.controlclient method)": [[13, "lewis.core.control_client.ControlClient.json_rpc", false]], "julabostreaminterfacev1 (class in lewis.devices.julabo.interfaces.julabo_stream_interface_1)": [[35, "lewis.devices.julabo.interfaces.julabo_stream_interface_1.JulaboStreamInterfaceV1", false]], "julabostreaminterfacev2 (class in lewis.devices.julabo.interfaces.julabo_stream_interface_2)": [[36, "lewis.devices.julabo.interfaces.julabo_stream_interface_2.JulaboStreamInterfaceV2", false]], "last_command (lewis.devices.chopper.interfaces.epics_interface.chopperepicsinterface property)": [[29, "lewis.devices.chopper.interfaces.epics_interface.ChopperEpicsInterface.last_command", false]], "lewis": [[5, "module-lewis", false]], "lewis.adapters": [[6, "module-lewis.adapters", false]], "lewis.adapters.epics": [[7, "module-lewis.adapters.epics", false]], "lewis.adapters.modbus": [[8, "module-lewis.adapters.modbus", false]], "lewis.adapters.stream": [[9, "module-lewis.adapters.stream", false]], "lewis.core": [[10, "module-lewis.core", false]], "lewis.core.adapters": [[11, "module-lewis.core.adapters", false]], "lewis.core.approaches": [[12, "module-lewis.core.approaches", false]], "lewis.core.control_client": [[13, "module-lewis.core.control_client", false]], "lewis.core.control_server": [[14, "module-lewis.core.control_server", false]], "lewis.core.devices": [[15, "module-lewis.core.devices", false]], "lewis.core.exceptions": [[16, "module-lewis.core.exceptions", false]], "lewis.core.logging": [[17, "module-lewis.core.logging", false]], "lewis.core.processor": [[18, "module-lewis.core.processor", false]], "lewis.core.simulation": [[19, "module-lewis.core.simulation", false]], "lewis.core.statemachine": [[20, "module-lewis.core.statemachine", false]], "lewis.core.utils": [[21, "module-lewis.core.utils", false]], "lewis.devices": [[22, "module-lewis.devices", false]], "lewis.devices.chopper": [[23, "module-lewis.devices.chopper", false]], "lewis.devices.chopper.devices": [[24, "module-lewis.devices.chopper.devices", false]], "lewis.devices.chopper.devices.bearings": [[25, "module-lewis.devices.chopper.devices.bearings", false]], "lewis.devices.chopper.devices.device": [[26, "module-lewis.devices.chopper.devices.device", false]], "lewis.devices.chopper.devices.states": [[27, "module-lewis.devices.chopper.devices.states", false]], "lewis.devices.chopper.interfaces": [[28, "module-lewis.devices.chopper.interfaces", false]], "lewis.devices.chopper.interfaces.epics_interface": [[29, "module-lewis.devices.chopper.interfaces.epics_interface", false]], "lewis.devices.julabo": [[30, "module-lewis.devices.julabo", false]], "lewis.devices.julabo.devices": [[31, "module-lewis.devices.julabo.devices", false]], "lewis.devices.julabo.devices.device": [[32, "module-lewis.devices.julabo.devices.device", false]], "lewis.devices.julabo.devices.states": [[33, "module-lewis.devices.julabo.devices.states", false]], "lewis.devices.julabo.interfaces": [[34, "module-lewis.devices.julabo.interfaces", false]], "lewis.devices.julabo.interfaces.julabo_stream_interface_1": [[35, "module-lewis.devices.julabo.interfaces.julabo_stream_interface_1", false]], "lewis.devices.julabo.interfaces.julabo_stream_interface_2": [[36, "module-lewis.devices.julabo.interfaces.julabo_stream_interface_2", false]], "lewis.devices.linkam_t95": [[37, "module-lewis.devices.linkam_t95", false]], "lewis.devices.linkam_t95.devices": [[38, "module-lewis.devices.linkam_t95.devices", false]], "lewis.devices.linkam_t95.devices.device": [[39, "module-lewis.devices.linkam_t95.devices.device", false]], "lewis.devices.linkam_t95.devices.states": [[40, "module-lewis.devices.linkam_t95.devices.states", false]], "lewis.devices.linkam_t95.interfaces": [[41, "module-lewis.devices.linkam_t95.interfaces", false]], "lewis.devices.linkam_t95.interfaces.stream_interface": [[42, "module-lewis.devices.linkam_t95.interfaces.stream_interface", false]], "lewis.examples": [[43, "module-lewis.examples", false]], "lewis.examples.dual_device": [[44, "module-lewis.examples.dual_device", false]], "lewis.examples.example_motor": [[45, "module-lewis.examples.example_motor", false]], "lewis.examples.modbus_device": [[46, "module-lewis.examples.modbus_device", false]], "lewis.examples.simple_device": [[47, "module-lewis.examples.simple_device", false]], "lewis.examples.timeout_device": [[48, "module-lewis.examples.timeout_device", false]], "lewis.scripts": [[49, "module-lewis.scripts", false]], "lewis.scripts.control": [[50, "module-lewis.scripts.control", false]], "lewis.scripts.run": [[51, "module-lewis.scripts.run", false]], "lewis.utils": [[52, "module-lewis.utils", false]], "lewis.utils.byte_conversions": [[53, "module-lewis.utils.byte_conversions", false]], "lewis.utils.command_builder": [[54, "module-lewis.utils.command_builder", false]], "lewis.utils.constants": [[55, "module-lewis.utils.constants", false]], "lewis.utils.replies": [[56, "module-lewis.utils.replies", false]], "lewisexception": [[16, "lewis.core.exceptions.LewisException", false]], "limitviolationexception": [[16, "lewis.core.exceptions.LimitViolationException", false]], "linear() (in module lewis.core.approaches)": [[12, "lewis.core.approaches.linear", false]], "linkamt95streaminterface (class in lewis.devices.linkam_t95.interfaces.stream_interface)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface", false]], "map_arguments() (lewis.adapters.stream.func method)": [[9, "lewis.adapters.stream.Func.map_arguments", false]], "map_return_value() (lewis.adapters.stream.func method)": [[9, "lewis.adapters.stream.Func.map_return_value", false]], "match() (lewis.adapters.stream.patternmatcher method)": [[9, "lewis.adapters.stream.PatternMatcher.match", false]], "match() (lewis.adapters.stream.regex method)": [[9, "lewis.adapters.stream.regex.match", false]], "mbex (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.MBEX", false]], "meta (lewis.adapters.epics.boundpv property)": [[7, "lewis.adapters.epics.BoundPV.meta", false]], "modbusadapter (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.ModbusAdapter", false]], "modbusbasicdatabank (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.ModbusBasicDataBank", false]], "modbusdatabank (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.ModbusDataBank", false]], "modbusdatastore (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.ModbusDataStore", false]], "modbusdevice (class in lewis.examples.modbus_device)": [[46, "lewis.examples.modbus_device.ModbusDevice", false]], "modbusinterface (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.ModbusInterface", false]], "modbusprotocol (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.ModbusProtocol", false]], "modbustcpframe (class in lewis.adapters.modbus)": [[8, "lewis.adapters.modbus.ModbusTCPFrame", false]], "module": [[5, "module-lewis", false], [6, "module-lewis.adapters", false], [7, "module-lewis.adapters.epics", false], [8, "module-lewis.adapters.modbus", false], [9, "module-lewis.adapters.stream", false], [10, "module-lewis.core", false], [11, "module-lewis.core.adapters", false], [12, "module-lewis.core.approaches", false], [13, "module-lewis.core.control_client", false], [14, "module-lewis.core.control_server", false], [15, "module-lewis.core.devices", false], [16, "module-lewis.core.exceptions", false], [17, "module-lewis.core.logging", false], [18, "module-lewis.core.processor", false], [19, "module-lewis.core.simulation", false], [20, "module-lewis.core.statemachine", false], [21, "module-lewis.core.utils", false], [22, "module-lewis.devices", false], [23, "module-lewis.devices.chopper", false], [24, "module-lewis.devices.chopper.devices", false], [25, "module-lewis.devices.chopper.devices.bearings", false], [26, "module-lewis.devices.chopper.devices.device", false], [27, "module-lewis.devices.chopper.devices.states", false], [28, "module-lewis.devices.chopper.interfaces", false], [29, "module-lewis.devices.chopper.interfaces.epics_interface", false], [30, "module-lewis.devices.julabo", false], [31, "module-lewis.devices.julabo.devices", false], [32, "module-lewis.devices.julabo.devices.device", false], [33, "module-lewis.devices.julabo.devices.states", false], [34, "module-lewis.devices.julabo.interfaces", false], [35, "module-lewis.devices.julabo.interfaces.julabo_stream_interface_1", false], [36, "module-lewis.devices.julabo.interfaces.julabo_stream_interface_2", false], [37, "module-lewis.devices.linkam_t95", false], [38, "module-lewis.devices.linkam_t95.devices", false], [39, "module-lewis.devices.linkam_t95.devices.device", false], [40, "module-lewis.devices.linkam_t95.devices.states", false], [41, "module-lewis.devices.linkam_t95.interfaces", false], [42, "module-lewis.devices.linkam_t95.interfaces.stream_interface", false], [43, "module-lewis.examples", false], [44, "module-lewis.examples.dual_device", false], [45, "module-lewis.examples.example_motor", false], [46, "module-lewis.examples.modbus_device", false], [47, "module-lewis.examples.simple_device", false], [48, "module-lewis.examples.timeout_device", false], [49, "module-lewis.scripts", false], [50, "module-lewis.scripts.control", false], [51, "module-lewis.scripts.run", false], [52, "module-lewis.utils", false], [53, "module-lewis.utils.byte_conversions", false], [54, "module-lewis.utils.command_builder", false], [55, "module-lewis.utils.constants", false], [56, "module-lewis.utils.replies", false]], "name (lewis.core.devices.devicebuilder property)": [[15, "lewis.core.devices.DeviceBuilder.name", false]], "nolock (class in lewis.core.adapters)": [[11, "lewis.core.adapters.NoLock", false]], "objectproxy (class in lewis.core.control_client)": [[13, "lewis.core.control_client.ObjectProxy", false]], "on_entry() (lewis.core.statemachine.state method)": [[20, "lewis.core.statemachine.State.on_entry", false]], "on_entry() (lewis.devices.chopper.devices.states.defaultacceleratingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultAcceleratingState.on_entry", false]], "on_entry() (lewis.devices.chopper.devices.states.defaultinitstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultInitState.on_entry", false]], "on_entry() (lewis.devices.chopper.devices.states.defaultparkingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultParkingState.on_entry", false]], "on_entry() (lewis.devices.chopper.devices.states.defaultphaselockingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultPhaseLockingState.on_entry", false]], "on_entry() (lewis.devices.chopper.devices.states.defaultstoppedstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultStoppedState.on_entry", false]], "on_entry() (lewis.devices.chopper.devices.states.defaultstoppingstate method)": [[27, "lewis.devices.chopper.devices.states.DefaultStoppingState.on_entry", false]], "on_entry() (lewis.devices.linkam_t95.devices.states.defaultstartedstate method)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultStartedState.on_entry", false]], "on_entry() (lewis.devices.linkam_t95.devices.states.defaultstoppedstate method)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultStoppedState.on_entry", false]], "on_exit() (lewis.core.statemachine.state method)": [[20, "lewis.core.statemachine.State.on_exit", false]], "on_exit() (lewis.devices.linkam_t95.devices.states.defaultcoolstate method)": [[40, "lewis.devices.linkam_t95.devices.states.DefaultCoolState.on_exit", false]], "optional() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.optional", false]], "pattern (lewis.adapters.stream.patternmatcher property)": [[9, "lewis.adapters.stream.PatternMatcher.pattern", false]], "pattern (lewis.adapters.stream.scanf property)": [[9, "lewis.adapters.stream.scanf.pattern", false]], "patternmatcher (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.PatternMatcher", false]], "pause() (lewis.core.simulation.simulation method)": [[19, "lewis.core.simulation.Simulation.pause", false]], "poll_interval (lewis.adapters.epics.boundpv property)": [[7, "lewis.adapters.epics.BoundPV.poll_interval", false]], "process() (lewis.adapters.modbus.modbusprotocol method)": [[8, "lewis.adapters.modbus.ModbusProtocol.process", false]], "process() (lewis.core.control_server.controlserver method)": [[14, "lewis.core.control_server.ControlServer.process", false]], "protocolexception": [[13, "lewis.core.control_client.ProtocolException", false]], "protocols (lewis.core.adapters.adaptercollection property)": [[11, "lewis.core.adapters.AdapterCollection.protocols", false]], "protocols (lewis.core.devices.devicebuilder property)": [[15, "lewis.core.devices.DeviceBuilder.protocols", false]], "pump_command() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.pump_command", false]], "pv (class in lewis.adapters.epics)": [[7, "lewis.adapters.epics.PV", false]], "raw_bytes_to_float() (in module lewis.utils.byte_conversions)": [[53, "lewis.utils.byte_conversions.raw_bytes_to_float", false]], "raw_bytes_to_int() (in module lewis.utils.byte_conversions)": [[53, "lewis.utils.byte_conversions.raw_bytes_to_int", false]], "read_only (lewis.adapters.epics.boundpv property)": [[7, "lewis.adapters.epics.BoundPV.read_only", false]], "regex (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.regex", false]], "regex() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.regex", false]], "remoteexception": [[13, "lewis.core.control_client.RemoteException", false]], "remove_adapter() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.remove_adapter", false]], "remove_object() (lewis.core.control_server.exposedobjectcollection method)": [[14, "lewis.core.control_server.ExposedObjectCollection.remove_object", false]], "reset() (lewis.core.statemachine.statemachine method)": [[20, "lewis.core.statemachine.StateMachine.reset", false]], "resume() (lewis.core.simulation.simulation method)": [[19, "lewis.core.simulation.Simulation.resume", false]], "run_simulation() (in module lewis.scripts.run)": [[51, "lewis.scripts.run.run_simulation", false]], "runtime (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.runtime", false]], "scanf (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.scanf", false]], "second (lewis.examples.dual_device.verysimpledevice property)": [[44, "lewis.examples.dual_device.VerySimpleDevice.second", false]], "second_int (lewis.examples.dual_device.verysimpleinterface property)": [[44, "lewis.examples.dual_device.VerySimpleInterface.second_int", false]], "seconds_since() (in module lewis.core.utils)": [[21, "lewis.core.utils.seconds_since", false]], "set() (lewis.adapters.modbus.modbusdatabank method)": [[8, "lewis.adapters.modbus.ModbusDataBank.set", false]], "set_circulating() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_circulating", false]], "set_context() (lewis.core.statemachine.hascontext method)": [[20, "lewis.core.statemachine.HasContext.set_context", false]], "set_device() (lewis.core.adapters.adaptercollection method)": [[11, "lewis.core.adapters.AdapterCollection.set_device", false]], "set_device_parameters() (lewis.core.simulation.simulation method)": [[19, "lewis.core.simulation.Simulation.set_device_parameters", false]], "set_external_d() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_external_d", false]], "set_external_i() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_external_i", false]], "set_external_p() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_external_p", false]], "set_internal_d() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_internal_d", false]], "set_internal_i() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_internal_i", false]], "set_internal_p() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_internal_p", false]], "set_limit() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.set_limit", false]], "set_param() (lewis.examples.simple_device.verysimpleinterface method)": [[47, "lewis.examples.simple_device.VerySimpleInterface.set_param", false]], "set_rate() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.set_rate", false]], "set_set_point() (lewis.devices.julabo.devices.device.simulatedjulabo method)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo.set_set_point", false]], "set_target() (lewis.examples.example_motor.examplemotorstreaminterface method)": [[45, "lewis.examples.example_motor.ExampleMotorStreamInterface.set_target", false]], "setups (lewis.core.devices.devicebuilder property)": [[15, "lewis.core.devices.DeviceBuilder.setups", false]], "setups (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.setups", false]], "simulatedbearings (class in lewis.devices.chopper.devices.device)": [[26, "lewis.devices.chopper.devices.device.SimulatedBearings", false]], "simulatedchopper (class in lewis.devices.chopper.devices.device)": [[26, "lewis.devices.chopper.devices.device.SimulatedChopper", false]], "simulatedexamplemotor (class in lewis.examples.example_motor)": [[45, "lewis.examples.example_motor.SimulatedExampleMotor", false]], "simulatedjulabo (class in lewis.devices.julabo.devices.device)": [[32, "lewis.devices.julabo.devices.device.SimulatedJulabo", false]], "simulatedlinkamt95 (class in lewis.devices.linkam_t95.devices.device)": [[39, "lewis.devices.linkam_t95.devices.device.SimulatedLinkamT95", false]], "simulation (class in lewis.core.simulation)": [[19, "lewis.core.simulation.Simulation", false]], "simulationfactory (class in lewis.core.simulation)": [[19, "lewis.core.simulation.SimulationFactory", false]], "spaces() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.spaces", false]], "speed (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.speed", false]], "start() (lewis.core.simulation.simulation method)": [[19, "lewis.core.simulation.Simulation.start", false]], "start() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.start", false]], "start_server() (lewis.adapters.epics.epicsadapter method)": [[7, "lewis.adapters.epics.EpicsAdapter.start_server", false]], "start_server() (lewis.adapters.modbus.modbusadapter method)": [[8, "lewis.adapters.modbus.ModbusAdapter.start_server", false]], "start_server() (lewis.adapters.stream.streamadapter method)": [[9, "lewis.adapters.stream.StreamAdapter.start_server", false]], "start_server() (lewis.core.adapters.adapter method)": [[11, "lewis.core.adapters.Adapter.start_server", false]], "start_server() (lewis.core.control_server.controlserver method)": [[14, "lewis.core.control_server.ControlServer.start_server", false]], "state (class in lewis.core.statemachine)": [[20, "lewis.core.statemachine.State", false]], "state (lewis.core.statemachine.statemachine property)": [[20, "lewis.core.statemachine.StateMachine.state", false]], "state (lewis.devices.chopper.devices.device.simulatedchopper property)": [[26, "lewis.devices.chopper.devices.device.SimulatedChopper.state", false]], "statemachine (class in lewis.core.statemachine)": [[20, "lewis.core.statemachine.StateMachine", false]], "statemachinedevice (class in lewis.devices)": [[22, "lewis.devices.StateMachineDevice", false]], "statemachineexception": [[20, "lewis.core.statemachine.StateMachineException", false]], "stop() (lewis.core.simulation.simulation method)": [[19, "lewis.core.simulation.Simulation.stop", false]], "stop() (lewis.devices.linkam_t95.interfaces.stream_interface.linkamt95streaminterface method)": [[42, "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface.stop", false]], "stop() (lewis.examples.example_motor.simulatedexamplemotor method)": [[45, "lewis.examples.example_motor.SimulatedExampleMotor.stop", false]], "stop_server() (lewis.adapters.epics.epicsadapter method)": [[7, "lewis.adapters.epics.EpicsAdapter.stop_server", false]], "stop_server() (lewis.adapters.modbus.modbusadapter method)": [[8, "lewis.adapters.modbus.ModbusAdapter.stop_server", false]], "stop_server() (lewis.adapters.stream.streamadapter method)": [[9, "lewis.adapters.stream.StreamAdapter.stop_server", false]], "stop_server() (lewis.core.adapters.adapter method)": [[11, "lewis.core.adapters.Adapter.stop_server", false]], "streamadapter (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.StreamAdapter", false]], "streamhandler (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.StreamHandler", false]], "streaminterface (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.StreamInterface", false]], "string() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.string", false]], "stx() (lewis.utils.command_builder.cmdbuilder method)": [[54, "lewis.utils.command_builder.CmdBuilder.stx", false]], "switch_setup() (lewis.core.simulation.simulation method)": [[19, "lewis.core.simulation.Simulation.switch_setup", false]], "timed_reply() (in module lewis.utils.replies)": [[56, "lewis.utils.replies.timed_reply", false]], "timeterminateddevice (class in lewis.examples.timeout_device)": [[48, "lewis.examples.timeout_device.TimeTerminatedDevice", false]], "timeterminatedinterface (class in lewis.examples.timeout_device)": [[48, "lewis.examples.timeout_device.TimeTerminatedInterface", false]], "to_bytearray() (lewis.adapters.modbus.modbustcpframe method)": [[8, "lewis.adapters.modbus.ModbusTCPFrame.to_bytearray", false]], "transition (class in lewis.core.statemachine)": [[20, "lewis.core.statemachine.Transition", false]], "uptime (lewis.core.simulation.simulation property)": [[19, "lewis.core.simulation.Simulation.uptime", false]], "value (lewis.adapters.epics.boundpv property)": [[7, "lewis.adapters.epics.BoundPV.value", false]], "var (class in lewis.adapters.stream)": [[9, "lewis.adapters.stream.Var", false]], "verysimpledevice (class in lewis.examples.dual_device)": [[44, "lewis.examples.dual_device.VerySimpleDevice", false]], "verysimpledevice (class in lewis.examples.simple_device)": [[47, "lewis.examples.simple_device.VerySimpleDevice", false]], "verysimpleinterface (class in lewis.examples.dual_device)": [[44, "lewis.examples.dual_device.VerySimpleInterface", false]], "verysimpleinterface (class in lewis.examples.simple_device)": [[47, "lewis.examples.simple_device.VerySimpleInterface", false]], "verysimplestreaminterface (class in lewis.examples.dual_device)": [[44, "lewis.examples.dual_device.VerySimpleStreamInterface", false]]}, "objects": {"": [[5, 0, 0, "-", "lewis"]], "lewis": [[6, 0, 0, "-", "adapters"], [10, 0, 0, "-", "core"], [22, 0, 0, "-", "devices"], [43, 0, 0, "-", "examples"], [49, 0, 0, "-", "scripts"], [52, 0, 0, "-", "utils"]], "lewis.adapters": [[7, 0, 0, "-", "epics"], [8, 0, 0, "-", "modbus"], [9, 0, 0, "-", "stream"]], "lewis.adapters.epics": [[7, 1, 1, "", "BoundPV"], [7, 1, 1, "", "EpicsAdapter"], [7, 1, 1, "", "EpicsInterface"], [7, 1, 1, "", "PV"]], "lewis.adapters.epics.BoundPV": [[7, 2, 1, "", "config"], [7, 2, 1, "", "doc"], [7, 2, 1, "", "meta"], [7, 2, 1, "", "poll_interval"], [7, 2, 1, "", "read_only"], [7, 2, 1, "", "value"]], "lewis.adapters.epics.EpicsAdapter": [[7, 2, 1, "", "documentation"], [7, 3, 1, "", "handle"], [7, 2, 1, "", "is_running"], [7, 3, 1, "", "start_server"], [7, 3, 1, "", "stop_server"]], "lewis.adapters.epics.EpicsInterface": [[7, 2, 1, "", "adapter"]], "lewis.adapters.epics.PV": [[7, 3, 1, "", "bind"]], "lewis.adapters.modbus": [[8, 1, 1, "", "MBEX"], [8, 1, 1, "", "ModbusAdapter"], [8, 1, 1, "", "ModbusBasicDataBank"], [8, 1, 1, "", "ModbusDataBank"], [8, 1, 1, "", "ModbusDataStore"], [8, 1, 1, "", "ModbusInterface"], [8, 1, 1, "", "ModbusProtocol"], [8, 1, 1, "", "ModbusTCPFrame"]], "lewis.adapters.modbus.ModbusAdapter": [[8, 3, 1, "", "handle"], [8, 2, 1, "", "is_running"], [8, 3, 1, "", "start_server"], [8, 3, 1, "", "stop_server"]], "lewis.adapters.modbus.ModbusDataBank": [[8, 3, 1, "", "get"], [8, 3, 1, "", "set"]], "lewis.adapters.modbus.ModbusInterface": [[8, 2, 1, "", "adapter"]], "lewis.adapters.modbus.ModbusProtocol": [[8, 3, 1, "", "process"]], "lewis.adapters.modbus.ModbusTCPFrame": [[8, 3, 1, "", "create_exception"], [8, 3, 1, "", "create_response"], [8, 3, 1, "", "from_bytearray"], [8, 3, 1, "", "is_valid"], [8, 3, 1, "", "to_bytearray"]], "lewis.adapters.stream": [[9, 1, 1, "", "Cmd"], [9, 1, 1, "", "CommandBase"], [9, 1, 1, "", "Func"], [9, 1, 1, "", "PatternMatcher"], [9, 1, 1, "", "StreamAdapter"], [9, 1, 1, "", "StreamHandler"], [9, 1, 1, "", "StreamInterface"], [9, 1, 1, "", "Var"], [9, 1, 1, "", "regex"], [9, 1, 1, "", "scanf"]], "lewis.adapters.stream.Func": [[9, 3, 1, "", "map_arguments"], [9, 3, 1, "", "map_return_value"]], "lewis.adapters.stream.PatternMatcher": [[9, 2, 1, "", "arg_count"], [9, 2, 1, "", "argument_mappings"], [9, 3, 1, "", "match"], [9, 2, 1, "", "pattern"]], "lewis.adapters.stream.StreamAdapter": [[9, 2, 1, "", "documentation"], [9, 3, 1, "", "handle"], [9, 2, 1, "", "is_running"], [9, 3, 1, "", "start_server"], [9, 3, 1, "", "stop_server"]], "lewis.adapters.stream.StreamInterface": [[9, 2, 1, "", "adapter"], [9, 3, 1, "", "handle_error"]], "lewis.adapters.stream.regex": [[9, 2, 1, "", "arg_count"], [9, 2, 1, "", "argument_mappings"], [9, 3, 1, "", "match"]], "lewis.adapters.stream.scanf": [[9, 2, 1, "", "argument_mappings"], [9, 2, 1, "", "pattern"]], "lewis.core": [[11, 0, 0, "-", "adapters"], [12, 0, 0, "-", "approaches"], [13, 0, 0, "-", "control_client"], [14, 0, 0, "-", "control_server"], [15, 0, 0, "-", "devices"], [16, 0, 0, "-", "exceptions"], [17, 0, 0, "-", "logging"], [18, 0, 0, "-", "processor"], [19, 0, 0, "-", "simulation"], [20, 0, 0, "-", "statemachine"], [21, 0, 0, "-", "utils"]], "lewis.core.adapters": [[11, 1, 1, "", "Adapter"], [11, 1, 1, "", "AdapterCollection"], [11, 1, 1, "", "NoLock"]], "lewis.core.adapters.Adapter": [[11, 2, 1, "", "documentation"], [11, 3, 1, "", "handle"], [11, 2, 1, "", "interface"], [11, 2, 1, "", "is_running"], [11, 3, 1, "", "start_server"], [11, 3, 1, "", "stop_server"]], "lewis.core.adapters.AdapterCollection": [[11, 3, 1, "", "add_adapter"], [11, 3, 1, "", "configuration"], [11, 3, 1, "", "connect"], [11, 2, 1, "", "device_lock"], [11, 3, 1, "", "disconnect"], [11, 3, 1, "", "documentation"], [11, 3, 1, "", "is_connected"], [11, 2, 1, "", "protocols"], [11, 3, 1, "", "remove_adapter"], [11, 3, 1, "", "set_device"]], "lewis.core.approaches": [[12, 4, 1, "", "linear"]], "lewis.core.control_client": [[13, 1, 1, "", "ControlClient"], [13, 1, 1, "", "ObjectProxy"], [13, 5, 1, "", "ProtocolException"], [13, 5, 1, "", "RemoteException"]], "lewis.core.control_client.ControlClient": [[13, 3, 1, "", "get_object_collection"], [13, 3, 1, "", "json_rpc"]], "lewis.core.control_server": [[14, 1, 1, "", "ControlServer"], [14, 1, 1, "", "ExposedObject"], [14, 1, 1, "", "ExposedObjectCollection"]], "lewis.core.control_server.ControlServer": [[14, 2, 1, "", "exposed_object"], [14, 2, 1, "", "is_running"], [14, 3, 1, "", "process"], [14, 3, 1, "", "start_server"]], "lewis.core.control_server.ExposedObject": [[14, 3, 1, "", "get_api"]], "lewis.core.control_server.ExposedObjectCollection": [[14, 3, 1, "", "add_object"], [14, 3, 1, "", "get_objects"], [14, 3, 1, "", "remove_object"]], "lewis.core.devices": [[15, 1, 1, "", "DeviceBase"], [15, 1, 1, "", "DeviceBuilder"], [15, 1, 1, "", "DeviceRegistry"], [15, 1, 1, "", "InterfaceBase"], [15, 4, 1, "", "is_device"], [15, 4, 1, "", "is_interface"]], "lewis.core.devices.DeviceBuilder": [[15, 3, 1, "", "create_device"], [15, 3, 1, "", "create_interface"], [15, 2, 1, "", "default_device_type"], [15, 2, 1, "", "default_protocol"], [15, 2, 1, "", "device_types"], [15, 2, 1, "", "interfaces"], [15, 2, 1, "", "name"], [15, 2, 1, "", "protocols"], [15, 2, 1, "", "setups"]], "lewis.core.devices.DeviceRegistry": [[15, 3, 1, "", "device_builder"], [15, 2, 1, "", "devices"]], "lewis.core.devices.InterfaceBase": [[15, 2, 1, "", "adapter"], [15, 2, 1, "", "device"]], "lewis.core.exceptions": [[16, 5, 1, "", "AccessViolationException"], [16, 5, 1, "", "LewisException"], [16, 5, 1, "", "LimitViolationException"]], "lewis.core.logging": [[17, 1, 1, "", "HasLog"], [17, 4, 1, "", "has_log"]], "lewis.core.processor": [[18, 1, 1, "", "CanProcess"], [18, 1, 1, "", "CanProcessComposite"]], "lewis.core.simulation": [[19, 1, 1, "", "Simulation"], [19, 1, 1, "", "SimulationFactory"]], "lewis.core.simulation.Simulation": [[19, 2, 1, "", "control_server"], [19, 2, 1, "", "cycle_delay"], [19, 2, 1, "", "cycles"], [19, 2, 1, "", "is_paused"], [19, 2, 1, "", "is_started"], [19, 3, 1, "", "pause"], [19, 3, 1, "", "resume"], [19, 2, 1, "", "runtime"], [19, 3, 1, "", "set_device_parameters"], [19, 2, 1, "", "setups"], [19, 2, 1, "", "speed"], [19, 3, 1, "", "start"], [19, 3, 1, "", "stop"], [19, 3, 1, "", "switch_setup"], [19, 2, 1, "", "uptime"]], "lewis.core.simulation.SimulationFactory": [[19, 3, 1, "", "create"], [19, 2, 1, "", "devices"], [19, 3, 1, "", "get_protocols"]], "lewis.core.statemachine": [[20, 1, 1, "", "HasContext"], [20, 1, 1, "", "State"], [20, 1, 1, "", "StateMachine"], [20, 5, 1, "", "StateMachineException"], [20, 1, 1, "", "Transition"]], "lewis.core.statemachine.HasContext": [[20, 3, 1, "", "set_context"]], "lewis.core.statemachine.State": [[20, 3, 1, "", "in_state"], [20, 3, 1, "", "on_entry"], [20, 3, 1, "", "on_exit"]], "lewis.core.statemachine.StateMachine": [[20, 3, 1, "", "bind_handlers_by_name"], [20, 3, 1, "", "can"], [20, 3, 1, "", "doProcess"], [20, 3, 1, "", "reset"], [20, 2, 1, "", "state"]], "lewis.core.utils": [[21, 1, 1, "", "FromOptionalDependency"], [21, 1, 1, "", "check_limits"], [21, 4, 1, "", "dict_strict_update"], [21, 4, 1, "", "extract_module_name"], [21, 4, 1, "", "format_doc_text"], [21, 4, 1, "", "get_members"], [21, 4, 1, "", "get_submodules"], [21, 4, 1, "", "seconds_since"]], "lewis.core.utils.FromOptionalDependency": [[21, 3, 1, "", "do_import"]], "lewis.devices": [[22, 1, 1, "", "Device"], [22, 1, 1, "", "StateMachineDevice"], [23, 0, 0, "-", "chopper"], [30, 0, 0, "-", "julabo"], [37, 0, 0, "-", "linkam_t95"]], "lewis.devices.chopper": [[24, 0, 0, "-", "devices"], [28, 0, 0, "-", "interfaces"]], "lewis.devices.chopper.devices": [[25, 0, 0, "-", "bearings"], [26, 0, 0, "-", "device"], [27, 0, 0, "-", "states"]], "lewis.devices.chopper.devices.device": [[26, 1, 1, "", "SimulatedBearings"], [26, 1, 1, "", "SimulatedChopper"]], "lewis.devices.chopper.devices.device.SimulatedChopper": [[26, 2, 1, "", "state"]], "lewis.devices.chopper.devices.states": [[27, 1, 1, "", "DefaultAcceleratingState"], [27, 1, 1, "", "DefaultIdleState"], [27, 1, 1, "", "DefaultInitState"], [27, 1, 1, "", "DefaultParkedState"], [27, 1, 1, "", "DefaultParkingState"], [27, 1, 1, "", "DefaultPhaseLockedState"], [27, 1, 1, "", "DefaultPhaseLockingState"], [27, 1, 1, "", "DefaultStoppedState"], [27, 1, 1, "", "DefaultStoppingState"]], "lewis.devices.chopper.devices.states.DefaultAcceleratingState": [[27, 3, 1, "", "in_state"], [27, 3, 1, "", "on_entry"]], "lewis.devices.chopper.devices.states.DefaultIdleState": [[27, 3, 1, "", "in_state"]], "lewis.devices.chopper.devices.states.DefaultInitState": [[27, 3, 1, "", "on_entry"]], "lewis.devices.chopper.devices.states.DefaultParkingState": [[27, 3, 1, "", "in_state"], [27, 3, 1, "", "on_entry"]], "lewis.devices.chopper.devices.states.DefaultPhaseLockingState": [[27, 3, 1, "", "in_state"], [27, 3, 1, "", "on_entry"]], "lewis.devices.chopper.devices.states.DefaultStoppedState": [[27, 3, 1, "", "on_entry"]], "lewis.devices.chopper.devices.states.DefaultStoppingState": [[27, 3, 1, "", "in_state"], [27, 3, 1, "", "on_entry"]], "lewis.devices.chopper.interfaces": [[29, 0, 0, "-", "epics_interface"]], "lewis.devices.chopper.interfaces.epics_interface": [[29, 1, 1, "", "ChopperEpicsInterface"]], "lewis.devices.chopper.interfaces.epics_interface.ChopperEpicsInterface": [[29, 2, 1, "", "execute_command"], [29, 2, 1, "", "last_command"]], "lewis.devices.julabo": [[31, 0, 0, "-", "devices"], [34, 0, 0, "-", "interfaces"]], "lewis.devices.julabo.devices": [[32, 0, 0, "-", "device"], [33, 0, 0, "-", "states"]], "lewis.devices.julabo.devices.device": [[32, 1, 1, "", "SimulatedJulabo"]], "lewis.devices.julabo.devices.device.SimulatedJulabo": [[32, 3, 1, "", "set_circulating"], [32, 3, 1, "", "set_external_d"], [32, 3, 1, "", "set_external_i"], [32, 3, 1, "", "set_external_p"], [32, 3, 1, "", "set_internal_d"], [32, 3, 1, "", "set_internal_i"], [32, 3, 1, "", "set_internal_p"], [32, 3, 1, "", "set_set_point"]], "lewis.devices.julabo.devices.states": [[33, 1, 1, "", "DefaultCirculatingState"], [33, 1, 1, "", "DefaultNotCirculatingState"]], "lewis.devices.julabo.devices.states.DefaultCirculatingState": [[33, 3, 1, "", "in_state"]], "lewis.devices.julabo.interfaces": [[35, 0, 0, "-", "julabo_stream_interface_1"], [36, 0, 0, "-", "julabo_stream_interface_2"]], "lewis.devices.julabo.interfaces.julabo_stream_interface_1": [[35, 1, 1, "", "JulaboStreamInterfaceV1"]], "lewis.devices.julabo.interfaces.julabo_stream_interface_2": [[36, 1, 1, "", "JulaboStreamInterfaceV2"]], "lewis.devices.linkam_t95": [[38, 0, 0, "-", "devices"], [41, 0, 0, "-", "interfaces"]], "lewis.devices.linkam_t95.devices": [[39, 0, 0, "-", "device"], [40, 0, 0, "-", "states"]], "lewis.devices.linkam_t95.devices.device": [[39, 1, 1, "", "SimulatedLinkamT95"]], "lewis.devices.linkam_t95.devices.states": [[40, 1, 1, "", "DefaultCoolState"], [40, 1, 1, "", "DefaultHeatState"], [40, 1, 1, "", "DefaultHoldState"], [40, 1, 1, "", "DefaultInitState"], [40, 1, 1, "", "DefaultStartedState"], [40, 1, 1, "", "DefaultStoppedState"]], "lewis.devices.linkam_t95.devices.states.DefaultCoolState": [[40, 3, 1, "", "in_state"], [40, 3, 1, "", "on_exit"]], "lewis.devices.linkam_t95.devices.states.DefaultHeatState": [[40, 3, 1, "", "in_state"]], "lewis.devices.linkam_t95.devices.states.DefaultStartedState": [[40, 3, 1, "", "on_entry"]], "lewis.devices.linkam_t95.devices.states.DefaultStoppedState": [[40, 3, 1, "", "on_entry"]], "lewis.devices.linkam_t95.interfaces": [[42, 0, 0, "-", "stream_interface"]], "lewis.devices.linkam_t95.interfaces.stream_interface": [[42, 1, 1, "", "LinkamT95StreamInterface"]], "lewis.devices.linkam_t95.interfaces.stream_interface.LinkamT95StreamInterface": [[42, 3, 1, "", "cool"], [42, 3, 1, "", "get_status"], [42, 3, 1, "", "handle_error"], [42, 3, 1, "", "heat"], [42, 3, 1, "", "hold"], [42, 3, 1, "", "pump_command"], [42, 3, 1, "", "set_limit"], [42, 3, 1, "", "set_rate"], [42, 3, 1, "", "start"], [42, 3, 1, "", "stop"]], "lewis.examples": [[44, 0, 0, "-", "dual_device"], [45, 0, 0, "-", "example_motor"], [46, 0, 0, "-", "modbus_device"], [47, 0, 0, "-", "simple_device"], [48, 0, 0, "-", "timeout_device"]], "lewis.examples.dual_device": [[44, 1, 1, "", "VerySimpleDevice"], [44, 1, 1, "", "VerySimpleInterface"], [44, 1, 1, "", "VerySimpleStreamInterface"]], "lewis.examples.dual_device.VerySimpleDevice": [[44, 3, 1, "", "get_param"], [44, 2, 1, "", "second"]], "lewis.examples.dual_device.VerySimpleInterface": [[44, 2, 1, "", "second_int"]], "lewis.examples.example_motor": [[45, 1, 1, "", "DefaultMovingState"], [45, 1, 1, "", "ExampleMotorStreamInterface"], [45, 1, 1, "", "SimulatedExampleMotor"]], "lewis.examples.example_motor.DefaultMovingState": [[45, 3, 1, "", "in_state"]], "lewis.examples.example_motor.ExampleMotorStreamInterface": [[45, 3, 1, "", "get_position"], [45, 3, 1, "", "get_status"], [45, 3, 1, "", "get_target"], [45, 3, 1, "", "set_target"]], "lewis.examples.example_motor.SimulatedExampleMotor": [[45, 3, 1, "", "stop"]], "lewis.examples.modbus_device": [[46, 1, 1, "", "ExampleModbusInterface"], [46, 1, 1, "", "ModbusDevice"]], "lewis.examples.simple_device": [[47, 1, 1, "", "VerySimpleDevice"], [47, 1, 1, "", "VerySimpleInterface"]], "lewis.examples.simple_device.VerySimpleInterface": [[47, 3, 1, "", "get_param"], [47, 3, 1, "", "handle_error"], [47, 3, 1, "", "set_param"]], "lewis.examples.timeout_device": [[48, 1, 1, "", "TimeTerminatedDevice"], [48, 1, 1, "", "TimeTerminatedInterface"]], "lewis.examples.timeout_device.TimeTerminatedInterface": [[48, 3, 1, "", "handle_error"]], "lewis.scripts": [[50, 0, 0, "-", "control"], [49, 4, 1, "", "get_usage_text"], [51, 0, 0, "-", "run"]], "lewis.scripts.run": [[51, 4, 1, "", "run_simulation"]], "lewis.utils": [[53, 0, 0, "-", "byte_conversions"], [54, 0, 0, "-", "command_builder"], [55, 0, 0, "-", "constants"], [56, 0, 0, "-", "replies"]], "lewis.utils.byte_conversions": [[53, 4, 1, "", "float_to_raw_bytes"], [53, 4, 1, "", "int_to_raw_bytes"], [53, 4, 1, "", "raw_bytes_to_float"], [53, 4, 1, "", "raw_bytes_to_int"]], "lewis.utils.command_builder": [[54, 1, 1, "", "CmdBuilder"]], "lewis.utils.command_builder.CmdBuilder": [[54, 3, 1, "", "ack"], [54, 3, 1, "", "add_ascii_character"], [54, 3, 1, "", "any"], [54, 3, 1, "", "any_except"], [54, 3, 1, "", "arg"], [54, 3, 1, "", "build"], [54, 3, 1, "", "char"], [54, 3, 1, "", "digit"], [54, 3, 1, "", "enq"], [54, 3, 1, "", "enum"], [54, 3, 1, "", "eos"], [54, 3, 1, "", "eot"], [54, 3, 1, "", "escape"], [54, 3, 1, "", "etx"], [54, 3, 1, "", "float"], [54, 3, 1, "", "get_multicommands"], [54, 3, 1, "", "int"], [54, 3, 1, "", "optional"], [54, 3, 1, "", "regex"], [54, 3, 1, "", "spaces"], [54, 3, 1, "", "string"], [54, 3, 1, "", "stx"]], "lewis.utils.replies": [[56, 4, 1, "", "conditional_reply"], [56, 4, 1, "", "timed_reply"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "property", "Python property"], "3": ["py", "method", "Python method"], "4": ["py", "function", "Python function"], "5": ["py", "exception", "Python exception"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:property", "3": "py:method", "4": "py:function", "5": "py:exception"}, "terms": {"": [1, 4, 7, 8, 9, 11, 14, 15, 17, 18, 20, 21, 22, 42, 45, 51, 54, 58, 59, 61, 62, 63, 65, 75, 76, 77], "0": [4, 7, 8, 9, 11, 12, 13, 15, 19, 21, 27, 32, 42, 45, 51, 56, 58, 63, 64, 65, 72, 73, 75, 76], "010": 75, "05": [27, 76], "0garbag": 9, "0x01": 61, "0x06": 61, "0x0f": 61, "0x10": 61, "0x1000": 8, "0x1fff": 8, "0x2": 54, "0x3": 54, "0x4": 54, "0x5": 54, "0x6": 54, "0xdeadbeef": 75, "1": [4, 7, 8, 9, 11, 12, 13, 15, 19, 32, 58, 72, 75], "10": [4, 9, 12, 15, 42, 65, 76], "100": [7, 9, 12, 29, 42, 58, 65, 75], "10000": [13, 58, 75], "106584": 58, "12": 75, "120": 4, "123": 75, "1234": 73, "123_enhance_logic_flow": 1, "127": [4, 13, 58, 62, 75], "142721999999964": 58, "15": 21, "150": 56, "159932": 58, "17": 73, "172": 73, "2": [4, 7, 13, 19, 44, 58, 62, 63, 68, 72, 75], "20": [4, 12, 15, 58, 61, 75], "2017": [61, 62], "23e10": 75, "24": 21, "24th": 62, "250": [4, 45], "255": 73, "26th": 61, "2700": 54, "273": 21, "29": 58, "3": [13, 20, 32, 57, 63, 64, 72, 75, 77], "3000": 13, "34": 29, "4": [9, 21, 47], "40": 7, "42": 7, "45": 58, "49": 54, "5": [9, 27, 29, 44, 54, 75], "5020": 62, "555": 4, "6": [4, 9, 77], "640386": 58, "64038600000002": 58, "65535": 8, "78": 58, "8": [54, 75], "9": [9, 12, 32, 58], "99": [21, 32], "999": 32, "9998": 62, "9999": [4, 9, 32, 58, 62, 73], "A": [1, 2, 4, 7, 8, 9, 11, 14, 15, 19, 20, 21, 22, 44, 47, 48, 50, 51, 53, 54, 60, 61, 62, 77], "And": 58, "As": [2, 4, 9, 58, 65], "At": 9, "But": [4, 21, 75], "By": [7, 9, 11, 50, 62], "For": [1, 4, 7, 8, 9, 14, 17, 19, 22, 50, 54, 58, 60, 61, 62, 63, 65, 73, 75, 77], "If": [1, 3, 4, 7, 8, 9, 11, 12, 13, 14, 15, 17, 19, 20, 21, 42, 45, 46, 50, 51, 53, 54, 58, 61, 62, 63, 77], "In": [4, 7, 9, 11, 14, 15, 19, 20, 21, 58, 62, 63, 65], "It": [1, 3, 4, 7, 8, 9, 11, 14, 15, 16, 17, 19, 21, 22, 44, 54, 57, 58, 60, 61, 62, 63, 65, 75, 76], "Its": 60, "NO": [73, 77], "Not": 8, "On": [2, 4, 73, 77], "One": [46, 63], "TO": 20, "That": [8, 77], "The": [1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 26, 29, 32, 42, 44, 45, 46, 47, 48, 51, 53, 54, 56, 58, 59, 60, 61, 62, 63, 65, 73, 75, 76], "Then": [13, 29, 62], "There": [1, 2, 7, 13, 15, 17, 18, 54, 77], "These": [1, 3, 4, 8, 9, 11, 15, 59, 77], "To": [1, 4, 7, 9, 11, 13, 17, 19, 20, 21, 29, 47, 48, 50, 62, 63, 65, 75, 77], "With": [1, 9, 58, 63, 76], "_": [14, 21, 60, 61], "__": [1, 4, 21, 77], "__call__": 20, "__init__": [3, 4, 8, 15, 17, 22, 62], "__version__": 3, "_bar": 21, "_bind_devic": [11, 15], "_build": 1, "_context": [4, 20], "_csm": [4, 22], "_devic": 56, "_ex": 7, "_example_high_limit": 7, "_example_low_limit": 7, "_foo": 62, "_get_initial_st": [4, 22], "_get_state_handl": [4, 22], "_get_transition_handl": [4, 22], "_in_state_": 20, "_initialize_data": [4, 22], "_on_entry_": 20, "_on_exit_": 20, "_option": 11, "_set_logging_context": 17, "_target": 4, "_temp": 21, "a0": 42, "abil": [62, 69], "abl": [18, 21], "abort": 51, "about": [4, 7, 9, 13, 19, 42, 61, 76, 77], "abov": [2, 4, 7, 9, 15, 20, 58, 63, 75, 77], "absolut": 21, "absolute_path": 21, "abstract": [9, 59], "acceler": [27, 29], "accept": [4, 8, 9, 11, 15, 21, 62, 63, 75], "access": [4, 7, 11, 14, 16, 20, 50, 51, 57, 58, 59, 62, 63], "accessviolationexcept": 16, "accompani": 54, "accord": [4, 7, 15, 19], "account": [3, 75], "accumul": 19, "achiev": [4, 7, 11, 75], "ack": [54, 56], "acknowledge_pressur": 56, "acquir": [8, 11, 14], "across": 46, "action": [4, 16, 56], "activ": [1, 3, 21, 58, 62], "actual": [1, 4, 7, 11, 14, 17, 19, 21, 51, 61, 62, 65, 76], "ad": [9, 11, 18, 21, 51, 54, 60, 61, 62, 65, 66, 67, 68, 69], "adapt": [2, 4, 15, 19, 51, 54, 57, 58, 61, 62, 63, 65, 75, 77], "adapter_opt": 51, "adaptercollect": [11, 58], "add": [1, 9, 11, 14, 17, 51, 54, 61, 62, 66, 67, 69, 70], "add_adapt": 11, "add_ascii_charact": 54, "add_object": 14, "add_path": 51, "add_processor": [18, 62], "addit": [2, 4, 9, 15, 58, 65, 77], "addition": 4, "addr": 8, "address": [8, 46, 60, 61, 62, 73], "adher": 15, "adjust": [2, 42, 76], "adrian": 61, "advantag": [1, 4, 22], "advis": 75, "affect": [65, 75], "after": [4, 12, 13, 18, 19, 20, 29, 47, 48, 50, 59, 62, 63, 65, 75], "afterward": 14, "again": [1, 3, 15, 22, 58, 64, 76], "against": [1, 2, 3, 9, 20, 62], "aim": [58, 61], "alarm": 61, "all": [1, 2, 3, 4, 7, 8, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 42, 46, 50, 58, 61, 63, 65, 73, 75, 77], "allocate_lock": 11, "allow": [2, 4, 7, 15, 16, 19, 20, 22, 54, 58, 61, 62], "allowed_valu": 54, "alon": 7, "along": [1, 2, 4, 6, 58, 76, 77], "alreadi": [4, 11, 14, 20, 21, 45, 60], "also": [1, 3, 4, 7, 9, 11, 14, 15, 17, 20, 21, 22, 42, 58, 61, 62, 63, 75, 76, 77], "altern": [1, 21], "although": 17, "alwai": [2, 4, 9, 20, 21, 27, 33, 40, 45, 58], "amount": 2, "an": [1, 2, 3, 4, 7, 8, 9, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 32, 44, 45, 47, 49, 51, 53, 54, 56, 58, 60, 62, 63, 64, 65, 75, 76, 77], "analog": [15, 65], "ani": [2, 3, 4, 7, 8, 9, 11, 14, 17, 19, 20, 21, 47, 48, 50, 51, 54, 58, 59, 61, 62, 63, 68, 75], "anoth": [1, 3, 4, 19, 58, 62, 63, 75], "any_except": 54, "anymor": [62, 63], "anyon": [4, 14], "anystr": 54, "anyth": [7, 8, 9, 11, 21, 47, 54, 65], "anywai": 4, "api": [6, 14, 21, 50, 61, 65], "appear": 17, "append": 4, "appli": [7, 9, 11, 17], "applic": 1, "approach": [2, 4, 14, 18, 57], "appropri": [4, 11, 13, 20], "approv": 1, "approvaltest": 1, "approxim": [7, 8, 9, 11, 51], "ar": [1, 2, 3, 4, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 29, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55, 57, 58, 61, 62, 63, 64, 65, 68, 75, 76, 77], "arbitrari": [11, 47, 61], "archiv": 77, "aren": [3, 20], "arg": [11, 13, 15, 17, 42, 54], "arg_count": 9, "arg_regex": 54, "arg_sep": 54, "argument": [4, 7, 9, 11, 13, 14, 15, 17, 19, 21, 49, 50, 51, 54, 56, 58, 61, 62, 63, 73, 75, 77], "argument_list": 51, "argument_map": [4, 9, 54], "argumentpars": 49, "around": [2, 21], "arrai": 42, "arriv": 61, "ascii": 54, "asid": 58, "ask": 19, "aspect": 19, "assign": [9, 11, 19, 20, 46, 75], "assum": [4, 15, 19, 20, 21, 58, 62, 75, 77], "ast": 75, "async_chat": 9, "asynchron": [65, 75], "at_least_on": 54, "attempt": [9, 21, 62, 75], "attribut": [7, 8, 9, 19, 20, 21, 46, 61, 65, 75], "attributeerror": 56, "authent": 14, "auto": [9, 20, 42], "autom": [2, 13, 58], "automat": [2, 4, 7, 9, 14, 20, 42, 63, 64, 65], "avail": [2, 3, 4, 9, 11, 14, 15, 19, 22, 42, 48, 51, 57, 58, 60, 61, 62, 63, 65, 75, 77], "avoid": [4, 75], "awar": [14, 77], "b": [1, 9, 21, 62, 75], "back": [1, 14, 61], "backend": 9, "bad": 61, "bar": [9, 17, 21, 48], "bar_max": 21, "bar_min": 21, "base": [1, 2, 4, 7, 8, 9, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 26, 27, 29, 32, 33, 35, 36, 39, 40, 42, 44, 45, 46, 47, 48, 54, 59, 61, 63, 65, 75, 77], "base_dict": 21, "basenam": 21, "bashwork": 8, "basi": [2, 18], "basic": [8, 14, 46, 58, 77], "baz": 17, "bdist_wheel": 3, "bear": 57, "becaus": [4, 7, 13, 14, 19, 21, 22, 65, 68, 75], "becom": [2, 4, 11, 58, 62, 64], "been": [1, 2, 3, 4, 7, 8, 9, 11, 14, 15, 19, 51, 59, 60, 61, 62, 63, 64, 65, 76, 77], "befor": [1, 2, 9, 11, 14, 18, 19, 20, 49, 51, 62, 63], "begin": [1, 8, 21], "begun": 9, "behav": [4, 63], "behavior": [4, 7, 9, 11, 12, 14, 17, 61, 62, 63, 75], "behaviour": [2, 20, 75], "being": [17, 19, 20, 65], "belong": [17, 21], "below": [4, 58, 63], "benefit": 2, "besid": [7, 63, 75], "best": [3, 4, 9], "better": [9, 16, 65], "between": [2, 4, 7, 9, 11, 15, 17, 19, 21, 22, 32, 42, 51, 54, 56, 58, 77], "bin": [3, 58], "bind": [2, 4, 7, 8, 9, 11, 14, 20, 61, 62, 65], "bind_address": [4, 9, 62, 73], "bind_handlers_by_nam": 20, "bindhandlersbynam": 2, "bit": [1, 17, 65, 77], "black": 68, "blank": 54, "block": [8, 11, 13, 14, 75], "blurb": 3, "bool": [8, 11, 22, 26, 32, 39, 45, 53, 54], "boolean": [11, 56], "both": [2, 4, 7, 9, 11, 14, 58, 59, 62, 63, 73, 75, 77], "bound": [4, 7, 9, 11, 15, 21, 60], "boundpv": [7, 16], "box": 9, "branch": [1, 3, 4], "break": [1, 4, 21], "breviti": 4, "bridg": 9, "brief": 75, "broad": [21, 58], "broadcast": 73, "broken": 15, "buffer": [8, 65], "build": [1, 52, 54, 65], "builder": [15, 54], "built": [2, 75], "builtin": 13, "bypass": [58, 62], "byte": [8, 42, 53, 69, 70], "byte_convers": 57, "bytearrai": 8, "c": [21, 32, 42, 51, 58, 75], "ca": 29, "cabl": 75, "caget": [7, 29], "calcul": [11, 12, 18, 19], "call": [2, 4, 7, 8, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 50, 54, 56, 58, 61, 62, 65, 75, 77], "callabl": [4, 7, 8, 9, 14, 17, 20, 22, 26, 32, 39, 45, 56, 62], "callback": 2, "calld": 4, "can": [1, 2, 3, 4, 7, 8, 9, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 29, 35, 36, 42, 45, 47, 51, 54, 57, 58, 60, 61, 62, 63, 65, 73, 75, 76, 77], "cannot": [9, 75], "canprocess": [18, 20, 22, 26], "canprocesscomposit": [18, 22], "capabl": 61, "captur": [2, 4, 9, 14, 54], "caput": [7, 29], "carri": [8, 11, 29], "case": [4, 7, 8, 9, 11, 13, 14, 15, 17, 19, 20, 21, 53, 54, 58, 62, 63, 75], "cast": 54, "caught": 16, "caus": [2, 4, 7, 8, 9, 11, 17, 62, 65, 76], "cd": [1, 3], "central": [14, 20], "certain": [2, 4, 9, 11, 12, 14, 17, 19, 21, 22, 48, 63, 76, 77], "certainti": 63, "cfg": 20, "chang": [1, 4, 7, 8, 12, 15, 17, 19, 20, 21, 42, 58, 61, 63, 75], "channel": [50, 56, 59, 62, 75, 76], "channelaccess": [7, 29], "char": [48, 54], "char_numb": 54, "charact": [21, 54, 58, 62], "characterist": 4, "check": [1, 2, 3, 4, 8, 9, 20, 62, 63, 68], "check_limit": [16, 21], "checklist": 57, "checkout": 1, "chic": 59, "choic": 77, "choos": 4, "chopper": [4, 15, 57, 58, 59, 62, 73, 75, 77], "chopper_build": 15, "chopperepicsinterfac": 29, "chosen": 14, "chunk": 61, "ci": 1, "circul": 32, "circumst": [20, 65], "clark": 61, "class": [2, 4, 7, 8, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 26, 27, 29, 32, 33, 35, 36, 39, 40, 42, 44, 45, 46, 47, 48, 54, 61, 62, 63, 65], "clean": [3, 7, 9, 11, 21], "cleandoc": 21, "cleaner": 65, "cli": 58, "client": [7, 8, 9, 11, 13, 14, 20, 50, 57, 62, 63], "clone": [1, 3, 77], "close": [1, 2, 3, 7, 8, 9, 11, 61], "closer": [1, 65], "cmd": [4, 9, 29, 54, 63], "cmdbuilder": 54, "co": [8, 46, 61], "coars": 4, "code": [1, 4, 8, 13, 20, 22, 58, 61, 62, 65], "codebas": 60, "coerc": 64, "coil": 46, "collect": [4, 11, 13, 14, 18, 22], "colon": [14, 62], "com": [1, 3, 8, 51, 77], "combin": [4, 14, 19, 42], "come": [4, 14, 53], "command": [1, 4, 7, 9, 11, 21, 29, 35, 36, 42, 47, 48, 51, 54, 56, 57, 58, 59, 60, 61, 63, 65, 75, 76, 77], "command_build": 57, "command_separ": 54, "commandbas": 9, "commandlin": [9, 58], "commit": [1, 3, 68], "common": [7, 9, 15, 17, 61, 65], "commun": [2, 4, 11, 19, 51, 55, 57, 60, 61, 62, 63, 76, 77], "compar": [1, 76], "compat": [8, 61, 62, 73], "compens": 2, "compil": [9, 59], "complet": [3, 8, 9, 51, 58, 62, 63], "complex": [2, 4, 7, 15, 22, 57], "compon": [1, 8, 11], "composit": 18, "comprehens": 1, "compris": [9, 75], "comput": [19, 63, 76], "concaten": 11, "concept": [9, 18, 75], "conceptu": 4, "concret": [4, 6, 9, 11, 15, 17, 29, 42, 45], "condit": [1, 2, 20, 22, 56], "conditional_repli": 56, "conf": 3, "config": 7, "configur": [4, 7, 8, 9, 11, 14, 20, 29, 42, 51, 62, 65, 75, 77], "confirm": 35, "connect": [4, 7, 8, 9, 11, 13, 42, 47, 50, 56, 57, 61, 73, 75], "connection_str": 14, "consequ": [15, 63, 75], "consid": [4, 9, 20, 48], "consist": [4, 9, 14, 21, 22, 42, 47, 58], "constant": [9, 57], "constantli": 12, "construct": [2, 8, 9, 11, 14, 15, 19, 20, 21, 22, 51], "constructor": [9, 15, 20, 22, 54], "consult": [4, 9], "consum": 8, "contain": [2, 4, 6, 7, 8, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 29, 52, 54, 56, 61, 62, 75, 77], "content": [57, 63], "context": [4, 11, 17, 20, 56], "continu": [4, 8, 19, 60], "contribut": [61, 63], "contributor": 4, "control": [1, 2, 4, 8, 9, 11, 13, 14, 17, 19, 21, 45, 51, 57, 59, 60, 61, 62, 63, 65, 68, 76, 77], "control_cli": [14, 57, 58, 62, 75], "control_serv": [13, 19, 57, 62, 65], "controlcli": [13, 58, 62, 75], "controlserv": [13, 14, 19, 62, 65], "conveni": [4, 8, 18, 20, 58, 62, 63, 75], "convent": 20, "convers": 4, "convert": [4, 8, 9, 53, 63, 75], "cool": 42, "copi": 29, "core": [2, 4, 6, 57, 58, 61, 62, 63, 65, 75], "correct": [7, 9], "correctli": [1, 3, 11, 63, 77], "correspond": [7, 9, 14, 15, 19, 20], "could": [4, 7, 8, 17], "count": [7, 8, 9], "cover": 77, "crash_pump": 56, "creat": [1, 3, 4, 5, 7, 8, 9, 13, 15, 19, 20, 21, 45, 46, 54, 58, 65], "create_devic": 15, "create_except": 8, "create_interfac": 15, "create_request": 8, "create_respons": 8, "creation": 19, "critic": 51, "crlf": 58, "crnl": [9, 73], "csm": 22, "ctrl": 58, "current": [2, 3, 4, 8, 9, 11, 12, 15, 19, 20, 26, 42, 45, 56, 58, 61, 62, 65, 75, 77], "custom": [20, 21], "cut": 75, "cycl": [4, 8, 11, 18, 19, 20, 22, 27, 33, 40, 45, 50, 51, 58, 59, 63, 65, 76], "cycle_delai": [7, 8, 9, 11, 19, 51, 76], "d": [9, 21, 54], "data": [4, 7, 8, 9, 14, 22, 48, 61, 65], "databank": [8, 46, 61], "datastor": 8, "date": 3, "datetim": 21, "david": 60, "deactiv": 3, "debug": [2, 4, 17, 51, 58, 61, 62], "decid": [65, 77], "declar": [7, 51], "decor": [17, 21, 56], "decreas": [2, 19], "dedic": [1, 63], "def": [4, 7, 9, 17, 21, 56, 61, 62], "default": [7, 9, 11, 14, 15, 17, 20, 21, 46, 47, 48, 50, 51, 53, 54, 58, 62, 63, 65, 73, 75, 76], "default_device_typ": 15, "default_log_format": 17, "default_opt": 11, "default_protocol": 15, "default_valu": 8, "defaultacceleratingst": 27, "defaultcirculatingst": 33, "defaultcoolst": 40, "defaultheatst": 40, "defaultholdst": 40, "defaultidlest": 27, "defaultinitst": [27, 40], "defaultmovingst": [4, 45], "defaultnotcirculatingst": 33, "defaultparkedst": 27, "defaultparkingst": 27, "defaultphaselockedst": 27, "defaultphaselockingst": 27, "defaultstartedst": 40, "defaultstoppedst": [27, 40], "defaultstoppingst": 27, "defer": 21, "defin": [4, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 54, 61, 62, 63], "definit": [4, 9, 63], "deinit": 29, "delai": [48, 51, 56, 58], "delimit": 54, "delta": [18, 19, 20, 27, 33, 40, 45], "demo": 58, "demonstr": [4, 46, 48], "depend": [4, 14, 19, 21, 29, 35, 36, 75, 77], "deposit": 62, "deprec": 51, "depth": 21, "deriv": [2, 8, 11, 15, 19, 20, 32], "describ": [4, 14, 15, 22, 42], "descript": [4, 7, 9, 29, 65, 75], "design": [2, 7, 11, 18], "desir": [1, 2, 7, 8, 11, 14, 19, 20, 56], "despit": 20, "detail": [4, 9, 15, 17, 20, 51, 57, 58, 65, 77], "detect": [4, 11], "determin": [7, 19, 26, 51, 65], "determinist": [2, 59], "dev": 1, "develop": [3, 4, 7, 59, 77], "developer_guid": [65, 77], "deviat": [8, 11], "devic": [1, 2, 3, 5, 7, 8, 9, 11, 16, 17, 19, 20, 44, 45, 46, 47, 48, 50, 51, 54, 56, 57, 58, 59, 60, 61, 62, 63, 65, 67, 76, 77], "device_1": 4, "device_2": 4, "device_build": [15, 19], "device_lock": [8, 11], "device_modul": 15, "device_nam": [15, 19, 77], "device_packag": 51, "device_typ": [4, 15], "devicebas": [11, 15, 22, 58], "devicebuild": [15, 19], "deviceinterfac": 63, "devicenam": 17, "deviceregistri": 15, "devices_packag": 19, "di": [8, 46, 61], "diagnos": [51, 62], "diagnost": 58, "dict": [4, 7, 11, 14, 15, 19, 20, 21, 22, 26, 32, 39, 45, 62], "dict_strict_upd": 21, "dict_valu": 75, "dictionari": [4, 7, 9, 11, 13, 14, 15, 19, 21, 22, 61, 62], "die": 4, "differ": [1, 4, 11, 17, 19, 21, 29, 35, 36, 44, 59, 61, 62, 63, 65, 75, 77], "digit": 54, "dir": 14, "direct": 21, "directli": [7, 8, 9, 13, 14, 15, 17, 20, 58, 63, 77], "directori": [3, 4, 21, 77], "disabl": [9, 17, 58, 64, 65], "disc": 29, "disconnect": [11, 61, 62, 75], "discov": 15, "discoveri": 15, "discret": 46, "dispatch": 14, "displai": [4, 16, 21, 58, 60, 61, 63], "dist": 3, "distinguish": [16, 17], "distribut": [4, 77], "dmsc": 51, "do": [1, 4, 7, 9, 11, 14, 15, 17, 20, 21, 22, 48, 54, 58, 62, 63, 65, 77], "do_import": 21, "doafterprocess": 18, "dobefor": 18, "dobeforeprocess": 18, "doc": [1, 3, 4, 7, 8, 9, 21, 65], "docstr": [4, 7, 9, 11], "document": [1, 3, 7, 9, 11, 17, 21, 29, 42, 45, 51, 58, 59, 60, 61, 63, 74, 75], "doe": [4, 7, 8, 9, 11, 14, 15, 18, 19, 21, 22, 29, 47, 48, 54, 56, 62, 63, 64, 65], "doesn": 1, "done": [3, 4, 18, 75, 77], "doprocess": [4, 18, 20, 22], "dot": 13, "doubl": [3, 75], "down": [7, 8, 9, 11, 58, 61], "download": 77, "draft": 3, "drive": 2, "dt": [4, 12, 18, 20, 27, 33, 40, 45], "dual_devic": [57, 58, 63], "due": [8, 9, 11, 61, 63, 64, 65], "dummi": [11, 21], "duplic": 4, "dure": [1, 2, 8, 9, 11, 19, 20, 47, 48], "dynam": [13, 29, 42, 45], "e": [13, 15, 21, 51, 54, 65], "each": [2, 4, 6, 8, 9, 11, 14, 15, 18, 19, 20, 21, 22, 46, 49, 51, 54, 62, 65], "earlier": [9, 63], "easi": [4, 9, 14, 57], "easier": [4, 7, 11, 20, 60, 61, 62, 63, 65], "easiest": 4, "easili": [17, 22, 29], "edit": [1, 65], "effect": [2, 4, 7, 19, 32, 51, 65, 75], "effort": 2, "eight": 61, "either": [1, 4, 9, 13, 14, 47], "elaps": [4, 18, 19, 21, 51], "electron": 2, "element": 21, "elig": 2, "els": [15, 21], "elsewher": 15, "empti": [4, 9, 11, 13, 14, 32, 42, 65, 73], "emul": [52, 54], "en": [4, 77], "enabl": [16, 17, 19, 20, 58, 60, 63], "encod": 54, "encourag": 63, "end": [1, 2, 8, 13, 20, 21, 54, 65], "endian": 53, "enforc": [16, 60], "enough": [4, 7, 8, 21], "enq": 54, "enquirei": 54, "ensur": [3, 11], "enter": [2, 20, 27, 40, 47, 58, 63], "entir": [4, 9, 19, 58, 65], "entri": [8, 14, 20, 22, 27, 40], "enum": [7, 54], "environ": [1, 3, 4, 21, 29, 57, 58, 65], "eo": 54, "eoferror": 8, "eot": 54, "epic": [4, 9, 11, 29, 44, 57, 59, 60, 61, 62, 63, 65, 75, 77], "epics_ca_addr_list": [73, 77], "epics_ca_auto_addr_list": [73, 77], "epics_cas_intf_addr_list": [73, 77], "epics_devic": [62, 63], "epics_interfac": 57, "epicsadapt": [7, 11, 62, 63], "epicsinterfac": [7, 9, 29, 44, 63], "equal": 45, "equival": [15, 54, 75], "err": 4, "error": [1, 4, 9, 15, 16, 19, 21, 42, 45, 47, 48, 51, 60, 62, 63, 65], "escap": [54, 58, 75], "especi": [4, 65], "ess": [29, 51, 59], "essenti": [18, 21, 75], "establish": [7, 8, 9, 11], "etc": [11, 13, 14, 22, 61, 62], "etx": 54, "evalu": 75, "even": [1, 4, 7, 20, 21, 22, 65], "event": [2, 20, 27, 33, 40, 45], "eventu": 4, "everi": [2, 20, 27, 33, 40, 45, 62], "everyth": [1, 4, 11, 14, 17, 21], "ex": 54, "exact": 9, "exact_match": 9, "exactli": [2, 7, 9, 20, 27, 33, 40, 45, 47, 63, 77], "exampl": [1, 7, 8, 9, 11, 12, 14, 15, 16, 17, 21, 29, 54, 56, 57, 59, 61, 62, 63, 64, 65, 73, 75, 76, 77], "example_meta": 7, "example_motor": [4, 57, 58], "examplemodbusinterfac": 46, "examplemotorstreaminterfac": [4, 45, 58], "exceed": 56, "except": [4, 8, 9, 13, 19, 20, 21, 47, 48, 56, 57, 61, 66], "exception_typ": 13, "exchang": 9, "exclud": 14, "exclude_inherit": 14, "exclus": 21, "execut": [4, 20, 29, 56, 58], "execute_command": 29, "exist": [1, 3, 4, 7, 9, 11, 15, 18, 19, 22, 51, 63, 75], "exit": [2, 20, 40, 50, 51, 62], "expect": [1, 3, 7, 8, 9, 11, 16, 75], "expens": 63, "experi": 4, "explain": 4, "explan": 9, "explicitli": [11, 14, 15, 60], "export": [73, 77], "expos": [4, 7, 8, 9, 11, 13, 14, 15, 19, 44, 51, 58, 61, 63, 75, 76], "exposed_object": 14, "exposedobject": 14, "exposedobjectcollect": 14, "express": [4, 7, 9, 54, 62, 63], "extend": [61, 62, 63], "extens": 21, "extern": [4, 32, 62], "extract": [9, 21, 49], "extract_module_nam": 21, "f": [4, 9, 21, 58, 63], "fact": 58, "factor": 2, "factori": [15, 19], "fail": [1, 61, 62, 64], "failur": 4, "fairli": 75, "fall": 8, "fals": [7, 8, 9, 14, 15, 20, 21, 54, 56, 73, 75], "familiar": 58, "far": 4, "fashion": [2, 75], "fast": [2, 59], "faster": 76, "faulti": 4, "featur": [4, 58], "feel": 63, "fetch": 61, "few": [1, 3, 4, 14, 15, 58, 60, 61, 77], "fidel": 2, "file": [1, 4, 9, 15, 21, 60, 65, 77], "fill": 21, "filter": 21, "final": [4, 9, 19, 54, 76], "find": [58, 65, 75], "fine": 62, "finish": 60, "finit": [4, 22, 59], "first": [1, 2, 4, 7, 8, 9, 15, 20, 22, 29, 53, 56, 60, 65, 77], "fit": 11, "five": 47, "fix": [3, 65, 69], "flag": [4, 9, 50, 51, 60, 61, 62, 63, 65], "flake8": [1, 60, 68], "flexibl": [21, 75], "float": [4, 9, 11, 12, 21, 22, 26, 32, 39, 44, 45, 53, 54, 56, 63], "float_to_raw_byt": 53, "float_valu": 75, "fluent": 54, "flush": 65, "focu": 4, "folder": 4, "follow": [1, 2, 4, 7, 8, 9, 14, 15, 20, 22, 48, 51, 58, 62, 73, 75, 77], "font": 63, "foo": [9, 17, 21, 48, 62], "foobar": 48, "foodevic": 62, "foodeviceinterfac": 62, "forc": [20, 50], "forev": 64, "fork": 4, "form": [4, 9, 18], "format": [1, 4, 9, 13, 17, 21, 49, 51, 59, 63, 68, 77], "format_doc_text": 21, "former": [7, 65], "forward": [2, 4, 7, 9, 15, 59], "found": [3, 4, 14, 15, 20, 51, 65], "four": [8, 46], "fourth": 4, "fp50": [36, 61], "fp50_mh": 35, "fragment": 8, "frame": [8, 61], "framework": [1, 11, 15, 51, 57, 61, 62, 63, 65], "framework_vers": [4, 15, 62, 63, 64], "free": [7, 9, 17, 63], "frequenc": [7, 65], "frequent": 19, "fresh": [1, 3, 4, 65], "from": [1, 2, 3, 4, 7, 8, 9, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 49, 51, 57, 58, 61, 62, 63, 65, 75, 76], "from_bytearrai": 8, "fromoptionaldepend": 21, "front": 8, "fulfil": 8, "full": [46, 75], "fulli": 4, "func": [7, 9], "function": [2, 4, 7, 8, 9, 11, 12, 13, 14, 17, 19, 20, 21, 22, 42, 49, 50, 51, 54, 56, 58, 61, 62, 63, 67, 68, 70, 75, 76], "functool": 54, "furthermor": 14, "futur": [8, 51, 61, 62], "g": [15, 54], "gener": [1, 4, 7, 8, 9, 11, 13, 14, 29, 42, 45, 50, 54, 60, 62, 65, 75, 77], "get": [4, 7, 8, 9, 11, 14, 21, 58, 75, 77], "get_api": 14, "get_exampl": 7, "get_foo": 62, "get_memb": 21, "get_multicommand": 54, "get_object": [13, 14, 58, 75], "get_object_collect": 13, "get_param": [44, 47], "get_posit": [4, 45], "get_pr": 54, "get_protocol": 19, "get_spe": 9, "get_statu": [4, 42, 45], "get_submodul": 21, "get_target": [4, 45], "get_usage_text": 49, "getter": [7, 9, 14, 62], "git": [1, 77], "github": [1, 4, 8, 51, 57, 59, 77], "give": 58, "given": [2, 8, 12, 14, 20, 53, 54], "global": 62, "go": [1, 3, 4, 58], "goe": [63, 75], "golden": 1, "good": [1, 4, 11], "gpl": 57, "gracefulli": 70, "grain": 62, "granular": 59, "greater": [12, 19], "groundwork": [8, 62], "group": [4, 9, 54], "guarante": 19, "guid": [3, 66, 77], "guidelin": [8, 11, 19, 60], "gz": 3, "h": [4, 50, 51, 58], "ha": [1, 2, 4, 7, 8, 9, 11, 13, 14, 15, 19, 21, 22, 45, 47, 51, 56, 58, 60, 61, 62, 63, 65, 73, 75, 76, 77], "had": [20, 60, 64], "halt": 7, "handl": [2, 7, 8, 9, 11, 16, 20, 27, 33, 40, 45, 47, 48, 63, 66, 70], "handle_error": [9, 42, 47, 48, 65], "handler": [2, 4, 14, 20, 22], "hang": [11, 19, 62, 64], "happen": [1, 4, 7, 11, 15, 19, 29, 54], "hard": [11, 21], "hardwar": [5, 9, 35, 36], "has_log": 17, "hascontext": 20, "haslog": 17, "have": [1, 3, 4, 7, 8, 9, 11, 17, 18, 19, 20, 22, 29, 35, 36, 46, 48, 51, 58, 59, 60, 61, 62, 63, 65, 75, 77], "he": 36, "heart": 4, "heartbeat": 2, "heat": 42, "heater": 32, "hello": [48, 75], "hello_world": 75, "help": [14, 21, 49, 50, 51, 52, 62, 77], "helper": [21, 49], "here": [4, 11, 46, 67, 75], "high": 7, "highest": 19, "hihi": 7, "hilim": 7, "hint": 62, "hold": [8, 15, 42, 46], "hook": 1, "host": [4, 9, 13, 14, 19, 42, 45, 47, 48, 50, 51, 58, 75], "hostnam": 60, "how": [2, 4, 8, 9, 17, 48, 58, 63, 65, 75, 76], "howev": [7, 9, 13, 19, 29, 65, 75], "hr": [8, 46], "html": [1, 77], "http": [1, 3, 4, 8, 51, 77], "hypothet": 4, "i": [1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 26, 27, 29, 32, 33, 40, 42, 44, 45, 46, 48, 50, 51, 53, 54, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 75, 76, 77], "ibex": 61, "icanon": 48, "id": 13, "ideal": [3, 4], "ident": 4, "identifi": [2, 14, 62], "idl": [4, 20, 27, 33, 40, 45, 58], "iff": 20, "ignor": [4, 9, 21, 47, 48, 51, 54, 62, 63], "ignore_cas": 54, "imag": 63, "immedi": [4, 45], "implement": [2, 6, 7, 8, 9, 11, 15, 18, 20, 22, 46, 47, 48, 58, 59, 61, 63], "impli": 19, "implicit": 2, "import": [1, 4, 7, 8, 9, 11, 15, 17, 19, 21, 58, 62, 63, 66, 75], "import_devic": 4, "import_modul": 15, "importantli": [11, 16], "importerror": 21, "importlib": 15, "imposs": 61, "improv": [57, 66], "in_stat": [2, 4, 20, 27, 33, 40, 45], "in_state_new_st": 20, "in_termin": [4, 9, 65], "inbound": 9, "includ": [1, 2, 3, 8, 20, 54, 63, 65, 73, 77], "incom": [8, 9, 65], "incomplet": [8, 62], "increas": [2, 19], "increment": 65, "indent": [7, 9, 11, 21, 49], "independ": [2, 21], "index": 77, "indexerror": 8, "indic": [7, 8, 9, 11, 16, 19], "individu": 2, "infinit": [14, 63], "influenc": [4, 19, 51, 63], "info": [4, 7, 17, 51, 58], "inform": [4, 7, 9, 13, 19, 42, 49, 58, 61, 62, 75, 76], "infrastructur": [4, 8, 11, 14, 15], "inherit": [4, 7, 11, 14, 15, 20, 22, 62, 63], "inhibit": 56, "init": [2, 8, 20, 29], "initi": [2, 4, 8, 13, 15, 20, 21, 22, 29, 46, 59, 75], "initialis": 22, "inkscap": 63, "inner": 15, "input": [2, 8, 19, 21, 46, 51, 56, 62, 65], "insensit": 9, "insert": 17, "insid": [62, 77], "inspect": [7, 14, 15, 21], "instal": [1, 3, 4, 13, 21, 57, 60, 63, 65, 68, 74], "instanc": [7, 8, 13, 14, 15, 17, 19, 20, 21, 22, 42, 45, 46, 49, 58], "instanti": [8, 21], "instead": [9, 13, 19, 20, 56, 60, 61, 62, 63, 65, 75], "instruct": 77, "int": [7, 9, 21, 53, 54], "int_to_raw_byt": 53, "int_valu": 75, "integ": [9, 32, 44, 53, 54], "integr": [8, 32, 60, 63], "intend": [1, 22, 51], "intent": [7, 16], "intention": 4, "interact": [4, 8, 11, 29, 48, 50, 51, 58, 75], "interest": [4, 63], "interf": 65, "interfac": [7, 8, 9, 11, 13, 14, 15, 18, 19, 20, 44, 45, 46, 47, 51, 54, 57, 58, 60, 61, 63, 65, 77], "interfacebas": [7, 8, 9, 11, 15], "intermin": 9, "intern": [4, 9, 14, 15, 19, 22, 26, 32, 63], "interpret": [9, 14, 21, 57, 65], "interrupt": 51, "interv": [7, 9, 19, 45], "introduc": [9, 14, 60, 63], "invalid": [15, 19, 61], "invers": 9, "invoc": 60, "invok": [20, 47, 50], "involv": [2, 7, 11], "io": [11, 77], "ioc": 54, "ip": [4, 9, 60], "ir": [8, 46], "is_connect": [11, 75], "is_devic": 15, "is_interfac": 15, "is_paus": 19, "is_run": [7, 8, 9, 11, 14], "is_start": 19, "is_valid": 8, "isi": 69, "isiscomputinggroup": [1, 3, 77], "isort": 68, "issu": [1, 2, 3, 7, 9, 42, 57, 58, 61, 69], "item": 18, "item_that_implements_canprocess": 18, "iter": [9, 18], "its": [2, 4, 6, 8, 11, 14, 19, 20, 46, 54, 65, 75, 77], "itself": [7, 15, 20, 51, 58, 63, 75], "januari": 61, "job": 60, "join": 11, "joshburnett": 63, "json": [13, 14, 51, 61, 75], "json_rpc": 13, "jsonrpcresponsemanag": 14, "julabo": [57, 58, 61], "julabo_stream_interface_1": 57, "julabo_stream_interface_2": 57, "julabostreaminterfacev1": 35, "julabostreaminterfacev2": 36, "just": [1, 2, 46, 54, 58, 75, 77], "k": [4, 51, 58, 62, 63], "keep": [7, 11, 21, 59, 77], "kei": [7, 11, 14, 15, 19, 20, 21, 22, 54, 63], "keithlei": 54, "kept": 8, "keyword": 15, "kind": 9, "kindli": 61, "know": [4, 19], "known": [15, 20], "kwarg": [7, 8, 15, 17, 54], "l": [51, 62, 65], "laid": 62, "lambda": [4, 7, 9, 61, 62, 63], "languag": 4, "larg": 11, "last": [1, 2, 8, 20, 27, 29, 33, 40, 45, 56, 58], "last_addr": 8, "last_command": 29, "lastli": [9, 15], "later": [4, 11, 22, 62], "latest": 77, "latter": [4, 15], "launch": 58, "layer": 2, "lead": [65, 75], "least": [20, 21, 50, 53, 54], "leav": 20, "left": [4, 8, 9, 50, 62], "length": [4, 9, 53, 54], "less": [12, 56], "letter": 75, "level": [1, 4, 13, 17, 22, 51, 58, 61, 62, 77], "lewi": [2, 3, 4, 59, 60, 61, 62, 63, 64, 65, 68, 69, 73, 75, 76, 77], "lewis_control": 75, "lewis_test": 1, "lewisexcept": [15, 16, 21], "librari": [5, 17, 58], "licens": 57, "lifetim": [7, 8, 9, 11], "like": [1, 2, 4, 7, 9, 13, 14, 15, 18, 20, 22, 54, 58, 61, 62, 63, 75, 77], "likelihood": 4, "limit": [4, 7, 16, 21, 42, 61, 65, 75], "limitviolationexcept": [16, 21], "line": [9, 21, 29, 42, 49, 51, 57, 58, 59, 60, 75, 77], "linear": [4, 12], "linearli": 12, "linkam": [42, 59], "linkam_t95": [3, 4, 57, 58, 60, 61, 62, 73], "linkamt95streaminterfac": 42, "linux": [4, 73, 77], "list": [3, 4, 8, 9, 11, 13, 14, 19, 20, 21, 50, 51, 54, 55, 62, 71, 75, 77], "list_valu": 75, "listen": [7, 8, 9, 11, 13, 14, 42, 58, 73], "liter": 75, "literal_ev": 75, "littl": [4, 53, 63], "live": 4, "lnp": 42, "load": [7, 15, 19, 21, 51, 63, 65], "local": [1, 77], "localhost": [58, 60, 62, 73, 77], "locat": [6, 8, 15, 58, 63, 77], "lock": [8, 11, 14], "log": [15, 19, 51, 57, 61, 62, 63, 65, 75], "logger": [4, 17], "logo": 63, "lolim": 7, "lolo": 7, "long": [50, 62, 75, 76], "longer": [1, 19, 62, 64, 65], "look": [1, 4, 7, 8, 9, 11, 15, 56, 61], "loop": [14, 75], "loopback": 77, "lot": [4, 61], "low": [53, 63], "low_byte_first": 53, "low_bytes_first": 53, "lower": 21, "m": [1, 3, 9, 56, 65, 77], "m0": 42, "machin": [4, 14, 17, 19, 20, 22, 26, 58, 59, 63, 77], "machineri": 7, "made": [3, 4, 8, 9, 11, 19, 20, 60, 61, 62, 63, 65, 75], "magneticbear": 26, "mai": [2, 3, 4, 7, 8, 9, 11, 19, 20, 58, 61, 65, 75], "main": [3, 4, 16, 51, 58, 60], "mainli": [15, 22, 61], "maintain": 62, "major": [4, 63, 68], "make": [1, 3, 4, 7, 8, 9, 11, 12, 14, 17, 20, 21, 54, 57, 58, 60, 61, 62, 63, 75], "malform": 13, "man": 4, "manag": [11, 77], "mandatori": 15, "mani": [4, 8, 9, 58, 65, 76], "manipul": [13, 50], "manual": [1, 3, 21, 42, 58, 77], "map": [7, 9, 15, 54, 63], "map_argu": 9, "map_return_valu": 9, "march": 62, "markdown": 59, "master": 1, "match": [1, 4, 9, 15, 20, 35, 36, 54, 62, 63, 75], "matcher": 9, "matt": 61, "matter": [4, 7, 9, 20], "maximum": 51, "mbex": 8, "mean": [4, 11, 15, 19, 58, 73, 75, 77], "meaning": 16, "meant": [8, 15, 18, 19, 22, 58, 73], "mechan": 19, "member": [1, 4, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 25, 26, 27, 29, 32, 33, 35, 36, 39, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 56, 61, 62], "memori": [8, 46, 61], "mention": 20, "merg": [4, 7, 62], "messag": [4, 13, 15, 16, 17, 21, 47, 50, 51, 60, 61, 62, 63, 65, 67], "meta": [7, 61], "meta_data_properti": [7, 62], "meta_target": 7, "metadata": [61, 65], "method": [2, 4, 7, 8, 9, 11, 13, 14, 15, 17, 18, 19, 21, 22, 47, 48, 50, 54, 56, 58, 62, 75], "method_call_with_two_string_arg": 75, "michel": 60, "might": [1, 4, 7, 11, 21, 54, 75], "millisecond": [13, 65], "mimic": 9, "min": 42, "mind": 21, "minim": [4, 11], "minimum": 56, "minimum_time_delai": 56, "minor": [63, 66, 67, 68, 69, 70, 71], "mirror": 13, "mismatch": [4, 51, 62, 63], "miss": [58, 62, 65], "mix": 70, "mixin": 20, "mm": [4, 45], "mod": 61, "modbu": [57, 61, 62], "modbus_application_protocol_v1_1b3": 8, "modbus_devic": [8, 57, 58, 61, 62], "modbus_messaging_implementation_guide_v1_0b": 8, "modbusadapt": [8, 63], "modbusbasicdatabank": 8, "modbusdatabank": [8, 46], "modbusdatastor": 8, "modbusdevic": 46, "modbusinterfac": [8, 46, 63], "modbusprotocol": 8, "modbustcpfram": 8, "mode": [32, 42, 48], "model": [2, 4, 7, 8, 12, 42, 75], "modifi": [1, 4, 9, 11, 17, 21, 51, 58, 63], "modul": [4, 7, 8, 11, 13, 14, 15, 17, 18, 20, 21, 22, 51, 58, 61, 62, 63, 66, 77], "moduletyp": 21, "moment": 19, "more": [1, 7, 8, 9, 15, 16, 17, 20, 21, 22, 48, 58, 62, 63, 65, 75, 77], "most": [4, 9, 11, 15, 19, 20, 22, 53, 61, 62, 77], "motion": [2, 59], "motor": [4, 45, 57], "move": [4, 12, 29, 45, 58, 62, 65], "movement": [4, 45, 58], "msec": 9, "much": [2, 4, 7, 8, 13, 63, 65, 75, 76], "multipl": [4, 7, 8, 9, 11, 14, 15, 18, 19, 20, 51, 54, 61, 63, 65, 75], "multipli": [19, 42, 44, 51], "must": [4, 7, 8, 9, 11, 13, 15, 21, 22, 32, 42, 48, 50, 54, 62, 63, 73, 77], "my_devic": 4, "myenv": 58, "n": [4, 13, 42, 50, 51, 61, 63], "name": [1, 2, 3, 4, 7, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 29, 51, 54, 56, 60, 61, 62, 73, 75, 76], "named_object": 14, "namespac": 62, "nanodac": 61, "nativ": 61, "natur": [2, 20], "nc": 48, "necessari": [1, 2, 4, 8, 11, 15, 62, 63, 77], "necessarili": 54, "need": [2, 3, 4, 7, 11, 15, 19, 20, 22, 54, 61, 62, 63, 77], "neg": [19, 42, 53], "neither": 44, "net": 4, "netcat": 48, "network": [9, 11, 13, 14, 19, 58, 59, 61, 63, 64, 73, 75, 76], "never": [12, 13, 20], "nevertheless": 60, "new": [1, 2, 3, 4, 9, 11, 12, 15, 17, 19, 20, 21, 32, 45, 57, 58, 75], "new_bar": 9, "new_context": 20, "new_devic": 11, "new_exampl": 7, "new_param": 47, "new_po": 12, "new_regex": 54, "new_setup": [19, 62, 75], "new_spe": 9, "new_target": [4, 45], "new_valu": 21, "newer": 4, "newli": 4, "next": [3, 8, 20, 54, 62], "node": 58, "nolock": 11, "non": 4, "none": [3, 4, 7, 8, 9, 11, 13, 14, 15, 17, 19, 20, 21, 22, 26, 27, 32, 33, 39, 40, 42, 45, 47, 49, 50, 51, 54, 56], "noreturn": [9, 15], "normal": [1, 56, 58, 61, 65], "nose": 65, "not_char": 54, "not_exact": 9, "notabl": [61, 62], "note": [4, 8, 9, 14, 58, 59, 75, 76, 77], "noth": [4, 9, 14, 15, 18, 47, 48], "notic": 4, "now": [1, 4, 21, 29, 54, 58, 60, 61, 62, 63, 64, 65, 75], "number": [1, 2, 4, 8, 9, 11, 13, 14, 19, 35, 36, 47, 49, 53, 54, 63, 75, 76], "numer": [9, 21], "o": [51, 61], "obj": [14, 15, 21], "object": [2, 4, 7, 8, 9, 11, 13, 14, 15, 17, 18, 19, 20, 21, 22, 49, 50, 54, 58, 75, 76], "object_map": 14, "object_nam": 13, "objectproxi": 13, "obtain": [4, 7, 13, 14, 15, 19, 60, 62, 75, 76], "obviou": 4, "occur": [2, 4, 8, 15, 19, 20, 21, 27, 33, 40, 45], "octal": 75, "odd": 4, "off": 8, "offer": [4, 7, 22, 44, 61], "often": [2, 9, 22], "old": [51, 62, 63], "old_posit": 4, "older": [4, 9, 63], "omit": [9, 29, 50, 51, 75], "on_entri": [2, 4, 20, 27, 40], "on_entry_init": 2, "on_entry_new_st": 20, "on_exit": [2, 4, 20, 40], "on_exit_old_st": 20, "onc": [2, 3, 4, 9, 15, 19, 20, 27, 33, 40, 42, 45, 58, 65, 75, 77], "one": [1, 2, 4, 7, 8, 9, 11, 14, 15, 17, 18, 19, 20, 21, 22, 27, 33, 40, 42, 45, 47, 50, 54, 58, 60, 63, 66, 73, 75], "ones": [3, 16, 75], "onli": [2, 4, 7, 8, 9, 11, 14, 15, 16, 18, 19, 20, 21, 22, 26, 44, 47, 51, 58, 62, 63, 65, 73, 75, 76, 77], "onlin": [51, 60], "oo": 14, "open": [1, 3, 14, 57, 58], "oper": [4, 7, 8, 11, 13, 65], "opportun": 1, "opt1": 51, "opt2": 51, "option": [2, 4, 7, 8, 9, 11, 15, 19, 20, 21, 22, 42, 50, 51, 54, 58, 59, 60, 62, 63, 73, 75, 77], "order": [3, 4, 7, 8, 11, 15, 18, 20, 21], "ordereddict": [4, 20, 22], "org": [4, 8, 77], "organ": 3, "orient": [2, 20], "origin": [1, 13, 17, 60], "orphan": 3, "os": 77, "other": [1, 2, 4, 7, 11, 15, 20, 21, 46, 51, 54, 58, 68, 77], "otherwis": [1, 4, 11, 15, 45, 53, 63], "out": [1, 3, 4, 8, 9, 11, 13, 50, 51, 61, 62, 73, 75], "out_termin": [4, 9], "outlin": [9, 63], "output": [1, 50, 51, 56, 61, 62, 75], "outsid": [4, 8, 11, 21, 45, 75], "outtermin": 9, "over": [4, 7, 8, 9, 11, 13, 14, 51, 58, 59, 62, 75, 76], "overal": [4, 68], "overlaid": [46, 61], "overlook": 64, "overrid": [4, 7, 9, 11, 20, 22, 47, 48, 73], "overridden": [4, 7, 9, 11, 20, 22, 62], "override_initial_data": [4, 15, 22, 26, 32, 39, 45], "override_initial_st": [4, 15, 22, 26, 32, 39, 45], "override_st": [22, 26, 32, 39, 45], "override_transit": [22, 26, 32, 39, 45], "overshoot": 12, "oversight": 64, "overview": 58, "overwritten": [20, 22], "own": [4, 9, 11, 14, 46, 65], "p": [4, 9, 17, 47, 48, 51, 56, 58, 61, 62, 63, 73, 75, 77], "packag": [1, 4, 6, 9, 14, 15, 51, 52, 57, 60, 63, 65, 77], "packet": 9, "page": [4, 74], "pair": [7, 11, 14, 21], "parallel": 1, "param": [32, 42, 44, 54, 61], "paramet": [2, 4, 7, 8, 9, 11, 12, 13, 14, 15, 17, 19, 20, 21, 22, 26, 27, 32, 33, 40, 42, 44, 45, 47, 48, 49, 51, 53, 54, 56, 57, 58, 61, 62, 75, 77], "park": 29, "parking_spe": 27, "pars": 51, "parser": [49, 51], "part": [4, 7, 9, 11, 13, 14, 20, 21, 29, 44, 45, 47, 62, 74, 77], "partial": [1, 7, 8, 54, 61, 63], "particular": [11, 60], "pass": [2, 4, 7, 8, 9, 11, 14, 15, 19, 20, 22, 51, 53, 54, 58, 62, 63, 75, 76], "past": 29, "patch": 63, "path": [4, 21, 51, 58, 63, 77], "pattern": [4, 9, 18, 20, 61, 63], "patternmatch": 9, "paus": [19, 76], "pcaspi": [7, 60, 77], "pdf": 8, "peopl": 52, "pep8": 1, "per": [2, 4, 20, 22, 27, 33, 40, 45, 65, 76], "perform": [1, 2, 9, 13, 14, 16, 18, 20, 56], "permiss": [3, 8, 11], "ph": 29, "phase": 29, "phase_locking_spe": 27, "pip": [1, 3, 4, 57, 58, 60, 65], "pipelin": 60, "place": [11, 17, 75], "plain": [9, 14, 22], "plankton": [59, 66], "pleas": [4, 8, 9, 11, 14, 17, 58, 62, 63, 75, 76, 77], "plu": 13, "png": 63, "po": 7, "point": [4, 11, 14, 44, 51, 53, 54, 58, 59, 63], "poll": 7, "poll_interv": [7, 65], "port": [4, 9, 13, 14, 19, 42, 45, 47, 48, 50, 51, 58, 62, 73, 75], "posit": [4, 7, 12, 15, 32, 42, 45, 50, 51, 58], "possibl": [1, 4, 7, 8, 9, 11, 13, 14, 15, 17, 18, 19, 21, 22, 29, 42, 58, 60, 61, 62, 63, 75, 76], "possibli": [9, 22], "poster": 63, "potenti": [4, 7, 20, 21, 62], "potter": 61, "power": 4, "pr": 3, "practic": 1, "pre": [1, 4, 54, 68], "precaut": 65, "preced": [4, 7, 14], "predic": 21, "pref": 62, "prefer": 4, "prefix": [7, 13, 17, 20, 29, 62, 73, 75], "preliminari": [8, 61], "prerequisit": 63, "present": [4, 9, 16, 19, 58, 63], "preserv": [8, 9, 11, 14, 21], "press": [47, 58], "pressur": 54, "pretti": 21, "previou": [3, 20, 58, 63], "previous": [19, 20, 58], "principl": 11, "print": [17, 42, 50, 51, 61, 77], "prior": [3, 14], "probabl": [15, 17], "problem": [4, 7, 8, 9, 11, 14, 42, 51, 62, 64, 65], "procedur": 9, "proceed": 3, "process": [2, 4, 7, 8, 9, 11, 14, 15, 18, 19, 20, 47, 48, 65, 75, 76], "processor": 57, "produc": [3, 4, 15, 51, 63], "program": [1, 51, 74], "progress": 19, "propag": [2, 13], "proper": [9, 11], "properli": 15, "properti": [4, 7, 8, 9, 11, 13, 14, 15, 16, 19, 20, 21, 26, 29, 44, 50, 56, 58, 61, 62, 65, 75], "property_nam": 56, "proport": 32, "protocol": [2, 4, 7, 8, 9, 11, 13, 15, 17, 19, 35, 36, 51, 54, 58, 59, 61, 63, 65, 75], "protocol1": 63, "protocol2": 63, "protocolexcept": 13, "provid": [2, 3, 4, 7, 8, 9, 11, 13, 15, 17, 19, 20, 21, 22, 51, 58, 63, 74, 75, 77], "proxi": 13, "public": 14, "pull": [1, 3, 4], "pump": 42, "pump_command": 42, "pure": 64, "purpos": [4, 9, 14, 20, 21, 58, 60], "push": 1, "put": [4, 21, 49, 53], "pv": [7, 16, 29, 44, 61, 62, 65, 73], "pvdb": 7, "pvprefix": 7, "py": [1, 3, 4, 7, 15, 21, 62, 65, 73, 75, 77], "py3": 3, "pymodbu": 8, "pymodbustcp": 8, "pypi": [58, 60, 77], "pytest": [1, 65], "python": [1, 3, 13, 14, 57, 58, 60, 61, 65, 68, 69, 73], "queri": [1, 11, 15, 62, 75, 76], "question": [8, 11], "quick": 56, "quickli": 58, "quickstart": 66, "quirk": [2, 7], "quit": [1, 44], "quot": [62, 75], "quotat": 75, "r": [1, 4, 9, 47, 50, 51, 58, 61, 62, 75, 76, 77], "raies": 15, "rais": [2, 4, 8, 9, 11, 13, 14, 15, 16, 19, 20, 21, 27, 33, 40, 45, 47, 48], "random": [9, 13], "rang": [4, 8, 21, 46, 62], "rare": 7, "rate": [4, 12, 19, 42, 51, 65], "rather": 19, "raw": [8, 53], "raw_byt": 53, "raw_bytes_to_float": 53, "raw_bytes_to_int": 53, "re": [7, 8, 9, 11, 19, 58, 61], "reach": [4, 42], "reachabl": 20, "react": 15, "read": [4, 7, 8, 9, 14, 16, 21, 26, 54, 61, 62, 75], "read_onli": 7, "read_pattern": 9, "readi": [4, 13, 59], "readthedoc": 77, "readtimeout": [9, 65], "real": [2, 4, 22, 75], "real_numb": 53, "realli": [11, 14], "reason": [9, 65], "receiv": [2, 4, 11, 13, 14, 20, 45, 48, 62, 65], "recognis": 42, "recommend": [1, 4, 58, 65, 77], "reconnect": [62, 75], "recov": 76, "refactor": 4, "refer": [4, 8, 9, 15, 17, 66, 77], "reflect": [7, 8, 9, 11], "reg": 54, "regard": 9, "regardless": [20, 63], "regex": [9, 54], "regist": [11, 14, 20, 46], "registri": 15, "regular": [4, 7, 9, 19, 54, 63], "regularli": [1, 7, 54], "relat": [1, 13, 17, 18, 51, 75], "relax": [51, 62], "releas": [4, 14, 51], "release_not": 3, "relev": [4, 17], "reliabl": 62, "remain": [20, 76], "remaind": 8, "remot": [13, 14, 19, 50, 57, 58], "remoteexcept": 13, "remov": [3, 8, 9, 11, 14, 51, 64, 65, 66, 68], "remove_adapt": 11, "remove_object": 14, "renam": [51, 62, 63], "render": 63, "rep": 14, "repeat": 63, "repeatedli": [20, 27, 33, 40, 45], "replac": [13, 19, 20, 21, 77], "replai": 48, "repli": [4, 13, 48, 57, 69], "report": 61, "repositori": [63, 77], "repres": [4, 7, 8, 13, 46, 53], "represent": [8, 53], "reproduc": 65, "req": 13, "request": [1, 3, 4, 7, 8, 9, 11, 13, 14, 15, 21, 42, 47, 48, 58, 60, 61, 62, 65], "requir": [1, 3, 4, 7, 8, 9, 11, 13, 14, 15, 20, 22, 58, 60, 61, 63, 75, 77], "resembl": 1, "reset": [20, 22], "resid": [1, 4], "resolut": [63, 65], "resolv": [3, 4], "resourc": 8, "respect": [4, 7, 9, 13, 20, 46, 58, 63], "respond": [14, 76], "respons": [2, 4, 8, 13, 54, 65, 75, 76], "rest": 62, "restart": 76, "restrict": [4, 7, 14], "result": [2, 7, 8, 9, 11, 13, 14, 21, 47, 48, 53, 62, 63, 65, 75], "resum": [19, 76], "retain": 13, "retriev": [8, 14, 58], "return": [4, 7, 8, 9, 11, 12, 13, 14, 15, 17, 19, 20, 21, 22, 32, 42, 45, 47, 48, 49, 50, 53, 54, 56, 61, 62, 75], "return_map": [4, 9], "return_valu": 9, "review": 4, "right": 4, "root": 58, "roughli": 3, "rpc": [7, 13, 14, 50, 51, 75], "rpc_host": [50, 51], "rpcobject": 14, "rr": 9, "rrr": 9, "rs232": 9, "rtd": 60, "rubik": 63, "rule": 15, "run": [1, 2, 3, 4, 7, 8, 9, 11, 13, 14, 19, 50, 57, 61, 62, 63, 65, 68, 74, 76], "run_simul": 51, "runtim": [7, 9, 11, 19, 21, 61, 62, 64, 76], "runtimeerror": [4, 9, 11, 14, 15, 19, 21], "safe": 11, "safe_load": 65, "sai": 21, "same": [2, 4, 7, 9, 11, 14, 15, 19, 22, 47, 58, 61, 62, 63, 75, 76, 77], "sampl": 4, "satisfi": 77, "scan": 58, "scanf": [4, 9, 63], "scenario": [4, 22], "schedul": 60, "scheme": 22, "script": [1, 4, 57, 58, 61, 62, 63, 68, 74, 75, 76, 77], "sdist": 3, "seamlessli": 11, "search": 20, "second": [2, 4, 7, 8, 9, 11, 14, 19, 21, 22, 44, 61, 63, 76], "second_int": 44, "seconds_sinc": 21, "section": [3, 4, 8, 58, 77], "secur": [14, 65], "see": [1, 4, 8, 9, 11, 15, 19, 20, 51, 54, 58, 61, 62, 63, 65, 67, 74, 77], "seen": 19, "segment": [46, 61], "select": [11, 51], "self": [4, 7, 9, 17, 20, 21, 22, 54, 56, 61, 62], "semant": [4, 11], "send": [8, 42, 47, 54, 67, 69], "sender": 8, "sens": [4, 14], "sensit": 58, "sent": 56, "separ": [1, 2, 4, 9, 11, 14, 22, 46, 50, 54, 61, 62, 63, 65, 75], "sequenc": 75, "serializ": 61, "serv": [8, 11, 13, 14, 19], "server": [7, 8, 9, 11, 13, 14, 19, 47, 50, 58, 60, 61, 62, 65, 75, 76], "servic": [8, 11, 14, 51, 59, 63, 64, 75], "session": 58, "set": [4, 7, 8, 9, 11, 14, 15, 17, 19, 20, 21, 29, 32, 35, 36, 42, 45, 47, 48, 50, 51, 53, 54, 58, 61, 62, 63, 75], "set_circul": 32, "set_context": 20, "set_devic": 11, "set_device_paramet": [19, 61, 75], "set_exampl": 7, "set_external_d": 32, "set_external_i": 32, "set_external_p": 32, "set_internal_d": 32, "set_internal_i": 32, "set_internal_p": 32, "set_limit": 42, "set_param": 47, "set_phas": 29, "set_pr": 54, "set_rat": 42, "set_set_point": 32, "set_spe": 9, "set_target": [4, 45], "set_temperatur": 21, "setlimit": 42, "setpoint": 29, "setrat": 42, "settabl": 62, "setter": [4, 7, 9, 14, 21, 62], "setup": [1, 3, 7, 8, 9, 11, 15, 19, 22, 51, 62, 64, 75, 77], "sever": [15, 17], "shape": 76, "share": [2, 3, 46], "shell": 75, "ship": 58, "short": [1, 22, 65], "shorter": [7, 19], "should": [1, 2, 3, 4, 7, 8, 9, 11, 12, 13, 14, 15, 19, 20, 21, 22, 29, 46, 54, 75, 76], "show": [50, 51, 58, 75], "shown": 75, "shut": [8, 11, 61], "side": [2, 13, 14, 63, 75], "sign": 53, "signatur": [7, 8, 62], "signific": [53, 62], "silent": [9, 21, 47, 48, 75], "sim": [62, 75], "sim2": 73, "similar": [4, 7, 9, 16, 21], "similarli": 17, "simpl": [4, 7, 9, 15, 22, 44, 47, 48], "simple_devic": [57, 58], "simpledeviceepicsinterfac": 7, "simpledevicestreaminterfac": 9, "simpler": 58, "simplest": [4, 7, 19], "simpli": [4, 7, 8, 9, 21], "simplifi": 63, "simul": [1, 2, 5, 8, 11, 14, 20, 22, 42, 45, 50, 51, 57, 58, 59, 61, 62, 63, 65, 75, 77], "simulatedbear": 26, "simulatedchopp": 26, "simulateddevicetyp": 15, "simulatedexamplemotor": [4, 45, 58], "simulatedjulabo": 32, "simulatedlinkamt95": 39, "simulationfactori": 19, "simultan": 19, "sinc": [2, 3, 7, 8, 9, 11, 19, 20, 21, 27, 33, 40, 45, 58, 61, 65], "singl": [4, 11, 20, 42, 54, 58], "situat": [14, 16], "skim": 58, "skip": 58, "sleep": 75, "slow": [2, 59, 63], "small": [7, 21, 49, 63, 65], "smaller": 63, "so": [1, 4, 7, 8, 9, 11, 14, 15, 17, 18, 19, 20, 21, 29, 47, 48, 54, 57, 58, 61, 62, 63, 64, 65, 75, 77], "sock": 9, "socket": [13, 14], "solv": [9, 14], "some": [4, 9, 11, 14, 17, 19, 21, 54, 56, 58, 60, 62, 63, 65, 69, 75, 76], "some_devic": 63, "some_method": 61, "someclassnam": 13, "somedevic": [7, 61], "someinterfac": [9, 63], "someth": [4, 11, 21, 47, 48], "sometim": [9, 21], "somewher": 77, "sort": 4, "sourc": [1, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 26, 27, 29, 32, 33, 35, 36, 39, 40, 42, 44, 45, 46, 47, 48, 49, 51, 53, 54, 56, 57, 58, 62, 63, 65], "sourceperl": 8, "space": [8, 21, 49, 54, 62, 63], "spd": 29, "speak": 32, "special": [9, 15, 20, 48, 54, 62], "specif": [1, 4, 6, 7, 9, 15, 16, 18, 21, 29, 57, 62, 63, 65, 75, 77], "specifi": [2, 4, 7, 9, 11, 13, 15, 19, 20, 21, 42, 48, 50, 51, 53, 54, 58, 61, 62, 63, 73, 75], "speed": [2, 4, 7, 9, 12, 19, 22, 29, 42, 51, 58, 59, 76], "spell": 65, "spend": [7, 9, 51], "spent": [7, 8, 11], "sphinx": [1, 60], "split": [4, 54], "sq": 63, "stage": 22, "standard": [1, 2, 8, 17, 21, 48, 61], "start": [1, 3, 4, 7, 8, 9, 11, 14, 19, 21, 29, 42, 45, 51, 58, 61, 62, 63, 64, 65, 75, 76], "start_addr": 8, "start_serv": [7, 8, 9, 11, 14, 19], "startup": [8, 11], "state": [2, 4, 17, 19, 20, 22, 26, 29, 32, 39, 45, 57, 58, 59, 61, 63, 75], "statemachin": [4, 17, 22, 27, 33, 40, 45, 57, 61], "statemachinedevic": [4, 15, 20, 22, 26, 32, 39, 45], "statemachineexcept": 20, "static": [9, 21], "statu": [1, 4, 11, 19, 42, 45, 58], "status": 11, "stderr": [51, 61], "stdout": 17, "step": [1, 3, 9, 29, 58, 65], "stick": [8, 11], "still": [1, 3, 9, 21, 51, 58, 59, 62, 63, 76], "stop": [4, 7, 8, 9, 11, 19, 29, 42, 45, 58, 75, 76], "stop_serv": [7, 8, 9, 11], "store": [4, 7, 8, 9, 11, 13, 14, 15, 19], "str": [11, 21, 22, 26, 29, 32, 39, 45, 54, 56, 69], "str_valu": 75, "str_value_looks_like_dict": 75, "str_value_looks_like_list": 75, "straightforward": [9, 22], "stream": [4, 8, 11, 42, 44, 45, 47, 54, 57, 58, 59, 61, 62, 63, 75, 76], "stream_interfac": 57, "stream_serv": 9, "streamadapt": [4, 9, 11, 63], "streamhandl": 9, "streaminterfac": [4, 9, 35, 36, 42, 44, 45, 47, 48, 63, 65], "streamserv": 58, "strict": [21, 63], "strict_vers": 15, "strictli": 4, "string": [4, 9, 13, 14, 17, 19, 21, 22, 32, 42, 49, 50, 51, 53, 54, 56, 62, 63, 64, 70, 73, 75], "strip": [4, 9], "strongli": [1, 65], "struct": 8, "structur": [4, 8, 65], "stty": 48, "stub": 21, "stuff": 7, "stx": 54, "style": [60, 62], "sub": [4, 7, 9, 11, 15, 18, 21, 22], "subclass": [7, 8, 9, 15, 18, 22], "subject": [8, 62], "submit": [3, 4, 13], "submodul": [5, 6, 10, 22, 23, 24, 28, 30, 31, 34, 37, 38, 41, 43, 49, 52], "subset": 11, "substitut": 7, "success": 8, "successfulli": [29, 75], "suggest": 4, "summari": 1, "super": 17, "suppli": [4, 7, 9, 11, 13, 14, 15, 19, 21, 49, 50, 51, 62, 63, 75], "support": [4, 8, 11, 20, 61, 65, 68, 71, 75], "suppos": [8, 11, 22], "sure": [1, 3, 4, 7, 8, 9, 11, 12, 21, 54, 58], "suspend": 19, "svg": 63, "switch": [19, 42, 48, 62, 64, 75], "switch_setup": [19, 62, 75], "symbol": 21, "synchron": [11, 75], "syntax": [4, 21, 57, 62], "system": [1, 65, 68, 77], "system_test": 1, "t": [1, 3, 4, 9, 14, 15, 17, 20, 27, 33, 40, 42, 45, 50, 56, 58, 61, 62], "t95": [42, 59], "t_in_kelvin": 21, "tag": [3, 13], "take": [1, 3, 9, 11, 13, 14, 15, 20, 65, 73, 75], "taken": 3, "talk": 58, "tar": 3, "target": [4, 7, 9, 12, 15, 17, 20, 32, 42, 45, 54, 58], "target_memb": 9, "target_method": 54, "target_phas": [61, 75], "target_properti": [7, 62], "target_spe": [61, 75], "targz": 3, "task": 1, "tcp": [4, 8, 9, 42, 44, 45, 47, 58, 59, 61, 62, 73, 76], "team": [1, 4, 61], "tear": [7, 8, 9, 11], "techniqu": 9, "tell": [2, 42, 58], "telnet": [4, 42, 45, 47, 48, 57, 61, 73], "telnet_mod": [9, 62, 73], "temper": 42, "temperatur": [21, 22, 32, 42, 59], "ten": 76, "term": [4, 17, 21, 75], "termin": [4, 9, 11, 42, 48, 58, 61, 65, 73, 75], "test": [1, 2, 15, 58, 61, 65, 68], "text": [9, 21, 49, 54, 63], "textwrap": 21, "than": [4, 12, 15, 17, 19, 54, 56, 63, 76], "thank": [60, 61, 63], "thei": [3, 4, 7, 9, 16, 21, 22, 63, 75, 76], "them": [1, 2, 4, 11, 20, 21, 47, 58, 62, 63], "themselv": 15, "therefor": 65, "therei": 51, "thi": [1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 26, 27, 29, 33, 35, 36, 40, 42, 44, 45, 47, 48, 49, 50, 51, 52, 54, 58, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77], "thin": 21, "thing": [1, 3, 7, 18, 44], "think": 58, "third": [20, 22], "those": [4, 7, 14, 15, 21, 22, 29, 50, 75], "though": 7, "thread": [8, 11, 14, 63, 65], "three": [2, 4, 9, 18, 22, 64], "through": [7, 15, 19, 50, 61, 62, 63, 65, 75, 77], "throughout": 2, "thu": [11, 21], "tick": [2, 58], "time": [1, 2, 4, 7, 8, 9, 11, 12, 13, 14, 18, 19, 20, 21, 48, 51, 56, 59, 60, 63, 75, 76], "timed_repli": 56, "timeout": [9, 13, 14, 48, 50, 62, 65], "timeout_devic": [57, 58], "timeterminateddevic": 48, "timeterminatedinterfac": 48, "tn": 32, "to_bytearrai": 8, "togeth": [1, 7, 9, 11, 63], "too": [4, 56], "tool": [6, 50, 57, 58], "top": [3, 4, 13, 62, 77], "total": 19, "toward": [4, 7, 9, 12, 29], "tox": [1, 65], "track": 77, "trait": 2, "transfer": 9, "transform": 9, "transit": [2, 4, 20, 22], "translat": 9, "transmiss": 9, "transmission_control_protocol": 4, "transpar": 75, "transport": 13, "travers": 58, "treat": [1, 4, 9, 63, 65], "tree": [1, 18, 58], "tri": [4, 7, 9, 11, 14, 15, 21, 70], "trigger": [2, 4, 14, 20, 65], "trivial": 75, "true": [4, 7, 8, 9, 14, 15, 19, 20, 21, 53, 54, 56, 62, 73, 75], "trust": [14, 75, 76], "try": [4, 19, 58], "tty": 48, "tupl": [4, 7, 20, 21, 22, 26, 32, 39, 45, 62], "turn": [2, 9, 14, 15, 75], "tutori": 58, "tv": 32, "twice": 76, "twine": 3, "two": [4, 7, 9, 14, 15, 17, 18, 19, 20, 46, 51, 58, 59, 61, 63, 74, 75, 77], "txt": [1, 60, 77], "type": [4, 7, 8, 9, 13, 15, 16, 17, 20, 21, 47, 48, 54, 58, 77], "typic": [8, 12, 51], "typo": 4, "u": [58, 75], "unconfirm": 36, "under": [1, 7, 13, 14, 57, 60, 65], "underli": [13, 65], "underscor": [14, 20], "understand": 58, "unexpect": [16, 75], "unfamiliar": 60, "unicode_valu": 75, "uniform": 11, "unit": [1, 3, 42], "unknown": 13, "unless": [7, 9, 15, 53], "unlik": 20, "unlock": 29, "unmodifi": 47, "unnot": 4, "unpack": 77, "unsign": 53, "unsolicit": [67, 69], "until": [4, 13, 14, 21, 42], "up": [2, 3, 7, 9, 11, 20, 58], "updat": [2, 4, 7, 19, 21, 61, 62, 65, 71], "update_dict": 21, "upon": [11, 13, 20], "upper": 21, "upstream": 4, "uptim": [19, 76], "us": [1, 3, 4, 7, 8, 9, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 48, 50, 51, 54, 55, 58, 59, 60, 61, 62, 63, 65, 68, 69, 75, 76, 77], "usag": [8, 50, 51, 56, 57, 61, 74], "user": [7, 8, 9, 11, 16, 22, 51, 59, 61, 62, 63, 77], "user_guid": 4, "usual": [13, 29, 58, 75], "utf": 54, "util": [3, 57, 69, 75], "uuid": 13, "uuid4": 13, "v": [9, 47, 50, 51, 62, 63, 65], "val": 51, "valid": [7, 8, 9, 14, 21, 46, 75], "valu": [1, 4, 7, 8, 9, 12, 14, 15, 19, 20, 21, 22, 29, 32, 45, 46, 47, 48, 50, 51, 54, 57, 58, 61, 62, 65], "valueerror": 4, "var": 9, "variabl": [4, 9, 12, 15, 17, 20, 22, 63, 65, 77], "variou": [54, 77], "velo": 7, "venv": 3, "verbos": 47, "veri": [2, 4, 7, 8, 11, 15, 20, 21, 22, 47, 58, 60], "verif": 1, "verifi": [51, 62, 65], "version": [1, 9, 14, 15, 35, 36, 50, 51, 57, 60, 61, 62, 63, 64, 71], "verysimpledevic": [44, 47], "verysimpleinterfac": [44, 47], "verysimplestreaminterfac": 44, "via": [1, 2, 4, 7, 8, 9, 11, 13, 14, 15, 19, 20, 21, 29, 42, 45, 47, 51, 57, 59, 60, 61, 62, 63, 75, 76], "view": 59, "violat": [4, 16, 21], "virtual": [1, 3, 4, 57, 58, 61, 65], "w": 1, "wa": [4, 9, 17, 19, 20, 21, 29, 47, 48, 61, 62, 63, 65], "wai": [1, 2, 4, 7, 8, 9, 11, 14, 18, 19, 20, 21, 58, 62, 75, 76, 77], "wait": [9, 11, 13, 48, 65, 75], "want": [22, 46, 54, 58, 77], "warn": [4, 15, 17, 51, 56, 62, 63, 75], "watch": 3, "waterbath": 61, "we": [58, 59, 61, 65, 77], "well": [1, 4, 14, 15, 60, 61, 62, 75, 76], "were": [8, 9, 59, 60, 62, 64], "what": [1, 2, 4, 9, 15, 58, 61, 62], "wheel": 3, "when": [2, 3, 4, 7, 8, 9, 11, 14, 15, 17, 19, 20, 27, 40, 42, 45, 51, 54, 56, 58, 62, 63, 64, 65, 73, 77], "whenev": [8, 15, 19], "where": [4, 7, 8, 11, 13, 14, 15, 16, 19, 20, 21, 29, 45, 48, 51, 54, 57, 62], "wherea": [4, 7, 9, 19], "whether": [4, 7, 8, 9, 11, 14, 20, 32, 53, 63], "which": [1, 2, 4, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 29, 44, 45, 47, 50, 51, 53, 54, 55, 58, 61, 62, 63, 65, 75, 77], "while": [1, 2, 4, 7, 8, 9, 11, 19, 20, 27, 33, 40, 45, 58, 62, 75, 76], "whl": 3, "who": 65, "why": [58, 61, 75], "wide": 1, "wider": 62, "wiki": 4, "wikipedia": 4, "window": [58, 75], "within": [4, 21, 77], "without": [2, 7, 14, 20, 21, 22, 58, 63, 68, 75], "won": 62, "word": [1, 54], "work": [1, 3, 4, 8, 11, 15, 17, 19, 21, 58, 62, 63, 64, 65, 75, 77], "workaround": 21, "workflow": [65, 77], "world": [4, 48, 75], "would": [4, 7, 9, 17, 54, 58, 75, 76, 77], "wrap": [4, 9, 14, 21], "wrapper": [14, 21], "write": [7, 8, 9, 22, 57, 58, 61, 62, 75], "write_pattern": 9, "written": [4, 9, 15], "www": 8, "x": [3, 4, 63], "xp": 32, "y": 3, "yaml": 65, "yet": [4, 14, 61], "you": [4, 17, 46, 48, 54, 57, 58, 75, 77], "your": [4, 54, 58, 62, 77], "z": 3, "zero": 54, "zmq": [13, 14, 51, 75], "\u03b4t": [2, 18]}, "titles": ["API", "Developing Lewis", "Framework Details", "Release Checklist", "Writing Device Simulators", "lewis", "lewis.adapters", "lewis.adapters.epics", "lewis.adapters.modbus", "lewis.adapters.stream", "lewis.core", "lewis.core.adapters", "lewis.core.approaches", "lewis.core.control_client", "lewis.core.control_server", "lewis.core.devices", "lewis.core.exceptions", "lewis.core.logging", "lewis.core.processor", "lewis.core.simulation", "lewis.core.statemachine", "lewis.core.utils", "lewis.devices", "lewis.devices.chopper", "lewis.devices.chopper.devices", "lewis.devices.chopper.devices.bearings", "lewis.devices.chopper.devices.device", "lewis.devices.chopper.devices.states", "lewis.devices.chopper.interfaces", "lewis.devices.chopper.interfaces.epics_interface", "lewis.devices.julabo", "lewis.devices.julabo.devices", "lewis.devices.julabo.devices.device", "lewis.devices.julabo.devices.states", "lewis.devices.julabo.interfaces", "lewis.devices.julabo.interfaces.julabo_stream_interface_1", "lewis.devices.julabo.interfaces.julabo_stream_interface_2", "lewis.devices.linkam_t95", "lewis.devices.linkam_t95.devices", "lewis.devices.linkam_t95.devices.device", "lewis.devices.linkam_t95.devices.states", "lewis.devices.linkam_t95.interfaces", "lewis.devices.linkam_t95.interfaces.stream_interface", "lewis.examples", "lewis.examples.dual_device", "lewis.examples.example_motor", "lewis.examples.modbus_device", "lewis.examples.simple_device", "lewis.examples.timeout_device", "lewis.scripts", "lewis.scripts.control", "lewis.scripts.run", "lewis.utils", "lewis.utils.byte_conversions", "lewis.utils.command_builder", "lewis.utils.constants", "lewis.utils.replies", "Welcome to the Lewis documentation!", "Quickstart Guide", "Release 1.0", "Release 1.0.1", "Release 1.0.2", "Release 1.0.3", "Release 1.1", "Release 1.1.1", "Release 1.2", "Release 1.2.1", "Release 1.2.2", "Release 1.3.0", "Release 1.3.1", "Release 1.3.2", "Release 1.3.3", "Release notes", "Adapter Specifics", "Command line tools", "Remote Access to Devices", "Remote Access to Simulation Parameters", "Usage with Python"], "titleterms": {"0": [59, 60, 61, 62, 68], "1": [59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71], "2": [61, 65, 66, 67, 70], "3": [62, 68, 69, 70, 71], "access": [75, 76], "ad": 4, "adapt": [6, 7, 8, 9, 11, 73], "analysi": 4, "api": [0, 57, 58, 75], "approach": 12, "bear": 25, "bug": [60, 61, 62, 63, 64, 66], "bugfix": 65, "build": 3, "byte_convers": 53, "chang": [3, 62, 65, 68, 69, 70], "checklist": 3, "chopper": [23, 24, 25, 26, 27, 28, 29], "client": [58, 75], "command": [62, 74], "command_build": 54, "commun": 75, "compat": 4, "connect": 58, "constant": 55, "control": [50, 58, 74, 75], "control_cli": 13, "control_serv": 14, "core": [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], "cycl": 2, "detail": 2, "develop": [1, 57, 65, 68, 69, 70], "devic": [4, 15, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 75], "document": [4, 57, 66], "driven": 2, "dual_devic": 44, "environ": 77, "epic": [7, 73], "epics_interfac": 29, "exampl": [4, 43, 44, 45, 46, 47, 48, 58], "example_motor": 45, "except": 16, "face": 4, "featur": [59, 60, 61, 62, 63, 65, 67], "final": 3, "fix": [60, 61, 62, 63, 64, 66], "framework": [2, 4], "from": 77, "further": 4, "git": 3, "github": 3, "guid": [57, 58, 62, 63], "implement": 4, "improv": [60, 61, 62, 63, 65], "instal": [58, 77], "interfac": [4, 28, 29, 34, 35, 36, 41, 42, 62, 75], "interpret": 75, "julabo": [30, 31, 32, 33, 34, 35, 36], "julabo_stream_interface_1": 35, "julabo_stream_interface_2": 36, "lewi": [1, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 74], "line": [62, 74], "linkam_t95": [37, 38, 39, 40, 41, 42], "log": [4, 17], "merg": 3, "mileston": 3, "modbu": 8, "modbus_devic": 46, "more": 4, "motor": 58, "new": [60, 61, 62, 63, 65, 67], "note": [3, 57, 72], "other": [60, 61, 62, 63, 65], "packag": 3, "paramet": 76, "pip": 77, "prepar": [3, 4], "processor": 18, "pypi": 3, "python": [75, 77], "quickstart": [57, 58], "refer": 57, "releas": [3, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72], "remot": [75, 76], "repli": 56, "run": [51, 58, 77], "script": [49, 50, 51], "setup": 4, "simple_devic": 47, "simul": [4, 19, 76], "sourc": 77, "specif": 73, "state": [27, 33, 40], "statemachin": [2, 20], "step": 4, "stream": [9, 73], "stream_interfac": 42, "syntax": 75, "telnet": 58, "test": [3, 4], "timeout_devic": 48, "tool": 74, "unit": 4, "updat": 3, "upgrad": [62, 63], "upload": 3, "usag": 77, "user": [4, 57], "util": [21, 52, 53, 54, 55, 56], "valu": 75, "version": [3, 4], "via": [58, 77], "virtual": 77, "welcom": 57, "write": 4}}) \ No newline at end of file diff --git a/user_guide/adapter_specifics.html b/user_guide/adapter_specifics.html new file mode 100644 index 00000000..186c16fa --- /dev/null +++ b/user_guide/adapter_specifics.html @@ -0,0 +1,172 @@ + + + + + + + + + Adapter Specifics — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Adapter Specifics

+
+

EPICS Adapter Specifics

+

The EPICS adapter takes only one optional argument:

+
    +
  • prefix: This string is prefixed to all PV names. Defaults to empty / no prefix.

  • +
+

Arguments meant for the adapter can be specified with the adapter options. +For example:

+
$ python lewis.py chopper --adapter-options "epics: {prefix: 'SIM2:'}"
+
+
+

On Linux, this means that EPICS_CA_ADDR_LIST must include this +networks broadcast address:

+
$ export EPICS_CA_AUTO_ADDR_LIST=NO
+$ export EPICS_CA_ADDR_LIST=172.17.255.255
+$ export EPICS_CAS_INTF_ADDR_LIST=localhost
+
+
+
+
+

Stream Adapter Specifics

+

The TCP Stream adapter has the following optional arguments:

+
    +
  • bind_address: Address of network adapter to listen on. +Defaults to “0.0.0.0” (all network adapters).

  • +
  • port: Port to listen for connections on. Defaults to 9999.

  • +
  • telnet_mode: When True, overrides both in and out terminators +to CRNL for telnet compatibility. Defaults to False.

  • +
+

Arguments meant for the adapter can be specified with the adapter options. +For example:

+
$ python lewis.py linkam_t95 -p "stream: {bind_address: localhost, port: 1234}"
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/user_guide/command_line_tools.html b/user_guide/command_line_tools.html new file mode 100644 index 00000000..538d5a0f --- /dev/null +++ b/user_guide/command_line_tools.html @@ -0,0 +1,147 @@ + + + + + + + + + Command line tools — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Command line tools

+

This page documents the program usage for lewis and lewis-control, the two command line +tools provided as part of a Lewis installation.

+
+

lewis

+

See lewis.scripts.run()

+
+
+

lewis-control

+

See lewis.scripts.control()

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/user_guide/remote_access_devices.html b/user_guide/remote_access_devices.html new file mode 100644 index 00000000..54da18f0 --- /dev/null +++ b/user_guide/remote_access_devices.html @@ -0,0 +1,290 @@ + + + + + + + + + Remote Access to Devices — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Remote Access to Devices

+

Please note that this functionality should only be used on a trusted +network.

+

Besides the device specific protocols, the device can be made accessible +from the outside via JSON-RPC over ZMQ. This can be achieved by passing +the -r option with a host:port string to the simulation:

+
$ lewis chopper -r 127.0.0.1:10000 -p "epics: {prefix: 'SIM:'}"
+
+
+

Now the device can be controlled via the lewis-control.py-script +in a different terminal window. The service can be queried to show the +available objects by not supplying an object name:

+
$ lewis-control -r 127.0.0.1:10000
+
+
+

The -r (or --rpc-host) option defaults to the value shown here, +so it will be omitted in the following examples. To get information on +the API of an object, supplying an object name without a property or +method will list the object’s API:

+
$ lewis-control device
+
+
+

This will output a list of properties and methods which is available for +remote access. This may not comprise the full interface of the object +depending on the server side configuration. Obtaining the value of a +property is done like this:

+
$ lewis-control device state
+
+
+

The same syntax is used to call methods without parameters:

+
$ lewis-control device initialize
+
+
+

To set a property to a new value, the value has to be supplied on the +command line:

+
$ lewis-control device target_speed 100
+$ lewis-control device start
+
+
+

It is possible to set multiple device parameters at once, but this goes through the simulation +itself, so that it is generic to all devices:

+
$ lewis-control simulation set_device_parameters "{'target_speed': 1, 'target_phase': 20}"
+
+
+

Another case of device-related access to the simulation is switching the setup. To obtain a +list of available setups, the following command is available:

+
$ lewis-control simulation setups
+
+
+

It is then possible to switch the setup to one from the list, assuming it is called new_setup:

+
$ lewis-control simulation switch_setup new_setup
+
+
+

The setup switching process is logged.

+
+

Accessing the Device Communication Interface

+

Just as device model and communication interface are separate concepts in Lewis, these interfaces +can be controlled separately as well.

+

To query the available communication protocols, the following command is available:

+
$ lewis-control interface protocols
+
+
+

This will list all communication protocols that are currently exposing device behavior. +The following methods are available for interacting with the communication interfaces:

+
$ lewis-control interface disconnect
+$ lewis-control interface connect
+$ lewis-control interface is_connected
+$ lewis-control interface documentation
+
+
+

Without any arguments, these methods will affect all of the device’s interfaces, but specifying +any number of valid protocol names will limit the method to those protocols. Assuming a device +has two interfaces, one for the stream protocol and one for epics, the following sequence +would disconnect both, but then only reconnect the stream-adapter:

+
$ lewis-control interface disconnect
+$ lewis-control interface connect stream
+$ lewis-control interface is_connected stream
+True
+$ lewis-control interface is_connected
+{'stream': True, 'epics': False}
+
+
+

Disconnecting is essentially the equivalent of “cutting the cable”, no new connections +will be accepted and existing ones will be terminated.

+

To find out how to interact with any device via its usual communication channels a way would be:

+
$ lewis-control interface protocol
+['stream', 'epics']
+$ lewis-control interface documentation epics
+[ ... long description of protocol ... ]
+
+
+
+
+

Value Interpretation and Syntax

+

lewis_control interprets values as built-in Python literals or containers using +ast.literal_eval. This means any +syntax for literal evaluation supported by Python works here as well. The following are all valid +values which are interpreted as you might expect:

+
$ lewis-control device float_value 12.0
+$ lewis-control device float_value .5
+$ lewis-control device float_value 1.23e10
+$ lewis-control device int_value 123
+$ lewis-control device int_value 0xDEADBEEF
+$ lewis-control device int_value 010  # Value of 8 in base 8 (octal)
+$ lewis-control device str_value hello_world
+$ lewis-control device method_call_with_two_string_args hello world
+$ lewis-control device str_value "hello world"
+$ lewis-control device unicode_value "u'hello_world'"
+$ lewis-control device list_value "[1,2,3]"
+$ lewis-control device list_value "['a', 'b', 'c']"
+$ lewis-control device dict_value "{'a': 1, 'b': 2}"
+
+
+

WARNING: Any value that cannot be successfully evaluated is silently converted into a +string literal instead! The following attempts turn into strings because the letters +are not quoted:

+
$ lewis-control device str_value_looks_like_dict "{a: 1, b: 2}"
+$ lewis-control device str_value_looks_like_list "[a, b, c]"
+
+
+

This is done for convenience, to avoid having to double quote and/or escape quote trivial string +values to match Python syntax while also taking shell quotation and escapes into account. But it +can lead to unexpected results at times.

+
+
+

Control Client Python API

+

For use cases that require more flexibility and control, it is advised to write a Python script +using the API provided in lewis.core.control_client instead of using the command line utility. +This makes it possible to use the remote objects in a fairly transparent fashion.

+

Here is a brief example using the chopper device:

+
from time import sleep
+from lewis.core.control_client import ControlClient
+
+client = ControlClient(host='127.0.0.1', port='10000')
+chopper = client.get_object('device')
+
+chopper.target_speed = 100
+chopper.initialize()
+
+while chopper.state != 'stopped':
+    sleep(0.1)
+
+chopper.start()
+
+
+

All calls, reads and assignments are synchronous and blocking in terms of the methods and +attributes they access on the server. However, much like with real devices, the behaviour of the +simulated device is asynchronous from its interface. Consequently, depending on the specific +device, some effects of calling a method may take place long after the method is called (and +returns).

+

This is why, in the above example, a loop is used to wait for chopper.state to change in +response to the chopper.initialize() call.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/user_guide/remote_access_simulation.html b/user_guide/remote_access_simulation.html new file mode 100644 index 00000000..a995b0ad --- /dev/null +++ b/user_guide/remote_access_simulation.html @@ -0,0 +1,170 @@ + + + + + + + + + Remote Access to Simulation Parameters — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Remote Access to Simulation Parameters

+

Please note that this functionality should only be used on a trusted +network.

+

Certain control over the simulation is also exposed in the shape of an +object named simulation if Lewis is started with -r. The +simulation can be paused and resumed using the control script:

+
$ lewis-control simulation pause
+$ lewis-control simulation resume
+
+
+

With these commands, the simulation is paused, while the communication +with the device remains responsive. The communication channel (for +example TCP stream server) would still respond to queries and commands, +but they would not be processed by the device.

+

The speed of the simulation can be adjusted as well, along with the +number of cycles that are processed per second (via the cycle_delay +parameter).

+
$ lewis-control simulation speed 10
+$ lewis-control simulation cycle_delay 0.05
+
+
+

This will cause the twice as many cycles per second to be computed +compared to the default, and the simulation runs ten times faster than +actual time.

+

It’s also possible to obtain some information about the simulation, for +example how long it has been running and how much simulated time has +passed:

+
$ lewis-control simulation uptime
+$ lewis-control simulation runtime
+
+
+

Finally, the simulation can also be stopped:

+
$ lewis-control simulation stop
+
+
+

It is not possible to recover from that, as the processing of remote +commands stops as well. The only way to restart the simulation is to run +Lewis again with the same parameters.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/user_guide/usage_with_python.html b/user_guide/usage_with_python.html new file mode 100644 index 00000000..a14f9c8b --- /dev/null +++ b/user_guide/usage_with_python.html @@ -0,0 +1,239 @@ + + + + + + + + + Usage with Python — lewis documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Usage with Python

+

To use Lewis directly via Python you must first install its dependencies:

+
    +
  • Python 3.6+

  • +
  • pip (latest)

  • +
+

On most linux systems these can be installed via the distribution’s package manager.

+
+

Virtual environments

+

We recommend using a virtual environment.

+
+
+

Installation via pip

+

Lewis is available on the Python Package Index <https://pypi.python.org/pypi/lewis>__. That means +it can be installed using pip:

+
$ pip install lewis
+
+
+

This will install lewis along with its dependencies. If you would like to use EPICS based devices +and have a working EPICS environment on your machine, you can install it like this to get the +additional required dependencies:

+
$ pip install lewis[epics]
+
+
+

This will install two scripts in the path, lewis and lewis-control. Both scripts provide +command line help:

+
$ lewis --help
+$ lewis-control --help
+
+
+

To list available devices, just type lewis in the command line, a list of devices that are +available for simulation will be printed.

+

All following sections of this user manual assume that Lewis has been installed via pip and that +the lewis command is available.

+
+
+

Installation from source

+

Clone the repository in a location of your choice, we recommend that you do it inside a virtual +environment so that you can keep track of the dependencies:

+
$ git clone https://github.com/ISISComputingGroup/lewis.git
+
+
+

If you do not have git available, you can +also download this repository as an archive and unpack it somewhere. A +few additional dependencies must be installed. This can be done through +pip in the top level directory of Lewis, which contains the setup.py-file:

+
$ pip install .
+
+
+

Note: There are a few optional dependencies for certain adapter types. Currently the only +optional dependency is pcaspy for using devices with an EPICS interface, it requires a +working installation of EPICS base. Please refer to the installation instructions <https://pcaspy.readthedocs.io/en/latest/installation.html>__ of the module. +To include pcaspy in the installation of dependencies, use:

+
$ pip install ".[epics]"
+
+
+

If you also want to develop Lewis, the workflow is a bit different. Please refer to the +developer_guide for details.

+

If you want to use the EPICS adapter, you will also need to configure a few more +EPICS environment variables correctly. If you only want to communicate +using EPICS locally via the loopback device, you can configure it like +this:

+
$ export EPICS_CA_AUTO_ADDR_LIST=NO
+$ export EPICS_CA_ADDR_LIST=localhost
+$ export EPICS_CAS_INTF_ADDR_LIST=localhost
+
+
+

Once all dependencies and requirements are satisfied, Lewis can be +run using the following general format (from inside the Lewis +directory):

+
$ python -m lewis device_name [arguments]
+
+
+

You can then run Lewis as follows (from within the lewis +directory):

+
$ python -m lewis chopper -p epics
+
+
+

Details about parameters for the various adapters, and differences +between OSes are covered in the “Adapter Specifics” sections.

+

If you decided to install Lewis this way, please be aware that the lewis and lewis-control +calls in the other parts of the guide have to be replaced with python lewis.py.

+
+
+

Running from source

+

Lewis can be run directly from source. First it is necessary to install the basic requirements:

+
$ pip install -r requirements.txt
+
+
+

If you would like to use EPICS based devices +and have a working EPICS environment on your machine then it is necessary to install pcaspy like so:

+
$ pip install pcaspy
+
+
+

There are Python scripts for running both lewis and lewis-control in the top-level scripts directory. +These scripts work exactly the same as when Lewis is installed via pip (see above). For example:

+
$ python scripts/lewis.py --help
+$ python scripts/lewis-control.py --help
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file