Moved to Python 3.5

Code moved from Python 2.7 to 3.5. Substituted Twisted module with Asyncio.

Change-Id: I926a48ba2f6ce8b8c0ec43592105f864c4d0c7dc
This commit is contained in:
Nicola Peditto 2018-02-06 11:17:39 +01:00
parent f953021d76
commit 1fd573e3b2
34 changed files with 682 additions and 1028 deletions

View File

@ -1,3 +1,4 @@
[DEFAULT] [DEFAULT]
debug = True debug = True
log_file = /var/log/iotronic/lightning-rod.log log_file = /var/log/iotronic/lightning-rod.log
lightningrod_home = /var/lib/iotronic

View File

@ -13,17 +13,19 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
from datetime import datetime from datetime import datetime
# from dateutil.tz import tzlocal
import json import json
import os import os
from iotronic_lightningrod.config import iotronic_home from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
SETTINGS = iotronic_home + '/settings.json' CONF = cfg.CONF
SETTINGS = '/etc/iotronic/settings.json'
class Board(object): class Board(object):
@ -74,6 +76,9 @@ class Board(object):
''' '''
LOG.info("Lightning-rod home:")
LOG.info(" - " + CONF.lightningrod_home)
# Load all settings.json file # Load all settings.json file
self.iotronic_config = self.loadConf() self.iotronic_config = self.loadConf()

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import os import os
import signal import signal

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import os import os
import pkg_resources import pkg_resources
@ -23,6 +24,3 @@ entry_points_name = \
# Iotronic python package folder # Iotronic python package folder
package_path = os.path.join(dist.location, __package__) package_path = os.path.join(dist.location, __package__)
# Iotronic home folder
iotronic_home = "/var/lib/iotronic"

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "MDSLAB Team" __author__ = "Nicola Peditto <npeditto@unime.it"
import abc import abc
import six import six

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import abc import abc
import six import six

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
from oslo_log import log as logging from oslo_log import log as logging

View File

@ -13,10 +13,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from iotronic_lightningrod.devices.gpio import Gpio __author__ = "Nicola Peditto <npeditto@unime.it"
import os import os
import time import time
from iotronic_lightningrod.devices.gpio import Gpio
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)

View File

@ -13,8 +13,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import inspect import inspect
from twisted.internet.defer import returnValue
from iotronic_lightningrod.devices import Device from iotronic_lightningrod.devices import Device
from iotronic_lightningrod.devices.gpio import server from iotronic_lightningrod.devices.gpio import server
@ -46,10 +47,10 @@ class System(Device.Device):
""" """
pass pass
def testRPC(self): async def testRPC(self):
rpc_name = whoami() rpc_name = whoami()
LOG.info("RPC " + rpc_name + " CALLED...") LOG.info("RPC " + rpc_name + " CALLED...")
yield makeNothing() await makeNothing()
result = " - " + rpc_name + " result: testRPC is working!!!\n" result = " - " + rpc_name + " result: testRPC is working!!!\n"
LOG.info(result) LOG.info(result)
returnValue(result) return result

View File

@ -13,9 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# Linino references: http://wiki.linino.org/doku.php?id=wiki:lininoio_sysfs __author__ = "Nicola Peditto <npeditto@unime.it"
from twisted.internet.defer import returnValue
from iotronic_lightningrod.devices import Device from iotronic_lightningrod.devices import Device
from iotronic_lightningrod.devices.gpio import yun from iotronic_lightningrod.devices.gpio import yun
@ -23,6 +21,8 @@ from iotronic_lightningrod.devices.gpio import yun
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# Linino references: http://wiki.linino.org/doku.php?id=wiki:lininoio_sysfs
class System(Device.Device): class System(Device.Device):
@ -41,25 +41,25 @@ class System(Device.Device):
""" """
pass pass
def testLED(self): async def testLED(self):
LOG.info(" - testLED CALLED...") LOG.info(" - testLED CALLED...")
yield self.gpio.blinkLed() await self.gpio.blinkLed()
result = "testLED: LED blinking!\n" result = "testLED: LED blinking!\n"
LOG.info(result) LOG.info(result)
returnValue(result) return result
def setGPIOs(self, Dpin, direction, value): async def setGPIOs(self, Dpin, direction, value):
LOG.info(" - setGPIOs CALLED... digital pin " + Dpin LOG.info(" - setGPIOs CALLED... digital pin " + Dpin
+ " (GPIO n. " + self.gpio.MAPPING[Dpin] + ")") + " (GPIO n. " + self.gpio.MAPPING[Dpin] + ")")
result = yield self.gpio._setGPIOs(Dpin, direction, value) result = await self.gpio._setGPIOs(Dpin, direction, value)
LOG.info(result) LOG.info(result)
returnValue(result) return result
def readVoltage(self, Apin): async def readVoltage(self, Apin):
"""To read the voltage applied on the pin A0,A1,A2,A3,A4,A5 """To read the voltage applied on the pin A0,A1,A2,A3,A4,A5
""" """
@ -67,6 +67,6 @@ class System(Device.Device):
voltage = self.gpio._readVoltage(Apin) voltage = self.gpio._readVoltage(Apin)
result = yield "read voltage for " + Apin + " pin: " + voltage result = await "read voltage for " + Apin + " pin: " + voltage
LOG.info(result) LOG.info(result)
returnValue(result) return result

View File

@ -1,5 +1,4 @@
# Copyright 2017 MDSLAB - University of Messina # Copyright 2017 MDSLAB - University of Messina All Rights Reserved.
# All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -13,16 +12,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
# Autobahn and Twisted imports # Autobahn imports
from autobahn.twisted import wamp import asyncio
from autobahn.twisted.wamp import ApplicationSession from autobahn.asyncio.component import Component
from autobahn.twisted import websocket
from autobahn.wamp import exception from autobahn.wamp import exception
from autobahn.wamp import types import txaio
from twisted.internet.defer import inlineCallbacks
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.internet import reactor
# OSLO imports # OSLO imports
from oslo_config import cfg from oslo_config import cfg
@ -33,7 +29,6 @@ import inspect
import os import os
import pkg_resources import pkg_resources
import signal import signal
import socket
from stevedore import extension from stevedore import extension
import sys import sys
@ -46,7 +41,17 @@ import iotronic_lightningrod.wampmessage as WM
# Global variables # Global variables
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
lr_opts = [
cfg.StrOpt('lightningrod_home',
default='/var/lib/iotronic',
help=('Lightning Home Data')),
]
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(lr_opts)
SESSION = None SESSION = None
global board global board
board = None board = None
@ -54,6 +59,13 @@ reconnection = False
RPC = {} RPC = {}
RPC_devices = {} RPC_devices = {}
# ASYNCIO
loop = None
component = None
txaio.start_logging(level="info")
RUNNER = None
connected = False
def moduleReloadInfo(session): def moduleReloadInfo(session):
"""This function is used in the reconnection stage to register """This function is used in the reconnection stage to register
@ -101,9 +113,10 @@ def moduleWampRegister(session, meth_list):
# We don't considere the __init__ and finalize methods # We don't considere the __init__ and finalize methods
if (meth[0] != "__init__") & (meth[0] != "finalize"): if (meth[0] != "__init__") & (meth[0] != "finalize"):
rpc_addr = u'iotronic.' + board.uuid + '.' + meth[0] rpc_addr = u'iotronic.' + board.uuid + '.' + meth[0]
session.register(inlineCallbacks(meth[1]), rpc_addr)
session.register(meth[1], rpc_addr)
LOG.info(" --> " + str(meth[0])) LOG.info(" --> " + str(meth[0]))
# LOG.info(" --> " + str(rpc_addr))
def modulesLoader(session): def modulesLoader(session):
@ -169,8 +182,7 @@ def modulesLoader(session):
LOG.info("\n\nListening...") LOG.info("\n\nListening...")
@inlineCallbacks async def IotronicLogin(board, session, details):
def IotronicLogin(board, session, details):
"""Function called to connect the board to Iotronic. """Function called to connect the board to Iotronic.
The board: The board:
@ -187,18 +199,16 @@ def IotronicLogin(board, session, details):
global reconnection global reconnection
global SESSION
SESSION = session
try: try:
rpc = str(board.agent) + u'.stack4things.connection' rpc = str(board.agent) + u'.stack4things.connection'
with timeoutRPC(seconds=3, action=rpc): with timeoutRPC(seconds=3, action=rpc):
res = yield session.call(rpc, res = await session.call(
uuid=board.uuid, rpc,
session=details.session uuid=board.uuid,
) session=details.session
)
w_msg = WM.deserialize(res) w_msg = WM.deserialize(res)
@ -209,14 +219,14 @@ def IotronicLogin(board, session, details):
# LOADING BOARD MODULES # LOADING BOARD MODULES
try: try:
yield modulesLoader(session) modulesLoader(session)
except Exception as e: except Exception as e:
LOG.warning("WARNING - Could not register procedures: " LOG.warning("WARNING - Could not register procedures: "
+ str(e)) + str(e))
# Reset flag to False # Reset flag to False
reconnection = False # reconnection = False
else: else:
LOG.error(" - Access denied to Iotronic.") LOG.error(" - Access denied to Iotronic.")
@ -228,324 +238,16 @@ def IotronicLogin(board, session, details):
# the "stack4things.connection" RPC. # the "stack4things.connection" RPC.
# The board will disconnect from WAMP agent and retry later. # The board will disconnect from WAMP agent and retry later.
reconnection = True reconnection = True
session.disconnect()
# We stop the Component in order to trigger the onDisconnect event
component.stop()
except Exception as e: except Exception as e:
LOG.warning("Iotronic board connection error: " + str(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): def wampConnect(wamp_conf):
"""WAMP connection procedure. """WAMP connection procedures.
:param wamp_conf: WAMP configuration from settings.json file :param wamp_conf: WAMP configuration from settings.json file
@ -555,32 +257,326 @@ def wampConnect(wamp_conf):
try: try:
component_config = types.ComponentConfig( LOG.info("WAMP status:" +
realm=unicode(wamp_conf['realm']) "\n- board = " + str(board.status) +
"\n- reconnection = " + str(reconnection) +
"\n- connection = " + str(connected)
)
# LR creates the Autobahn Asyncio Component that points to the
# WAMP Agent (main/registration agent)
global component
component = Component(
transports=wamp_conf['url'],
realm=wamp_conf['realm']
) )
session_factory = wamp.ApplicationSessionFactory(
config=component_config
)
session_factory.session = WampFrontend
transport_factory = WampClientFactory( # To manage the registration stage: we got the info for the main
session_factory, # WAMP agent and LR is going to connect to it starting the Component
url=wamp_conf['url'] # with the new WAMP configuration.
) if connected == False and board.status == "registered" \
transport_factory.autoPingInterval = 5 and reconnection == False:
transport_factory.autoPingTimeout = 5 component.start(loop)
connector = websocket.connectWS(transport_factory) @component.on_join
async def join(session, details):
"""Execute the following procedures when the board connects
to Crossbar.
try: :param details: WAMP session details
addr = str(connector.getDestination().host) """
socket.inet_pton(socket.AF_INET, addr)
LOG.info(" - establishing connection to : " + str(addr))
except socket.error as err: global connected
LOG.error(" - IP address validation error: " + str(err)) connected = True
Bye()
# LIGHTNING-ROD STATES:
# - 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 = session
# LOG.debug(" - session: " + str(details))
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(board.session_id))
LOG.info(" - Board status: " + str(board.status))
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.")
rpc = u'stack4things.register'
with timeoutRPC(seconds=3, action=rpc):
res = await session.call(
rpc,
code=board.code,
session=board.session_id
)
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 in order 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")
# We stop the Component in order to trigger the
# onDisconnect event
component.stop()
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
# We stop the Component in order to trigger the
# onDisconnect event
component.stop()
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 connection
# 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()
LOG.info("WAMP status:" +
"\n- board = " + str(board.status) +
"\n- reconnection = " + str(reconnection) +
"\n- connection = " + str(connected)
)
await IotronicLogin(board, session, 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 stage until
# it will succeed to connect to Iotronic.
await IotronicLogin(board, session, 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 = await 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
# If the board is in WAMP connection recovery state
# we need to register again the RPCs of each module
try:
moduleReloadInfo(session)
# 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
# We stop the Component in order to trigger the
# onDisconnect event
component.stop()
except Exception as e:
LOG.warning("Board connection error after WAMP recovery: "
+ str(e))
Bye()
@component.on_leave
async def onLeave(session, details):
LOG.warning('WAMP Session Left: ' + str(details))
@component.on_disconnect
async def onDisconnect(session, was_clean):
"""Procedure triggered on WAMP connection lost, for istance
when we call component.stop().
:param connector: WAMP connector object
:param reason: WAMP connection failure reason
"""
LOG.warning('WAMP Transport Left: was_clean = ' + str(was_clean))
global connected
connected = False
global reconnection
LOG.info("WAMP status:" +
"\n- board = " + str(board.status) +
"\n- reconnection = " + str(reconnection) +
"\n- connection = " + str(connected)
)
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
# LR needs to reconncet to WAMP
if not connected:
component.start(loop)
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
# LR needs to reconncet to WAMP
if not connected:
component.start(loop)
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!")
except Exception as err: except Exception as err:
LOG.error(" - URI validation error: " + str(err)) LOG.error(" - URI validation error: " + str(err))
@ -594,34 +590,21 @@ class WampManager(object):
def __init__(self, wamp_conf): def __init__(self, wamp_conf):
# Connection to Crossbar server. # wampConnect configures and manages the connection to Crossbar server.
wampConnect(wamp_conf) wampConnect(wamp_conf)
def start(self): def start(self):
LOG.info(" - starting Lightning-rod WAMP server...") LOG.info(" - starting Lightning-rod WAMP server...")
reactor.run()
""" global loop
# TEMPORARY ------------------------------------------------------ loop = asyncio.get_event_loop()
from subprocess import call component.start(loop)
LOG.debug("Unmounting...") loop.run_forever()
try:
mountPoint = "/opt/BBB"
# errorCode = self.libc.umount(mountPoint, None)
errorCode = call(["umount", "-l", mountPoint])
LOG.debug("Unmount " + mountPoint + " result: " + str(errorCode))
except Exception as msg:
result = "Unmounting error:", msg
LOG.debug(result)
# ------------------------------------------------------------------
"""
def stop(self): def stop(self):
LOG.info("Stopping WAMP agent server...") LOG.info("Stopping WAMP agent server...")
reactor.stop() # Canceling pending tasks and stopping the loop
asyncio.gather(*asyncio.Task.all_tasks()).cancel()
LOG.info("WAMP server stopped!") LOG.info("WAMP server stopped!")
@ -646,6 +629,9 @@ class LightningRod(object):
CONF(project='iotronic') CONF(project='iotronic')
logging.setup(CONF, DOMAIN) logging.setup(CONF, DOMAIN)
if CONF.debug:
txaio.start_logging(level="debug")
signal.signal(signal.SIGINT, self.stop_handler) signal.signal(signal.SIGINT, self.stop_handler)
LogoLR() LogoLR()
@ -664,9 +650,7 @@ class LightningRod(object):
def stop_handler(self, signum, frame): def stop_handler(self, signum, frame):
LOG.info("LR is shutting down...") LOG.info("LR is shutting down...")
self.w.stop() self.w.stop()
Bye() Bye()

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "MDSLAB Team" __author__ = "Nicola Peditto <npeditto@unime.it"
import abc import abc
import six import six

View File

@ -13,10 +13,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import imp import imp
import inspect import inspect
import os import os
from twisted.internet.defer import inlineCallbacks
from iotronic_lightningrod.config import package_path from iotronic_lightningrod.config import package_path
from iotronic_lightningrod.lightningrod import RPC_devices from iotronic_lightningrod.lightningrod import RPC_devices
@ -39,7 +40,7 @@ def deviceWampRegister(dev_meth_list, board):
# LOG.info(" - " + str(meth[0])) # LOG.info(" - " + str(meth[0]))
rpc_addr = u'iotronic.' + board.uuid + '.' + meth[0] rpc_addr = u'iotronic.' + board.uuid + '.' + meth[0]
# LOG.debug(" --> " + str(rpc_addr)) # LOG.debug(" --> " + str(rpc_addr))
SESSION.register(inlineCallbacks(meth[1]), rpc_addr) SESSION.register(meth[1], rpc_addr)
LOG.info(" --> " + str(meth[0]) + " registered!") LOG.info(" --> " + str(meth[0]) + " registered!")

View File

@ -12,30 +12,31 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from __future__ import absolute_import from __future__ import absolute_import
__author__ = "Nicola Peditto <npeditto@unime.it"
from datetime import datetime from datetime import datetime
import imp import imp
import inspect import inspect
import json import json
import os import os
from Queue import Queue import queue
import shutil import shutil
import time import time
from twisted.internet.defer import returnValue
from iotronic_lightningrod.config import iotronic_home
from iotronic_lightningrod.modules import Module from iotronic_lightningrod.modules import Module
from iotronic_lightningrod.plugins import PluginSerializer from iotronic_lightningrod.plugins import PluginSerializer
import iotronic_lightningrod.wampmessage as WM import iotronic_lightningrod.wampmessage as WM
from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF
PLUGINS_THRS = {} PLUGINS_THRS = {}
PLUGINS_CONF_FILE = iotronic_home + "/plugins.json" PLUGINS_CONF_FILE = CONF.lightningrod_home + "/plugins.json"
def getFuncName(): def getFuncName():
@ -144,7 +145,8 @@ def RebootOnBootPlugins():
LOG.info(" - Rebooting plugin " + plugin_uuid) LOG.info(" - Rebooting plugin " + plugin_uuid)
plugin_home = iotronic_home + "/plugins/" + plugin_uuid plugin_home = CONF.lightningrod_home + "/plugins/" \
+ plugin_uuid
plugin_filename = plugin_home + "/" + plugin_uuid + ".py" plugin_filename = plugin_home + "/" + plugin_uuid + ".py"
plugin_params_file = \ plugin_params_file = \
plugin_home + "/" + plugin_uuid + ".json" plugin_home + "/" + plugin_uuid + ".json"
@ -224,7 +226,7 @@ class PluginManager(Module.Module):
# Reboot boot enabled plugins # Reboot boot enabled plugins
RebootOnBootPlugins() RebootOnBootPlugins()
def PluginInject(self, plugin, onboot): async def PluginInject(self, plugin, onboot):
"""Plugin injection procedure into the board: """Plugin injection procedure into the board:
1. get Plugin files 1. get Plugin files
@ -254,7 +256,8 @@ class PluginManager(Module.Module):
loaded = ser.deserialize_entity(code) loaded = ser.deserialize_entity(code)
# LOG.debug("- plugin loaded code:\n" + loaded) # LOG.debug("- plugin loaded code:\n" + loaded)
plugin_path = iotronic_home + "/plugins/" + plugin_uuid + "/" plugin_path = CONF.lightningrod_home \
+ "/plugins/" + plugin_uuid + "/"
plugin_filename = plugin_path + plugin_uuid + ".py" plugin_filename = plugin_path + plugin_uuid + ".py"
# Plugin folder creation if does not exist # Plugin folder creation if does not exist
@ -311,17 +314,18 @@ class PluginManager(Module.Module):
json.dump(plugins_conf, f, indent=4) json.dump(plugins_conf, f, indent=4)
LOG.info(" - " + message) LOG.info(" - " + message)
w_msg = yield WM.WampSuccess(message) w_msg = await WM.WampSuccess(message)
except Exception as err: except Exception as err:
message = "Plugin injection error: " + str(err) message = "Plugin injection error: " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
returnValue(w_msg.serialize()) return w_msg.serialize()
def PluginStart(self, plugin_uuid, parameters=None): return w_msg.serialize()
async def PluginStart(self, plugin_uuid, parameters=None):
"""To start an asynchronous plugin; """To start an asynchronous plugin;
the plugin will run until the PluginStop is called. the plugin will run until the PluginStop is called.
@ -352,12 +356,12 @@ class PluginManager(Module.Module):
message = "ALREADY STARTED!" message = "ALREADY STARTED!"
LOG.warning(" - Plugin " LOG.warning(" - Plugin "
+ plugin_uuid + " already started!") + plugin_uuid + " already started!")
w_msg = yield WM.WampError(message) w_msg = await WM.WampError(message)
else: else:
plugin_home = \ plugin_home = \
iotronic_home + "/plugins/" + plugin_uuid CONF.lightningrod_home + "/plugins/" + plugin_uuid
plugin_filename = \ plugin_filename = \
plugin_home + "/" + plugin_uuid + ".py" plugin_home + "/" + plugin_uuid + ".py"
plugin_params_file = \ plugin_params_file = \
@ -404,32 +408,33 @@ class PluginManager(Module.Module):
response = "STARTED" response = "STARTED"
LOG.info(" - " + worker.complete(rpc_name, response)) LOG.info(" - " + worker.complete(rpc_name, response))
w_msg = yield WM.WampSuccess(response) w_msg = await WM.WampSuccess(response)
else: else:
message = \ message = \
rpc_name + " - ERROR " \ rpc_name + " - ERROR " \
+ plugin_filename + " does not exist!" + plugin_filename + " does not exist!"
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(message) w_msg = await WM.WampError(message)
else: else:
message = "Plugin " + plugin_uuid \ message = "Plugin " + plugin_uuid \
+ " does not exist in this board!" + " does not exist in this board!"
LOG.warning(" - " + message) LOG.warning(" - " + message)
w_msg = yield WM.WampError(message) w_msg = await WM.WampError(message)
except Exception as err: except Exception as err:
message = \ message = \
rpc_name + " - ERROR - plugin (" + plugin_uuid + ") - " \ rpc_name + " - ERROR - plugin (" + plugin_uuid + ") - " \
+ str(err) + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
returnValue(w_msg.serialize()) return w_msg.serialize()
def PluginStop(self, plugin_uuid, parameters=None): return w_msg.serialize()
async def PluginStop(self, plugin_uuid, parameters=None):
"""To stop an asynchronous plugin """To stop an asynchronous plugin
:param plugin_uuid: ID of plufin to stop :param plugin_uuid: ID of plufin to stop
@ -459,13 +464,13 @@ class PluginManager(Module.Module):
if 'delay' in parameters: if 'delay' in parameters:
time.sleep(delay) time.sleep(delay)
yield worker.stop() await worker.stop()
del PLUGINS_THRS[plugin_uuid] del PLUGINS_THRS[plugin_uuid]
message = "STOPPED" message = "STOPPED"
LOG.info(" - " + worker.complete(rpc_name, message)) LOG.info(" - " + worker.complete(rpc_name, message))
w_msg = yield WM.WampSuccess(message) w_msg = await WM.WampSuccess(message)
else: else:
message = \ message = \
@ -473,26 +478,27 @@ class PluginManager(Module.Module):
+ " - ERROR - plugin (" + plugin_uuid \ + " - ERROR - plugin (" + plugin_uuid \
+ ") is instantiated but is not running anymore!" + ") is instantiated but is not running anymore!"
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(message) w_msg = await WM.WampError(message)
else: else:
message = \ message = \
rpc_name + " - WARNING " \ rpc_name + " - WARNING " \
+ plugin_uuid + " is not running!" + plugin_uuid + " is not running!"
LOG.warning(" - " + message) LOG.warning(" - " + message)
w_msg = yield WM.WampWarning(message) w_msg = await WM.WampWarning(message)
except Exception as err: except Exception as err:
message = \ message = \
rpc_name \ rpc_name \
+ " - ERROR - plugin (" + plugin_uuid + ") - " + str(err) + " - ERROR - plugin (" + plugin_uuid + ") - " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
returnValue(w_msg.serialize()) return w_msg.serialize()
def PluginCall(self, plugin_uuid, parameters=None): return w_msg.serialize()
async def PluginCall(self, plugin_uuid, parameters=None):
"""To execute a synchronous plugin into the board """To execute a synchronous plugin into the board
:param plugin_uuid: :param plugin_uuid:
@ -512,11 +518,12 @@ class PluginManager(Module.Module):
message = "Plugin " + plugin_uuid + " already started!" message = "Plugin " + plugin_uuid + " already started!"
LOG.warning(" - " + message) LOG.warning(" - " + message)
w_msg = yield WM.WampWarning(message) w_msg = WM.WampWarning(message)
else: else:
plugin_home = iotronic_home + "/plugins/" + plugin_uuid plugin_home = CONF.lightningrod_home \
+ "/plugins/" + plugin_uuid
plugin_filename = plugin_home + "/" + plugin_uuid + ".py" plugin_filename = plugin_home + "/" + plugin_uuid + ".py"
plugin_params_file = plugin_home + "/" + plugin_uuid + ".json" plugin_params_file = plugin_home + "/" + plugin_uuid + ".json"
@ -532,14 +539,15 @@ class PluginManager(Module.Module):
LOG.info(" - Plugin " + plugin_uuid + " imported!") LOG.info(" - Plugin " + plugin_uuid + " imported!")
q_result = Queue() q_result = queue.Queue()
except Exception as err: except Exception as err:
message = "Error importing plugin " \ message = "Error importing plugin " \
+ plugin_filename + ": " + str(err) + plugin_filename + ": " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = WM.WampError(str(err))
returnValue(w_msg.serialize())
return w_msg.serialize()
try: try:
@ -575,33 +583,35 @@ class PluginManager(Module.Module):
response = q_result.get() response = q_result.get()
LOG.info(" - " + worker.complete(rpc_name, response)) LOG.info(" - " + worker.complete(rpc_name, response))
w_msg = yield WM.WampSuccess(response) w_msg = WM.WampSuccess(response)
except Exception as err: except Exception as err:
message = "Error spawning plugin " \ message = "Error spawning plugin " \
+ plugin_filename + ": " + str(err) + plugin_filename + ": " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = WM.WampError(str(err))
returnValue(w_msg.serialize())
return w_msg.serialize()
else: else:
message = \ message = \
rpc_name \ rpc_name \
+ " - ERROR " + plugin_filename + " does not exist!" + " - ERROR " + plugin_filename + " does not exist!"
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(message) w_msg = WM.WampError(message)
except Exception as err: except Exception as err:
message = \ message = \
rpc_name \ rpc_name \
+ " - ERROR - plugin (" + plugin_uuid + ") - " + str(err) + " - ERROR - plugin (" + plugin_uuid + ") - " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = WM.WampError(str(err))
returnValue(w_msg.serialize())
returnValue(w_msg.serialize()) return w_msg.serialize()
def PluginRemove(self, plugin_uuid): return w_msg.serialize()
async def PluginRemove(self, plugin_uuid):
"""To remove a plugin from the board """To remove a plugin from the board
:param plugin_uuid: :param plugin_uuid:
@ -613,15 +623,16 @@ class PluginManager(Module.Module):
LOG.info("RPC " + rpc_name + " for plugin " + plugin_uuid) LOG.info("RPC " + rpc_name + " for plugin " + plugin_uuid)
plugin_path = iotronic_home + "/plugins/" + plugin_uuid + "/" plugin_path = CONF.lightningrod_home + "/plugins/" + plugin_uuid + "/"
if os.path.exists(plugin_path) is False \ if os.path.exists(plugin_path) is False \
or os.path.exists(PLUGINS_CONF_FILE) is False: or os.path.exists(PLUGINS_CONF_FILE) is False:
message = "Plugin paths or files do not exist!" message = "Plugin paths or files do not exist!"
LOG.error(message) LOG.error(message)
w_msg = yield WM.WampError(message) w_msg = await WM.WampError(message)
returnValue(w_msg.serialize())
return w_msg.serialize()
else: else:
@ -641,8 +652,9 @@ class PluginManager(Module.Module):
message = "Removing plugin's files error in " \ message = "Removing plugin's files error in " \
+ plugin_path + ": " + str(err) + plugin_path + ": " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
return w_msg.serialize()
# Remove from plugins.json file its configuration # Remove from plugins.json file its configuration
try: try:
@ -678,22 +690,25 @@ class PluginManager(Module.Module):
+ plugin_uuid + " already removed!" + plugin_uuid + " already removed!"
LOG.warning(" - " + message) LOG.warning(" - " + message)
w_msg = yield WM.WampSuccess(message) w_msg = await WM.WampSuccess(message)
returnValue(w_msg.serialize())
return w_msg.serialize()
except Exception as err: except Exception as err:
message = "Updating plugins.json error: " + str(err) message = "Updating plugins.json error: " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
return w_msg.serialize()
except Exception as err: except Exception as err:
message = "Plugin removing error: {0}".format(err) message = "Plugin removing error: {0}".format(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
def PluginReboot(self, plugin_uuid, parameters=None): return w_msg.serialize()
async def PluginReboot(self, plugin_uuid, parameters=None):
"""To reboot an asynchronous plugin (callable = false) into the board. """To reboot an asynchronous plugin (callable = false) into the board.
:return: return a response to RPC request :return: return a response to RPC request
@ -710,7 +725,7 @@ class PluginManager(Module.Module):
try: try:
plugin_home = iotronic_home + "/plugins/" + plugin_uuid plugin_home = CONF.lightningrod_home + "/plugins/" + plugin_uuid
plugin_filename = plugin_home + "/" + plugin_uuid + ".py" plugin_filename = plugin_home + "/" + plugin_uuid + ".py"
plugin_params_file = plugin_home + "/" + plugin_uuid + ".json" plugin_params_file = plugin_home + "/" + plugin_uuid + ".json"
@ -774,23 +789,24 @@ class PluginManager(Module.Module):
message = "REBOOTED" message = "REBOOTED"
LOG.info(" - " + worker.complete(rpc_name, message)) LOG.info(" - " + worker.complete(rpc_name, message))
w_msg = yield WM.WampSuccess(message) w_msg = await WM.WampSuccess(message)
else: else:
message = "ERROR '" + plugin_filename + "' does not exist!" message = "ERROR '" + plugin_filename + "' does not exist!"
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(message) w_msg = await WM.WampError(message)
except Exception as err: except Exception as err:
message = "Error rebooting plugin '" \ message = "Error rebooting plugin '" \
+ plugin_uuid + "': " + str(err) + plugin_uuid + "': " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
returnValue(w_msg.serialize()) return w_msg.serialize()
def PluginStatus(self, plugin_uuid): return w_msg.serialize()
async def PluginStatus(self, plugin_uuid):
"""Check status thread plugin """Check status thread plugin
:param plugin_uuid: :param plugin_uuid:
@ -814,20 +830,21 @@ class PluginManager(Module.Module):
result = "DEAD" result = "DEAD"
LOG.info(" - " + worker.complete(rpc_name, result)) LOG.info(" - " + worker.complete(rpc_name, result))
w_msg = yield WM.WampSuccess(result) w_msg = await WM.WampSuccess(result)
else: else:
result = "DEAD" result = "DEAD"
LOG.info(" - " + rpc_name + " result for " LOG.info(" - " + rpc_name + " result for "
+ plugin_uuid + ": " + result) + plugin_uuid + ": " + result)
w_msg = yield WM.WampSuccess(result) w_msg = await WM.WampSuccess(result)
except Exception as err: except Exception as err:
message = \ message = \
rpc_name \ rpc_name \
+ " - ERROR - plugin (" + plugin_uuid + ") - " + str(err) + " - ERROR - plugin (" + plugin_uuid + ") - " + str(err)
LOG.error(" - " + message) LOG.error(" - " + message)
w_msg = yield WM.WampError(str(err)) w_msg = await WM.WampError(str(err))
returnValue(w_msg.serialize())
returnValue(w_msg.serialize()) return w_msg.serialize()
return w_msg.serialize()

View File

@ -13,10 +13,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import asyncio
from autobahn.twisted.util import sleep
from iotronic_lightningrod.modules import Module from iotronic_lightningrod.modules import Module
from twisted.internet.defer import returnValue
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -28,15 +29,15 @@ class Test(Module.Module):
super(Test, self).__init__("Test", board) super(Test, self).__init__("Test", board)
def test_function(self): async def test_function(self):
import random import random
s = random.uniform(0.5, 1.5) s = random.uniform(0.5, 1.5)
yield sleep(s) await asyncio.sleep(s)
result = "DEVICE test result: TEST!" result = "DEVICE test result: TEST!"
LOG.info(result) LOG.info(result)
returnValue(result) return result
def add(self, x, y): async def add(self, x, y):
c = yield x + y c = await x + y
LOG.info("DEVICE add result: " + str(c)) LOG.info("DEVICE add result: " + str(c))
returnValue(c) return c

View File

@ -13,22 +13,21 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
from autobahn.twisted.util import sleep import asyncio
from iotronic_lightningrod.config import entry_points_name
from iotronic_lightningrod.modules import Module
import pkg_resources import pkg_resources
from six import moves from six import moves
from stevedore import extension from stevedore import extension
import sys import sys
from twisted.internet.defer import returnValue
from iotronic_lightningrod.config import entry_points_name
from iotronic_lightningrod.lightningrod import SESSION
from iotronic_lightningrod.modules import Module
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
from iotronic_lightningrod.lightningrod import SESSION
def refresh_stevedore(namespace=None): def refresh_stevedore(namespace=None):
"""Trigger reload of entry points. """Trigger reload of entry points.
@ -62,17 +61,17 @@ class Utility(Module.Module):
def finalize(self): def finalize(self):
pass pass
def hello(self, client_name, message): async def hello(self, client_name, message):
import random import random
s = random.uniform(0.5, 3.0) s = random.uniform(0.5, 3.0)
yield sleep(s) await asyncio.sleep(s)
result = "Hello by board to Conductor " + client_name + \ result = "Hello by board to Conductor " + client_name + \
" that said me " + message + " - Time: " + '%.2f' % s " that said me " + message + " - Time: " + '%.2f' % s
LOG.info("DEVICE hello result: " + str(result)) LOG.info("DEVICE hello result: " + str(result))
returnValue(result) return result
def plug_and_play(self, new_module, new_class): async def plug_and_play(self, new_module, new_class):
LOG.info("LR modules loaded:\n\t" + new_module) LOG.info("LR modules loaded:\n\t" + new_module)
# Updating entry_points # Updating entry_points
@ -92,28 +91,28 @@ class Utility(Module.Module):
for ep in pkg_resources.iter_entry_points(group='s4t.modules'): for ep in pkg_resources.iter_entry_points(group='s4t.modules'):
named_objects.update({ep.name: ep.load()}) named_objects.update({ep.name: ep.load()})
yield named_objects await named_objects
SESSION.disconnect() SESSION.disconnect()
returnValue(str(named_objects)) return str(named_objects)
def changeConf(self, conf): async def changeConf(self, conf):
yield self.board.getConf(conf) await self.board.getConf(conf)
self.board.setUpdateTime() self.board.setUpdateTime()
result = "Board configuration changed!" result = "Board configuration changed!"
LOG.info("PROVISIONING RESULT: " + str(result)) LOG.info("PROVISIONING RESULT: " + str(result))
returnValue(result) return result
def destroyNode(self, conf): async def destroyNode(self, conf):
yield self.board.setConf(conf) await self.board.setConf(conf)
result = "Board configuration cleaned!" result = "Board configuration cleaned!"
LOG.info("DESTROY RESULT: " + str(result)) LOG.info("DESTROY RESULT: " + str(result))
returnValue(result) return result

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import errno import errno
from fuse import FuseOSError from fuse import FuseOSError
import os import os

View File

@ -12,15 +12,14 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from __future__ import with_statement from __future__ import with_statement
__author__ = "Nicola Peditto <npeditto@unime.it"
import errno import errno
import os import os
from subprocess import call from subprocess import call
import threading import threading
from twisted.internet.defer import inlineCallbacks
from twisted.internet.defer import returnValue
# Iotronic imports # Iotronic imports
from iotronic_lightningrod.modules import Module from iotronic_lightningrod.modules import Module
@ -71,7 +70,7 @@ class VfsManager(Module.Module):
result = "Mounting error:", msg result = "Mounting error:", msg
print(result) print(result)
yield returnValue(result) return result
def unmountLocal(self, mountPoint): def unmountLocal(self, mountPoint):
@ -88,7 +87,7 @@ class VfsManager(Module.Module):
result = "Unmounting error:", msg result = "Unmounting error:", msg
print(result) print(result)
yield returnValue(result) return result
def mountRemote(self, def mountRemote(self,
mountSource, mountSource,
@ -116,7 +115,7 @@ class VfsManager(Module.Module):
result = "Mounting error:", msg result = "Mounting error:", msg
print(result) print(result)
yield returnValue(result) return result
def unmountRemote(self, mountPoint): def unmountRemote(self, mountPoint):
@ -133,7 +132,7 @@ class VfsManager(Module.Module):
result = "Unmounting error:", msg result = "Unmounting error:", msg
print(result) print(result)
yield returnValue(result) return result
class MounterLocal(threading.Thread): class MounterLocal(threading.Thread):
@ -209,12 +208,11 @@ class MounterRemote(threading.Thread):
LOG.error("Mounting FUSE error: " + str(msg)) LOG.error("Mounting FUSE error: " + str(msg))
@inlineCallbacks async def makeCall(msg=None, agent=None, session=None):
def makeCall(msg=None, agent=None, session=None):
rpc_addr = str(agent) + '.stack4things.echo' rpc_addr = str(agent) + '.stack4things.echo'
LOG.debug("VFS - I'm calling " + rpc_addr) LOG.debug("VFS - I'm calling " + rpc_addr)
try: try:
res = yield session.call(rpc_addr, msg) res = await session.call(rpc_addr, msg)
LOG.info("NOTIFICATION " + str(res)) LOG.info("NOTIFICATION " + str(res))
except Exception as e: except Exception as e:
LOG.warning("NOTIFICATION error: {0}".format(e)) LOG.warning("NOTIFICATION error: {0}".format(e))

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import abc import abc
import six import six

View File

@ -13,14 +13,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import cPickle as pickle __author__ = "Nicola Peditto <npeditto@unime.it"
# import oslo_messaging
import _pickle as pickle
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# class ObjectSerializer(oslo_messaging.NoOpSerializer):
class ObjectSerializer(object): class ObjectSerializer(object):
"""A PluginObject-aware Serializer. """A PluginObject-aware Serializer.
@ -31,7 +31,6 @@ class ObjectSerializer(object):
and RpcDispatcher objects. and RpcDispatcher objects.
""" """
# def serialize_entity(self, context, entity):
def serialize_entity(self, entity): def serialize_entity(self, entity):
dumped = pickle.dumps(entity, 0) dumped = pickle.dumps(entity, 0)
@ -40,10 +39,9 @@ class ObjectSerializer(object):
return dumped return dumped
# def deserialize_entity(self, context, entity):
def deserialize_entity(self, entity): def deserialize_entity(self, entity):
loaded = pickle.loads(str(entity)) loaded = pickle.loads(str.encode(entity))
# LOG.debug(" - plugin deserialized") # LOG.debug(" - plugin deserialized")

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import httplib2 import httplib2
import json import json

View File

@ -1,70 +1,70 @@
# Copyright 2017 MDSLAB - University of Messina # Copyright 2017 MDSLAB - University of Messina
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from iotronic_lightningrod.devices.gpio import yun from iotronic_lightningrod.devices.gpio import yun
from iotronic_lightningrod.plugins import Plugin from iotronic_lightningrod.plugins import Plugin
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# User imports # User imports
import datetime import datetime
import math import math
import time import time
ADCres = 1023.0 ADCres = 1023.0
Beta = 3950 Beta = 3950
Kelvin = 273.15 Kelvin = 273.15
Rb = 10000 Rb = 10000
Ginf = 120.6685 Ginf = 120.6685
# User global variables # User global variables
resource_id = "" # temperature resource id resource_id = "" # temperature resource id
action_URL = "http://smartme-data.unime.it/api/3/action/datastore_upsert" action_URL = "http://smartme-data.unime.it/api/3/action/datastore_upsert"
api_key = '' api_key = ''
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
'Authorization': "" + api_key + "" 'Authorization': "" + api_key + ""
} }
polling_time = 10 polling_time = 10
class Worker(Plugin.Plugin): class Worker(Plugin.Plugin):
def __init__(self, name, params=None): def __init__(self, name, params=None):
super(Worker, self).__init__(name, params) super(Worker, self).__init__(name, params)
def run(self): def run(self):
device = yun.YunGpio() device = yun.YunGpio()
while (self._is_running): while (self._is_running):
voltage = device._readVoltage("A0") voltage = device._readVoltage("A0")
Rthermistor = float(Rb) * (float(ADCres) / float(voltage) - 1) Rthermistor = float(Rb) * (float(ADCres) / float(voltage) - 1)
rel_temp = float(Beta) / (math.log( rel_temp = float(Beta) / (math.log(
float(Rthermistor) * float(Ginf)) float(Rthermistor) * float(Ginf))
) )
temp = rel_temp - Kelvin temp = rel_temp - Kelvin
m_value = str(temp) m_value = str(temp)
m_timestamp = datetime.datetime.now().strftime( m_timestamp = datetime.datetime.now().strftime(
'%Y-%m-%dT%H:%M:%S.%f' '%Y-%m-%dT%H:%M:%S.%f'
) )
LOG.info(m_value + " - " + m_timestamp) LOG.info(m_value + " - " + m_timestamp)
time.sleep(polling_time) time.sleep(polling_time)

View File

@ -0,0 +1,3 @@
{
"pin":0
}

View File

@ -0,0 +1,37 @@
# 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
device = API.getBoardGpio()
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))
device._setGPIOs("D13", "out", str(self.params['pin']))
LOG.info("Plugin " + self.name + " process completed!")
self.q_result.put("Led D13: " + str(self.params['pin']))

View File

@ -14,6 +14,7 @@
# under the License. # under the License.
from iotronic_lightningrod.plugins import Plugin from iotronic_lightningrod.plugins import Plugin
# from iotronic_lightningrod.plugins import pluginApis as API
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)

View File

@ -1,13 +0,0 @@
{
"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}

View File

@ -1,409 +0,0 @@
# 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!")

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
__author__ = "Nicola Peditto <npeditto@unime.it"
import json import json
SUCCESS = 'SUCCESS' SUCCESS = 'SUCCESS'

View File

@ -2,11 +2,11 @@
# of appearance. Changing the order has an impact on the overall integration # of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later. # process, which may cause wedges in the gate later.
pbr>=2.0.0 # Apache-2.0 pbr>=2.0.0,!=2.1.0 # Apache-2.0
autobahn>=0.10.1 # MIT License
six>=1.10.0 # MIT
httplib2>=0.9.1 # MIT
# Openstack modules # Openstack modules
oslo.config>=3.22.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0
autobahn>=0.10.1 # MIT License
httplib2>=0.7.5 # MIT

View File

@ -16,8 +16,7 @@ classifier =
Programming Language :: Python :: 2 Programming Language :: Python :: 2
Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.7
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.4
[files] [files]
packages = packages =
@ -58,3 +57,7 @@ s4t.modules =
utility = iotronic_lightningrod.modules.utils:Utility utility = iotronic_lightningrod.modules.utils:Utility
plugin = iotronic_lightningrod.modules.plugin_manager:PluginManager plugin = iotronic_lightningrod.modules.plugin_manager:PluginManager
device = iotronic_lightningrod.modules.device_manager:DeviceManager device = iotronic_lightningrod.modules.device_manager:DeviceManager
[options]
build_scripts =
executable= /usr/bin/env python

36
tox.ini
View File

@ -1,18 +1,25 @@
[tox] [tox]
minversion = 2.0 minversion = 2.3.1
envlist = py35,py27,pypy,pep8 envlist = py35,pep8
skipsdist = True skipsdist = True
[testenv] [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 = setenv =
VIRTUAL_ENV={envdir} VIRTUAL_ENV={envdir}
PYTHONWARNINGS=default::DeprecationWarning PYTHONWARNINGS=default::DeprecationWarning
LANGUAGE=en_US
LC_ALL=en_US.utf-8
whitelist_externals = bash
find
rm
usedevelop = True
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
deps = -r{toxinidir}/test-requirements.txt deps = -r{toxinidir}/test-requirements.txt
commands = find . -type f -name "*.pyc" -delete commands =
find . -type f -name "*.pyc" -delete
[testenv:pep8] [testenv:pep8]
basepython = python2.7
commands = flake8 {posargs} commands = flake8 {posargs}
[testenv:venv] [testenv:venv]
@ -22,19 +29,26 @@ commands = {posargs}
commands = python setup.py test --coverage --testr-args='{posargs}' commands = python setup.py test --coverage --testr-args='{posargs}'
[testenv:docs] [testenv:docs]
commands = python setup.py build_sphinx commands =
rm -fr doc/build
python setup.py build_sphinx
[testenv:releasenotes] [testenv:releasenotes]
commands = commands =
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[testenv:debug] [testenv:py35]
commands = oslo_debug_helper {posargs} basepython = python3.5
[flake8] [flake8]
# TODO(dmllr): Analyze or fix the warnings blacklisted below
# E711 comparison to None should be 'if cond is not None:'
# E712 comparison to True should be 'if cond is True:' or 'if cond:'
# H404 multi line docstring should start with a summary
# H405 multi line docstring summary not separated with an empty line
# E123, E125 skipped as they are invalid PEP-8. # E123, E125 skipped as they are invalid PEP-8.
show-source = True show-source = True
ignore = E123,E125
builtins = _ builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build ignore = E711,E712,H404,H405,E123,E125,E901
exclude = .venv,.git,.tox,dist,doc,etc,*lib/python*,*egg,build,iotronic_lightningrod/plugins/plugins_examples/