First commit.
Change-Id: I6dbddb3aec8f51f83bbf918d12d854b40754b9d5
This commit is contained in:
parent
a62bd91c37
commit
c6b06b7b08
|
@ -0,0 +1,11 @@
|
||||||
|
*.pyc
|
||||||
|
.directory
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
.tox/
|
||||||
|
.idea/
|
||||||
|
iotronic_lightningrod.egg-info
|
||||||
|
build
|
||||||
|
AUTHORS
|
||||||
|
Authors
|
||||||
|
ChangeLog
|
|
@ -0,0 +1,17 @@
|
||||||
|
If you would like to contribute to the development of OpenStack, you must
|
||||||
|
follow the steps in this page:
|
||||||
|
|
||||||
|
http://docs.openstack.org/infra/manual/developers.html
|
||||||
|
|
||||||
|
If you already have a good understanding of how the system works and your
|
||||||
|
OpenStack accounts are set up, you can skip to the development workflow
|
||||||
|
section of this documentation to learn how changes to OpenStack should be
|
||||||
|
submitted for review via the Gerrit tool:
|
||||||
|
|
||||||
|
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||||
|
|
||||||
|
Pull requests submitted through GitHub will be ignored.
|
||||||
|
|
||||||
|
Bugs should be filed on Launchpad, not GitHub:
|
||||||
|
|
||||||
|
https://bugs.launchpad.net/iotronic-lightning-rod
|
|
@ -0,0 +1,4 @@
|
||||||
|
iotronic_lightningrod Style Commandments
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
|
@ -0,0 +1,175 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
|
@ -0,0 +1,6 @@
|
||||||
|
include AUTHORS
|
||||||
|
include ChangeLog
|
||||||
|
exclude .gitignore
|
||||||
|
exclude .gitreview
|
||||||
|
|
||||||
|
global-exclude *.pyc
|
|
@ -0,0 +1,20 @@
|
||||||
|
===============================
|
||||||
|
Iotronic Lightning-rod Agent
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Python implementation of Lightning-rod Agent,
|
||||||
|
the Stack4Things (http://stack4things.unime.it/) board-side probe.
|
||||||
|
|
||||||
|
Please fill here a long description which must be at least 3 lines wrapped on
|
||||||
|
80 cols, so that distribution package maintainers can use it in their packages.
|
||||||
|
Note that this is a hard requirement.
|
||||||
|
|
||||||
|
* Free software: Apache license
|
||||||
|
* Documentation: http://docs.openstack.org/developer/iotronic_lightningrod
|
||||||
|
* Source: https://github.com/openstack/iotronic-lightning-rod
|
||||||
|
* Bugs: https://bugs.launchpad.net/iotronic-lightning-rod
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* TODO
|
|
@ -0,0 +1,75 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath('../..'))
|
||||||
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
|
# 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.intersphinx',
|
||||||
|
'oslosphinx'
|
||||||
|
]
|
||||||
|
|
||||||
|
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||||
|
# text edit cycles.
|
||||||
|
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'iotronic_lightningrod'
|
||||||
|
copyright = u'2016, OpenStack Foundation'
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||||
|
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||||
|
# html_theme_path = ["."]
|
||||||
|
# html_theme = '_theme'
|
||||||
|
# html_static_path = ['static']
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = '%sdoc' % project
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass
|
||||||
|
# [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index',
|
||||||
|
'%s.tex' % project,
|
||||||
|
u'%s Documentation' % project,
|
||||||
|
u'OpenStack Foundation', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
#intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -0,0 +1,4 @@
|
||||||
|
============
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
.. include:: ../../CONTRIBUTING.rst
|
|
@ -0,0 +1,24 @@
|
||||||
|
.. iotronic_lightningrod documentation master file, created by
|
||||||
|
sphinx-quickstart on Tue Jul 9 22:26:36 2013.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Welcome to iotronic_lightningrod's documentation!
|
||||||
|
========================================================
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
readme
|
||||||
|
installation
|
||||||
|
usage
|
||||||
|
contributing
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
|
@ -0,0 +1,12 @@
|
||||||
|
============
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
At the command line::
|
||||||
|
|
||||||
|
$ pip install iotronic_lightningrod
|
||||||
|
|
||||||
|
Or, if you have virtualenvwrapper installed::
|
||||||
|
|
||||||
|
$ mkvirtualenv iotronic_lightningrod
|
||||||
|
$ pip install iotronic_lightningrod
|
|
@ -0,0 +1 @@
|
||||||
|
.. include:: ../../README.rst
|
|
@ -0,0 +1,7 @@
|
||||||
|
========
|
||||||
|
Usage
|
||||||
|
========
|
||||||
|
|
||||||
|
To use iotronic_lightningrod in a project::
|
||||||
|
|
||||||
|
import iotronic_lightningrod
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/bin/ash
|
||||||
|
|
||||||
|
workdir=/usr/bin
|
||||||
|
|
||||||
|
start() {
|
||||||
|
|
||||||
|
cd $workdir
|
||||||
|
|
||||||
|
pid=`ps www | grep "/usr/bin/python $workdir/lightning-rod" | grep -v grep | awk '{ print $1 }'`
|
||||||
|
|
||||||
|
|
||||||
|
if [ -r $pid ]; then
|
||||||
|
/usr/bin/python $workdir/lightning-rod &
|
||||||
|
sleep 2
|
||||||
|
pid=`ps www | grep "/usr/bin/python $workdir/lightning-rod" | grep -v grep | awk '{ print $1 }'`
|
||||||
|
echo "PID:" $pid
|
||||||
|
echo "Lightning-rod is started."
|
||||||
|
else
|
||||||
|
echo "Lightning-rod is already started with PID $pid."
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
|
||||||
|
pid=`ps www | grep "/usr/bin/python $workdir/lightning-rod" | grep -v grep | awk '{ print $1 }'`
|
||||||
|
|
||||||
|
if [ -r $pid ]; then
|
||||||
|
echo "Lightning-rod is already stopped!"
|
||||||
|
else
|
||||||
|
|
||||||
|
echo "PID:" $pid
|
||||||
|
kill -9 $pid
|
||||||
|
sleep 2
|
||||||
|
echo "Lightning-rod stopped."
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status(){
|
||||||
|
|
||||||
|
pid=`ps www | grep "/usr/bin/python $workdir/lightning-rod" | grep -v grep | awk '{ print $1 }'`
|
||||||
|
|
||||||
|
if [ -r $pid ]; then
|
||||||
|
echo "Lightning-rod is stopped."
|
||||||
|
else
|
||||||
|
|
||||||
|
echo "PID:" $pid
|
||||||
|
echo "Lightning-rod is started."
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
file:///home/webwolf/Scrivania/Stack4Things/test_ipk/s4t-lr
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: /etc/init.d/lightning-rod {start|stop|restart|status}"
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
exit 0
|
|
@ -0,0 +1,3 @@
|
||||||
|
[DEFAULT]
|
||||||
|
debug = True
|
||||||
|
log_file = /var/log/s4t-lightning-rod.log
|
|
@ -0,0 +1,17 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Iotronic Lightning-Rod
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
StandardInput=null
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
WorkingDirectory=/usr/bin/
|
||||||
|
ExecStart=/usr/bin/python /usr/bin/lightning-rod
|
||||||
|
Restart=on-abort
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,165 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
# from dateutil.tz import tzlocal
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from iotronic_lightningrod.config import iotronic_home
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SETTINGS = iotronic_home + '/settings.json'
|
||||||
|
|
||||||
|
|
||||||
|
class Board(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.iotronic_config = {}
|
||||||
|
|
||||||
|
self.board_config = {}
|
||||||
|
self.name = None
|
||||||
|
self.type = None
|
||||||
|
self.status = None
|
||||||
|
self.uuid = None
|
||||||
|
self.code = None
|
||||||
|
self.agent = None
|
||||||
|
self.mobile = None
|
||||||
|
self.session = None
|
||||||
|
self.session_id = None
|
||||||
|
|
||||||
|
self.location = {}
|
||||||
|
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
self.wamp_config = None
|
||||||
|
self.extra = {}
|
||||||
|
|
||||||
|
self.loadSettings()
|
||||||
|
|
||||||
|
def loadConf(self):
|
||||||
|
"""This method loads the JSON configuraton file: settings.json.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
with open(SETTINGS) as settings:
|
||||||
|
lr_settings = json.load(settings)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Parsing error in " + SETTINGS + ": " + str(err))
|
||||||
|
lr_settings = None
|
||||||
|
|
||||||
|
return lr_settings
|
||||||
|
|
||||||
|
def loadSettings(self):
|
||||||
|
'''This method gets and sets the board attributes from the conf file.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Load all settings.json file
|
||||||
|
self.iotronic_config = self.loadConf()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# STATUS OPERATIVE
|
||||||
|
board_config = self.iotronic_config['iotronic']['board']
|
||||||
|
self.uuid = board_config['uuid']
|
||||||
|
self.code = board_config['code']
|
||||||
|
self.name = board_config['name']
|
||||||
|
self.status = board_config['status']
|
||||||
|
self.type = board_config['type']
|
||||||
|
self.mobile = board_config['mobile']
|
||||||
|
self.extra = board_config['extra']
|
||||||
|
self.agent = board_config['agent']
|
||||||
|
self.created_at = board_config['created_at']
|
||||||
|
self.updated_at = board_config['updated_at'] # self.getTimestamp()
|
||||||
|
self.location = board_config['location']
|
||||||
|
|
||||||
|
self.extra = self.iotronic_config['iotronic']['extra']
|
||||||
|
|
||||||
|
LOG.info('Board settings:')
|
||||||
|
LOG.info(' - code: ' + str(self.code))
|
||||||
|
LOG.info(' - uuid: ' + str(self.uuid))
|
||||||
|
# LOG.debug(" - conf:\n" + json.dumps(board_config, indent=4))
|
||||||
|
|
||||||
|
self.getWampAgent(self.iotronic_config)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.warning("settings.json file exception: " + str(err))
|
||||||
|
# STATUS REGISTERED
|
||||||
|
try:
|
||||||
|
self.code = board_config['code']
|
||||||
|
LOG.info('First registration board settings: ')
|
||||||
|
LOG.info(' - code: ' + str(self.code))
|
||||||
|
self.getWampAgent(self.iotronic_config)
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Wrong code: " + str(err))
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
def getWampAgent(self, config):
|
||||||
|
'''This method gets and sets the WAMP Board attributes from the conf file.
|
||||||
|
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
self.wamp_config = config['iotronic']['wamp']['main-agent']
|
||||||
|
LOG.info('WAMP Agent settings:')
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
if (self.status is None) | (self.status == "registered"):
|
||||||
|
self.wamp_config = \
|
||||||
|
config['iotronic']['wamp']['registration-agent']
|
||||||
|
LOG.info('Registration Agent settings:')
|
||||||
|
else:
|
||||||
|
LOG.error(
|
||||||
|
"WAMP Agent configuration is wrong... "
|
||||||
|
"please check settings.json WAMP configuration... Bye!"
|
||||||
|
)
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
LOG.info(' - agent: ' + str(self.agent))
|
||||||
|
LOG.info(' - url: ' + str(self.wamp_config['url']))
|
||||||
|
LOG.info(' - realm: ' + str(self.wamp_config['realm']))
|
||||||
|
# LOG.debug("- conf:\n" + json.dumps(self.wamp_config, indent=4))
|
||||||
|
|
||||||
|
def setConf(self, conf):
|
||||||
|
# LOG.info("\nNEW CONFIGURATION:\n" + str(json.dumps(conf, indent=4)))
|
||||||
|
|
||||||
|
with open(SETTINGS, 'w') as f:
|
||||||
|
json.dump(conf, f, indent=4)
|
||||||
|
|
||||||
|
# Reload configuration
|
||||||
|
self.loadSettings()
|
||||||
|
|
||||||
|
def updateStatus(self, status):
|
||||||
|
self.iotronic_config['iotronic']['board']["status"] = status
|
||||||
|
|
||||||
|
with open(SETTINGS, 'w') as f:
|
||||||
|
json.dump(self.iotronic_config, f, indent=4)
|
||||||
|
|
||||||
|
def getTimestamp(self):
|
||||||
|
# datetime.now(tzlocal()).isoformat()
|
||||||
|
return datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
|
||||||
|
def setUpdateTime(self):
|
||||||
|
self.iotronic_config['iotronic']['board']["updated_at"] = \
|
||||||
|
self.updated_at
|
||||||
|
|
||||||
|
with open(SETTINGS, 'w') as f:
|
||||||
|
json.dump(self.iotronic_config, f, indent=4)
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def manageTimeout(error_message, action):
|
||||||
|
try:
|
||||||
|
|
||||||
|
raise TimeoutError(error_message, action)
|
||||||
|
|
||||||
|
except TimeoutError as err:
|
||||||
|
details = err.args[0]
|
||||||
|
LOG.warning("Board connection call timeout: " + str(details))
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutError(Exception):
|
||||||
|
|
||||||
|
def __init__(self, message, action):
|
||||||
|
# Call the base class constructor with the parameters it needs
|
||||||
|
super(TimeoutError, self).__init__(message)
|
||||||
|
|
||||||
|
# Now for your custom code...
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
|
||||||
|
class timeout(object):
|
||||||
|
|
||||||
|
def __init__(self, seconds=1, error_message='Timeout', action=None):
|
||||||
|
self.seconds = seconds
|
||||||
|
self.error_message = error_message
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
def handle_timeout(self, signum, frame):
|
||||||
|
raise TimeoutError(self.error_message, self.action)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
signal.signal(signal.SIGALRM, self.handle_timeout)
|
||||||
|
signal.alarm(self.seconds)
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
signal.alarm(0)
|
||||||
|
|
||||||
|
|
||||||
|
class timeoutRPC(object):
|
||||||
|
|
||||||
|
def __init__(self, seconds=1, error_message='Timeout', action=None):
|
||||||
|
self.seconds = seconds
|
||||||
|
self.error_message = error_message
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
def handle_timeout(self, signum, frame):
|
||||||
|
manageTimeout(self.error_message, self.action)
|
||||||
|
# LOG.warning("RPC timeout: " + str(self.error_message))
|
||||||
|
# os._exit(1)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
signal.signal(signal.SIGALRM, self.handle_timeout)
|
||||||
|
signal.alarm(self.seconds)
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
signal.alarm(0)
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
dist = pkg_resources.get_distribution(__package__)
|
||||||
|
entry_points_name = \
|
||||||
|
os.path.join(dist.location, dist.egg_name()) + ".egg-info/entry_points.txt"
|
||||||
|
|
||||||
|
# Iotronic python package folder
|
||||||
|
package_path = os.path.join(dist.location, __package__)
|
||||||
|
|
||||||
|
# Iotronic home folder
|
||||||
|
iotronic_home = "/opt/stack4things/iotronic"
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
__author__ = "MDSLAB Team"
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Device(object):
|
||||||
|
"""Base class for each s4t Lightning-rod device.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, device_type):
|
||||||
|
self.device_type = device_type
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
pass
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from iotronic_lightningrod.config import package_path
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Gpio(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.path = package_path + "/gpio/" + self.name + ".py"
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def EnableGPIO(self):
|
||||||
|
"""Enable reading and writing functionalities of the GPIO module
|
||||||
|
|
||||||
|
:return: status of the operation (String)
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def DisableGPIO(self):
|
||||||
|
"""Disable reading and writing functionalities of the GPIO module
|
||||||
|
|
||||||
|
:return: status of the operation (String)
|
||||||
|
"""
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from iotronic_lightningrod.devices.gpio import Gpio
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGpio(Gpio.Gpio):
|
||||||
|
def __init__(self):
|
||||||
|
super(ServerGpio, self).__init__("server")
|
||||||
|
LOG.info("Server GPIO module importing...")
|
||||||
|
|
||||||
|
# Enable GPIO
|
||||||
|
def EnableGPIO(self):
|
||||||
|
result = ' - GPIO not available for server device!'
|
||||||
|
LOG.info(result)
|
||||||
|
|
||||||
|
def DisableGPIO(self):
|
||||||
|
result = ' - GPIO not available for server device!'
|
||||||
|
LOG.info(result)
|
|
@ -0,0 +1,213 @@
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from iotronic_lightningrod.devices.gpio import Gpio
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
i2c_path = "/sys/devices/mcuio/0:0.0/0:1.4/i2c-0"
|
||||||
|
device1_path = i2c_path + "/0-0060/iio:device1/"
|
||||||
|
device0_path = "/sys/bus/iio/devices/iio:device0/"
|
||||||
|
|
||||||
|
|
||||||
|
class YunGpio(Gpio.Gpio):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(YunGpio, self).__init__("yun")
|
||||||
|
|
||||||
|
self.MAPPING = {
|
||||||
|
'D8': '104',
|
||||||
|
'D9': '105',
|
||||||
|
'D10': '106',
|
||||||
|
'D11': '107',
|
||||||
|
'D5': '114',
|
||||||
|
'D13': '115',
|
||||||
|
'D3': '116',
|
||||||
|
'D2': '117',
|
||||||
|
'D4': '120',
|
||||||
|
'D12': '122',
|
||||||
|
'D6': '123'}
|
||||||
|
|
||||||
|
# LOG.info("Arduino YUN gpio module importing...")
|
||||||
|
|
||||||
|
def EnableGPIO(self):
|
||||||
|
"""Enable GPIO (device0).
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
with open('/sys/bus/iio/devices/iio:device0/enable', 'a') as f:
|
||||||
|
f.write('1')
|
||||||
|
|
||||||
|
result = " - GPIO enabled!\n"
|
||||||
|
LOG.info(result)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error enabling GPIO (device0): " + str(err))
|
||||||
|
|
||||||
|
def DisableGPIO(self):
|
||||||
|
"""Disable GPIO (device0).
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open('/sys/bus/iio/devices/iio:device0/enable', 'a') as f:
|
||||||
|
f.write('0')
|
||||||
|
|
||||||
|
result = " - GPIO disabled!\n"
|
||||||
|
LOG.info(result)
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error disabling GPIO (device0): " + str(err))
|
||||||
|
|
||||||
|
def EnableI2c(self):
|
||||||
|
"""Enable i2c device (device1).
|
||||||
|
|
||||||
|
From ideino-linino-lib library:
|
||||||
|
Board.prototype.addI2c = function(name, driver, addr, bus)
|
||||||
|
board.addI2c('BAR', 'mpl3115', '0x60', 0):
|
||||||
|
- i2c_device.driver: mpl3115
|
||||||
|
- i2c_device.addr: 0x60
|
||||||
|
- i2c_device.name: BAR
|
||||||
|
- i2c_device.bus: 0
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if os.path.exists('/sys/bus/i2c/devices/i2c-0/0-0060'):
|
||||||
|
result = " - I2C device already enabled!"
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
with open('/sys/bus/i2c/devices/i2c-0/new_device', 'a') as f:
|
||||||
|
# 'echo '+i2c_device.driver+' '+i2c_device.addr+ '
|
||||||
|
f.write('mpl3115 0x60')
|
||||||
|
result = " - I2C device enabled!"
|
||||||
|
|
||||||
|
LOG.info(result)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error enabling I2C (device1): " + str(err))
|
||||||
|
|
||||||
|
def i2cRead(self, sensor):
|
||||||
|
"""Read i2c raw value.
|
||||||
|
|
||||||
|
sensor options:
|
||||||
|
- in_pressure_raw
|
||||||
|
- in_temp_raw
|
||||||
|
|
||||||
|
:param sensor: name of the sensor connected to I2C port
|
||||||
|
:return: I2C raw value
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
with open(device1_path + "in_" + sensor + "_raw") as raw:
|
||||||
|
value = raw.read()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error reading I2C device: " + str(err))
|
||||||
|
value = None
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def setPIN(self, DPIN, value):
|
||||||
|
"""Function to set digital PIN value.
|
||||||
|
|
||||||
|
:param DPIN: pin
|
||||||
|
:param value: value to set the pin
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open('/sys/class/gpio/' + DPIN + '/value', 'a') as f:
|
||||||
|
f.write(value)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error setting PIN value: " + str(err))
|
||||||
|
|
||||||
|
def _setGPIOs(self, Dpin, direction, value):
|
||||||
|
"""GPIO mapping on lininoIO
|
||||||
|
|
||||||
|
-------------------------
|
||||||
|
GPIO n. OUTPUT
|
||||||
|
104 D8
|
||||||
|
105 D9
|
||||||
|
106 D10
|
||||||
|
107 D11
|
||||||
|
114 D5
|
||||||
|
115 D13
|
||||||
|
116 D3
|
||||||
|
117 D2
|
||||||
|
120 D4
|
||||||
|
122 D12
|
||||||
|
123 D6
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
with open('/sys/class/gpio/export', 'a') as f_export:
|
||||||
|
f_export.write(self.MAPPING[Dpin])
|
||||||
|
|
||||||
|
with open('/sys/class/gpio/' + Dpin + '/direction', 'a') as f_dir:
|
||||||
|
f_dir.write(direction)
|
||||||
|
|
||||||
|
with open('/sys/class/gpio/' + Dpin + '/value', 'a') as f_value:
|
||||||
|
f_value.write(value)
|
||||||
|
|
||||||
|
with open('/sys/class/gpio/' + Dpin + '/value') as f_value:
|
||||||
|
result = "PIN " + Dpin + " value " + f_value.read()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error setting GPIO value: " + str(err))
|
||||||
|
result = None
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _readVoltage(self, pin):
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(device0_path + "in_voltage_" + pin + "_raw") as raw:
|
||||||
|
voltage = raw.read()
|
||||||
|
# print("VOLTAGE: " + voltage)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error reading voltage: " + str(err))
|
||||||
|
voltage = None
|
||||||
|
|
||||||
|
return voltage
|
||||||
|
|
||||||
|
def blinkLed(self):
|
||||||
|
"""LED: 13. There is a built-in LED connected to digital pin 13.
|
||||||
|
|
||||||
|
When the pin has HIGH value, the LED is on,
|
||||||
|
when the pin has LOW value, it is off.
|
||||||
|
|
||||||
|
"""
|
||||||
|
with open('/sys/class/gpio/export', 'a') as f:
|
||||||
|
f.write('115')
|
||||||
|
|
||||||
|
with open('/sys/class/gpio/D13/direction', 'a') as f:
|
||||||
|
f.write('out')
|
||||||
|
|
||||||
|
with open('/sys/class/gpio/D13/value', 'a') as f:
|
||||||
|
f.write('1')
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
with open('/sys/class/gpio/D13/value', 'a') as f:
|
||||||
|
f.write('0')
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
from twisted.internet.defer import returnValue
|
||||||
|
|
||||||
|
from iotronic_lightningrod.devices import Device
|
||||||
|
from iotronic_lightningrod.devices.gpio import server
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def whoami():
|
||||||
|
return inspect.stack()[1][3]
|
||||||
|
|
||||||
|
|
||||||
|
def makeNothing():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class System(Device.Device):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(System, self).__init__("server")
|
||||||
|
|
||||||
|
server.ServerGpio().EnableGPIO()
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
"""Function called at the end of module loading (after RPC registration).
|
||||||
|
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def testRPC(self):
|
||||||
|
rpc_name = whoami()
|
||||||
|
LOG.info("RPC " + rpc_name + " CALLED...")
|
||||||
|
yield makeNothing()
|
||||||
|
result = " - " + rpc_name + " result: testRPC is working!!!\n"
|
||||||
|
LOG.info(result)
|
||||||
|
returnValue(result)
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Copyright 2011 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
# Linino references: http://wiki.linino.org/doku.php?id=wiki:lininoio_sysfs
|
||||||
|
|
||||||
|
from twisted.internet.defer import returnValue
|
||||||
|
|
||||||
|
from iotronic_lightningrod.devices import Device
|
||||||
|
from iotronic_lightningrod.devices.gpio import yun
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class System(Device.Device):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(System, self).__init__("yun")
|
||||||
|
|
||||||
|
self.gpio = yun.YunGpio()
|
||||||
|
|
||||||
|
self.gpio.EnableGPIO()
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
"""Function called at the end of module loading (after RPC registration).
|
||||||
|
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def testLED(self):
|
||||||
|
LOG.info(" - testLED CALLED...")
|
||||||
|
|
||||||
|
yield self.gpio.blinkLed()
|
||||||
|
|
||||||
|
result = "testLED: LED blinking!\n"
|
||||||
|
LOG.info(result)
|
||||||
|
returnValue(result)
|
||||||
|
|
||||||
|
def setGPIOs(self, Dpin, direction, value):
|
||||||
|
|
||||||
|
LOG.info(" - setGPIOs CALLED... digital pin " + Dpin
|
||||||
|
+ " (GPIO n. " + self.gpio.MAPPING[Dpin] + ")")
|
||||||
|
|
||||||
|
result = yield self.gpio._setGPIOs(Dpin, direction, value)
|
||||||
|
LOG.info(result)
|
||||||
|
returnValue(result)
|
||||||
|
|
||||||
|
def readVoltage(self, Apin):
|
||||||
|
"""To read the voltage applied on the pin A0,A1,A2,A3,A4,A5
|
||||||
|
|
||||||
|
"""
|
||||||
|
LOG.info(" - readVoltage CALLED... reading pin " + Apin)
|
||||||
|
|
||||||
|
voltage = self.gpio._readVoltage(Apin)
|
||||||
|
|
||||||
|
result = yield "read voltage for " + Apin + " pin: " + voltage
|
||||||
|
LOG.info(result)
|
||||||
|
returnValue(result)
|
|
@ -0,0 +1,656 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
# Autobahn and Twisted imports
|
||||||
|
from autobahn.twisted import wamp
|
||||||
|
from autobahn.twisted.wamp import ApplicationSession
|
||||||
|
from autobahn.twisted import websocket
|
||||||
|
from autobahn.wamp import exception
|
||||||
|
from autobahn.wamp import types
|
||||||
|
from twisted.internet.defer import inlineCallbacks
|
||||||
|
from twisted.internet.protocol import ReconnectingClientFactory
|
||||||
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
# OSLO imports
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
# MODULES imports
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
import pkg_resources
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
from stevedore import extension
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
# IoTronic imports
|
||||||
|
from iotronic_lightningrod.Board import Board
|
||||||
|
from iotronic_lightningrod.common.exception import timeoutRPC
|
||||||
|
import iotronic_lightningrod.wampmessage as WM
|
||||||
|
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
SESSION = None
|
||||||
|
global board
|
||||||
|
board = None
|
||||||
|
reconnection = False
|
||||||
|
RPC = {}
|
||||||
|
RPC_devices = {}
|
||||||
|
|
||||||
|
|
||||||
|
def moduleReloadInfo(session):
|
||||||
|
"""This function is used in the reconnection stage to register
|
||||||
|
|
||||||
|
again the RPCs of each module and for device.
|
||||||
|
|
||||||
|
:param session: WAMP session object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOG.info("Modules reloading after WAMP recovery...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# Register RPCs for each Lightning-rod module
|
||||||
|
for mod in RPC:
|
||||||
|
LOG.info("- Reloading module RPcs for " + str(mod))
|
||||||
|
moduleWampRegister(session, RPC[mod])
|
||||||
|
|
||||||
|
# Register RPCs for the device
|
||||||
|
for dev in RPC_devices:
|
||||||
|
LOG.info("- Reloading device RPCs for " + str(dev))
|
||||||
|
moduleWampRegister(session, RPC_devices[dev])
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.warning("Board modules reloading error: " + str(err))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
|
||||||
|
def moduleWampRegister(session, meth_list):
|
||||||
|
"""This function register for each module methods the relative RPC.
|
||||||
|
|
||||||
|
:param session:
|
||||||
|
:param meth_list:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(meth_list) == 2:
|
||||||
|
|
||||||
|
LOG.info(" - No procedures to register!")
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
for meth in meth_list:
|
||||||
|
# We don't considere the __init__ and finalize methods
|
||||||
|
if (meth[0] != "__init__") & (meth[0] != "finalize"):
|
||||||
|
rpc_addr = u'iotronic.' + board.uuid + '.' + meth[0]
|
||||||
|
session.register(inlineCallbacks(meth[1]), rpc_addr)
|
||||||
|
LOG.info(" --> " + str(meth[0]))
|
||||||
|
# LOG.info(" --> " + str(rpc_addr))
|
||||||
|
|
||||||
|
|
||||||
|
def modulesLoader(session):
|
||||||
|
"""Modules loader method thorugh stevedore libraries.
|
||||||
|
|
||||||
|
:param session:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOG.info("Available modules: ")
|
||||||
|
|
||||||
|
ep = []
|
||||||
|
|
||||||
|
for ep in pkg_resources.iter_entry_points(group='s4t.modules'):
|
||||||
|
LOG.info(" - " + str(ep))
|
||||||
|
|
||||||
|
if not ep:
|
||||||
|
|
||||||
|
LOG.info("No modules available!")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
modules = extension.ExtensionManager(
|
||||||
|
namespace='s4t.modules',
|
||||||
|
# invoke_on_load=True,
|
||||||
|
# invoke_args=(session,),
|
||||||
|
)
|
||||||
|
|
||||||
|
LOG.info('Modules to load:')
|
||||||
|
|
||||||
|
for ext in modules.extensions:
|
||||||
|
|
||||||
|
# LOG.debug(ext.name)
|
||||||
|
|
||||||
|
if (ext.name == 'gpio') & (board.type == 'server'):
|
||||||
|
LOG.info('- GPIO module disabled for laptop devices')
|
||||||
|
|
||||||
|
else:
|
||||||
|
mod = ext.plugin(board, session)
|
||||||
|
|
||||||
|
# Methods list for each module
|
||||||
|
meth_list = inspect.getmembers(mod, predicate=inspect.ismethod)
|
||||||
|
|
||||||
|
global RPC
|
||||||
|
RPC[mod.name] = meth_list
|
||||||
|
|
||||||
|
if len(meth_list) == 2:
|
||||||
|
# there are at least two methods for each module:
|
||||||
|
# "__init__" and "finalize"
|
||||||
|
|
||||||
|
LOG.info(" - No RPC to register for "
|
||||||
|
+ str(ext.name) + " module!")
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.info(" - RPC list of " + str(mod.name) + ":")
|
||||||
|
moduleWampRegister(SESSION, meth_list)
|
||||||
|
|
||||||
|
# Call the finalize procedure for each module
|
||||||
|
mod.finalize()
|
||||||
|
|
||||||
|
LOG.info("Lightning-rod modules loaded.")
|
||||||
|
LOG.info("\n\nListening...")
|
||||||
|
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def IotronicLogin(board, session, details):
|
||||||
|
"""Function called to connect the board to Iotronic.
|
||||||
|
|
||||||
|
The board:
|
||||||
|
1. logs in to Iotronic
|
||||||
|
2. loads the modules
|
||||||
|
|
||||||
|
:param board:
|
||||||
|
:param session:
|
||||||
|
:param details:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOG.info("IoTronic Authentication:")
|
||||||
|
|
||||||
|
global reconnection
|
||||||
|
|
||||||
|
global SESSION
|
||||||
|
SESSION = session
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
rpc = str(board.agent) + u'.stack4things.connection'
|
||||||
|
|
||||||
|
with timeoutRPC(seconds=3, action=rpc):
|
||||||
|
res = yield session.call(rpc,
|
||||||
|
uuid=board.uuid,
|
||||||
|
session=details.session
|
||||||
|
)
|
||||||
|
|
||||||
|
w_msg = WM.deserialize(res)
|
||||||
|
|
||||||
|
if w_msg.result == WM.SUCCESS:
|
||||||
|
|
||||||
|
LOG.info(" - Access granted to Iotronic.")
|
||||||
|
|
||||||
|
# LOADING BOARD MODULES
|
||||||
|
try:
|
||||||
|
|
||||||
|
yield modulesLoader(session)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("WARNING - Could not register procedures: "
|
||||||
|
+ str(e))
|
||||||
|
|
||||||
|
# Reset flag to False
|
||||||
|
reconnection = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.error(" - Access denied to Iotronic.")
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
except exception.ApplicationError as e:
|
||||||
|
LOG.error(" - Iotronic Connection RPC error: " + str(e))
|
||||||
|
# Iotronic is offline the board can not call
|
||||||
|
# the "stack4things.connection" RPC.
|
||||||
|
# The board will disconnect from WAMP agent and retry later.
|
||||||
|
reconnection = True
|
||||||
|
session.disconnect()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("Iotronic board connection error: " + str(e))
|
||||||
|
|
||||||
|
|
||||||
|
class WampFrontend(ApplicationSession):
|
||||||
|
"""Function to manage the WAMP connection events.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def onJoin(self, details):
|
||||||
|
"""Execute the following procedures when the board connects to WAMP server.
|
||||||
|
|
||||||
|
:param details: WAMP session details
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# LIGHTNING-ROD STATE:
|
||||||
|
# - REGISTRATION STATE: the first connection to Iotronic
|
||||||
|
# - FIRST CONNECTION: the board become operative after registration
|
||||||
|
# - LIGHTNING-ROD BOOT: the first connection to WAMP
|
||||||
|
# after Lightning-rod starting
|
||||||
|
# - WAMP RECOVERY: when the established WAMP connection fails
|
||||||
|
|
||||||
|
global reconnection
|
||||||
|
|
||||||
|
# reconnection flag is False when the board is:
|
||||||
|
# - LIGHTNING-ROD BOOT
|
||||||
|
# - REGISTRATION STATE
|
||||||
|
# - FIRST CONNECTION
|
||||||
|
#
|
||||||
|
# reconnection flag is True when the board is:
|
||||||
|
# - WAMP RECOVERY
|
||||||
|
|
||||||
|
global SESSION
|
||||||
|
SESSION = self
|
||||||
|
|
||||||
|
# LOG.debug(" - session: " + str(details))
|
||||||
|
|
||||||
|
board.session = self
|
||||||
|
board.session_id = details.session
|
||||||
|
|
||||||
|
LOG.info(" - Joined in realm " + board.wamp_config['realm'] + ":")
|
||||||
|
LOG.info(" - WAMP Agent: " + str(board.agent))
|
||||||
|
LOG.info(" - Session ID: " + str(details.session))
|
||||||
|
|
||||||
|
if reconnection is False:
|
||||||
|
|
||||||
|
if board.uuid is None:
|
||||||
|
|
||||||
|
######################
|
||||||
|
# REGISTRATION STATE #
|
||||||
|
######################
|
||||||
|
|
||||||
|
# If in the LR configuration file there is not the Board UUID
|
||||||
|
# specified it means the board is a new one and it has to call
|
||||||
|
# IoTronic in order to complete the registration.
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
LOG.info(" - Board needs to be registered to Iotronic.")
|
||||||
|
|
||||||
|
rpc = u'stack4things.register'
|
||||||
|
|
||||||
|
with timeoutRPC(seconds=3, action=rpc):
|
||||||
|
res = yield self.call(
|
||||||
|
rpc,
|
||||||
|
code=board.code,
|
||||||
|
session=details.session
|
||||||
|
)
|
||||||
|
|
||||||
|
w_msg = WM.deserialize(res)
|
||||||
|
|
||||||
|
# LOG.info(" - Board registration result: \n" +
|
||||||
|
# json.loads(w_msg.message, indent=4))
|
||||||
|
|
||||||
|
if w_msg.result == WM.SUCCESS:
|
||||||
|
|
||||||
|
LOG.info("Registration authorized by Iotronic:\n"
|
||||||
|
+ str(w_msg.message))
|
||||||
|
|
||||||
|
# the 'message' field contains
|
||||||
|
# the board configuration to load
|
||||||
|
board.setConf(w_msg.message)
|
||||||
|
|
||||||
|
# We need to disconnect the client from the
|
||||||
|
# registration-agent inorder to reconnect
|
||||||
|
# to the WAMP agent assigned by Iotronic
|
||||||
|
# at the provisioning stage
|
||||||
|
LOG.info("\n\nDisconnecting from Registration Agent "
|
||||||
|
"to load new settings...\n\n")
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.error("Registration denied by Iotronic: "
|
||||||
|
+ str(w_msg.message))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
except exception.ApplicationError as e:
|
||||||
|
LOG.error("IoTronic registration error: " + str(e))
|
||||||
|
# Iotronic is offline the board can not call the
|
||||||
|
# "stack4things.connection" RPC.
|
||||||
|
# The board will disconnect from WAMP agent and retry later
|
||||||
|
|
||||||
|
# TO ACTIVE BOOT CONNECTION RECOVERY MODE
|
||||||
|
reconnection = True
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning(" - Board registration call error: " + str(e))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
if board.status == "registered":
|
||||||
|
####################
|
||||||
|
# FIRST CONNECTION #
|
||||||
|
####################
|
||||||
|
|
||||||
|
# In this case we manage the first reconnection
|
||||||
|
# after the registration stage:
|
||||||
|
# Lightining-rod sets its status to "operative"
|
||||||
|
# completing the provisioning and configuration stage.
|
||||||
|
LOG.info("\n\n\nBoard is becoming operative...\n\n\n")
|
||||||
|
board.updateStatus("operative")
|
||||||
|
board.loadSettings()
|
||||||
|
IotronicLogin(board, self, details)
|
||||||
|
|
||||||
|
elif board.status == "operative":
|
||||||
|
######################
|
||||||
|
# LIGHTNING-ROD BOOT #
|
||||||
|
######################
|
||||||
|
|
||||||
|
# After join to WAMP agent, Lightning-rod will:
|
||||||
|
# - authenticate to Iotronic
|
||||||
|
# - load the enabled modules
|
||||||
|
|
||||||
|
# The board will keep at this tage until it will succeed
|
||||||
|
# to connect to Iotronic.
|
||||||
|
IotronicLogin(board, self, details)
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.error("Wrong board status '" + board.status + "'.")
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
#################
|
||||||
|
# WAMP RECOVERY #
|
||||||
|
#################
|
||||||
|
|
||||||
|
LOG.info("IoTronic connection recovery:")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
rpc = str(board.agent) + u'.stack4things.connection'
|
||||||
|
|
||||||
|
with timeoutRPC(seconds=3, action=rpc):
|
||||||
|
res = yield self.call(
|
||||||
|
rpc,
|
||||||
|
uuid=board.uuid,
|
||||||
|
session=details.session
|
||||||
|
)
|
||||||
|
|
||||||
|
w_msg = WM.deserialize(res)
|
||||||
|
|
||||||
|
if w_msg.result == WM.SUCCESS:
|
||||||
|
|
||||||
|
LOG.info(" - Access granted to Iotronic.")
|
||||||
|
|
||||||
|
# LOADING BOARD MODULES
|
||||||
|
# If the board is in WAMP connection recovery state
|
||||||
|
# we need to register again the RPCs of each module
|
||||||
|
try:
|
||||||
|
|
||||||
|
yield moduleReloadInfo(self)
|
||||||
|
|
||||||
|
# Reset flag to False
|
||||||
|
reconnection = False
|
||||||
|
|
||||||
|
LOG.info("WAMP Session Recovered!")
|
||||||
|
|
||||||
|
LOG.info("\n\nListening...\n\n")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("WARNING - Could not register procedures: "
|
||||||
|
+ str(e))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.error("Access to IoTronic denied: "
|
||||||
|
+ str(w_msg.message))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
except exception.ApplicationError as e:
|
||||||
|
LOG.error("IoTronic connection error: " + str(e))
|
||||||
|
# Iotronic is offline the board can not call
|
||||||
|
# the "stack4things.connection" RPC.
|
||||||
|
# The board will disconnect from WAMP agent and retry later
|
||||||
|
|
||||||
|
# TO ACTIVE WAMP CONNECTION RECOVERY MODE
|
||||||
|
reconnection = False
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("Board connection error after WAMP recovery: "
|
||||||
|
+ str(e))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def onLeave(self, details):
|
||||||
|
LOG.warning('WAMP Session Left: ' + str(details))
|
||||||
|
|
||||||
|
|
||||||
|
class WampClientFactory(websocket.WampWebSocketClientFactory,
|
||||||
|
ReconnectingClientFactory):
|
||||||
|
|
||||||
|
def clientConnectionFailed(self, connector, reason):
|
||||||
|
"""Procedure triggered on WAMP connection failure.
|
||||||
|
|
||||||
|
:param connector: WAMP connector object
|
||||||
|
:param reason: WAMP connection failure reason
|
||||||
|
|
||||||
|
"""
|
||||||
|
LOG.warning("WAMP Connection Failed: Crossbar server unreachable.")
|
||||||
|
ReconnectingClientFactory.clientConnectionFailed(
|
||||||
|
self,
|
||||||
|
connector,
|
||||||
|
reason
|
||||||
|
)
|
||||||
|
|
||||||
|
def clientConnectionLost(self, connector, reason):
|
||||||
|
"""Procedure triggered on WAMP connection lost.
|
||||||
|
|
||||||
|
:param connector: WAMP connector object
|
||||||
|
:param reason: WAMP connection failure reason
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOG.warning("WAMP Connection Lost.")
|
||||||
|
|
||||||
|
global reconnection
|
||||||
|
|
||||||
|
LOG.warning("WAMP status: board = " + str(board.status)
|
||||||
|
+ " - reconnection = " + str(reconnection))
|
||||||
|
|
||||||
|
if board.status == "operative" and reconnection is False:
|
||||||
|
|
||||||
|
#################
|
||||||
|
# WAMP RECOVERY #
|
||||||
|
#################
|
||||||
|
|
||||||
|
# we need to recover wamp session and
|
||||||
|
# we set reconnection flag to True in order to activate
|
||||||
|
# the RPCs module registration procedure for each module
|
||||||
|
|
||||||
|
reconnection = True
|
||||||
|
|
||||||
|
LOG.info("Reconnecting to " + str(connector.getDestination().host)
|
||||||
|
+ ":" + str(connector.getDestination().port))
|
||||||
|
|
||||||
|
ReconnectingClientFactory.clientConnectionLost(
|
||||||
|
self,
|
||||||
|
connector,
|
||||||
|
reason
|
||||||
|
)
|
||||||
|
|
||||||
|
elif board.status == "operative" and reconnection is True:
|
||||||
|
|
||||||
|
######################
|
||||||
|
# LIGHTNING-ROD BOOT #
|
||||||
|
######################
|
||||||
|
|
||||||
|
# At this stage if the reconnection flag was set to True
|
||||||
|
# it means that we forced the reconnection procedure
|
||||||
|
# because of the board is not able to connect to IoTronic
|
||||||
|
# calling "stack4things.connection" RPC...
|
||||||
|
# it means IoTronic is offline!
|
||||||
|
|
||||||
|
# We need to reset the recconnection flag to False in order to
|
||||||
|
# do not enter in RPCs module registration procedure...
|
||||||
|
# At this stage the board tries to reconnect to
|
||||||
|
# IoTronic until it will come online again.
|
||||||
|
reconnection = False
|
||||||
|
|
||||||
|
LOG.info("Connecting to " + str(connector.getDestination().host)
|
||||||
|
+ ":" + str(connector.getDestination().port))
|
||||||
|
|
||||||
|
ReconnectingClientFactory.clientConnectionLost(
|
||||||
|
self,
|
||||||
|
connector,
|
||||||
|
reason
|
||||||
|
)
|
||||||
|
|
||||||
|
elif (board.status == "registered"):
|
||||||
|
######################
|
||||||
|
# REGISTRATION STATE #
|
||||||
|
######################
|
||||||
|
|
||||||
|
# LR was disconnected from Registration Agent
|
||||||
|
# in order to connect it to the assigned WAMP Agent.
|
||||||
|
|
||||||
|
LOG.debug("\n\nReconnecting after registration...\n\n")
|
||||||
|
|
||||||
|
# LR load the new configuration and gets the new WAMP Agent
|
||||||
|
board.loadSettings()
|
||||||
|
|
||||||
|
# LR has to connect to the assigned WAMP Agent
|
||||||
|
wampConnect(board.wamp_config)
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.error("Reconnection wrong status!")
|
||||||
|
|
||||||
|
|
||||||
|
def wampConnect(wamp_conf):
|
||||||
|
"""WAMP connection procedure.
|
||||||
|
|
||||||
|
:param wamp_conf: WAMP configuration from settings.json file
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOG.info("WAMP connection precedures:")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
component_config = types.ComponentConfig(
|
||||||
|
realm=unicode(wamp_conf['realm'])
|
||||||
|
)
|
||||||
|
session_factory = wamp.ApplicationSessionFactory(
|
||||||
|
config=component_config
|
||||||
|
)
|
||||||
|
session_factory.session = WampFrontend
|
||||||
|
|
||||||
|
transport_factory = WampClientFactory(
|
||||||
|
session_factory,
|
||||||
|
url=wamp_conf['url']
|
||||||
|
)
|
||||||
|
transport_factory.autoPingInterval = 5
|
||||||
|
transport_factory.autoPingTimeout = 5
|
||||||
|
|
||||||
|
connector = websocket.connectWS(transport_factory)
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
addr = str(connector.getDestination().host)
|
||||||
|
socket.inet_pton(socket.AF_INET, addr)
|
||||||
|
LOG.info(" - establishing connection to "
|
||||||
|
+ board.agent + ": " + addr)
|
||||||
|
|
||||||
|
except socket.error as err:
|
||||||
|
LOG.error(" - IP address validation error: " + str(err))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error(" - URI validation error: " + str(err))
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
|
||||||
|
class WampManager(object):
|
||||||
|
"""WAMP Manager: through this LR manages the connection to Crossbar server.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, wamp_conf):
|
||||||
|
# Connection to Crossbar server.
|
||||||
|
wampConnect(wamp_conf)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
LOG.info(" - starting Lightning-rod WAMP server...")
|
||||||
|
reactor.run()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
LOG.info("Stopping WAMP agent server...")
|
||||||
|
reactor.stop()
|
||||||
|
LOG.info("WAMP server stopped!")
|
||||||
|
|
||||||
|
|
||||||
|
def Bye():
|
||||||
|
LOG.info("Bye!")
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def LogoLR():
|
||||||
|
LOG.info('')
|
||||||
|
LOG.info('##############################')
|
||||||
|
LOG.info(' Stack4Things Lightning-rod')
|
||||||
|
LOG.info('##############################')
|
||||||
|
|
||||||
|
|
||||||
|
class LightningRod(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
logging.register_options(CONF)
|
||||||
|
DOMAIN = "s4t-lightning-rod"
|
||||||
|
CONF(project='iotronic')
|
||||||
|
logging.setup(CONF, DOMAIN)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, self.stop_handler)
|
||||||
|
|
||||||
|
LogoLR()
|
||||||
|
|
||||||
|
global board
|
||||||
|
board = Board()
|
||||||
|
|
||||||
|
LOG.info('Info:')
|
||||||
|
LOG.info(' - Logs: /var/log/s4t-lightning-rod.log')
|
||||||
|
current_time = board.getTimestamp()
|
||||||
|
LOG.info(" - Current time: " + current_time)
|
||||||
|
|
||||||
|
self.w = WampManager(board.wamp_config)
|
||||||
|
|
||||||
|
self.w.start()
|
||||||
|
|
||||||
|
def stop_handler(self, signum, frame):
|
||||||
|
LOG.info("LR is shutting down...")
|
||||||
|
|
||||||
|
self.w.stop()
|
||||||
|
|
||||||
|
Bye()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
LightningRod()
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
__author__ = "MDSLAB Team"
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Module(object):
|
||||||
|
"""Base class for each s4t Lightning-rod module.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# __metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
|
def __init__(self, name, board):
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.board = board
|
||||||
|
|
||||||
|
LOG.info("Loading module " + self.name + "...")
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def finalize(self):
|
||||||
|
pass
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import imp
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
from twisted.internet.defer import inlineCallbacks
|
||||||
|
|
||||||
|
from iotronic_lightningrod.config import package_path
|
||||||
|
from iotronic_lightningrod.lightningrod import RPC_devices
|
||||||
|
from iotronic_lightningrod.lightningrod import SESSION
|
||||||
|
from iotronic_lightningrod.modules import Module
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def deviceWampRegister(dev_meth_list, board):
|
||||||
|
|
||||||
|
LOG.info(" - " + str(board.type).capitalize()
|
||||||
|
+ " device registering RPCs:")
|
||||||
|
|
||||||
|
for meth in dev_meth_list:
|
||||||
|
|
||||||
|
if (meth[0] != "__init__") & (meth[0] != "finalize"):
|
||||||
|
# LOG.info(" - " + str(meth[0]))
|
||||||
|
rpc_addr = u'iotronic.' + board.uuid + '.' + meth[0]
|
||||||
|
# LOG.debug(" --> " + str(rpc_addr))
|
||||||
|
SESSION.register(inlineCallbacks(meth[1]), rpc_addr)
|
||||||
|
|
||||||
|
LOG.info(" --> " + str(meth[0]) + " registered!")
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceManager(Module.Module):
|
||||||
|
|
||||||
|
def __init__(self, board, session):
|
||||||
|
|
||||||
|
# Module declaration
|
||||||
|
super(DeviceManager, self).__init__("DeviceManager", board)
|
||||||
|
|
||||||
|
device_type = board.type
|
||||||
|
|
||||||
|
path = package_path + "/devices/" + device_type + ".py"
|
||||||
|
|
||||||
|
if os.path.exists(path):
|
||||||
|
|
||||||
|
device_module = imp.load_source("device", path)
|
||||||
|
|
||||||
|
LOG.info(" - Device " + device_type + " module imported!")
|
||||||
|
|
||||||
|
device = device_module.System()
|
||||||
|
|
||||||
|
dev_meth_list = inspect.getmembers(
|
||||||
|
device,
|
||||||
|
predicate=inspect.ismethod
|
||||||
|
)
|
||||||
|
|
||||||
|
RPC_devices[device_type] = dev_meth_list
|
||||||
|
|
||||||
|
deviceWampRegister(dev_meth_list, board)
|
||||||
|
|
||||||
|
board.device = device
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.warning("Device " + device_type + " not supported!")
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
pass
|
|
@ -0,0 +1,817 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import imp
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from Queue import Queue
|
||||||
|
import shutil
|
||||||
|
import time
|
||||||
|
from twisted.internet.defer import returnValue
|
||||||
|
|
||||||
|
from iotronic_lightningrod.config import iotronic_home
|
||||||
|
from iotronic_lightningrod.modules import Module
|
||||||
|
from iotronic_lightningrod.plugins import PluginSerializer
|
||||||
|
import iotronic_lightningrod.wampmessage as WM
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PLUGINS_THRS = {}
|
||||||
|
PLUGINS_CONF_FILE = iotronic_home + "/plugins.json"
|
||||||
|
|
||||||
|
|
||||||
|
def getFuncName():
|
||||||
|
return inspect.stack()[1][3]
|
||||||
|
|
||||||
|
|
||||||
|
def createPluginsConf():
|
||||||
|
"""Create plugins.json file if it does not exist.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not os.path.exists(PLUGINS_CONF_FILE):
|
||||||
|
LOG.debug("plugins.json does not exist: creating...")
|
||||||
|
plugins_conf = {'plugins': {}}
|
||||||
|
with open(PLUGINS_CONF_FILE, 'w') as f:
|
||||||
|
json.dump(plugins_conf, f, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
def loadPluginsConf():
|
||||||
|
"""Load plugins.json JSON configuration.
|
||||||
|
|
||||||
|
:return: JSON Plugins configuration
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
with open(PLUGINS_CONF_FILE) as settings:
|
||||||
|
plugins_conf = json.load(settings)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Parsing error in " + PLUGINS_CONF_FILE + ": " + str(err))
|
||||||
|
plugins_conf = None
|
||||||
|
|
||||||
|
return plugins_conf
|
||||||
|
|
||||||
|
|
||||||
|
def getEnabledPlugins():
|
||||||
|
"""This function gets the list of all asynchronous plugins.
|
||||||
|
|
||||||
|
We considered only those plugins with 'callable' flag set to False
|
||||||
|
and 'onboot' flag set to True.
|
||||||
|
|
||||||
|
:return: enabledPlugins List
|
||||||
|
|
||||||
|
"""
|
||||||
|
enabledPlugins = []
|
||||||
|
plugins_conf = loadPluginsConf()
|
||||||
|
|
||||||
|
for plugin in plugins_conf['plugins']:
|
||||||
|
|
||||||
|
if plugins_conf['plugins'][plugin]['callable'] is False:
|
||||||
|
|
||||||
|
if plugins_conf['plugins'][plugin]['onboot'] is True:
|
||||||
|
|
||||||
|
if plugins_conf['plugins'][plugin]['status'] == "operative":
|
||||||
|
enabledPlugins.append(plugin)
|
||||||
|
|
||||||
|
if len(enabledPlugins) != 0:
|
||||||
|
LOG.info(" - Enabled plugins list: " + str(enabledPlugins))
|
||||||
|
|
||||||
|
return enabledPlugins
|
||||||
|
|
||||||
|
|
||||||
|
def makeNothing():
|
||||||
|
"""Sandbox function.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def RebootOnBootPlugins():
|
||||||
|
"""Reboot at boot each enabled asynchronous plugin
|
||||||
|
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
LOG.info("Rebooting enabled plugins:")
|
||||||
|
|
||||||
|
enabledPlugins = getEnabledPlugins()
|
||||||
|
|
||||||
|
if enabledPlugins.__len__() == 0:
|
||||||
|
|
||||||
|
message = "No plugin to reboot!"
|
||||||
|
LOG.info(" - " + message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
for plugin_uuid in enabledPlugins:
|
||||||
|
|
||||||
|
plugins_conf = loadPluginsConf()
|
||||||
|
plugin_name = plugins_conf['plugins'][plugin_uuid]['name']
|
||||||
|
# plugin_status = plugins_conf['plugins'][plugin_uuid]['status']
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if (plugin_uuid in PLUGINS_THRS) and (
|
||||||
|
PLUGINS_THRS[plugin_uuid].isAlive()
|
||||||
|
):
|
||||||
|
|
||||||
|
LOG.warning(" - Plugin "
|
||||||
|
+ plugin_uuid + " already started!")
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
LOG.info(" - Rebooting plugin " + plugin_uuid)
|
||||||
|
|
||||||
|
plugin_home = iotronic_home + "/plugins/" + plugin_uuid
|
||||||
|
plugin_filename = plugin_home + "/" + plugin_uuid + ".py"
|
||||||
|
plugin_params_file = \
|
||||||
|
plugin_home + "/" + plugin_uuid + ".json"
|
||||||
|
|
||||||
|
if os.path.exists(plugin_filename):
|
||||||
|
|
||||||
|
task = imp.load_source("plugin", plugin_filename)
|
||||||
|
|
||||||
|
if os.path.exists(plugin_params_file):
|
||||||
|
|
||||||
|
with open(plugin_params_file) as conf:
|
||||||
|
plugin_params = json.load(conf)
|
||||||
|
|
||||||
|
worker = task.Worker(
|
||||||
|
plugin_uuid,
|
||||||
|
plugin_name,
|
||||||
|
q_result=None,
|
||||||
|
params=plugin_params
|
||||||
|
)
|
||||||
|
|
||||||
|
PLUGINS_THRS[plugin_uuid] = worker
|
||||||
|
LOG.info(" - Starting plugin " + str(worker))
|
||||||
|
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = "ERROR " \
|
||||||
|
+ plugin_params_file + " does not exist!"
|
||||||
|
|
||||||
|
LOG.error(" - "
|
||||||
|
+ worker.complete(rpc_name, message))
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = "ERROR " \
|
||||||
|
+ plugin_filename + " does not exist!"
|
||||||
|
|
||||||
|
LOG.error(" - " + worker.complete(rpc_name, message))
|
||||||
|
|
||||||
|
message = "rebooted!"
|
||||||
|
|
||||||
|
LOG.info(" - " + worker.complete(rpc_name, message))
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Error rebooting plugin " \
|
||||||
|
+ plugin_uuid + ": " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginManager(Module.Module):
|
||||||
|
|
||||||
|
"""Plugin module to manage board plugins.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, board, session):
|
||||||
|
"""Init function for PluginManager module.
|
||||||
|
|
||||||
|
:param board:
|
||||||
|
:param session:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Module declaration
|
||||||
|
super(PluginManager, self).__init__("PluginManager", board)
|
||||||
|
|
||||||
|
# Creation of plugins.json configuration file
|
||||||
|
createPluginsConf()
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
"""Function called at the end of module loading.
|
||||||
|
|
||||||
|
This function in this module reloads
|
||||||
|
the enabled (asynchronous) plugins at boot.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Reboot boot enabled plugins
|
||||||
|
RebootOnBootPlugins()
|
||||||
|
|
||||||
|
def PluginInject(self, plugin, onboot):
|
||||||
|
"""Plugin injection procedure into the board:
|
||||||
|
|
||||||
|
1. get Plugin files
|
||||||
|
2. deserialize files
|
||||||
|
3. store files
|
||||||
|
|
||||||
|
:param plugin:
|
||||||
|
:param onboot:
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
plugin_uuid = plugin['uuid']
|
||||||
|
plugin_name = plugin['name']
|
||||||
|
code = plugin['code']
|
||||||
|
callable = plugin['callable']
|
||||||
|
|
||||||
|
LOG.info("RPC " + rpc_name + " for plugin '"
|
||||||
|
+ plugin_name + "' (" + plugin_uuid + ")")
|
||||||
|
|
||||||
|
# Deserialize the plugin code received
|
||||||
|
ser = PluginSerializer.ObjectSerializer()
|
||||||
|
loaded = ser.deserialize_entity(code)
|
||||||
|
# LOG.debug("- plugin loaded code:\n" + loaded)
|
||||||
|
|
||||||
|
plugin_path = iotronic_home + "/plugins/" + plugin_uuid + "/"
|
||||||
|
plugin_filename = plugin_path + plugin_uuid + ".py"
|
||||||
|
|
||||||
|
# Plugin folder creation if does not exist
|
||||||
|
if not os.path.exists(plugin_path):
|
||||||
|
os.makedirs(plugin_path)
|
||||||
|
|
||||||
|
# Plugin code file creation
|
||||||
|
with open(plugin_filename, "w") as pluginfile:
|
||||||
|
pluginfile.write(loaded)
|
||||||
|
|
||||||
|
# Load plugins.json configuration file
|
||||||
|
plugins_conf = loadPluginsConf()
|
||||||
|
|
||||||
|
# LOG.debug("Plugin setup:\n"
|
||||||
|
# + json.dumps(plugin, indent=4, sort_keys=True))
|
||||||
|
|
||||||
|
# Save plugin settings in plugins.json
|
||||||
|
if plugin_uuid not in plugins_conf['plugins']:
|
||||||
|
|
||||||
|
# It is a new plugin
|
||||||
|
plugins_conf['plugins'][plugin_uuid] = {}
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['name'] = plugin_name
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['onboot'] = onboot
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['callable'] = callable
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['injected_at'] = \
|
||||||
|
datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['updated_at'] = ""
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['status'] = "injected"
|
||||||
|
|
||||||
|
LOG.info("Plugin " + plugin_name + " created!")
|
||||||
|
message = rpc_name + " result: INJECTED"
|
||||||
|
|
||||||
|
else:
|
||||||
|
# The plugin was already injected and we are updating it
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['name'] = plugin_name
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['onboot'] = onboot
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['callable'] = callable
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['updated_at'] = \
|
||||||
|
datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['status'] = "updated"
|
||||||
|
|
||||||
|
LOG.info("Plugin " + plugin_name
|
||||||
|
+ " (" + str(plugin_uuid) + ") updated!")
|
||||||
|
message = rpc_name + " result: UPDATED"
|
||||||
|
|
||||||
|
LOG.info("Plugin setup:\n" + json.dumps(
|
||||||
|
plugins_conf['plugins'][plugin_uuid],
|
||||||
|
indent=4,
|
||||||
|
sort_keys=True
|
||||||
|
))
|
||||||
|
|
||||||
|
# Apply the changes to plugins.json
|
||||||
|
with open(PLUGINS_CONF_FILE, 'w') as f:
|
||||||
|
json.dump(plugins_conf, f, indent=4)
|
||||||
|
|
||||||
|
LOG.info(" - " + message)
|
||||||
|
w_msg = yield WM.WampSuccess(message)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Plugin injection error: " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
def PluginStart(self, plugin_uuid, parameters=None):
|
||||||
|
"""To start an asynchronous plugin;
|
||||||
|
|
||||||
|
the plugin will run until the PluginStop is called.
|
||||||
|
|
||||||
|
:param plugin_uuid:
|
||||||
|
:param parameters:
|
||||||
|
:return: return a response to RPC request
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
LOG.info("RPC " + rpc_name + " called for '"
|
||||||
|
+ plugin_uuid + "' plugin:")
|
||||||
|
|
||||||
|
plugins_conf = loadPluginsConf()
|
||||||
|
|
||||||
|
if plugin_uuid in plugins_conf['plugins']:
|
||||||
|
|
||||||
|
plugin_name = plugins_conf['plugins'][plugin_uuid]['name']
|
||||||
|
|
||||||
|
# Check if the plugin is already running
|
||||||
|
if (plugin_uuid in PLUGINS_THRS) and (
|
||||||
|
PLUGINS_THRS[plugin_uuid].isAlive()
|
||||||
|
):
|
||||||
|
|
||||||
|
message = "ALREADY STARTED!"
|
||||||
|
LOG.warning(" - Plugin "
|
||||||
|
+ plugin_uuid + " already started!")
|
||||||
|
w_msg = yield WM.WampError(message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
plugin_home = \
|
||||||
|
iotronic_home + "/plugins/" + plugin_uuid
|
||||||
|
plugin_filename = \
|
||||||
|
plugin_home + "/" + plugin_uuid + ".py"
|
||||||
|
plugin_params_file = \
|
||||||
|
plugin_home + "/" + plugin_uuid + ".json"
|
||||||
|
|
||||||
|
# Import plugin (as python module)
|
||||||
|
if os.path.exists(plugin_filename):
|
||||||
|
|
||||||
|
task = imp.load_source("plugin", plugin_filename)
|
||||||
|
|
||||||
|
LOG.info(" - Plugin '" + plugin_uuid + "' imported!")
|
||||||
|
|
||||||
|
# Store input parameters of the plugin
|
||||||
|
if parameters is not None:
|
||||||
|
|
||||||
|
with open(plugin_params_file, 'w') as f:
|
||||||
|
json.dump(parameters, f, indent=4)
|
||||||
|
|
||||||
|
with open(plugin_params_file) as conf:
|
||||||
|
plugin_params = json.load(conf)
|
||||||
|
|
||||||
|
LOG.info(" - plugin with parameters:")
|
||||||
|
LOG.info(" " + str(plugin_params))
|
||||||
|
|
||||||
|
else:
|
||||||
|
plugin_params = None
|
||||||
|
|
||||||
|
worker = task.Worker(
|
||||||
|
plugin_uuid,
|
||||||
|
plugin_name,
|
||||||
|
params=plugin_params
|
||||||
|
)
|
||||||
|
|
||||||
|
PLUGINS_THRS[plugin_uuid] = worker
|
||||||
|
LOG.debug(" - Starting plugin " + str(worker))
|
||||||
|
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
# Apply the changes to plugins.json
|
||||||
|
with open(PLUGINS_CONF_FILE, 'w') as f:
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['status'] = \
|
||||||
|
'operative'
|
||||||
|
json.dump(plugins_conf, f, indent=4)
|
||||||
|
|
||||||
|
response = "STARTED"
|
||||||
|
LOG.info(" - " + worker.complete(rpc_name, response))
|
||||||
|
w_msg = yield WM.WampSuccess(response)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = \
|
||||||
|
rpc_name + " - ERROR " \
|
||||||
|
+ plugin_filename + " does not exist!"
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = "Plugin " + plugin_uuid \
|
||||||
|
+ " does not exist in this board!"
|
||||||
|
LOG.warning(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(message)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = \
|
||||||
|
rpc_name + " - ERROR - plugin (" + plugin_uuid + ") - " \
|
||||||
|
+ str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
def PluginStop(self, plugin_uuid, parameters=None):
|
||||||
|
"""To stop an asynchronous plugin
|
||||||
|
|
||||||
|
:param plugin_uuid: ID of plufin to stop
|
||||||
|
:param parameters: JSON OPTIONAL stop parameters; 'delay' in seconds
|
||||||
|
:return: return a response to RPC request
|
||||||
|
|
||||||
|
"""
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
LOG.info("RPC " + rpc_name + " CALLED for '"
|
||||||
|
+ plugin_uuid + "' plugin:")
|
||||||
|
|
||||||
|
if parameters is not None:
|
||||||
|
LOG.info(" - " + rpc_name + " parameters: " + str(parameters))
|
||||||
|
if 'delay' in parameters:
|
||||||
|
delay = parameters['delay']
|
||||||
|
LOG.info(" --> stop delay: " + str(delay))
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if plugin_uuid in PLUGINS_THRS:
|
||||||
|
|
||||||
|
worker = PLUGINS_THRS[plugin_uuid]
|
||||||
|
LOG.debug(" - Stopping plugin " + str(worker))
|
||||||
|
|
||||||
|
if worker.isAlive():
|
||||||
|
|
||||||
|
if 'delay' in parameters:
|
||||||
|
time.sleep(delay)
|
||||||
|
|
||||||
|
yield worker.stop()
|
||||||
|
|
||||||
|
del PLUGINS_THRS[plugin_uuid]
|
||||||
|
|
||||||
|
message = "STOPPED"
|
||||||
|
LOG.info(" - " + worker.complete(rpc_name, message))
|
||||||
|
w_msg = yield WM.WampSuccess(message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = \
|
||||||
|
rpc_name \
|
||||||
|
+ " - ERROR - plugin (" + plugin_uuid \
|
||||||
|
+ ") is instantiated but is not running anymore!"
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = \
|
||||||
|
rpc_name + " - WARNING " \
|
||||||
|
+ plugin_uuid + " is not running!"
|
||||||
|
LOG.warning(" - " + message)
|
||||||
|
w_msg = yield WM.WampWarning(message)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = \
|
||||||
|
rpc_name \
|
||||||
|
+ " - ERROR - plugin (" + plugin_uuid + ") - " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
def PluginCall(self, plugin_uuid, parameters=None):
|
||||||
|
"""To execute a synchronous plugin into the board
|
||||||
|
|
||||||
|
:param plugin_uuid:
|
||||||
|
:param parameters:
|
||||||
|
:return: return a response to RPC request
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
LOG.info("RPC " + rpc_name + " CALLED for " + plugin_uuid + " plugin:")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if (plugin_uuid in PLUGINS_THRS) and (
|
||||||
|
PLUGINS_THRS[plugin_uuid].isAlive()
|
||||||
|
):
|
||||||
|
|
||||||
|
message = "Plugin " + plugin_uuid + " already started!"
|
||||||
|
LOG.warning(" - " + message)
|
||||||
|
w_msg = yield WM.WampWarning(message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
plugin_home = iotronic_home + "/plugins/" + plugin_uuid
|
||||||
|
plugin_filename = plugin_home + "/" + plugin_uuid + ".py"
|
||||||
|
plugin_params_file = plugin_home + "/" + plugin_uuid + ".json"
|
||||||
|
|
||||||
|
plugins_conf = loadPluginsConf()
|
||||||
|
plugin_name = plugins_conf['plugins'][plugin_uuid]['name']
|
||||||
|
|
||||||
|
# Import plugin (as python module)
|
||||||
|
if os.path.exists(plugin_filename):
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
task = imp.load_source("plugin", plugin_filename)
|
||||||
|
|
||||||
|
LOG.info(" - Plugin " + plugin_uuid + " imported!")
|
||||||
|
|
||||||
|
q_result = Queue()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Error importing plugin " \
|
||||||
|
+ plugin_filename + ": " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# Store input parameters of the plugin
|
||||||
|
if parameters is not None:
|
||||||
|
with open(plugin_params_file, 'w') as f:
|
||||||
|
json.dump(parameters, f, indent=4)
|
||||||
|
|
||||||
|
with open(plugin_params_file) as conf:
|
||||||
|
plugin_params = json.load(conf)
|
||||||
|
|
||||||
|
LOG.info(" - Plugin configuration:\n"
|
||||||
|
+ str(plugin_params))
|
||||||
|
|
||||||
|
else:
|
||||||
|
plugin_params = None
|
||||||
|
|
||||||
|
worker = task.Worker(
|
||||||
|
plugin_uuid,
|
||||||
|
plugin_name,
|
||||||
|
q_result=q_result,
|
||||||
|
params=plugin_params
|
||||||
|
)
|
||||||
|
|
||||||
|
PLUGINS_THRS[plugin_uuid] = worker
|
||||||
|
LOG.debug(" - Executing plugin " + str(worker))
|
||||||
|
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
while q_result.empty():
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = q_result.get()
|
||||||
|
|
||||||
|
LOG.info(" - " + worker.complete(rpc_name, response))
|
||||||
|
w_msg = yield WM.WampSuccess(response)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Error spawning plugin " \
|
||||||
|
+ plugin_filename + ": " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = \
|
||||||
|
rpc_name \
|
||||||
|
+ " - ERROR " + plugin_filename + " does not exist!"
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(message)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = \
|
||||||
|
rpc_name \
|
||||||
|
+ " - ERROR - plugin (" + plugin_uuid + ") - " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
def PluginRemove(self, plugin_uuid):
|
||||||
|
"""To remove a plugin from the board
|
||||||
|
|
||||||
|
:param plugin_uuid:
|
||||||
|
:return: return a response to RPC request
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
|
||||||
|
LOG.info("RPC " + rpc_name + " for plugin " + plugin_uuid)
|
||||||
|
|
||||||
|
plugin_path = iotronic_home + "/plugins/" + plugin_uuid + "/"
|
||||||
|
|
||||||
|
if os.path.exists(plugin_path) is False \
|
||||||
|
or os.path.exists(PLUGINS_CONF_FILE) is False:
|
||||||
|
|
||||||
|
message = "Plugin paths or files do not exist!"
|
||||||
|
LOG.error(message)
|
||||||
|
w_msg = yield WM.WampError(message)
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
LOG.info(" - Removing plugin...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
shutil.rmtree(
|
||||||
|
plugin_path,
|
||||||
|
ignore_errors=False,
|
||||||
|
onerror=None
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Removing plugin's files error in " \
|
||||||
|
+ plugin_path + ": " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
# Remove from plugins.json file its configuration
|
||||||
|
try:
|
||||||
|
|
||||||
|
plugins_conf = loadPluginsConf()
|
||||||
|
|
||||||
|
if plugin_uuid in plugins_conf['plugins']:
|
||||||
|
|
||||||
|
plugin_name = \
|
||||||
|
plugins_conf['plugins'][plugin_uuid]['name']
|
||||||
|
|
||||||
|
del plugins_conf['plugins'][plugin_uuid]
|
||||||
|
|
||||||
|
with open(PLUGINS_CONF_FILE, 'w') as f:
|
||||||
|
json.dump(plugins_conf, f, indent=4)
|
||||||
|
|
||||||
|
if plugin_uuid in PLUGINS_THRS:
|
||||||
|
worker = PLUGINS_THRS[plugin_uuid]
|
||||||
|
if worker.isAlive():
|
||||||
|
LOG.info(" - Plugin "
|
||||||
|
+ plugin_name + " is running...")
|
||||||
|
worker.stop()
|
||||||
|
LOG.info(" ...stopped!")
|
||||||
|
|
||||||
|
del PLUGINS_THRS[plugin_uuid]
|
||||||
|
|
||||||
|
message = "PluginRemove result: " \
|
||||||
|
+ plugin_uuid + " removed!"
|
||||||
|
LOG.info(" - " + message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = "PluginRemove result: " \
|
||||||
|
+ plugin_uuid + " already removed!"
|
||||||
|
LOG.warning(" - " + message)
|
||||||
|
|
||||||
|
w_msg = yield WM.WampSuccess(message)
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Updating plugins.json error: " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Plugin removing error: {0}".format(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
def PluginReboot(self, plugin_uuid):
|
||||||
|
"""To reboot an asynchronous plugin (callable = false) into the board.
|
||||||
|
|
||||||
|
:return: return a response to RPC request
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
|
||||||
|
LOG.info("RPC " + rpc_name + " CALLED for '"
|
||||||
|
+ plugin_uuid + "' plugin:")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
plugin_home = iotronic_home + "/plugins/" + plugin_uuid
|
||||||
|
plugin_filename = plugin_home + "/" + plugin_uuid + ".py"
|
||||||
|
plugin_params_file = plugin_home + "/" + plugin_uuid + ".json"
|
||||||
|
|
||||||
|
plugins_conf = loadPluginsConf()
|
||||||
|
plugin_name = plugins_conf['plugins'][plugin_uuid]['name']
|
||||||
|
callable = plugins_conf['plugins'][plugin_uuid]['callable']
|
||||||
|
|
||||||
|
if callable is False:
|
||||||
|
|
||||||
|
if plugin_uuid in PLUGINS_THRS:
|
||||||
|
|
||||||
|
worker = PLUGINS_THRS[plugin_uuid]
|
||||||
|
|
||||||
|
if worker.isAlive():
|
||||||
|
# STOP PLUGIN------------------------------------------
|
||||||
|
LOG.info(" - Thread "
|
||||||
|
+ plugin_uuid + " is running, stopping...")
|
||||||
|
LOG.debug(" - Stopping plugin " + str(worker))
|
||||||
|
worker.stop()
|
||||||
|
|
||||||
|
# Remove from plugin thread list
|
||||||
|
del PLUGINS_THRS[plugin_uuid]
|
||||||
|
|
||||||
|
# START PLUGIN-------------------------------------------------
|
||||||
|
if os.path.exists(plugin_filename):
|
||||||
|
|
||||||
|
# Import plugin python module
|
||||||
|
task = imp.load_source("plugin", plugin_filename)
|
||||||
|
|
||||||
|
if os.path.exists(plugin_params_file):
|
||||||
|
|
||||||
|
with open(plugin_params_file) as conf:
|
||||||
|
plugin_params = json.load(conf)
|
||||||
|
|
||||||
|
else:
|
||||||
|
plugin_params = None
|
||||||
|
|
||||||
|
worker = task.Worker(
|
||||||
|
plugin_uuid,
|
||||||
|
plugin_name,
|
||||||
|
params=plugin_params
|
||||||
|
)
|
||||||
|
|
||||||
|
PLUGINS_THRS[plugin_uuid] = worker
|
||||||
|
LOG.info(" - Starting plugin " + str(worker))
|
||||||
|
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
message = "REBOOTED"
|
||||||
|
LOG.info(" - " + worker.complete(rpc_name, message))
|
||||||
|
w_msg = yield WM.WampSuccess(message)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message = "ERROR '" + plugin_filename + "' does not exist!"
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(message)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = "Error rebooting plugin '" \
|
||||||
|
+ plugin_uuid + "': " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
def PluginStatus(self, plugin_uuid):
|
||||||
|
"""Check status thread plugin
|
||||||
|
|
||||||
|
:param plugin_uuid:
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_name = getFuncName()
|
||||||
|
LOG.info("RPC " + rpc_name + " CALLED for '"
|
||||||
|
+ plugin_uuid + "' plugin:")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
if plugin_uuid in PLUGINS_THRS:
|
||||||
|
|
||||||
|
worker = PLUGINS_THRS[plugin_uuid]
|
||||||
|
|
||||||
|
if worker.isAlive():
|
||||||
|
result = "ALIVE"
|
||||||
|
else:
|
||||||
|
result = "DEAD"
|
||||||
|
|
||||||
|
LOG.info(" - " + worker.complete(rpc_name, result))
|
||||||
|
w_msg = yield WM.WampSuccess(result)
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = "DEAD"
|
||||||
|
LOG.info(" - " + rpc_name + " result for "
|
||||||
|
+ plugin_uuid + ": " + result)
|
||||||
|
w_msg = yield WM.WampSuccess(result)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
message = \
|
||||||
|
rpc_name \
|
||||||
|
+ " - ERROR - plugin (" + plugin_uuid + ") - " + str(err)
|
||||||
|
LOG.error(" - " + message)
|
||||||
|
w_msg = yield WM.WampError(str(err))
|
||||||
|
returnValue(w_msg.serialize())
|
||||||
|
|
||||||
|
returnValue(w_msg.serialize())
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from autobahn.twisted.util import sleep
|
||||||
|
from iotronic_lightningrod.modules import Module
|
||||||
|
from twisted.internet.defer import returnValue
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Test(Module.Module):
|
||||||
|
|
||||||
|
def __init__(self, board):
|
||||||
|
|
||||||
|
super(Test, self).__init__("Test", board)
|
||||||
|
|
||||||
|
def test_function(self):
|
||||||
|
import random
|
||||||
|
s = random.uniform(0.5, 1.5)
|
||||||
|
yield sleep(s)
|
||||||
|
result = "DEVICE test result: TEST!"
|
||||||
|
LOG.info(result)
|
||||||
|
returnValue(result)
|
||||||
|
|
||||||
|
def add(self, x, y):
|
||||||
|
c = yield x + y
|
||||||
|
LOG.info("DEVICE add result: " + str(c))
|
||||||
|
returnValue(c)
|
|
@ -0,0 +1,119 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from autobahn.twisted.util import sleep
|
||||||
|
from iotronic_lightningrod.config import entry_points_name
|
||||||
|
from iotronic_lightningrod.modules import Module
|
||||||
|
import pkg_resources
|
||||||
|
from six import moves
|
||||||
|
from stevedore import extension
|
||||||
|
import sys
|
||||||
|
from twisted.internet.defer import returnValue
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from iotronic_lightningrod.lightningrod import SESSION
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_stevedore(namespace=None):
|
||||||
|
"""Trigger reload of entry points.
|
||||||
|
|
||||||
|
Useful to have dynamic loading/unloading of stevedore modules.
|
||||||
|
"""
|
||||||
|
# NOTE(sheeprine): pkg_resources doesn't support reload on python3 due to
|
||||||
|
# defining basestring which is still there on reload hence executing
|
||||||
|
# python2 related code.
|
||||||
|
try:
|
||||||
|
del sys.modules['pkg_resources'].basestring
|
||||||
|
except AttributeError:
|
||||||
|
# python2, do nothing
|
||||||
|
pass
|
||||||
|
# Force working_set reload
|
||||||
|
moves.reload_module(sys.modules['pkg_resources'])
|
||||||
|
# Clear stevedore cache
|
||||||
|
cache = extension.ExtensionManager.ENTRY_POINT_CACHE
|
||||||
|
if namespace:
|
||||||
|
if namespace in cache:
|
||||||
|
del cache[namespace]
|
||||||
|
else:
|
||||||
|
cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class Utility(Module.Module):
|
||||||
|
|
||||||
|
def __init__(self, board, session):
|
||||||
|
super(Utility, self).__init__("Utility", board)
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def hello(self, client_name, message):
|
||||||
|
import random
|
||||||
|
s = random.uniform(0.5, 3.0)
|
||||||
|
yield sleep(s)
|
||||||
|
result = "Hello by board to Conductor " + client_name + \
|
||||||
|
" that said me " + message + " - Time: " + '%.2f' % s
|
||||||
|
LOG.info("DEVICE hello result: " + str(result))
|
||||||
|
|
||||||
|
returnValue(result)
|
||||||
|
|
||||||
|
def plug_and_play(self, new_module, new_class):
|
||||||
|
LOG.info("LR modules loaded:\n\t" + new_module)
|
||||||
|
|
||||||
|
# Updating entry_points
|
||||||
|
with open(entry_points_name, 'a') as entry_points:
|
||||||
|
entry_points.write(
|
||||||
|
new_module +
|
||||||
|
'= iotronic_lightningrod.modules.' + new_module + ':'
|
||||||
|
+ new_class
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reload entry_points
|
||||||
|
refresh_stevedore('s4t.modules')
|
||||||
|
LOG.info("New entry_points loaded!")
|
||||||
|
|
||||||
|
# Reading updated entry_points
|
||||||
|
named_objects = {}
|
||||||
|
for ep in pkg_resources.iter_entry_points(group='s4t.modules'):
|
||||||
|
named_objects.update({ep.name: ep.load()})
|
||||||
|
|
||||||
|
yield named_objects
|
||||||
|
|
||||||
|
SESSION.disconnect()
|
||||||
|
|
||||||
|
returnValue(str(named_objects))
|
||||||
|
|
||||||
|
def changeConf(self, conf):
|
||||||
|
|
||||||
|
yield self.board.getConf(conf)
|
||||||
|
|
||||||
|
self.board.setUpdateTime()
|
||||||
|
|
||||||
|
result = "Board configuration changed!"
|
||||||
|
LOG.info("PROVISIONING RESULT: " + str(result))
|
||||||
|
|
||||||
|
returnValue(result)
|
||||||
|
|
||||||
|
def destroyNode(self, conf):
|
||||||
|
|
||||||
|
yield self.board.setConf(conf)
|
||||||
|
|
||||||
|
result = "Board configuration cleaned!"
|
||||||
|
LOG.info("DESTROY RESULT: " + str(result))
|
||||||
|
|
||||||
|
returnValue(result)
|
|
@ -0,0 +1,162 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
|
from fuse import FuseOSError
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
# Logging conf
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FuseLib(object):
|
||||||
|
def __init__(self, mountSource):
|
||||||
|
self.mountSource = mountSource
|
||||||
|
|
||||||
|
def _full_path(self, partial):
|
||||||
|
if partial.startswith("/"):
|
||||||
|
partial = partial[1:]
|
||||||
|
path = os.path.join(self.mountSource, partial)
|
||||||
|
print(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Filesystem methods
|
||||||
|
# ==================
|
||||||
|
|
||||||
|
def access(self, path, mode):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
if not os.access(full_path, mode):
|
||||||
|
raise FuseOSError(errno.EACCES)
|
||||||
|
|
||||||
|
def chmod(self, path, mode):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
return os.chmod(full_path, mode)
|
||||||
|
|
||||||
|
def chown(self, path, uid, gid):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
return os.chown(full_path, uid, gid)
|
||||||
|
|
||||||
|
def getattr(self, path, fh=None):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
st = os.lstat(full_path)
|
||||||
|
attr = dict((key, getattr(st, key))
|
||||||
|
for key in (
|
||||||
|
'st_atime',
|
||||||
|
'st_ctime',
|
||||||
|
'st_gid',
|
||||||
|
'st_mode',
|
||||||
|
'st_mtime',
|
||||||
|
'st_nlink',
|
||||||
|
'st_size',
|
||||||
|
'st_uid'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return attr
|
||||||
|
|
||||||
|
def readdir(self, path, fh):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
|
||||||
|
dirents = ['.', '..']
|
||||||
|
if os.path.isdir(full_path):
|
||||||
|
dirents.extend(os.listdir(full_path))
|
||||||
|
for r in dirents:
|
||||||
|
yield r
|
||||||
|
|
||||||
|
def readlink(self, path):
|
||||||
|
pathname = os.readlink(self._full_path(path))
|
||||||
|
if pathname.startswith("/"):
|
||||||
|
# Path name is absolute, sanitize it.
|
||||||
|
return os.path.relpath(pathname, self.mountSource)
|
||||||
|
else:
|
||||||
|
return pathname
|
||||||
|
|
||||||
|
def mknod(self, path, mode, dev):
|
||||||
|
return os.mknod(self._full_path(path), mode, dev)
|
||||||
|
|
||||||
|
def rmdir(self, path):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
return os.rmdir(full_path)
|
||||||
|
|
||||||
|
def mkdir(self, path, mode):
|
||||||
|
return os.mkdir(self._full_path(path), mode)
|
||||||
|
|
||||||
|
def statfs(self, path):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
stv = os.statvfs(full_path)
|
||||||
|
stat = dict((key, getattr(stv, key))
|
||||||
|
for key in ('f_bavail',
|
||||||
|
'f_bfree',
|
||||||
|
'f_blocks',
|
||||||
|
'f_bsize',
|
||||||
|
'f_favail',
|
||||||
|
'f_ffree',
|
||||||
|
'f_files',
|
||||||
|
'f_flag',
|
||||||
|
'f_frsize',
|
||||||
|
'f_namemax'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return stat
|
||||||
|
|
||||||
|
def unlink(self, path):
|
||||||
|
return os.unlink(self._full_path(path))
|
||||||
|
|
||||||
|
def symlink(self, name, target):
|
||||||
|
return os.symlink(name, self._full_path(target))
|
||||||
|
|
||||||
|
def rename(self, old, new):
|
||||||
|
return os.rename(self._full_path(old), self._full_path(new))
|
||||||
|
|
||||||
|
def link(self, target, name):
|
||||||
|
return os.link(self._full_path(target), self._full_path(name))
|
||||||
|
|
||||||
|
def utimens(self, path, times=None):
|
||||||
|
return os.utime(self._full_path(path), times)
|
||||||
|
|
||||||
|
# File methods
|
||||||
|
# ============
|
||||||
|
|
||||||
|
def open(self, path, flags):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
return os.open(full_path, flags)
|
||||||
|
|
||||||
|
def create(self, path, mode, fi=None):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode)
|
||||||
|
|
||||||
|
def read(self, path, length, offset, fh):
|
||||||
|
os.lseek(fh, offset, os.SEEK_SET)
|
||||||
|
return os.read(fh, length)
|
||||||
|
|
||||||
|
def write(self, path, buf, offset, fh):
|
||||||
|
os.lseek(fh, offset, os.SEEK_SET)
|
||||||
|
return os.write(fh, buf)
|
||||||
|
|
||||||
|
def truncate(self, path, length, fh=None):
|
||||||
|
full_path = self._full_path(path)
|
||||||
|
with open(full_path, 'r+') as f:
|
||||||
|
f.truncate(length)
|
||||||
|
|
||||||
|
def flush(self, path, fh):
|
||||||
|
return os.fsync(fh)
|
||||||
|
|
||||||
|
def release(self, path, fh):
|
||||||
|
return os.close(fh)
|
||||||
|
|
||||||
|
def fsync(self, path, fdatasync, fh):
|
||||||
|
return self.flush(path, fh)
|
|
@ -0,0 +1,508 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
from subprocess import call
|
||||||
|
import threading
|
||||||
|
from twisted.internet.defer import inlineCallbacks
|
||||||
|
from twisted.internet.defer import returnValue
|
||||||
|
|
||||||
|
# Iotronic imports
|
||||||
|
from iotronic_lightningrod.modules import Module
|
||||||
|
|
||||||
|
# Fuse imports
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
from fuse import FUSE
|
||||||
|
from fuse import FuseOSError
|
||||||
|
from fuse import Operations
|
||||||
|
|
||||||
|
# Logging conf
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VfsManager(Module.Module):
|
||||||
|
|
||||||
|
def __init__(self, board, session):
|
||||||
|
super(VfsManager, self).__init__("VFS", board)
|
||||||
|
|
||||||
|
self.session = session
|
||||||
|
self.board = board
|
||||||
|
|
||||||
|
"""
|
||||||
|
#print session
|
||||||
|
from iotronic_lightningrod.modules import vfs_library
|
||||||
|
fuse=vfs_library.FuseLib("/opt/AAA")
|
||||||
|
print fuse.getattr("/aaa.txt")
|
||||||
|
"""
|
||||||
|
|
||||||
|
libcPath = ctypes.util.find_library("c")
|
||||||
|
self.libc = ctypes.CDLL(libcPath)
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mountLocal(self, mountSource, mountPoint):
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
mounter = MounterLocal(mountSource, mountPoint)
|
||||||
|
mounter.start()
|
||||||
|
|
||||||
|
result = "Mounted " + mountSource + " in " + mountPoint
|
||||||
|
|
||||||
|
except Exception as msg:
|
||||||
|
result = "Mounting error:", msg
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
yield returnValue(result)
|
||||||
|
|
||||||
|
def unmountLocal(self, mountPoint):
|
||||||
|
|
||||||
|
print("Unmounting...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# errorCode = self.libc.umount(mountPoint, None)
|
||||||
|
errorCode = call(["umount", "-l", mountPoint])
|
||||||
|
|
||||||
|
result = "Unmount " + mountPoint + " result: " + str(errorCode)
|
||||||
|
|
||||||
|
except Exception as msg:
|
||||||
|
result = "Unmounting error:", msg
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
yield returnValue(result)
|
||||||
|
|
||||||
|
def mountRemote(self,
|
||||||
|
mountSource,
|
||||||
|
mountPoint,
|
||||||
|
boardRemote=None,
|
||||||
|
agentRemote=None
|
||||||
|
):
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
mounter = MounterRemote(
|
||||||
|
mountSource,
|
||||||
|
mountPoint,
|
||||||
|
self.board,
|
||||||
|
self.session,
|
||||||
|
boardRemote,
|
||||||
|
agentRemote
|
||||||
|
)
|
||||||
|
|
||||||
|
mounter.start()
|
||||||
|
|
||||||
|
result = "Mounted " + mountSource + " in " + mountPoint
|
||||||
|
|
||||||
|
except Exception as msg:
|
||||||
|
result = "Mounting error:", msg
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
yield returnValue(result)
|
||||||
|
|
||||||
|
def unmountRemote(self, mountPoint):
|
||||||
|
|
||||||
|
print("Unmounting...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
# errorCode = self.libc.umount(mountPoint, None)
|
||||||
|
errorCode = call(["umount", "-l", mountPoint])
|
||||||
|
|
||||||
|
result = "Unmount " + mountPoint + " result: " + str(errorCode)
|
||||||
|
|
||||||
|
except Exception as msg:
|
||||||
|
result = "Unmounting error:", msg
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
yield returnValue(result)
|
||||||
|
|
||||||
|
|
||||||
|
class MounterLocal(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, mountSource, mountPoint):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
# self.setDaemon(1)
|
||||||
|
self.setName("VFS-Mounter") # Set thread name
|
||||||
|
|
||||||
|
self.mountSource = mountSource
|
||||||
|
self.mountPoint = mountPoint
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Mount FUSE FS
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
FUSE(
|
||||||
|
FuseManager(self.mountSource),
|
||||||
|
self.mountPoint,
|
||||||
|
nothreads=False,
|
||||||
|
foreground=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as msg:
|
||||||
|
LOG.error("Mounting FUSE error: " + str(msg))
|
||||||
|
|
||||||
|
|
||||||
|
class MounterRemote(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
mountSource,
|
||||||
|
mountPoint,
|
||||||
|
board,
|
||||||
|
session,
|
||||||
|
boardRemote,
|
||||||
|
agentRemote
|
||||||
|
):
|
||||||
|
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
# self.setDaemon(1)
|
||||||
|
self.setName("VFS-Mounter") # Set thread name
|
||||||
|
|
||||||
|
self.mountSource = mountSource
|
||||||
|
self.mountPoint = mountPoint
|
||||||
|
self.session = session
|
||||||
|
self.board = board
|
||||||
|
self.boardRemote = boardRemote
|
||||||
|
self.agentRemote = agentRemote
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Mount FUSE FS.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
FUSE(
|
||||||
|
FuseRemoteManager(
|
||||||
|
self.mountSource,
|
||||||
|
self.board.agent,
|
||||||
|
self.session,
|
||||||
|
self.boardRemote,
|
||||||
|
self.agentRemote
|
||||||
|
),
|
||||||
|
self.mountPoint,
|
||||||
|
nothreads=False,
|
||||||
|
foreground=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as msg:
|
||||||
|
LOG.error("Mounting FUSE error: " + str(msg))
|
||||||
|
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def makeCall(msg=None, agent=None, session=None):
|
||||||
|
rpc_addr = str(agent) + '.stack4things.echo'
|
||||||
|
LOG.debug("VFS - I'm calling " + rpc_addr)
|
||||||
|
try:
|
||||||
|
res = yield session.call(rpc_addr, msg)
|
||||||
|
LOG.info("NOTIFICATION " + str(res))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("NOTIFICATION error: {0}".format(e))
|
||||||
|
|
||||||
|
|
||||||
|
class FuseRemoteManager(Operations):
|
||||||
|
|
||||||
|
def __init__(self, mountSource, agent, session, boardRemote, agentRemote):
|
||||||
|
|
||||||
|
self.mountSource = mountSource
|
||||||
|
self.session = session
|
||||||
|
self.agent = agent
|
||||||
|
self.boardRemote = boardRemote
|
||||||
|
self.agentRemote = agentRemote
|
||||||
|
|
||||||
|
# makeCall("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU",
|
||||||
|
# self.agent, self.session) # TEMPORARY
|
||||||
|
|
||||||
|
def join_path(self, partial):
|
||||||
|
if partial.startswith("/"):
|
||||||
|
partial = partial[1:]
|
||||||
|
path = os.path.join(self.mountSource, partial)
|
||||||
|
print(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Filesystem methods
|
||||||
|
# ==================
|
||||||
|
|
||||||
|
def access(self, path, mode):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
if not os.access(full_path, mode):
|
||||||
|
raise FuseOSError(errno.EACCES)
|
||||||
|
|
||||||
|
def chmod(self, path, mode):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.chmod(full_path, mode)
|
||||||
|
|
||||||
|
def chown(self, path, uid, gid):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.chown(full_path, uid, gid)
|
||||||
|
|
||||||
|
def getattr(self, path, fh=None):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
st = os.lstat(full_path)
|
||||||
|
attr = dict((key, getattr(st, key))
|
||||||
|
for key in (
|
||||||
|
'st_atime',
|
||||||
|
'st_ctime',
|
||||||
|
'st_gid',
|
||||||
|
'st_mode',
|
||||||
|
'st_mtime',
|
||||||
|
'st_nlink',
|
||||||
|
'st_size',
|
||||||
|
'st_uid'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return attr
|
||||||
|
|
||||||
|
def readdir(self, path, fh):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
|
||||||
|
dirents = ['.', '..']
|
||||||
|
if os.path.isdir(full_path):
|
||||||
|
dirents.extend(os.listdir(full_path))
|
||||||
|
for r in dirents:
|
||||||
|
yield r
|
||||||
|
|
||||||
|
def readlink(self, path):
|
||||||
|
pathname = os.readlink(self.join_path(path))
|
||||||
|
if pathname.startswith("/"):
|
||||||
|
# Path name is absolute, sanitize it.
|
||||||
|
return os.path.relpath(pathname, self.mountSource)
|
||||||
|
else:
|
||||||
|
return pathname
|
||||||
|
|
||||||
|
def mknod(self, path, mode, dev):
|
||||||
|
return os.mknod(self.join_path(path), mode, dev)
|
||||||
|
|
||||||
|
def rmdir(self, path):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.rmdir(full_path)
|
||||||
|
|
||||||
|
def mkdir(self, path, mode):
|
||||||
|
return os.mkdir(self.join_path(path), mode)
|
||||||
|
|
||||||
|
def statfs(self, path):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
stv = os.statvfs(full_path)
|
||||||
|
stat = dict((key, getattr(stv, key))
|
||||||
|
for key in ('f_bavail',
|
||||||
|
'f_bfree',
|
||||||
|
'f_blocks',
|
||||||
|
'f_bsize',
|
||||||
|
'f_favail',
|
||||||
|
'f_ffree',
|
||||||
|
'f_files',
|
||||||
|
'f_flag',
|
||||||
|
'f_frsize',
|
||||||
|
'f_namemax'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return stat
|
||||||
|
|
||||||
|
def unlink(self, path):
|
||||||
|
return os.unlink(self.join_path(path))
|
||||||
|
|
||||||
|
def symlink(self, name, target):
|
||||||
|
return os.symlink(name, self.join_path(target))
|
||||||
|
|
||||||
|
def rename(self, old, new):
|
||||||
|
return os.rename(self.join_path(old), self.join_path(new))
|
||||||
|
|
||||||
|
def link(self, target, name):
|
||||||
|
return os.link(self.join_path(target), self.join_path(name))
|
||||||
|
|
||||||
|
def utimens(self, path, times=None):
|
||||||
|
return os.utime(self.join_path(path), times)
|
||||||
|
|
||||||
|
# File methods
|
||||||
|
# ============
|
||||||
|
|
||||||
|
def open(self, path, flags):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.open(full_path, flags)
|
||||||
|
|
||||||
|
def create(self, path, mode, fi=None):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode)
|
||||||
|
|
||||||
|
def read(self, path, length, offset, fh):
|
||||||
|
os.lseek(fh, offset, os.SEEK_SET)
|
||||||
|
return os.read(fh, length)
|
||||||
|
|
||||||
|
def write(self, path, buf, offset, fh):
|
||||||
|
os.lseek(fh, offset, os.SEEK_SET)
|
||||||
|
return os.write(fh, buf)
|
||||||
|
|
||||||
|
def truncate(self, path, length, fh=None):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
with open(full_path, 'r+') as f:
|
||||||
|
f.truncate(length)
|
||||||
|
|
||||||
|
def flush(self, path, fh):
|
||||||
|
return os.fsync(fh)
|
||||||
|
|
||||||
|
def release(self, path, fh):
|
||||||
|
return os.close(fh)
|
||||||
|
|
||||||
|
def fsync(self, path, fdatasync, fh):
|
||||||
|
return self.flush(path, fh)
|
||||||
|
|
||||||
|
|
||||||
|
class FuseManager(Operations):
|
||||||
|
|
||||||
|
def __init__(self, mountSource):
|
||||||
|
self.mountSource = mountSource
|
||||||
|
|
||||||
|
def join_path(self, partial):
|
||||||
|
if partial.startswith("/"):
|
||||||
|
partial = partial[1:]
|
||||||
|
path = os.path.join(self.mountSource, partial)
|
||||||
|
print(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Filesystem methods
|
||||||
|
# ==================
|
||||||
|
|
||||||
|
def access(self, path, mode):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
if not os.access(full_path, mode):
|
||||||
|
raise FuseOSError(errno.EACCES)
|
||||||
|
|
||||||
|
def chmod(self, path, mode):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.chmod(full_path, mode)
|
||||||
|
|
||||||
|
def chown(self, path, uid, gid):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.chown(full_path, uid, gid)
|
||||||
|
|
||||||
|
def getattr(self, path, fh=None):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
st = os.lstat(full_path)
|
||||||
|
attr = dict((key, getattr(st, key))
|
||||||
|
for key in (
|
||||||
|
'st_atime',
|
||||||
|
'st_ctime',
|
||||||
|
'st_gid',
|
||||||
|
'st_mode',
|
||||||
|
'st_mtime',
|
||||||
|
'st_nlink',
|
||||||
|
'st_size',
|
||||||
|
'st_uid'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return attr
|
||||||
|
|
||||||
|
def readdir(self, path, fh):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
|
||||||
|
dirents = ['.', '..']
|
||||||
|
if os.path.isdir(full_path):
|
||||||
|
dirents.extend(os.listdir(full_path))
|
||||||
|
for r in dirents:
|
||||||
|
yield r
|
||||||
|
|
||||||
|
def readlink(self, path):
|
||||||
|
pathname = os.readlink(self.join_path(path))
|
||||||
|
if pathname.startswith("/"):
|
||||||
|
# Path name is absolute, sanitize it.
|
||||||
|
return os.path.relpath(pathname, self.mountSource)
|
||||||
|
else:
|
||||||
|
return pathname
|
||||||
|
|
||||||
|
def mknod(self, path, mode, dev):
|
||||||
|
return os.mknod(self.join_path(path), mode, dev)
|
||||||
|
|
||||||
|
def rmdir(self, path):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.rmdir(full_path)
|
||||||
|
|
||||||
|
def mkdir(self, path, mode):
|
||||||
|
return os.mkdir(self.join_path(path), mode)
|
||||||
|
|
||||||
|
def statfs(self, path):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
stv = os.statvfs(full_path)
|
||||||
|
stat = dict((key, getattr(stv, key))
|
||||||
|
for key in ('f_bavail',
|
||||||
|
'f_bfree',
|
||||||
|
'f_blocks',
|
||||||
|
'f_bsize',
|
||||||
|
'f_favail',
|
||||||
|
'f_ffree',
|
||||||
|
'f_files',
|
||||||
|
'f_flag',
|
||||||
|
'f_frsize',
|
||||||
|
'f_namemax'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return stat
|
||||||
|
|
||||||
|
def unlink(self, path):
|
||||||
|
return os.unlink(self.join_path(path))
|
||||||
|
|
||||||
|
def symlink(self, name, target):
|
||||||
|
return os.symlink(name, self.join_path(target))
|
||||||
|
|
||||||
|
def rename(self, old, new):
|
||||||
|
return os.rename(self.join_path(old), self.join_path(new))
|
||||||
|
|
||||||
|
def link(self, target, name):
|
||||||
|
return os.link(self.join_path(target), self.join_path(name))
|
||||||
|
|
||||||
|
def utimens(self, path, times=None):
|
||||||
|
return os.utime(self.join_path(path), times)
|
||||||
|
|
||||||
|
# File methods
|
||||||
|
# ============
|
||||||
|
|
||||||
|
def open(self, path, flags):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.open(full_path, flags)
|
||||||
|
|
||||||
|
def create(self, path, mode, fi=None):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode)
|
||||||
|
|
||||||
|
def read(self, path, length, offset, fh):
|
||||||
|
os.lseek(fh, offset, os.SEEK_SET)
|
||||||
|
return os.read(fh, length)
|
||||||
|
|
||||||
|
def write(self, path, buf, offset, fh):
|
||||||
|
os.lseek(fh, offset, os.SEEK_SET)
|
||||||
|
return os.write(fh, buf)
|
||||||
|
|
||||||
|
def truncate(self, path, length, fh=None):
|
||||||
|
full_path = self.join_path(path)
|
||||||
|
with open(full_path, 'r+') as f:
|
||||||
|
f.truncate(length)
|
||||||
|
|
||||||
|
def flush(self, path, fh):
|
||||||
|
return os.fsync(fh)
|
||||||
|
|
||||||
|
def release(self, path, fh):
|
||||||
|
return os.close(fh)
|
||||||
|
|
||||||
|
def fsync(self, path, fdatasync, fh):
|
||||||
|
return self.flush(path, fh)
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
from twisted.internet.defer import inlineCallbacks
|
||||||
|
|
||||||
|
@inlineCallbacks
|
||||||
|
def sendNotification(msg=None):
|
||||||
|
try:
|
||||||
|
res = yield SESSION.call(u'agent.stack4things.echo', msg)
|
||||||
|
LOG.info("NOTIFICATION " + str(res))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning("NOTIFICATION error: {0}".format(e))
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Plugin(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, uuid, name, q_result=None, params=None):
|
||||||
|
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
# self.setDaemon(1)
|
||||||
|
self.setName("Plugin " + str(self.name)) # Set thread name
|
||||||
|
|
||||||
|
self.uuid = uuid
|
||||||
|
self.name = name
|
||||||
|
self.status = "INITED"
|
||||||
|
self.setStatus(self.status)
|
||||||
|
self._is_running = True
|
||||||
|
self.params = params
|
||||||
|
self.q_result = q_result
|
||||||
|
self.type = type
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def run(self):
|
||||||
|
"""RUN method where to implement the user's plugin logic
|
||||||
|
|
||||||
|
"""
|
||||||
|
def stop(self):
|
||||||
|
self._is_running = False
|
||||||
|
|
||||||
|
"""
|
||||||
|
def Done(self):
|
||||||
|
self.setStatus("COMPLETED")
|
||||||
|
sendNotification(msg="hello!")
|
||||||
|
self.checkStatus()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def checkStatus(self):
|
||||||
|
# LOG.debug("Plugin " + self.name + " check status: " + self.status)
|
||||||
|
return self.status
|
||||||
|
|
||||||
|
def setStatus(self, status):
|
||||||
|
self.status = status
|
||||||
|
# LOG.debug("Plugin " + self.name + " changed status: " + self.status)
|
||||||
|
|
||||||
|
def complete(self, rpc_name, result):
|
||||||
|
self.setStatus(result)
|
||||||
|
result = rpc_name + " result: " + self.checkStatus()
|
||||||
|
|
||||||
|
return result
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import cPickle as pickle
|
||||||
|
# import oslo_messaging
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# class ObjectSerializer(oslo_messaging.NoOpSerializer):
|
||||||
|
class ObjectSerializer(object):
|
||||||
|
"""A PluginObject-aware Serializer.
|
||||||
|
|
||||||
|
This implements the Oslo Serializer interface and provides the
|
||||||
|
ability to serialize and deserialize PluginObject entities.
|
||||||
|
Any service that needs to accept or return PluginObject as
|
||||||
|
arguments or result values should pass this to its RpcProxy
|
||||||
|
and RpcDispatcher objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# def serialize_entity(self, context, entity):
|
||||||
|
def serialize_entity(self, entity):
|
||||||
|
|
||||||
|
dumped = pickle.dumps(entity, 0)
|
||||||
|
|
||||||
|
# LOG.debug(" - plugin serialized")
|
||||||
|
|
||||||
|
return dumped
|
||||||
|
|
||||||
|
# def deserialize_entity(self, context, entity):
|
||||||
|
def deserialize_entity(self, entity):
|
||||||
|
|
||||||
|
loaded = pickle.loads(str(entity))
|
||||||
|
|
||||||
|
# LOG.debug(" - plugin deserialized")
|
||||||
|
|
||||||
|
return loaded
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import httplib2
|
||||||
|
import json
|
||||||
|
|
||||||
|
from iotronic_lightningrod.lightningrod import board
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def getBoardID():
|
||||||
|
return board.uuid
|
||||||
|
|
||||||
|
|
||||||
|
def getLocation():
|
||||||
|
return board.location
|
||||||
|
|
||||||
|
|
||||||
|
def getBoardGpio():
|
||||||
|
return board.device.gpio
|
||||||
|
|
||||||
|
|
||||||
|
def sendRequest(url, action, headers=None, body=None, verbose=False):
|
||||||
|
"""Generic REST client for plugin users.
|
||||||
|
|
||||||
|
:param url: resource URI
|
||||||
|
:param action: POST, GET, PUT, etc
|
||||||
|
:param headers: request header
|
||||||
|
:param data: request body
|
||||||
|
:param verbose: flag to enable/disable verbose output
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
http = httplib2.Http()
|
||||||
|
headers = headers
|
||||||
|
response, send = http.request(url, action, headers=headers, body=body)
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
req = json.loads(send)
|
||||||
|
LOG.info("\nREST REQUEST: HTTP " + str(response['status'])
|
||||||
|
+ " - success = " + str(req['success'])
|
||||||
|
+ " - " + str(req['result']['records']))
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("sendRequest error: " + str(err))
|
||||||
|
|
||||||
|
return send
|
|
@ -0,0 +1,70 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from iotronic_lightningrod.devices.gpio import yun
|
||||||
|
from iotronic_lightningrod.plugins import Plugin
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# User imports
|
||||||
|
import datetime
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
|
||||||
|
ADCres = 1023.0
|
||||||
|
Beta = 3950
|
||||||
|
Kelvin = 273.15
|
||||||
|
Rb = 10000
|
||||||
|
Ginf = 120.6685
|
||||||
|
|
||||||
|
# User global variables
|
||||||
|
resource_id = "" # temperature resource id
|
||||||
|
action_URL = "http://smartme-data.unime.it/api/3/action/datastore_upsert"
|
||||||
|
api_key = ''
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
'Authorization': "" + api_key + ""
|
||||||
|
}
|
||||||
|
polling_time = 10
|
||||||
|
|
||||||
|
|
||||||
|
class Worker(Plugin.Plugin):
|
||||||
|
def __init__(self, name, params=None):
|
||||||
|
super(Worker, self).__init__(name, params)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
device = yun.YunGpio()
|
||||||
|
|
||||||
|
while (self._is_running):
|
||||||
|
|
||||||
|
voltage = device._readVoltage("A0")
|
||||||
|
|
||||||
|
Rthermistor = float(Rb) * (float(ADCres) / float(voltage) - 1)
|
||||||
|
|
||||||
|
rel_temp = float(Beta) / (math.log(
|
||||||
|
float(Rthermistor) * float(Ginf))
|
||||||
|
)
|
||||||
|
temp = rel_temp - Kelvin
|
||||||
|
|
||||||
|
m_value = str(temp)
|
||||||
|
m_timestamp = datetime.datetime.now().strftime(
|
||||||
|
'%Y-%m-%dT%H:%M:%S.%f'
|
||||||
|
)
|
||||||
|
|
||||||
|
LOG.info(m_value + " - " + m_timestamp)
|
||||||
|
|
||||||
|
time.sleep(polling_time)
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"message": "Hello!"
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from iotronic_lightningrod.plugins import Plugin
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# User imports
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class Worker(Plugin.Plugin):
|
||||||
|
def __init__(self, uuid, name, q_result=None, params=None):
|
||||||
|
super(Worker, self).__init__(uuid, name, q_result, params)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
LOG.info("Plugin " + self.name + " starting...")
|
||||||
|
LOG.info(self.params)
|
||||||
|
|
||||||
|
while(self._is_running):
|
||||||
|
LOG.info(self.params['message'])
|
||||||
|
time.sleep(1)
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"polling" : "600",
|
||||||
|
"ckan_enabled" : false,
|
||||||
|
"temperature": { "pin" : "A0", "enabled":true },
|
||||||
|
"brightness": { "pin" : "A1", "enabled":true },
|
||||||
|
"humidity": { "pin" : "A2", "enabled":true },
|
||||||
|
"gas": { "pin" : "A3", "enabled":true },
|
||||||
|
"noise": { "pin" : "A4", "enabled":true },
|
||||||
|
"pressure": { "pin" : "i2c", "enabled":true }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{"delay" : 10}
|
|
@ -0,0 +1,409 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from iotronic_lightningrod.plugins import Plugin
|
||||||
|
from iotronic_lightningrod.plugins import pluginApis as API
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# User imports
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
# User global variables
|
||||||
|
ckan_addr = 'smartme-data.unime.it'
|
||||||
|
action_URL = "http://" + ckan_addr + "/api/3/action/datastore_upsert"
|
||||||
|
api_key = '22c5cfa7-9dea-4dd9-9f9d-eedf296852ae'
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
'Authorization': "" + api_key + ""
|
||||||
|
}
|
||||||
|
|
||||||
|
sensors_list = [
|
||||||
|
'temperature',
|
||||||
|
'brightness',
|
||||||
|
'humidity',
|
||||||
|
'pressure',
|
||||||
|
'noise'
|
||||||
|
# , 'gas'
|
||||||
|
]
|
||||||
|
position = None
|
||||||
|
|
||||||
|
SENSORS = {}
|
||||||
|
|
||||||
|
location = {}
|
||||||
|
|
||||||
|
device = API.getBoardGpio()
|
||||||
|
|
||||||
|
THR_KILL = None
|
||||||
|
|
||||||
|
|
||||||
|
# Sensors gloabl parameters
|
||||||
|
|
||||||
|
# Temperature Parameters
|
||||||
|
ADCres = 1023.0
|
||||||
|
Beta = 3950
|
||||||
|
Kelvin = 273.15
|
||||||
|
Rb = 10000
|
||||||
|
Ginf = 120.6685
|
||||||
|
latest_temp = None
|
||||||
|
|
||||||
|
# Noise Parameters
|
||||||
|
samples_number = 1000
|
||||||
|
amplitudes_sum = 0
|
||||||
|
amplitudes_count = 0
|
||||||
|
|
||||||
|
|
||||||
|
def Temperature():
|
||||||
|
"""To get Temperature value.
|
||||||
|
|
||||||
|
:return: Temperature value (float)
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
voltage = device._readVoltage(SENSORS['temperature']['pin'])
|
||||||
|
|
||||||
|
Rthermistor = float(Rb) * (float(ADCres) / float(voltage) - 1)
|
||||||
|
rel_temp = float(Beta) / (math.log(float(Rthermistor) * float(Ginf)))
|
||||||
|
temp = rel_temp - Kelvin
|
||||||
|
|
||||||
|
# LOG.info("Temperature " + str(temp) + u" \u2103")
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error getting temperature: " + str(err))
|
||||||
|
|
||||||
|
return temp
|
||||||
|
|
||||||
|
|
||||||
|
def Brightness():
|
||||||
|
"""To get Brightness value.
|
||||||
|
|
||||||
|
:return: Brightness value (float)
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
voltage = float(device._readVoltage(SENSORS['brightness']['pin']))
|
||||||
|
|
||||||
|
ldr = (2500 / (5 - voltage * float(0.004887)) - 500) / float(3.3)
|
||||||
|
|
||||||
|
LOG.info("Brightness: " + str(ldr) + " (lux)")
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error getting brightness: " + str(err))
|
||||||
|
|
||||||
|
return ldr
|
||||||
|
|
||||||
|
|
||||||
|
def Humidity():
|
||||||
|
"""To get Humidity value: this function uses the Temperature sensor too.
|
||||||
|
|
||||||
|
:return: Humidity value (float)
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
degCelsius = Temperature()
|
||||||
|
supplyVolt = float(4.64)
|
||||||
|
HIH4030_Value = float(device._readVoltage(SENSORS['humidity']['pin']))
|
||||||
|
voltage = HIH4030_Value / float(1023.) * supplyVolt
|
||||||
|
sensorRH = float(161.0) * float(voltage) / supplyVolt - float(25.8)
|
||||||
|
relHum = sensorRH / (float(1.0546) - float(0.0026) * degCelsius)
|
||||||
|
|
||||||
|
LOG.info("Humidity " + str(relHum) + " percent")
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error getting humidity: " + str(err))
|
||||||
|
|
||||||
|
return relHum
|
||||||
|
|
||||||
|
|
||||||
|
def Pressure():
|
||||||
|
"""To get Pressure value.
|
||||||
|
|
||||||
|
:return: Pressure value (float)
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
in_pressure_raw = device.i2cRead('pressure')
|
||||||
|
pressure = float(in_pressure_raw) * float(0.00025) * 10
|
||||||
|
|
||||||
|
LOG.info("Pressure: " + str(pressure) + " hPa")
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error getting pressure: " + str(err))
|
||||||
|
|
||||||
|
return pressure
|
||||||
|
|
||||||
|
|
||||||
|
def Noise():
|
||||||
|
"""To get Noise value.
|
||||||
|
|
||||||
|
Elaborate a noise avarange value from noise listener.
|
||||||
|
|
||||||
|
:return: Noise value (float)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
global amplitudes_sum, amplitudes_count
|
||||||
|
|
||||||
|
if amplitudes_count == float(0):
|
||||||
|
amplitude = float(0)
|
||||||
|
|
||||||
|
else:
|
||||||
|
amplitude = float(amplitudes_sum / amplitudes_count)
|
||||||
|
|
||||||
|
amplitudes_sum = 0
|
||||||
|
amplitudes_count = 0
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error getting noise: " + str(err))
|
||||||
|
|
||||||
|
return amplitude
|
||||||
|
|
||||||
|
|
||||||
|
def noise_listner():
|
||||||
|
"""Each two seconds collect a Noise sample.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
global THR_KILL
|
||||||
|
|
||||||
|
vect = []
|
||||||
|
|
||||||
|
if THR_KILL:
|
||||||
|
|
||||||
|
# LOG.info("listening noise..." + str(THR_KILL))
|
||||||
|
|
||||||
|
for x in range(samples_number):
|
||||||
|
|
||||||
|
read = float(device._readVoltage(SENSORS['noise']['pin']))
|
||||||
|
vect.append(read)
|
||||||
|
|
||||||
|
sorted_vect = sorted(vect)
|
||||||
|
|
||||||
|
minimum = float(sorted_vect[50])
|
||||||
|
maximum = float(sorted_vect[samples_number - 51])
|
||||||
|
tmp_amplitude = float(maximum - minimum)
|
||||||
|
|
||||||
|
global amplitudes_sum, amplitudes_count
|
||||||
|
amplitudes_sum = float(amplitudes_sum + tmp_amplitude)
|
||||||
|
amplitudes_count = float(amplitudes_count + 1)
|
||||||
|
# LOG.info("amplitudes_sum = " + str(amplitudes_sum))
|
||||||
|
# LOG.info("amplitudes_count = " + str(amplitudes_count))
|
||||||
|
|
||||||
|
threading.Timer(2.0, noise_listner).start()
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.debug("Cancelled SmartME noise listening: " + str(THR_KILL))
|
||||||
|
|
||||||
|
|
||||||
|
def getMetric(metric, ckan):
|
||||||
|
"""Function to get metric values.
|
||||||
|
|
||||||
|
This function call the function relative to the 'metric'
|
||||||
|
specified and if the 'ckan' flag is True we create the body for the
|
||||||
|
REST request to send to CKAN database to store the sample there;
|
||||||
|
|
||||||
|
:param metric: name of the metric analized: 'Temperature', etc
|
||||||
|
:param ckan: flag True --> create JSON body for the CKAN request
|
||||||
|
:return: ckan_data --> JSON data to send as request body to CKAN
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Call Sensors Metrics: Temperature(), etc...
|
||||||
|
m_value = str(globals()[metric.capitalize()]())
|
||||||
|
|
||||||
|
m_timestamp = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
|
||||||
|
if metric == 'noise':
|
||||||
|
LOG.info("Noise: " + str(m_value) + " amplitude")
|
||||||
|
|
||||||
|
elif metric == 'temperature':
|
||||||
|
LOG.info("Temperature " + str(m_value) + u" \u2103")
|
||||||
|
|
||||||
|
if ckan:
|
||||||
|
|
||||||
|
ckan_data = {}
|
||||||
|
ckan_data["resource_id"] = str(SENSORS[metric]['ckanID'])
|
||||||
|
ckan_data["method"] = "insert"
|
||||||
|
ckan_data["records"] = []
|
||||||
|
sample = {}
|
||||||
|
sample["Latitude"] = location['latitude']
|
||||||
|
sample["Longitude"] = location['longitude']
|
||||||
|
sample["Altitude"] = location['altitude']
|
||||||
|
metric_func_name = metric.capitalize()
|
||||||
|
sample[metric_func_name] = m_value
|
||||||
|
sample["Date"] = m_timestamp
|
||||||
|
ckan_data["records"].append(sample)
|
||||||
|
|
||||||
|
ckan_data = json.dumps(ckan_data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
ckan_data = None
|
||||||
|
|
||||||
|
return ckan_data
|
||||||
|
|
||||||
|
|
||||||
|
def getCKANdataset(board_uuid):
|
||||||
|
"""To get CKAN resource IDs for each metric type managed by SmartME boards.
|
||||||
|
|
||||||
|
:param board_uuid:
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
datasets_url = "http://" + ckan_addr + "/api/rest/dataset/" + board_uuid
|
||||||
|
datasets = API.sendRequest(url=datasets_url, action='GET')
|
||||||
|
ckan_data = json.loads(datasets)
|
||||||
|
|
||||||
|
for resource in ckan_data['resources']:
|
||||||
|
|
||||||
|
# LOG.info(resource['name'].capitalize())
|
||||||
|
|
||||||
|
if resource['name'] in sensors_list:
|
||||||
|
# LOG.debug(resource['name'])
|
||||||
|
SENSORS[resource['name']]['ckanID'] = resource['id']
|
||||||
|
# LOG.info(resource['name'] + " - " + resource['id'])
|
||||||
|
|
||||||
|
|
||||||
|
def setSensorsLayout(params):
|
||||||
|
for sensor in sensors_list:
|
||||||
|
SENSORS[sensor] = {}
|
||||||
|
SENSORS[sensor]['pin'] = params[sensor]['pin']
|
||||||
|
SENSORS[sensor]['enabled'] = params[sensor]['enabled']
|
||||||
|
|
||||||
|
|
||||||
|
def InitSmartMeBoard(params):
|
||||||
|
"""This function init the SmartME board.
|
||||||
|
|
||||||
|
In the SmartME Arduino YUN board this function enables the needed
|
||||||
|
devices and set the needed parameters about sensors and location.
|
||||||
|
|
||||||
|
:param params: plugin parameters to configure the board.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# get location
|
||||||
|
global location
|
||||||
|
location = API.getLocation()
|
||||||
|
LOG.info(
|
||||||
|
"Board location: \n"
|
||||||
|
+ json.dumps(location, indent=4, separators=(',', ': '))
|
||||||
|
)
|
||||||
|
|
||||||
|
# set devices
|
||||||
|
try:
|
||||||
|
|
||||||
|
device.EnableI2c()
|
||||||
|
device.EnableGPIO()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOG.error("Error configuring devices: " + str(err))
|
||||||
|
global THR_KILL
|
||||||
|
THR_KILL = False
|
||||||
|
|
||||||
|
# set up sensors
|
||||||
|
setSensorsLayout(params)
|
||||||
|
|
||||||
|
|
||||||
|
class Worker(Plugin.Plugin):
|
||||||
|
|
||||||
|
def __init__(self, uuid, name, q_result=None, params=None):
|
||||||
|
super(Worker, self).__init__(
|
||||||
|
uuid, name,
|
||||||
|
q_result=q_result,
|
||||||
|
params=params
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
LOG.info("SmartME plugin starting...")
|
||||||
|
|
||||||
|
global THR_KILL
|
||||||
|
THR_KILL = self._is_running
|
||||||
|
|
||||||
|
# Board initialization
|
||||||
|
LOG.info("PARAMS list: " + str(self.params.keys()))
|
||||||
|
|
||||||
|
if len(self.params.keys()) != 0:
|
||||||
|
|
||||||
|
InitSmartMeBoard(self.params)
|
||||||
|
|
||||||
|
# Get polling time
|
||||||
|
polling_time = float(self.params['polling'])
|
||||||
|
LOG.info("Polling time: " + str(polling_time))
|
||||||
|
|
||||||
|
# GET CKAN SENSORS UUID
|
||||||
|
getCKANdataset(API.getBoardID())
|
||||||
|
|
||||||
|
LOG.info(
|
||||||
|
"SENSORS: \n"
|
||||||
|
+ json.dumps(SENSORS, indent=4, separators=(',', ': '))
|
||||||
|
)
|
||||||
|
|
||||||
|
# START NOISE LISTENER if sensor enabled
|
||||||
|
if SENSORS['noise']['enabled']:
|
||||||
|
LOG.info("Starting noise listening...")
|
||||||
|
noise_listner()
|
||||||
|
|
||||||
|
LOG.info("CKAN enabled: " + str(self.params['ckan_enabled']))
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
while (self._is_running and THR_KILL):
|
||||||
|
|
||||||
|
if sensors_list.__len__() != 0:
|
||||||
|
|
||||||
|
LOG.info("\n\n")
|
||||||
|
|
||||||
|
for sensor in sensors_list:
|
||||||
|
|
||||||
|
if SENSORS[sensor]['enabled']:
|
||||||
|
|
||||||
|
if self.params['ckan_enabled']:
|
||||||
|
|
||||||
|
API.sendRequest(
|
||||||
|
url=action_URL,
|
||||||
|
action='POST',
|
||||||
|
headers=headers,
|
||||||
|
body=getMetric(sensor, ckan=True),
|
||||||
|
verbose=False
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
getMetric(sensor, ckan=False)
|
||||||
|
|
||||||
|
counter = counter + 1
|
||||||
|
LOG.info("Samples sent: " + str(counter))
|
||||||
|
|
||||||
|
time.sleep(polling_time)
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.warning("No sensors!")
|
||||||
|
self._is_running = False
|
||||||
|
THR_KILL = self._is_running
|
||||||
|
|
||||||
|
# Update the thread status: at this stage THR_KILL will be False
|
||||||
|
THR_KILL = self._is_running
|
||||||
|
|
||||||
|
else:
|
||||||
|
LOG.error("No parameters provided!")
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from iotronic_lightningrod.plugins import Plugin
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# User imports
|
||||||
|
|
||||||
|
|
||||||
|
class Worker(Plugin.Plugin):
|
||||||
|
|
||||||
|
def __init__(self, uuid, name, q_result, params=None):
|
||||||
|
super(Worker, self).__init__(uuid, name, q_result, params)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
LOG.info("Input parameters: " + str(self.params))
|
||||||
|
LOG.info("Plugin " + self.name + " process completed!")
|
||||||
|
self.q_result.put("ZERO RESULT")
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Copyright 2017 MDSLAB - University of Messina
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
SUCCESS = 'SUCCESS'
|
||||||
|
ERROR = 'ERROR'
|
||||||
|
WARNING = 'WARNING'
|
||||||
|
|
||||||
|
|
||||||
|
def deserialize(received):
|
||||||
|
m = json.loads(received)
|
||||||
|
return WampMessage(**m)
|
||||||
|
|
||||||
|
|
||||||
|
class WampMessage(object):
|
||||||
|
def __init__(self, message=None, result=None):
|
||||||
|
self.message = message
|
||||||
|
self.result = result
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return json.dumps(self, default=lambda o: o.__dict__)
|
||||||
|
"""
|
||||||
|
def deserialize(self, received):
|
||||||
|
self.__dict__ = json.loads(received)
|
||||||
|
return self
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class WampSuccess(WampMessage):
|
||||||
|
def __init__(self, msg=None):
|
||||||
|
super(WampSuccess, self).__init__(msg, SUCCESS)
|
||||||
|
|
||||||
|
|
||||||
|
class WampError(WampMessage):
|
||||||
|
def __init__(self, msg=None):
|
||||||
|
super(WampError, self).__init__(msg, ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
class WampWarning(WampMessage):
|
||||||
|
def __init__(self, msg=None):
|
||||||
|
super(WampWarning, self).__init__(msg, WARNING)
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"zero": {
|
||||||
|
"onboot": "false",
|
||||||
|
"callable": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"iotronic": {
|
||||||
|
"board": {
|
||||||
|
"code": "<REGISTRATION-TOKEN>"
|
||||||
|
},
|
||||||
|
"wamp": {
|
||||||
|
"registration-agent": {
|
||||||
|
"url": "ws://<WAMP-SERVER>:<WAMP-PORT>/",
|
||||||
|
"realm": "<IOTRONIC-REALM>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
# The order of packages is significant, because pip processes them in the order
|
||||||
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
|
pbr>=2.0.0 # Apache-2.0
|
||||||
|
|
||||||
|
# Openstack modules
|
||||||
|
oslo.config>=3.22.0 # Apache-2.0
|
||||||
|
oslo.log>=3.22.0 # Apache-2.0
|
|
@ -0,0 +1,60 @@
|
||||||
|
[metadata]
|
||||||
|
name = iotronic_lightningrod
|
||||||
|
summary = Implementation of the Lightning-rod, the Stack4Things board-side probe
|
||||||
|
description-file =
|
||||||
|
README.rst
|
||||||
|
author = Nicola Peditto, Fabio Verboso
|
||||||
|
author-email = unime.mdslab@gmail.com
|
||||||
|
home-page = http://stack4things.unime.it/
|
||||||
|
classifier =
|
||||||
|
Environment :: OpenStack
|
||||||
|
Intended Audience :: Information Technology
|
||||||
|
Intended Audience :: System Administrators
|
||||||
|
License :: OSI Approved :: Apache Software License
|
||||||
|
Operating System :: POSIX :: Linux
|
||||||
|
Programming Language :: Python
|
||||||
|
Programming Language :: Python :: 2
|
||||||
|
Programming Language :: Python :: 2.7
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.3
|
||||||
|
Programming Language :: Python :: 3.4
|
||||||
|
|
||||||
|
[files]
|
||||||
|
packages =
|
||||||
|
iotronic_lightningrod
|
||||||
|
|
||||||
|
[build_sphinx]
|
||||||
|
source-dir = doc/source
|
||||||
|
build-dir = doc/build
|
||||||
|
all_files = 1
|
||||||
|
|
||||||
|
[upload_sphinx]
|
||||||
|
upload-dir = doc/build/html
|
||||||
|
|
||||||
|
[compile_catalog]
|
||||||
|
directory = iotronic_lightningrod/locale
|
||||||
|
domain = iotronic_lightningrod
|
||||||
|
|
||||||
|
[update_catalog]
|
||||||
|
domain = iotronic_lightningrod
|
||||||
|
output_dir = iotronic_lightningrod/locale
|
||||||
|
input_file = iotronic_lightningrod/locale/iotronic_lightningrod.pot
|
||||||
|
|
||||||
|
[extract_messages]
|
||||||
|
keywords = _ gettext ngettext l_ lazy_gettext
|
||||||
|
mapping_file = babel.cfg
|
||||||
|
output_file = iotronic_lightningrod/locale/iotronic_lightningrod.pot
|
||||||
|
|
||||||
|
[build_releasenotes]
|
||||||
|
all_files = 1
|
||||||
|
build-dir = releasenotes/build
|
||||||
|
source-dir = releasenotes/source
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
console_scripts =
|
||||||
|
lightning-rod = iotronic_lightningrod.lightningrod:main
|
||||||
|
|
||||||
|
s4t.modules =
|
||||||
|
utility = iotronic_lightningrod.modules.utils:Utility
|
||||||
|
plugin = iotronic_lightningrod.modules.plugin_manager:PluginManager
|
||||||
|
device = iotronic_lightningrod.modules.device_manager:DeviceManager
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||||
|
# setuptools if some other modules registered functions in `atexit`.
|
||||||
|
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||||
|
try:
|
||||||
|
import multiprocessing # noqa
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
setup_requires=['pbr>=1.8'],
|
||||||
|
pbr=True,
|
||||||
|
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
# The order of packages is significant, because pip processes them in the order
|
||||||
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
|
hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0
|
||||||
|
|
||||||
|
coverage>=4.0 # Apache-2.0
|
||||||
|
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||||
|
sphinx>=1.5.1 # BSD
|
||||||
|
oslosphinx>=4.7.0 # Apache-2.0
|
||||||
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
|
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||||
|
testscenarios>=0.4 # Apache-2.0/BSD
|
||||||
|
testtools>=1.4.0 # MIT
|
||||||
|
|
||||||
|
|
||||||
|
# releasenotes
|
||||||
|
reno>=1.8.0 # Apache-2.0
|
|
@ -0,0 +1,40 @@
|
||||||
|
[tox]
|
||||||
|
minversion = 2.0
|
||||||
|
envlist = py35,py27,pypy,pep8
|
||||||
|
skipsdist = True
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
usedevelop = True
|
||||||
|
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
||||||
|
setenv =
|
||||||
|
VIRTUAL_ENV={envdir}
|
||||||
|
PYTHONWARNINGS=default::DeprecationWarning
|
||||||
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
commands = find . -type f -name "*.pyc" -delete
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
commands = flake8 {posargs}
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[testenv:cover]
|
||||||
|
commands = python setup.py test --coverage --testr-args='{posargs}'
|
||||||
|
|
||||||
|
[testenv:docs]
|
||||||
|
commands = python setup.py build_sphinx
|
||||||
|
|
||||||
|
[testenv:releasenotes]
|
||||||
|
commands =
|
||||||
|
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||||
|
|
||||||
|
[testenv:debug]
|
||||||
|
commands = oslo_debug_helper {posargs}
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
# E123, E125 skipped as they are invalid PEP-8.
|
||||||
|
|
||||||
|
show-source = True
|
||||||
|
ignore = E123,E125
|
||||||
|
builtins = _
|
||||||
|
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
Loading…
Reference in New Issue