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