from meta_protocol import ProtocolMeta
import inspect
from twisted.internet import defer
from parlay.server.broker import run_in_broker, run_in_thread
from parlay.protocols.utils import timeout,PrivateDeferred
from collections import deque
[docs]class BaseProtocol(object):
"""
This the base protocol that *all* parlay protocols must inherit from. Subclass this to make custom protocols to
talk to 3rd party equipment.
"""
__metaclass__ = ProtocolMeta
def __init__(self):
self._new_data = PrivateDeferred()
self.items = getattr(self, "items", [])
@classmethod
[docs] def open(cls, adapter):
"""
Override this with a generic method that will open the protocol.
The first argument must be the broker, the rest will be parameters that the user can set. Default arguments will
be sent to the UI and can be overwritten bu the user
It must return the built protocol (subclass of Protocol) that will be registered with the Broker
Be sure to decorate it with @classmethod
e.g.
@classmethod
def open(cls, adapter, ip, port=8080):
return protocol(ip,port)
"""
raise NotImplementedError()
[docs] def close(self):
"""
Override this with a generic method that will close the protocol.
e.g.
@classmethod
def close():
"""
raise NotImplementedError()
@classmethod
[docs] def get_open_params(cls):
"""
Returns the params for the cls.open() class method. Feel free to overwrite this in a sub-class if this default
implementation doesn't fit your protocol's needs.
:return: A list of parameter names
:rtype: list
"""
# get the arguments
# (don't use argspec because it is needlesly strict and fails on perfectly valid Cython functions)
args, varargs, varkw = inspect.getargs(cls.open.func_code)
return args[2:] # remove 'cls' and 'broker'
@classmethod
[docs] def get_open_params_defaults(cls):
"""
return the defaults for parameters to the cls.open() using inspect. Feel free to overwrite this in a sub-class
if this default implementation doesn't fit your protocol's needs.
:return: A dictionary of parameter names -> default values.
:rtype: dict
"""
# (don't use argspec because it is needlesly strict and fails on perfectly valid Cython functions)
defaults = cls.open.func_defaults if cls.open.func_defaults is not None else []
params = cls.get_open_params()
# cut params to only the last x (defaults are always at the end of the signature)
params = params[len(params) - len(defaults):]
return dict(zip(params, defaults))
[docs] def get_discovery(self):
"""
This will get called when a discovery message is sent out. Return a deferred that will be called back with
all attached:
item types, message types, and connected item instances
"""
return {'TEMPLATE': 'Protocol',
'NAME': str(self),
'protocol_type': getattr(self, "_protocol_type_name", "UNKNOWN"),
'CHILDREN': [x.get_discovery() for x in self.items]}
[docs] def get_new_data_wait_handler(self):
return WaitHandler(self._new_data)
@run_in_broker
[docs] def got_new_data(self, data):
"""
Call this when you have new data and want to pass it to any waiting Items
"""
print data
old_new_data = self._new_data
# setup the new data in case it causes a callback to fire
self._new_data = PrivateDeferred()
old_new_data._callback(data)
[docs]class WaitHandler(object):
"""
An Object used to do safe cross thread waits on deferreds
"""
def __init__(self, deferred):
self._deferred = deferred
@run_in_broker
[docs] def addCallback(self, fn, async=True):
"""
Add a callback when you get new data
"""
if not async:
fn = run_in_thread(fn)
self._deferred.addCallback(fn)
@run_in_broker
[docs] def wait(self, timeout_secs=None):
"""
Call this to wait until there is data from the protocol.
If threaded: Will block. Return value is serial line data
If Async : Will not block. Return value is Deferred that will be called back with data
:param timeout_secs : Timeout if you don't get data in time. None if no timeout
:type timeout_secs : int|None
"""
return timeout(self._deferred, timeout_secs)