diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..179b099 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/automate.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/automate.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/automate" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/automate" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..6bc366b --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,6 @@ +-e git://github.com/tuomas2/automate.git@master#egg=automate +-e git://github.com/tuomas2/automate-arduino.git@master#egg=automate-arduino +-e git://github.com/tuomas2/automate-rpio.git@master#egg=automate-rpio +-e git://github.com/tuomas2/automate-wsgi.git@master#egg=automate-wsgi +-e git://github.com/tuomas2/automate-webui.git@master#egg=automate-webui +-e git://github.com/tuomas2/automate-rpc.git@master#egg=automate-rpc diff --git a/docs/source/_templates/srclinks.html b/docs/source/_templates/srclinks.html new file mode 100644 index 0000000..6630fdb --- /dev/null +++ b/docs/source/_templates/srclinks.html @@ -0,0 +1,51 @@ +%- if show_source and has_source and sourcename %} +

{{ _('This Page') }}

+ +

Source

+ {%- if srclink_src_url %} +
{{ + srclink_src_url.lstrip('https://').lstrip('http://') }} + {%- if srclink_src_rev_url %} + / {{ srclink_src_rev }} + {%- endif %} +
+ {%- endif %} + +{%- endif %} \ No newline at end of file diff --git a/docs/source/builtin_callables.rst b/docs/source/builtin_callables.rst new file mode 100644 index 0000000..d25fe4a --- /dev/null +++ b/docs/source/builtin_callables.rst @@ -0,0 +1,14 @@ +.. _builtin-callables: + +Builtin Callables +================= + +Module :mod:`~automate.callables.builtin_callables` provides classes that may be used to various purposes +in Automate :class:`~automate.program.Program`, in condition and action attributes. +They are loaded automatically into :mod:`automate.callables` along with callables from possible installed extensions. + +Builtin Callable Types +---------------------- + +.. automodule:: automate.callables.builtin_callables + :members: \ No newline at end of file diff --git a/docs/source/builtin_statusobjects.rst b/docs/source/builtin_statusobjects.rst new file mode 100644 index 0000000..6ddc044 --- /dev/null +++ b/docs/source/builtin_statusobjects.rst @@ -0,0 +1,17 @@ +Builtin Statusobject Types +========================== + +Here are the definitions for the builtin statusobject types (sensors and actuators). +More types are available in :ref:`automate-extensions`. + +Builtin Sensors +--------------- + +.. automodule:: automate.sensors.builtin_sensors + :members: + +Builtin Actuators +----------------- + +.. automodule:: automate.actuators.builtin_actuators + :members: diff --git a/docs/source/callables.rst b/docs/source/callables.rst new file mode 100644 index 0000000..aafed7d --- /dev/null +++ b/docs/source/callables.rst @@ -0,0 +1,143 @@ +.. _callables: + +Callables +========= + +Introduction +------------ + +Callables are used like a small *programming language* to define the programming logic within the +Automate system. All classes derived from :class:`~automate.program.ProgrammableSystemObject` have five +attributes that accept Callable type objects: + + * Conditions + + * :attr:`~automate.program.ProgrammableSystemObject.active_condition` + * :attr:`~automate.program.ProgrammableSystemObject.update_condition` + + * Actions + + * :attr:`~automate.program.ProgrammableSystemObject.on_activate` + * :attr:`~automate.program.ProgrammableSystemObject.on_update` + * :attr:`~automate.program.ProgrammableSystemObject.on_deactivate` + +Conditions determine *when* and actions, correspondingly, *what* to do. + +Actions are triggered by *triggers* that are Sensors and Actuators. Triggers are collected +from Callables (conditions and actions) automatically, and their status changes are subscribed and followed +automatically by a +:class:`~automate.program.ProgrammableSystemObject`. Thus, condition statuses are evaluated automatically, and +actions are executed based on condition statuses. + +Let us take a look at a small example that uses conditions and actions: + +.. code-block:: python + + from automate import * + + class CounterClock(System): + active_switch = UserBoolSensor() + periodical = IntervalTimerSensor(interval=1) + + target_actuator = IntActuator() + + prog = Program( + active_condition = Value(active_switch), + on_activate = SetStatus(target_actuator, 0), + on_update = SetStatus(target_actuator, + target_actuator + 1), + triggers = [periodical], + exclude_triggers = [target_actuator], + ) + + s = CounterClock(services=[WebService(read_only=False)]) + +.. image:: images/callables.svg + +When user has switched ``active_switch`` sensor to ``True``, this simple program will start adding +1 to target_actuator value every +second. Because ``periodical`` is not used as a trigger in any action/condition, we need to explicitly define it as a +trigger with triggers attribute. Correspondingly, ``target_actuator`` is automatically collected as prog's trigger (because +it is the second argument of SetStatus), so we need to explicitly exclude it with ``exclude_triggers`` attribute. + +.. tip:: + + Try the code yourself! Just cpaste the code into your IPython shell and go to http://localhost:8080 in your browser! + Screenshot: + + .. image:: images/counter_app.png + +.. _deriving-callables: + +Deriving Custom Callables +------------------------- + +A collection of useful Callables is provided by :mod:`~automate.callables.builtin_callables` module. +It is also easy to derive custom callables from :class:`~automate.callable.AbstractCallable` baseclass. +For most cases it is enough to re-define :meth:`~automate.callable.AbstractCallable.call` method. + +If Callable utilizes threads (like +:class:`~automate.callables.builtin_callables.Delay`, +:class:`~automate.callables.builtin_callables.WaitUntil` and +:class:`~automate.callables.builtin_callables.While`) +and continues as an background process after returning from call method, it is also necessary +to define :meth:`~automate.callable.AbstractCallable.cancel` that notifies threads that their processing +must be stopped. These threaded Callables can store their threads and other information in +:attr:`~automate.callable.AbstractCallable.state` dictionary, which stores information per +caller Program. Per-caller state information is fetched via +:meth:`~automate.callable.AbstractCallable.get_state`. After data is no longer needed, it must be cleared with +:meth:`~automate.callable.AbstractCallable.del_state` method. + +Arguments given to Callable are stored in +:attr:`~automate.callable.AbstractCallable._args` and keyword arguments in +:attr:`~automate.callable.AbstractCallable._kwargs`. There are the following shortcuts that may +be used: +:attr:`~automate.callable.AbstractCallable.obj`, +:attr:`~automate.callable.AbstractCallable.value` and +:attr:`~automate.callable.AbstractCallable.objects`. When accessing these, it is necessary (almost) always +to use :meth:`~automate.callable.AbstractCallable.call_eval` method, which evaluates concurrent status value +out of Callable, StatusObject, or string that represents name of an object residing in System namespace. +See more in the following section. + + +Trigger and Target Collection +----------------------------- + +Triggers and targets are automatically collected from Callables recursively. +All Callable types can specify which arguments are considered as triggers and which +are considered as targets, by defining +:meth:`~automate.callable.AbstractCallable._give_triggers` and +:meth:`~automate.callable.AbstractCallable._give_targets`, correspondingly. + +As a general rule, Callable should not consider criteria conditions as triggers +(for example the conditions of +:class:`~automate.callables.builtin_callables.If`, +:class:`~automate.callables.builtin_callables.Switch` +etc). + +Referring to Other Objects in Callables +--------------------------------------- + +Various system objects can be referred either by name (string), or by object references. +Name is preferred, because it allows to refer to objects that are defined in different +scopes (i.e. those that are defined either in :ref:`groups` or later in the code). + +If desired, automatic name referencing can be also disabled by setting +:attr:`~automate.system.System.allow_name_referencing` False. Then it is possible +to refer to other objects by using special construct Object('name'). + +All variables passed to Callables are/must be evaluated through +:meth:`~automate.callable.AbstractCallable.call_eval` method, i.e. +if Callables are used as arguments, they are evaluated by their +:meth:`~automate.callable.AbstractCallable.call` method +and :class:`~automate.statusobject.StatusObject`'s status attribute is used, respectively. + +Callable Abstract Base Class definition +--------------------------------------- + +Callable classes are are subclassed of :class:`~automate.callable.AbstractCallable`. + +.. autoclass:: automate.callable.AbstractCallable + :members: + :private-members: + + diff --git a/docs/source/components.rst b/docs/source/components.rst new file mode 100644 index 0000000..cbb4eaa --- /dev/null +++ b/docs/source/components.rst @@ -0,0 +1,66 @@ +Automate Components +=================== + +.. inheritance-diagram:: automate.program.Program + automate.program.DefaultProgram + automate.statusobject.StatusObject + automate.statusobject.AbstractSensor + automate.statusobject.AbstractActuator + automate.callable.AbstractCallable + automate.service.AbstractService + :parts: 1 + +Automate system is built of the following components: + +* **System** (derived by user from :class:`~automate.system.System`) binds all parts together into a single state machine +* **Services** (subclassed of :class:`~automate.service.AbstractService`) provide + programming interfaces with user and devices that can be used by **SystemObjects**. +* **SystemObjects** (subclassed of :class:`~automate.systemobject.SystemObject` or :class:`~automate.program.ProgrammableSystemObject`): + + * **Sensors** (subclassed on :class:`~automate.statusobject.AbstractSensor`) are used as an interface to the (usually read-only) + state of device or software. + * **Actuators** (subclassed on :class:`~automate.statusobject.AbstractActuator`) are used as an interface to set/write the state of + device or software. + * **Programs** (subclassed on :class:`~automate.program.ProgrammableSystemObject`) define the logic between + Sensors and Actuators. + They are used to control statuses of Actuators, by rules that are programmed by using special + **Callables** (subclasses of :class:`~automate.callable.AbstractCallable`) objects that depend on statuses of + Sensors and other components. Also Sensors and Actuators are often subclassed from + :class:`~automate.program.ProgrammableSystemObject` so + they also have similar features by themselves. Depending on the application, however, it might (or might not) + improve readability if plain :class:`~automate.program.Program` component is used. + +All Automate components are derived from :class:`~traits.has_traits.HasTraits`, provided by +Traits library, which provides automatic notification of attribute changes, which is used +extensively in Automate. Due to traits, all Automate components are configured by passing +attribute names as keyword arguments in object initialization (see for example attributes +:attr:`~automate_arduino.AbstractArduinoActuator.pin` +and +:attr:`~automate_arduino.AbstractArduinoActuator.dev` traits of +:class:`~automate_arduino.ArduinoDigitalActuator` +in the example below). + +Automate system is written by subclassing :class:`~automate.system.System` and adding there desired +:class:`~automate.systemobject.SystemObject` as its attributes, such as in the following example:: + + from automate import * + class MySystem(System): + mysensor = FloatSensor() + myactuator = ArduinoDigitalActuator(pin=13, dev=0) + myprogram = Program() + ... + +After defining the system, it can be instantiated. There, services with their necessary arguments +can be explicitly defined as follows:: + + mysys = MySystem(services=[WebService(http_port=8080), ArduinoService(dev='/dev/ttyS0')]) + +Some services (those that have :attr:`~automate.service.AbstractService.autoload` atribute set to True) +do not need to be explicitly defined. For example, +:class:`~automate_arduino.arduino_service..ArduinoService` would be used automatically +loaded because of the usage of :class:`~automate_arduino.arduino_actuator.ArduinoDigitalActuator`, +with default settings (``dev='/dev/ttyUSB0'``). Instantiating +System will launch IPython shell to access the system internals from the command line. This can be prevented, if +necessary, by defining keyword argument :attr:`~automate.system.System.exclude_services` as +``['TextUIService']``, which disables autoloading of +:class:`~automate.services.textui.TextUIService`. For further information about services, see :ref:`services`. diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..255c2d2 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,334 @@ +# -*- coding: utf-8 -*- +# +# automate documentation build configuration file, created by +# sphinx-quickstart on Mon Nov 24 08:14:15 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +# Get the project root dir, which is the parent dir of this +#cwd = os.getcwd() +#project_root = os.path.dirname(cwd) + +# Insert the project root dir as the first element in the PYTHONPATH. +# This lets us ensure that the source package is imported, and that its +# version is used. +#sys.path.insert(0, project_root) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx.ext.graphviz', + 'sphinx.ext.inheritance_diagram', + # 'sphinxcontrib.plantuml', + # 'sphinxcontrib.googleanalytics', + # 'sphinxcontrib.srclinks' + # 'numpydoc.traitsdoc', +] + +#srclink_project = 'https://github.com/westurner/sphinxcontrib-srclinks' +#srclink_src_path = 'docs/' +#srclink_branch = 'master' +# +# html_sidebars = { +# '**': [ +# 'localtoc.html', +# 'relations.html', +# 'searchbox.html', +# 'srclinks.html', +# ], +# 'index': [ +# 'globaltoc.html', +# 'relations.html', +# 'searchbox.html', +# 'srclinks.html', +# ], +#} +# +#numpydoc_edit_link = False +#numpydoc_use_plots =False +#numpydoc_show_class_members = True +#numpydoc_show_inherited_class_members = True +#numpydoc_class_members_toctree = True + + +#graphviz_output_format = 'svg' + +#inheritance_graph_attrs = dict(rankdir="TB") + +#, size='"6.0, 8.0"', +# fontsize=24, ratio='compress') + +# inheritance_node_attrs = dict(fontsize=24, height=0.75, +# ) + +#googleanalytics_id = 'UA-5516303-1' + +intersphinx_mapping = {'traits': ('http://traits.readthedocs.org/en/4.5.0/', None), + 'python': ('http://docs.python.org/2', None), + #'cookiecutter': ('http://cookiecutter.readthedocs.org/en/latest/', None), + } + +autodoc_member_order = 'bysource' + +#plantuml = 'java -jar /home/tairaksinen/.PyCharm40/config/plugins/plantuml4idea/lib/plantuml.jar' + +# Add any paths that contain templates here, relative to this directory. +#templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Automate' +copyright = u'2015, Tuomas Airaksinen' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +from automate import __version__, __status__ +version = __version__ +# The full version, including alpha/beta/rc tags. +release = '%s-%s' % (__version__, __status__) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = 'default' + +if on_rtd: + html_theme = 'default' +else: + html_theme = 'sphinx_rtd_theme' + +#html_theme = 'sphinxdoc' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'automatedoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + #'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'automate.tex', u'automate Documentation', + u'Tuomas Airaksinen', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'automate', u'automate Documentation', + [u'Tuomas Airaksinen'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'automate', u'automate Documentation', + u'Tuomas Airaksinen', 'automate', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/source/extensions.rst b/docs/source/extensions.rst new file mode 100644 index 0000000..4bd16b4 --- /dev/null +++ b/docs/source/extensions.rst @@ -0,0 +1,41 @@ +.. _extensions: + +Making your own Automate Extensions +=================================== + + +Extension Development +--------------------- + +Automate extensions allow extending Automate functionalities by writing external libraries +that may consist of new Service, Sensor, Actuator, or Callable classes. + +To start developing automate extensions, it is recommended to use +`cookiecutter `_ template. This is how it works: + +#. Install ``cookiecutter`` 1.0.0 or newer:: + + pip install cookiecutter + +#. Generate a Automate extension project:: + + cookiecutter https://github.com/tuomas2/cookiecutter-automate-ext-template.git + +Cookiecutter asks few questions and you have great basis for starting your template +development. There will be created Python files where you may add your new custom +Automate classes. + +For your classes to be exported to the Automate, make sure that they are listed in +``extension_classes`` list in ``__init__.py`` of the extension module. + + +All installed Automate Extensions are available from Automate applications and are imported +to automate namespace. + +.. tip:: + You can install your extension in *editable* mode by running ``pip install -e .`` + in your extension root directory. + +.. tip:: + You can look at :ref:`automate-extensions` for examples. + diff --git a/docs/source/hello_world.py b/docs/source/hello_world.py new file mode 100644 index 0000000..6a3fdd2 --- /dev/null +++ b/docs/source/hello_world.py @@ -0,0 +1,19 @@ +from automate import * + +class MySystem(System): + # HW swtich connected Raspberry Pi GPIO port 1 + hardware_switch = RpioSensor(port=1) + # Switch that is controllable, for example, from WEB interface + web_switch = UserBoolSensor() + # Lamp relay that switches lamp on/off, connected to GPIO port 2 + lamp = RpioActuator(port=2) + # Program that controls the system behaviour + program = Program( + active_condition=Or('web_switch', 'hardware_switch'), + on_activate=SetStatus('lamp', True) + ) + + +my_system = MySystem( + services=[WebService()] +) diff --git a/docs/source/images/callables.puml b/docs/source/images/callables.puml new file mode 100644 index 0000000..e445be8 --- /dev/null +++ b/docs/source/images/callables.puml @@ -0,0 +1,24 @@ +@startuml +skinparam state { +BackGroundColor<> #FFCCFF +BackGroundColor<> #FFFFCC +BackGroundColor<> #CCFFCC +} +state "prog" as prog <> +prog: Program +prog: Status: True +prog: Priority: 1 +periodical -[#009933]-> prog +active_switch -[#009933]-> prog +prog -[#FF0000]-> target_actuator +state "target_actuator" as target_actuator <> +target_actuator: IntActuator +target_actuator: prog :: 7.0 +target_actuator: dp_target_actuator :: 0 +state "periodical" as periodical <> +periodical: IntervalTimerSensor +periodical: Status: 1.0 +state "active_switch" as active_switch <> +active_switch: UserBoolSensor +active_switch: Status: True +@enduml \ No newline at end of file diff --git a/docs/source/images/callables.svg b/docs/source/images/callables.svg new file mode 100644 index 0000000..840bd26 --- /dev/null +++ b/docs/source/images/callables.svg @@ -0,0 +1 @@ +progProgramStatus: TruePriority: 1periodicalIntervalTimerSensorStatus: 1.0active_switchUserBoolSensorStatus: Truetarget_actuatorIntActuatorprog :: 7.0dp_target_actuator :: 0 \ No newline at end of file diff --git a/docs/source/images/counter_app.png b/docs/source/images/counter_app.png new file mode 100644 index 0000000..72ec65b Binary files /dev/null and b/docs/source/images/counter_app.png differ diff --git a/docs/source/images/hello_world.puml b/docs/source/images/hello_world.puml new file mode 100644 index 0000000..259e1c4 --- /dev/null +++ b/docs/source/images/hello_world.puml @@ -0,0 +1,23 @@ +@startuml +skinparam state { +BackGroundColor<> #FFCCFF +BackGroundColor<> #FFFFCC +BackGroundColor<> #CCFFCC +} +state "program" as program <> +program: Program +program: Status: False +program: Priority: 1 +hardware_switch -[#009933]-> program +web_switch -[#009933]-> program +program -[#4C4C4C]-> lamp +state "web_switch" as web_switch <> +web_switch: UserBoolSensor +web_switch: Status: False +state "lamp" as lamp <> +lamp: RpioActuator +lamp: dp_lamp :: False +state "hardware_switch" as hardware_switch <> +hardware_switch: RpioSensor +hardware_switch: Status: False +@enduml \ No newline at end of file diff --git a/docs/source/images/hello_world.svg b/docs/source/images/hello_world.svg new file mode 100644 index 0000000..2795aab --- /dev/null +++ b/docs/source/images/hello_world.svg @@ -0,0 +1 @@ +programProgramStatus: FalsePriority: 1hardware_switchRpioSensorStatus: Falseweb_switchUserBoolSensorStatus: FalselampRpioActuatordp_lamp :: False \ No newline at end of file diff --git a/docs/source/images/program.puml b/docs/source/images/program.puml new file mode 100644 index 0000000..d98dc09 --- /dev/null +++ b/docs/source/images/program.puml @@ -0,0 +1,37 @@ +@startuml +skinparam state { +BackGroundColor<> #FFCCFF +BackGroundColor<> #FFFFCC +BackGroundColor<> #CCFFCC +} +state "high_prio_prg" as high_prio_prg <> +high_prio_prg: UserBoolSensor +high_prio_prg: Status: True +high_prio_prg: Priority: 5.0 +high_prio_prg -[#009933]-> high_prio_prg +high_prio_prg -[#FF0000]-> actuator +state "inactive_high_prio_prg" as inactive_high_prio_prg <> +inactive_high_prio_prg: UserBoolSensor +inactive_high_prio_prg: Status: False +inactive_high_prio_prg: Priority: 6.0 +inactive_high_prio_prg -[#009933]-> inactive_high_prio_prg +inactive_high_prio_prg -[#4C4C4C]-> actuator +state "low_prio_prg" as low_prio_prg <> +low_prio_prg: UserBoolSensor +low_prio_prg: Status: True +low_prio_prg: Priority: -5.0 +low_prio_prg -[#009933]-> low_prio_prg +low_prio_prg -[#0000FF]-> actuator +state "med_prio_prg" as med_prio_prg <> +med_prio_prg: UserBoolSensor +med_prio_prg: Status: True +med_prio_prg: Priority: 1.0 +med_prio_prg -[#009933]-> med_prio_prg +med_prio_prg -[#0000FF]-> actuator +state "actuator" as actuator <> +actuator: FloatActuator +actuator: high_prio_prg :: 3.0 +actuator: med_prio_prg :: 2.0 +actuator: dp_actuator :: 0.0 +actuator: low_prio_prg :: 1.0 +@enduml \ No newline at end of file diff --git a/docs/source/images/program.svg b/docs/source/images/program.svg new file mode 100644 index 0000000..6712989 --- /dev/null +++ b/docs/source/images/program.svg @@ -0,0 +1 @@ +high_prio_prgUserBoolSensorStatus: TruePriority: 5.0actuatorFloatActuatorhigh_prio_prg :: 3.0med_prio_prg :: 2.0dp_actuator :: 0.0low_prio_prg :: 1.0inactive_high_prio_prgUserBoolSensorStatus: FalsePriority: 6.0low_prio_prgUserBoolSensorStatus: TruePriority: -5.0med_prio_prgUserBoolSensorStatus: TruePriority: 1.0 \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..deb6f58 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,31 @@ +.. automate documentation master file, created by + sphinx-quickstart on Mon Nov 24 08:14:15 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Automate's Documentation! +==================================== + +Table of Contents +----------------- + +.. toctree:: + :maxdepth: 2 + + introduction + install + components + program + statusobjects + builtin_statusobjects + callables + builtin_callables + system + services + official_extensions/index + extensions + +* :ref:`genindex` + + + diff --git a/docs/source/install.rst b/docs/source/install.rst new file mode 100644 index 0000000..ff40775 --- /dev/null +++ b/docs/source/install.rst @@ -0,0 +1,31 @@ +How to Install Automate? +======================== + +Automate can be installed like ordinary python package. I recommend installation +in within virtual environment (see `virtualenv `_). + +#. (optional): Create and start using virtualenv:: + + mkvirtualenv automate + workon automate + + +#. Install from pypi:: + + pip install automate + +#. If you want to install some extensions too, you may also run:: + + pip install automate-webui + pip install automate-rpc + pip install automate-arduino + pip install automate-rpio + +.. note:: Many examples in this documentation assume that extensions are installed! + +Optionally, you could install also by cloning GIT repository and installing manually:: + + git clone https://github.com/tuomas2/automate.git + cd automate + ./setup.py install + diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 0000000..9b13dc7 --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,64 @@ +============ +Introduction +============ + +What is Automate? +----------------- + +Automate is a general purpose automatization library for Python. +Its objective is to offer convenient and robust object-oriented programming +framework for complex state machine systems. Automate can be used to design +complex automation systems, yet it is easy to learn and fun to use. It was +originally developed with home robotics/automatization projects in mind, +but is quite general in nature and one could find applications from various +fields that could take advantage of Automate. Automate can be embedded +in other Python software as a component, which runs its operations in +its own threads. + +Highlights +---------- + +* Supported hardware: + + * Raspberry Pi GPIO input/output ports (:ref:`automate-rpio` via `RPIO `_ library) + * Arduino analog and digital input/output ports (:ref:`automate-arduino` via `pyFirmata `_ library) + * Easy to write extensions to support other hardware, see :ref:`extensions` + +* :ref:`state-saving` +* Intelligent design: + + * Comprehensively tested via ``py.test`` unit/integration tests + * Takes advantage of `Traits `_ library, especially its + notification system. + * `IPython `_ console to monitor, modify and control system on-the-fly + * Versatile function/callable library to write state program logic + +* RPC and Websocket interfaces (provided by :ref:`automate-rpc` and :ref:`automate-webui`) to connect + between other applications or other Automate systems. +* Comprehensive and customizable Web User Interface via :ref:`automate-webui` +* UML graphs can be drawn automaticlaly of the system (as can be seen in the examples of this documentation) + +.. _hello-world: + +"Hello World" in Automate +------------------------- +Let us consider following short Automate program as a first example: + +.. literalinclude:: hello_world.py + +This simple example has two sensors ``hardware_switch``, ``web_switch``, actuator (``lamp``) and a ``program`` that +contains logic what to do and when. Here, ``lamp`` is switched on if either ``web_switch`` or ``hardware_switch`` has +status True. :class:`~automate_webui.webui.WebService` with default settings is enabled so that user can +monitor system and set +status of ``web_switch``. The following figure (generated via :class:`~automate_webui.webui.WebService` interface) +illustrates the system in UML graph: + +.. image:: images/hello_world.svg + +Original application +-------------------- + +Automate was originally developed in order to enable simple and robust way of programming +home automatization with `Raspberry Pi `_ minicomputer, to obtain +automatization and automatic monitoring of rather complex planted aquarium safety/controlling +system. diff --git a/docs/source/official_extensions/arduino.rst b/docs/source/official_extensions/arduino.rst new file mode 100644 index 0000000..01de51f --- /dev/null +++ b/docs/source/official_extensions/arduino.rst @@ -0,0 +1,72 @@ +.. _automate-arduino: + +Arduino Support for Automate +============================ + +Github URL: http://github.com/tuomas2/automate-arduino + +Introduction +------------ + +This extension provides interface to Arduino devices via `pyFirmata library `_. + +Installation +------------ + +Install from Pypi:: + + pip install automate-arduino + +Optionally, you could install also by cloning GIT repository and installing manually:: + + git clone https://github.com/tuomas2/automate-arduino.git + cd automate-arduino + ./setup.py install + +Example application +------------------- + +This example application sets up couple of analog and digital Arduino Sensors and Actuators. +It also introduces Servo actuator with :class:`~automate.actuators.builtin_actuators.ConstantTimeActuator`, +which functions such a way that if value of ``a1`` changes, the value of ``servo`` will change smoothly +within given time interval. + +.. literalinclude:: arduino_example.py + + +Class definitions +----------------- + +Service +^^^^^^^ + +.. autoclass:: automate_arduino.ArduinoService + :members: + +Sensors +^^^^^^^ + +.. autoclass:: automate_arduino.AbstractArduinoSensor + :members: + +.. autoclass:: automate_arduino.ArduinoDigitalSensor + :members: + +.. autoclass:: automate_arduino.ArduinoAnalogSensor + :members: + + +Actuators +^^^^^^^^^ + +.. autoclass:: automate_arduino.AbstractArduinoActuator + :members: + +.. autoclass:: automate_arduino.ArduinoDigitalActuator + :members: + +.. autoclass:: automate_arduino.ArduinoPWMActuator + :members: + +.. autoclass:: automate_arduino.ArduinoServoActuator + :members: diff --git a/docs/source/official_extensions/arduino_example.py b/docs/source/official_extensions/arduino_example.py new file mode 100644 index 0000000..b717af8 --- /dev/null +++ b/docs/source/official_extensions/arduino_example.py @@ -0,0 +1,22 @@ +from automate import * + + +class MySystem(System): + a1 = ArduinoAnalogSensor(dev=0, pin=0) + d12 = ArduinoDigitalSensor(dev=0, pin=12) + + d13 = ArduinoDigitalActuator(dev=0, pin=13) # LED on Arduino board + servo = ArduinoServoActuator(min_pulse=200, max_pulse=8000, dev=0, pin=3, default=50, slave=True) + + interp = ConstantTimeActuator(change_time=2., change_frequency=20., slave_actuator=servo) + + prog = Program( + on_update=Run(Log("Value: %s", Value(a1)), SetStatus(d13, d12), SetStatus(interp, Value(180) * Value(a1))) + ) + +my_arduino = ArduinoService( + arduino_devs=["/dev/ttyUSB0"], + arduino_dev_types=["Arduino"], + arduino_dev_sampling=[500]) + +s = MySystem(services=[my_arduino, WebService()]) diff --git a/docs/source/official_extensions/gpio_example.py b/docs/source/official_extensions/gpio_example.py new file mode 100644 index 0000000..d4d4415 --- /dev/null +++ b/docs/source/official_extensions/gpio_example.py @@ -0,0 +1,10 @@ +from automate import * + + +class MySystem(System): + button = RpioSensor(port=22, buttontype='down') + light = RpioActuator(port=23, change_delay=2) + myprog = Program(active_condition=Value('mysensor'), + on_activate=SetStatus('myactuator', True)) + +mysystem = MySystem(services=[WebService()]) diff --git a/docs/source/official_extensions/images/edit_view.png b/docs/source/official_extensions/images/edit_view.png new file mode 100644 index 0000000..84453e0 Binary files /dev/null and b/docs/source/official_extensions/images/edit_view.png differ diff --git a/docs/source/official_extensions/images/main_view.png b/docs/source/official_extensions/images/main_view.png new file mode 100644 index 0000000..85912b0 Binary files /dev/null and b/docs/source/official_extensions/images/main_view.png differ diff --git a/docs/source/official_extensions/images/shell_view.png b/docs/source/official_extensions/images/shell_view.png new file mode 100644 index 0000000..177ec7a Binary files /dev/null and b/docs/source/official_extensions/images/shell_view.png differ diff --git a/docs/source/official_extensions/images/uml_view.png b/docs/source/official_extensions/images/uml_view.png new file mode 100644 index 0000000..a0940e1 Binary files /dev/null and b/docs/source/official_extensions/images/uml_view.png differ diff --git a/docs/source/official_extensions/index.rst b/docs/source/official_extensions/index.rst new file mode 100644 index 0000000..dc4c0fb --- /dev/null +++ b/docs/source/official_extensions/index.rst @@ -0,0 +1,18 @@ +.. _automate-extensions: + +Extensions +========== + +Automate functionality can be easily extended by various extension modules, and it is also +possible to make your own Automate extensions, for details see :ref:`extensions`. +The following extensions are officially supported by Automate, but are distributed +as separate packages. + +.. toctree:: + :maxdepth: 2 + + webui + wsgi + rpc + arduino + rpio diff --git a/docs/source/official_extensions/rpc.rst b/docs/source/official_extensions/rpc.rst new file mode 100644 index 0000000..51aa65d --- /dev/null +++ b/docs/source/official_extensions/rpc.rst @@ -0,0 +1,35 @@ +.. _automate-rpc: + +Remote Procedure Call Support for Automate +========================================== + +Github URL: http://github.com/tuomas2/automate-rpc + +Introduction +------------ + +This extension provides XmlRPC API for external applications. Exported API is by default defined by +:class:`automate_rpc.rpc.ExternalApi`. + +Installation +------------ + +Install from Pypi:: + + pip install automate-rpc + +Optionally, you could install also by cloning GIT repository and installing manually:: + + git clone https://github.com/tuomas2/automate-rpc.git + cd automate-rpc + ./setup.py install + +Class definitions +----------------- + +.. autoclass:: automate_rpc.RpcService + :members: + +.. autoclass:: automate_rpc.rpc.ExternalApi + :members: + diff --git a/docs/source/official_extensions/rpio.rst b/docs/source/official_extensions/rpio.rst new file mode 100644 index 0000000..d6cb3dd --- /dev/null +++ b/docs/source/official_extensions/rpio.rst @@ -0,0 +1,66 @@ +.. _automate-rpio: + +Raspberry Pi GPIO Support for Automate +====================================== + +Github URL: http://github.com/tuomas2/automate-rpio + +Introduction +------------ + +This extension provides interface to Raspberry Pi GPIO via RPIO library. `RPIO library `_. + +Installation +------------ + +Install from Pypi:: + + pip install automate-rpio + +Optionally, you could install also by cloning GIT repository and installing manually:: + + git clone https://github.com/tuomas2/automate-rpio.git + cd automate-rpio + ./setup.py install + +Example application +------------------- + +This simple example application sets up simple relation between input pin ``button`` in port 22 and +output pin ``light`` in port 23. If for a button is attached in ``button``, pushing it down +will light the led, that is attached to ``light``. + +.. literalinclude:: gpio_example.py + + +Class definitions +----------------- + +Service +^^^^^^^ + +.. autoclass:: automate_rpio.RpioService + :members: + + +Sensors +^^^^^^^ + +.. autoclass:: automate_rpio.RpioSensor + :members: + +.. autoclass:: automate_rpio.RpioSensor + :members: + +Actuators +^^^^^^^^^ + +.. autoclass:: automate_rpio.RpioActuator + :members: + +.. autoclass:: automate_rpio.TemperatureSensor + :members: + +.. autoclass:: automate_rpio.RpioPWMActuator + :members: + diff --git a/docs/source/official_extensions/webui.rst b/docs/source/official_extensions/webui.rst new file mode 100644 index 0000000..b3d173d --- /dev/null +++ b/docs/source/official_extensions/webui.rst @@ -0,0 +1,93 @@ +.. _automate-webui: + +Web User Interface for Automate +=============================== + +Github URL: http://github.com/tuomas2/automate-webui + +Introduction +------------ + +Automate Web UI extension provides easy to use approach to monitoring and modifying +Automate system and its components. Features: + +- Displayed data updated in real time via Websocket +- Responsive design (using `Bootstrap `_ CSS & JS library), suitable for + mobile and desktop use. +- Optional authentication +- Read-only and read-write modes. + + - Read-only mode allows only monitoring (default) + - Read-write mode allows modifying the System by: + + - adding new Actuators / Sensors / Programs + - modifying existing Actuators / Sensors / Programs + - Quick editing of user-editable sensors from main views + +- HTTP and secured HTTPS servers supported (powered by built in `Tornado Web Server `_) + +Main features are illustrated with a few screenshots: + +Installation +------------ + +Install from Pypi:: + + pip install automate-webui + +Optionally, you could install also by cloning GIT repository and installing manually:: + + git clone https://github.com/tuomas2/automate-webui.git + cd automate-webui + ./setup.py install + +Main view +^^^^^^^^^ + +In main view you can observe actuator and sensor statuses in real time, and also easily access +user-editable sensor statuses. Clicking object name will give more details of the selected item +as well as 'edit' button. (only in read-write mode). + +.. figure:: images/main_view.png + +Edit view +^^^^^^^^^ + +In edit view you can edit almost all the attributes of the objects that are in the system. +You can also create new ones. + +.. figure:: images/edit_view.png + +UML view +^^^^^^^^^ + +In UML view you can see nice UML diagram of the whole system. To enable UML diagram, you need to set +up :class:`~automate.services.builtin_services.PlantUMLService`. + +.. figure:: images/uml_view.png + +Console view +^^^^^^^^^^^^^ + +In console view you can see the log as well as type commands same way as in IPython shell. + +.. figure:: images/shell_view.png + + +Example application using Web UI +-------------------------------- + +This is extended example of the :ref:`hello-world`, that opens two web services, one for port 8085 and another +in 8086. It will go to UML view by default and in "User defined" -view you will see only ``web_switch``. + +.. literalinclude:: webui_example.py + +.. tip:: + Try to run the code in your IPython shell by copying & pasting it with ``cpaste`` command! + +WebService class definition +--------------------------- + +.. autoclass:: automate_webui.WebService + :members: + diff --git a/docs/source/official_extensions/webui_example.py b/docs/source/official_extensions/webui_example.py new file mode 100644 index 0000000..10a4eb8 --- /dev/null +++ b/docs/source/official_extensions/webui_example.py @@ -0,0 +1,34 @@ +from automate import * + +class MySystem(System): + # HW swtich connected Raspberry Pi GPIO port 1 + hardware_switch = RpioSensor(port=1) + # Switch that is controllable, for example, from WEB interface + web_switch = UserBoolSensor(tags=['user']) + # Lamp relay that switches lamp on/off, connected to GPIO port 2 + lamp = RpioActuator(port=2) + # Program that controls the system behaviour + program = Program( + active_condition = Or('web_switch', 'hardware_switch'), + on_activate = SetStatus('lamp', True) + ) + +# To view UML diagram, we need to set up PlantUMLService. Here we will use +# plantuml.com online service to render the UML graphics. +plantuml_service = PlantUMLService(url='http://www.plantuml.com/plantuml/svg/') +web_service = WebService( + read_only=False, + default_view='plantuml', + http_port=8085, + http_auth = ('myusername', 'mypassword'), + user_tags = ['user'], + ) + +# Just to give example of slave feature, let's open another server instance +# at port 8086. +slave = WebService( + http_port=8086, + slave=True, + ) + +my_system = MySystem(services=[plantuml_service, web_service, slave]) diff --git a/docs/source/official_extensions/wsgi.rst b/docs/source/official_extensions/wsgi.rst new file mode 100644 index 0000000..753a220 --- /dev/null +++ b/docs/source/official_extensions/wsgi.rst @@ -0,0 +1,17 @@ +.. _automate-wsgi: + +WSGI Support for Automate +========================= + +This extension provides Web server for WSGI-aware extensions, such as :ref:`automate-rpc`, +:ref:`automate-webui`. It is of no use alone. + +Github URL: http://github.com/tuomas2/automate-wsgi + +Class definition +---------------- + +.. autoclass:: automate_wsgi.TornadoService + :members: + + diff --git a/docs/source/program.rst b/docs/source/program.rst new file mode 100644 index 0000000..a39cd84 --- /dev/null +++ b/docs/source/program.rst @@ -0,0 +1,122 @@ +.. _automate-programs: + +Programs +-------- + +Program features are defined in :class:`~automate.program.ProgrammableSystemObject` class. +:class:`~automate.program.Program`, :class:`~automate.program.DefaultProgram` and +:class:`~automate.statusobject.StatusObject` classes are subclassed +from :class:`~automate.program.ProgrammableSystemObject`, as can be seen in the following +inheritance diagram. + + +.. inheritance-diagram:: automate.program.Program + automate.program.DefaultProgram + automate.statusobject.StatusObject + automate.statusobject.AbstractSensor + automate.statusobject.AbstractActuator + :parts: 1 + +Programs are used to define the logic on which system operates. Program behavior is determined by the conditions +(:attr:`~automate.program.ProgrammableSystemObject.active_condition`, +:attr:`~automate.program.ProgrammableSystemObject.update_condition`) and actions +(:attr:`~automate.program.ProgrammableSystemObject.on_activate`, +:attr:`~automate.program.ProgrammableSystemObject.on_update`, +:attr:`~automate.program.ProgrammableSystemObject.on_deactivate`), +that are of :class:`~automate.callable.AbstractCallable` +type. Callables are special objects that are used to implement the actual programming of Automate program objects +(see :ref:`callables`). There are many special Callable classes to perform different operations +(see :ref:`builtin-callables`) and it is also easy to develop your own Callables +(see :ref:`deriving-callables`). + +All Sensors and Actuators that affect the return value of a condition callable, +are :attr:`~automate.callable.AbstractCallable.triggers` of a Callable. All actuators (and writeable sensors) that +a callable may change, are :attr:`~automate.callable.AbstractCallable.targets`. Whenever any of the +triggers status change, programs +conditions are automatically updated and actions are taken if appropriate condition evaluates +as ``True``. + +Actions and conditions are used as follows. Programs can be either active or inactive depending on +:attr:`~automate.program.ProgrammableSystemObject.active_condition`. When program actives +(i.e. active_condition changes to ``True``), +:attr:`~automate.program.ProgrammableSystemObject.on_activate` +action is called. When program deactivates, +:attr:`~automate.program.ProgrammableSystemObject.on_deactivate`, +action is called, correspondingly. +When program is active, its targets can be continuously manipulated by +:attr:`~automate.program.ProgrammableSystemObject.on_update` +callable, which +is called whenever update_condition evaluates as ``True``. + +Actuator Status Manipulation +---------------------------- + +Program can control status +of one or more actuators. Programs manipulate Actuator statuses the following way: + +* One or more programs can control state of the same Actuator. Each program has + :attr:`~automate.program.ProgrammableSystemObject.priority` (floating point number), so that + the actual status of Actuator is determined + by program with highest priority +* If highest priority program deactivates, the control of Actuator status is moved + to the the second-highest priority active program. +* If there are no other Program, each Actuator has also one DefaultProgram, which then + takes over Actuator control. + +The following example application illustrates the priorities:: + + from automate import * + class MySystem(System): + low_prio_prg = UserBoolSensor(priority=-5, + active_condition=Value('low_prio_prg'), + on_activate=SetStatus('actuator', 1.0), + default=True, + ) + med_prio_prg = UserBoolSensor(priority=1, + active_condition=Value('med_prio_prg'), + on_activate=SetStatus('actuator', 2.0), + default=True, + ) + high_prio_prg = UserBoolSensor(priority=5, + active_condition=Value('high_prio_prg'), + on_activate=SetStatus('actuator', 3.0), + default=True, + ) + inactive_high_prio_prg = UserBoolSensor(priority=6, + active_condition=Value('inactive_high_prio_prg'), + on_activate=SetStatus('actuator', 4.0), + default=False, + ) + + actuator = FloatActuator() + + ms = MySystem(services=[WebService()]) + +.. image:: images/program.svg + +In this application, four programs (three manually defined programs and :class:`~automate.program.DefaultProgram` +``dp_actuator``) are active for actuator. +The actual status of actuator (now: ``3.0``) is determined by highest priority program. +If ``high_prio_prog`` goes inactive (i.e. if its +status is changed to ``False``):: + + high_prio_prg.status = False + +the status is then determined by ``med_prio_prg`` (=> ``2.0``). And so on. All the active programs +for actuator are visible in UML diagram. +Red arrow shows the dominating program, blue arrows show the other non-dominating active programs and gray arrows +show the inactive programs that have the actuator as a target (i.e. if they are activated, they will manipulate +the status of the actuator). ``low_prio_prg`` can never manipulate actuator status as its priority is lower than +default program ``dp_actuator`` priority. + +Program Features +---------------- + +Program features are defined in ``ProgrammableSystemObject`` class. Its definition is as follows: + +.. note:: + Unfortunately, due to current Sphinx autodoc limitation, all trait types are displayed in this + documentation as ``None``. For the real trait types, please see the source fode. + +.. autoclass:: automate.program.ProgrammableSystemObject + :members: diff --git a/docs/source/services.rst b/docs/source/services.rst new file mode 100644 index 0000000..bd0db9a --- /dev/null +++ b/docs/source/services.rst @@ -0,0 +1,60 @@ +.. _services: + +Services +======== + +Introduction +------------ + +There are two kinds of *Services* in Automate: *UserServices* and *SystemServices*. + +*SystemServices* are mainly designed to implement a practical way of writing an interface between your +custom SystemObjects and their corresponding resources (devices for example). For example, +:class:`~automate_rpio.RpioService` +provide access to Raspberry Pi GPIO pins for +:class:`~automate_rpio.RpioActuator` and +:class:`~automate_rpio.RpioSensor` objects, +and ArduinoService, correspondingly, provides access to Arduino devices for ArduinoActuator and ArduinoSensors. +(Arduino and RPIO support are provided by extensions, see :ref:`automate-extensions`). + +*UserServices*, on the other hand, provide user interfaces to the system. For example, +:class:`~automate_webui.WebService` +provides access to the system via web browser, +:class:`~automate.services.textui.TextUIService` +via *IPython* shell and +:class:`~automate_rpc.RpcService` +via XmlRPC (remote procedure call) interface for other applications. + +If not automatically loaded (services with :attr:`~automate.service.AbstractService.autoload` set to ``True``), +they need to be instantiated (contrary to :class:`~automate.systemobject.SystemObject`) +outside the System, and given in the initialization of the system (:attr:`~automate.system.System.services`). +For example of initialization and configuring of +:class:`~automate_webui.WebService`, see :ref:`hello-world`. + +Services Class Definitions +-------------------------- + +.. autoclass:: automate.service.AbstractService + :members: + +.. autoclass:: automate.service.AbstractUserService + :members: + +.. autoclass:: automate.service.AbstractSystemService + :members: + +Builtin Services +---------------- + +.. autoclass:: automate.services.logstore.LogStoreService + :members: + +.. autoclass:: automate.services.statussaver.StatusSaverService + :members: + +.. autoclass:: automate.services.plantumlserv.PlantUMLService + :members: + +.. autoclass:: automate.services.textui.TextUIService + :members: + diff --git a/docs/source/statusobjects.rst b/docs/source/statusobjects.rst new file mode 100644 index 0000000..871ee12 --- /dev/null +++ b/docs/source/statusobjects.rst @@ -0,0 +1,99 @@ +StatusObjects +============= + +.. inheritance-diagram:: automate.statusobject.StatusObject + automate.statusobject.AbstractSensor + automate.statusobject.AbstractActuator + :parts: 1 + + +Actuators (:class:`~automate.statusobject.AbstractActuator`) and +sensors (:class:`~automate.statusobject.AbstractSensor`) +are subclassed of :class:`~automate.statusobject.StatusObject`. +The most important property is :attr:`~automate.statusobject.StatusObject.status`, +which may be of various data types, depending of the implementation defined in subclasses. +Type of status is determined by :attr:`~automate.statusobject.StatusObject._status` trait. + +There are couple of useful features in StatusObjects that may be used to affect when status +is really changed. These are accessible via the following attributes: + + * :attr:`~automate.statusobject.StatusObject.safety_delay` and :attr:`~automate.statusobject.StatusObject.safety_mode` + can be used to define a minimum delay between status changes ("safety" ~ some devices might break if changed with big frequency) + * :attr:`~automate.statusobject.StatusObject.change_delay` and :attr:`~automate.statusobject.StatusObject.change_mode` can be used + to define a delay which (always) takes place before status is changed. + +Here, modes are one of ``'rising'``, ``'falling'``, ``'both'``, default being ``'rising'``. To disable +functionality completely, set corresponding delay parameter to zero. Functions are +described below. + +Creating Custom Sensors and Actuators +------------------------------------- + +Custom actuators and sensors can be easiliy written based on +:class:`~automate.statusobject.AbstractActuator` and :class:`~automate.statusobject.AbstractSensor` +classes, respectively. + +As an example, we will define one of each: + +.. code-block:: python + + # imports from your own library that you are using to define your sensor & actuator + from mylibrary import (setup_data_changed_callback, fetch_data_from_my_datasource, initialize_my_actuator_device, + change_status_in_my_actuator_device) + + class MySensor(AbstractSensor): + """ + Let us assume that you have your own library which has a status that you want to track + in your Automate program. + """ + # define your status data type + _status = CBool + def setup(self): + setup_my_datasource() + # we tell our library that update_status need to be called when status is changed + # We could use self.set_status directly, if library can pass new status as an argument. + setup_data_changed_callback(self.update_status) + def update_status(self): + # fetch new status from your datasource (this function is called by your library) + self.status = fetch_data_from_your_datasource() + def cleanup(self): + # define this if you need to clean things up when program is stopped + pass + + class MyActuator(AbstractActuator): + # define your status data type. Transient=True is a good idea because + # actuator status is normally determined by other values (sensors & programs etc) + _status = CFloat(transient=True) + def setup(self): + initialize_my_actuator_device() + def _status_changed(self): + chagnge_status_in_my_actuator_device(self.status) + +For more examples, look +:mod:`~automate.sensors.builtin_sensors` and +:mod:`~automate.actuators.builtin_actuators`. For more examples, see also :ref:`automate-extensions`, +especially support modules for Arduino and Raspberry Pi IO devices) + +StatusObject Definition +----------------------- + +.. autoclass:: automate.statusobject.StatusObject + :members: + +Sensor Baseclass Definition +--------------------------- + +.. inheritance-diagram:: automate.statusobject.AbstractSensor + :parts: 1 + +.. autoclass:: automate.statusobject.AbstractSensor + :members: + +Actuator Baseclass Definition +----------------------------- + +.. inheritance-diagram:: automate.statusobject.AbstractActuator + :parts: 1 + +.. autoclass:: automate.statusobject.AbstractActuator + :members: diff --git a/docs/source/system.rst b/docs/source/system.rst new file mode 100644 index 0000000..6c8a097 --- /dev/null +++ b/docs/source/system.rst @@ -0,0 +1,95 @@ +Automate System +=============== + +Introduction +------------ + +:class:`automate.system.System` encapsulates the state machine parts into single object. It has already been +explained how to use System. Here we will go further into some details. + + +.. _groups: + +Groups +------ + + +In Automate system, it is possible to group objects by putting them to Groups. Grouping helps organizing +objects in code level as well as in GUIs (:ref:`automate-webui` etc.). + +Here is an example:: + + class MySystem(System): + class group1(Group): + sensor1 = UserBoolSensor() + sensor2 = UserBoolSensor() + + class group2(Group): + sensor3 = UserBoolSensor() + sensor4 = UserBoolSensor() + +By adding SystemObject to a group, will assign it a tag corresponding to its groups class name. I.e. here, +``sensor1`` and ``sensor2`` will get tag *group:group1* and ``sensor3`` and ``sensor4`` will get tag *group:group2*. + +System has single namespace dictionary that contains names of all objects. That implies +that objects in different groups may not have same name. + +.. _state-saving: + +System State Saving and Restoring via Serialization +--------------------------------------------------- + +If System state is desired to be loaded later from periodically auto-saved state dumps, +system can be instantiated via :meth:`~automate.system.System.load_or_create` as follows:: + + my_system_instance = MySystem.load_or_create('my_statedump.dmp') + +Then system state will be saved periodically (by default, once per 30 minutes) by +:class:`~automate.services.statussaver.StatusSaverService`, which is automatically loaded +service (see :ref:`services`). If you desire to change +interval, you need to explicitly define +:attr:`~automate.services.statussaver.StatusSaverService.dump_interval` +as follows:: + + status_saver = StatusSaverService(dump_interval=10) # interval in seconds + my_system_instance = MySystem.load_or_create('my_statedump.dmp', services=[status_saver]) + + +SystemObject +------------ + +.. inheritance-diagram:: automate.system.SystemObject + :parts: 1 + +:class:`~automate.systemobject.SystemObject` is baseclass for all objects that may be used within +:class:`~automate.system.System` (most importantly, +Sensors, Actuators and Programs). + +Due to multiple inheritance, many SystemObjects, +such as Sensors (:class:`~automate.statusobject.AbstractSensor`), +Actuators (:class:`~automate.statusobject.AbstractActuator`), and +Programs (:class:`~automate.program.Program`) can act in *multiple roles*, +in addition to their primary role, as follows: + +* Sensors and actuators can always be used also as a program i.e. they may have conditions + and action callables defined, because they derive from :class:`~automate.program.ProgrammableSystemObject`. +* Both Actuators and Sensors can be used as *triggers* in Callables and via them in Programs +* Also plain Programs can be used as a Sensor. Then its activation status (boolean) serves as Sensor status. + +Sensors and Programs do not have Actuator properties (i.e. per-program statuses), but +Sensor status can still be set/written by a Program, similarly to actuators with +:attr:`~automate.statusobject.AbstractActuator.slave` attribute set to True. + +System Class Definition +----------------------- + +.. autoclass:: automate.system.System + :members: + + +SystemObjects Class Definition +------------------------------ + +.. autoclass:: automate.systemobject.SystemObject + :members: + diff --git a/setup.py b/setup.py index 8c82641..7bd4c06 100755 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ def get_version(filename): name="automate", version=get_version('src/automate/__init__.py'), packages=find_packages('src'), + package_dir={'': 'src'}, install_requires=[ 'traits==4.5.0', 'croniter==0.3.8',