Merge branch 'feature/refector-handler' into develop

pull/8/head
Nicolas Jouanin 2015-07-05 22:30:20 +02:00
commit 6009129cf3
5 zmienionych plików z 175 dodań i 239 usunięć

Wyświetl plik

@ -19,7 +19,6 @@ _defaults = {
'ping_delay': 1,
'default_qos': 0,
'default_retain': False,
'inflight-polling-interval': 1,
'subscriptions-polling-interval': 1,
}
@ -66,8 +65,8 @@ class MQTTClient:
:return:
"""
self.logger = logging.getLogger(__name__)
self.config = config.copy()
self.config.update(_defaults)
self.config = _defaults
self.config.update(config)
if client_id is not None:
self.client_id = client_id
else:
@ -98,8 +97,9 @@ class MQTTClient:
self.session = self._initsession(host, port, username, password, uri, cleansession)
self.logger.debug("Connect with session parameters: %s" % self.session)
yield from self._connect_coro()
return_code = yield from self._connect_coro()
self.machine.connect_success()
return return_code
except MachineError:
msg = "Connect call incompatible with client current state '%s'" % self.machine.current_state
self.logger.warn(msg)
@ -173,7 +173,7 @@ class MQTTClient:
@asyncio.coroutine
def subscribe(self, topics):
yield from self._handler.mqtt_subscribe(topics, self.session.next_packet_id)
return (yield from self._handler.mqtt_subscribe(topics, self.session.next_packet_id))
@asyncio.coroutine
def unsubscribe(self, topics):
@ -194,6 +194,7 @@ class MQTTClient:
self.session.state = SessionState.CONNECTED
self.logger.debug("connected to %s:%s" % (self.session.remote_address, self.session.remote_port))
return return_code
except Exception as e:
self.session.state = SessionState.DISCONNECTED
raise e

Wyświetl plik

@ -3,11 +3,13 @@
# See the file license.txt for copying permission.
import logging
import asyncio
from asyncio import futures
from hbmqtt.mqtt.packet import MQTTFixedHeader
from hbmqtt.mqtt import packet_class
from hbmqtt.errors import NoDataException
from hbmqtt.mqtt.packet import PacketType
from hbmqtt.mqtt.connect import ConnectVariableHeader, ConnectPacket, ConnectPayload
from hbmqtt.mqtt.connack import ConnackPacket
from hbmqtt.mqtt.disconnect import DisconnectPacket
from hbmqtt.mqtt.pingreq import PingReqPacket
from hbmqtt.mqtt.publish import PublishPacket
@ -16,16 +18,17 @@ from hbmqtt.mqtt.puback import PubackPacket
from hbmqtt.mqtt.pubrec import PubrecPacket
from hbmqtt.mqtt.pubcomp import PubcompPacket
from hbmqtt.mqtt.subscribe import SubscribePacket
from hbmqtt.mqtt.suback import SubackPacket
from hbmqtt.mqtt.unsubscribe import UnsubscribePacket
from hbmqtt.mqtt.unsuback import UnsubackPacket
from hbmqtt.session import Session
from blinker import Signal
from transitions import Machine, MachineError
class InFlightMessage:
states = ['new', 'published', 'acknowledged', 'received', 'released', 'completed']
def __init__(self, packet_id, qos):
self.packet_id = packet_id
def __init__(self, packet, qos):
self.packet = packet
self.qos = qos
self._init_states()
@ -44,8 +47,6 @@ class ProtocolHandler:
"""
Class implementing the MQTT communication protocol using asyncio features
"""
packet_sent = Signal()
packet_received = Signal()
def __init__(self, session: Session, config, loop=None):
self.logger = logging.getLogger(__name__)
@ -57,11 +58,8 @@ class ProtocolHandler:
self._loop = loop
self._reader_task = None
self._writer_task = None
self._inflight_task = None
self._reader_ready = asyncio.Event(loop=self._loop)
self._writer_ready = asyncio.Event(loop=self._loop)
self._inflight_ready = asyncio.Event(loop=self._loop)
self._inflight_changed = asyncio.Condition(loop=self._loop)
self._running = False
@ -72,6 +70,10 @@ class ProtocolHandler:
for p in PacketType:
self.incoming_queues[p] = asyncio.Queue()
self.outgoing_queue = asyncio.Queue()
self._puback_waiters = dict()
self._pubrec_waiters = dict()
self._pubrec_waiters = dict()
self._pubcomp_waiters = dict()
self.inflight_messages = dict()
@asyncio.coroutine
@ -79,66 +81,53 @@ class ProtocolHandler:
self._running = True
self._reader_task = asyncio.async(self._reader_coro(), loop=self._loop)
self._writer_task = asyncio.async(self._writer_coro(), loop=self._loop)
self._inflight_task = asyncio.async(self._inflight_coro(), loop=self._loop)
yield from asyncio.wait(
[self._reader_ready.wait(), self._writer_ready.wait(), self._inflight_ready.wait()], loop=self._loop)
[self._reader_ready.wait(), self._writer_ready.wait()], loop=self._loop)
self.logger.debug("Handler tasks started")
@asyncio.coroutine
def mqtt_publish(self, topic, message, packet_id, dup, qos, retain):
def qos_0_predicate():
ret = False
try:
if self.inflight_messages.get(packet_id).state == 'published':
ret = True
#self.logger.debug("qos_0 predicate return %s" % ret)
return ret
except KeyError:
return False
def qos_1_predicate():
ret = False
try:
if self.inflight_messages.get(packet_id).state == 'acknowledged':
ret = True
#self.logger.debug("qos_1 predicate return %s" % ret)
return ret
except KeyError:
return False
def qos_2_predicate():
ret = False
try:
if self.inflight_messages.get(packet_id).state == 'completed':
ret = True
#self.logger.debug("qos_1 predicate return %s" % ret)
return ret
except KeyError:
return False
if packet_id in self.inflight_messages:
self.logger.warn("A message with the same packet ID is already in flight")
packet = PublishPacket.build(topic, message, packet_id, dup, qos, retain)
yield from self.outgoing_queue.put(packet)
inflight_message = InFlightMessage(packet.variable_header.packet_id, qos)
inflight_message.publish()
inflight_message = InFlightMessage(packet, qos)
self.inflight_messages[packet.variable_header.packet_id] = inflight_message
yield from self._inflight_changed.acquire()
if qos == 0x00:
yield from self._inflight_changed.wait_for(qos_0_predicate)
inflight_message.publish()
if qos == 0x01:
yield from self._inflight_changed.wait_for(qos_1_predicate)
waiter = futures.Future(loop=self._loop)
self._puback_waiters[packet_id] = waiter
yield from waiter
inflight_message.acknowledge()
del self._puback_waiters[packet_id]
if qos == 0x02:
yield from self._inflight_changed.wait_for(qos_2_predicate)
self.inflight_messages.pop(packet.variable_header.packet_id)
self._inflight_changed.release()
return packet
# Wait for PUBREC
waiter = futures.Future(loop=self._loop)
self._pubrec_waiters[packet_id] = waiter
yield from waiter
del self._pubrec_waiters[packet_id]
inflight_message.receive()
# Send pubrel
pubrel = PubrelPacket.build(packet_id)
yield from self.outgoing_queue.put(pubrel)
inflight_message.release()
# Wait for pubcomp
waiter = futures.Future(loop=self._loop)
self._pubcomp_waiters[packet_id] = waiter
yield from waiter
del self._pubcomp_waiters[packet_id]
inflight_message.complete()
del self.inflight_messages[packet_id]
return inflight_message
@asyncio.coroutine
def stop(self):
self._running = False
self.session.reader.feed_eof()
yield from asyncio.wait([self._inflight_task, self._writer_task, self._reader_task], loop=self._loop)
yield from asyncio.wait([self._writer_task, self._reader_task], loop=self._loop)
@asyncio.coroutine
def _reader_coro(self):
@ -151,8 +140,21 @@ class ProtocolHandler:
cls = packet_class(fixed_header)
packet = yield from cls.from_stream(self.session.reader, fixed_header=fixed_header)
self.logger.debug(" <-in-- " + repr(packet))
yield from self.incoming_queues[packet.fixed_header.packet_type].put(packet)
self.packet_received.send(packet)
if packet.fixed_header.packet_type == PacketType.CONNACK:
yield from self.handle_connack(packet)
if packet.fixed_header.packet_type == PacketType.SUBACK:
yield from self.handle_suback(packet)
if packet.fixed_header.packet_type == PacketType.UNSUBACK:
yield from self.handle_unsuback(packet)
if packet.fixed_header.packet_type == PacketType.PUBACK:
yield from self.handle_puback(packet)
if packet.fixed_header.packet_type == PacketType.PUBREC:
yield from self.handle_pubrec(packet)
if packet.fixed_header.packet_type == PacketType.PUBCOMP:
yield from self.handle_pubcomp(packet)
else:
yield from self.incoming_queues[packet.fixed_header.packet_type].put(packet)
else:
self.logger.debug("No more data, stopping reader coro")
break
@ -168,16 +170,20 @@ class ProtocolHandler:
@asyncio.coroutine
def _writer_coro(self):
self.logger.debug("Starting writer coro")
keepalive_timeout = self.session.keep_alive - self.config['ping_delay']
while self._running:
try:
self._writer_ready.set()
packet = yield from asyncio.wait_for(self.outgoing_queue.get(), 5)
packet = yield from asyncio.wait_for(self.outgoing_queue.get(), keepalive_timeout)
yield from packet.to_stream(self.session.writer)
self.logger.debug(" -out-> " + repr(packet))
yield from self.session.writer.drain()
self.packet_sent.send(packet)
#self.outgoing_queue.task_done() # to be used with Python 3.5
except asyncio.TimeoutError as ce:
self.logger.debug("Output queue get timeout")
if self._running:
self.logger.debug("PING for keepalive")
self.handle_keepalive()
except Exception as e:
self.logger.warn("Unhandled exception in writer coro: %s" % e)
break
@ -195,49 +201,6 @@ class ProtocolHandler:
self.logger.warn("Unhandled exception in writer coro: %s" % e)
self.logger.debug("Writer coro stopped")
@asyncio.coroutine
def _inflight_coro(self):
self.logger.debug("Starting in-flight messages polling coro")
while self._running:
self._inflight_ready.set()
yield from asyncio.sleep(self.config['inflight-polling-interval'])
self.logger.debug("in-flight polling coro wake-up")
try:
while not self.incoming_queues[PacketType.PUBACK].empty():
packet = self.incoming_queues[PacketType.PUBACK].get_nowait()
packet_id = packet.variable_header.packet_id
inflight_message = self.inflight_messages.get(packet_id)
inflight_message.acknowledge()
self.logger.debug("Message with packet Id=%s acknowledged" % packet_id)
while not self.incoming_queues[PacketType.PUBREC].empty():
packet = self.incoming_queues[PacketType.PUBREC].get_nowait()
packet_id = packet.variable_header.packet_id
inflight_message = self.inflight_messages.get(packet_id)
inflight_message.receive()
self.logger.debug("Message with packet Id=%s received" % packet_id)
rel_packet = PubrelPacket.build(packet_id)
yield from self.outgoing_queue.put(rel_packet)
inflight_message.release()
self.logger.debug("Message with packet Id=%s released" % packet_id)
while not self.incoming_queues[PacketType.PUBCOMP].empty():
packet = self.incoming_queues[PacketType.PUBCOMP].get_nowait()
packet_id = packet.variable_header.packet_id
inflight_message = self.inflight_messages.get(packet_id)
inflight_message.complete()
self.logger.debug("Message with packet Id=%s completed" % packet_id)
yield from self._inflight_changed.acquire()
self._inflight_changed.notify_all()
self._inflight_changed.release()
except KeyError:
self.logger.warn("Received %s for unknown inflight message Id %d" % (packet.fixed_header.packet_type, packet_id))
except MachineError as me:
self.logger.warn("Packet type incompatible with message QOS: %s" % me)
self.logger.debug("In-flight messages polling coro stopped")
@asyncio.coroutine
def _receive_publish_coro(self):
while self._running:
@ -260,125 +223,95 @@ class ProtocolHandler:
yield from self.outgoing_queue.put(PubrecPacket.build(message_id))
return message
class Subscription:
states = ['new', 'subscribed', 'acknowledged']
def handle_keepalive(self):
pass
def __init__(self, packet_id, topics):
self.topics = topics
self.packet_id = packet_id
self._init_states()
@asyncio.coroutine
def handle_connack(self, connack: ConnackPacket):
pass
def _init_states(self):
self.machine = Machine(model=self, states=Subscription.states, initial='new')
self.machine.add_transition(trigger='subscribe', source='new', dest='subscribed')
self.machine.add_transition(trigger='acknowledge', source='subscribed', dest='acknowledged')
@asyncio.coroutine
def handle_suback(self, suback: SubackPacket):
pass
class UnSubscription:
states = ['new', 'unsubscribed', 'acknowledged']
@asyncio.coroutine
def handle_unsuback(self, unsuback: UnsubackPacket):
pass
def __init__(self, packet_id, topics):
self.topics = topics
self.packet_id = packet_id
self._init_states()
@asyncio.coroutine
def handle_puback(self, puback: PubackPacket):
packet_id = puback.variable_header.packet_id
try:
waiter = self._puback_waiters[packet_id]
waiter.set_result(None)
except KeyError as ke:
self.logger.warn("Received PUBACK for unknown pending subscription with Id: %s" % packet_id)
def _init_states(self):
self.machine = Machine(model=self, states=UnSubscription.states, initial='new')
self.machine.add_transition(trigger='unsubscribe', source='new', dest='unsubscribed')
self.machine.add_transition(trigger='acknowledge', source='unsubscribed', dest='acknowledged')
@asyncio.coroutine
def handle_pubrec(self, pubrec: PubrecPacket):
packet_id = pubrec.variable_header.packet_id
try:
waiter = self._pubrec_waiters[packet_id]
waiter.set_result(None)
except KeyError as ke:
self.logger.warn("Received PUBREC for unknown pending subscription with Id: %s" % packet_id)
@asyncio.coroutine
def handle_pubcomp(self, pubcomp: PubcompPacket):
packet_id = pubcomp.variable_header.packet_id
try:
waiter = self._pubcomp_waiters[packet_id]
waiter.set_result(None)
except KeyError as ke:
self.logger.warn("Received PUBCOMP for unknown pending subscription with Id: %s" % packet_id)
class ClientProtocolHandler(ProtocolHandler):
def __init__(self, session: Session, config, loop=None):
super().__init__(session, config, loop)
self._ping_task = None
self.subscriptions = dict()
self._subscription_task = None
self._subscriptions_changed = asyncio.Condition(loop=self._loop)
self._subscriptions_ready = asyncio.Event(loop=self._loop)
self._connack_waiter = None
self._subscriptions_waiter = dict()
self._unsubscriptions_waiter = dict()
@asyncio.coroutine
def start(self):
yield from super().start()
self.packet_sent.connect(self._do_keepalive)
self._subscription_task = asyncio.async(self._subscriptions_coro(), loop=self._loop)
yield from asyncio.wait([self._subscriptions_ready.wait()], loop=self._loop)
@asyncio.coroutine
def stop(self):
yield from super().stop()
yield from asyncio.wait([self._subscription_task], loop=self._loop)
if self._ping_task:
try:
self._ping_task.cancel()
except Exception:
pass
def _do_keepalive(self, message):
if self._ping_task:
try:
self._ping_task.cancel()
except Exception:
pass
next_ping = self.session.keep_alive - self.config['ping_delay']
if next_ping > 0:
self.logger.debug('Next ping in %d seconds if no new messages between' % next_ping)
self._ping_task = self._loop.call_later(next_ping, asyncio.async, self.mqtt_ping())
def _subscriptions_coro(self):
self.logger.debug("Starting subscriptions polling coro")
while self._running:
self._subscriptions_ready.set()
yield from asyncio.sleep(self.config['subscriptions-polling-interval'])
self.logger.debug("Subscriptions polling coro wake-up")
try:
while not self.incoming_queues[PacketType.SUBACK].empty():
packet = self.incoming_queues[PacketType.SUBACK].get_nowait()
packet_id = packet.variable_header.packet_id
subscription = self.subscriptions.get(packet_id)
for i in range(len(subscription.topics)):
subscription.topics[i]['return_code'] = packet.payload.return_codes[i]
subscription.acknowledge()
self.logger.debug("Subscription with packet Id=%s acknowledged" % packet_id)
while not self.incoming_queues[PacketType.UNSUBACK].empty():
packet = self.incoming_queues[PacketType.UNSUBACK].get_nowait()
packet_id = packet.variable_header.packet_id
subscription = self.subscriptions.get(packet_id)
subscription.acknowledge()
self.logger.debug("Unsubscription with packet Id=%s acknowledged" % packet_id)
yield from self._subscriptions_changed.acquire()
self._subscriptions_changed.notify_all()
self._subscriptions_changed.release()
except KeyError:
self.logger.warn("Received %s for unknown subscription message Id %d" % (packet.fixed_header.packet_type, packet_id))
except MachineError as me:
self.logger.warn("Packet type incompatible with message QOS: %s" % me)
self.logger.debug("Subscriptions polling coro stopped")
def handle_keepalive(self):
self._ping_task = self._loop.call_soon(asyncio.async, self.mqtt_ping())
@asyncio.coroutine
def mqtt_subscribe(self, topics, packet_id):
"""
:param topics: array of topics [{'filter':'/a/b', 'qos': 0x00}, ...]
:return:
"""
def acknowledged_predicate():
if self.subscriptions[subscribe.variable_header.packet_id].state == 'acknowledged':
return True
else:
return False
subscribe = SubscribePacket.build(topics, packet_id)
yield from self.outgoing_queue.put(subscribe)
subscription = Subscription(subscribe.variable_header.packet_id, topics)
subscription.subscribe()
self.subscriptions[subscribe.variable_header.packet_id] = subscription
yield from self._subscriptions_changed.acquire()
yield from self._subscriptions_changed.wait_for(acknowledged_predicate)
subscription = self.subscriptions.pop(subscribe.variable_header.packet_id)
self._subscriptions_changed.release()
return subscription
waiter = futures.Future(loop=self._loop)
self._subscriptions_waiter[subscribe.variable_header.packet_id] = waiter
return_codes = yield from waiter
del self._subscriptions_waiter[subscribe.variable_header.packet_id]
return return_codes
@asyncio.coroutine
def handle_suback(self, suback: SubackPacket):
packet_id = suback.variable_header.packet_id
try:
waiter = self._subscriptions_waiter.get(packet_id)
waiter.set_result(suback.payload.return_codes)
except KeyError as ke:
self.logger.warn("Received SUBACK for unknown pending subscription with Id: %s" % packet_id)
@asyncio.coroutine
def mqtt_unsubscribe(self, topics, packet_id):
@ -387,23 +320,21 @@ class ClientProtocolHandler(ProtocolHandler):
:param topics: array of topics ['/a/b', ...]
:return:
"""
def acknowledged_predicate():
if self.subscriptions[unsubscribe.variable_header.packet_id].state == 'acknowledged':
return True
else:
return False
unsubscribe = UnsubscribePacket.build(topics, packet_id)
yield from self.outgoing_queue.put(unsubscribe)
subscription = UnSubscription(unsubscribe.variable_header.packet_id, topics)
subscription.unsubscribe()
self.subscriptions[unsubscribe.variable_header.packet_id] = subscription
self.subscriptions[unsubscribe.variable_header.packet_id] = subscription
yield from self._subscriptions_changed.acquire()
yield from self._subscriptions_changed.wait_for(acknowledged_predicate)
subscription = self.subscriptions.pop(unsubscribe.variable_header.packet_id)
self._subscriptions_changed.release()
return subscription
waiter = futures.Future(loop=self._loop)
self._unsubscriptions_waiter[unsubscribe.variable_header.packet_id] = waiter
yield from waiter
del self._unsubscriptions_waiter[unsubscribe.variable_header.packet_id]
@asyncio.coroutine
def handle_unsuback(self, unsuback: UnsubackPacket):
packet_id = unsuback.variable_header.packet_id
try:
waiter = self._unsubscriptions_waiter.get(packet_id)
waiter.set_result(None)
except KeyError as ke:
self.logger.warn("Received UNSUBACK for unknown pending subscription with Id: %s" % packet_id)
@asyncio.coroutine
def mqtt_connect(self):
@ -441,19 +372,22 @@ class ClientProtocolHandler(ProtocolHandler):
packet = build_connect_packet(self.session)
yield from self.outgoing_queue.put(packet)
connack = yield from self.incoming_queues[PacketType.CONNACK].get()
self._connack_waiter = futures.Future(loop=self._loop)
return (yield from self._connack_waiter)
return connack.variable_header.return_code
@asyncio.coroutine
def handle_connack(self, connack: ConnackPacket):
self._connack_waiter.set_result(connack.variable_header.return_code)
@asyncio.coroutine
def mqtt_disconnect(self):
# yield from self.outgoing_queue.join() To be used in Python 3.5
disconnect_packet = DisconnectPacket()
yield from self.outgoing_queue.put(disconnect_packet)
self._ping_task.cancel()
self._connack_waiter = None
@asyncio.coroutine
def mqtt_ping(self):
self.logger.debug("Pinging ...")
ping_packet = PingReqPacket()
yield from self.outgoing_queue.put(ping_packet)
yield from self.incoming_queues[PacketType.PINGRESP].get()

Wyświetl plik

@ -0,0 +1,24 @@
import logging
from hbmqtt.client._client import MQTTClient
import asyncio
logger = logging.getLogger(__name__)
config = {
'keep_alive': 5,
'ping_delay': 1,
}
C = MQTTClient(config=config)
@asyncio.coroutine
def test_coro():
yield from C.connect(uri='mqtt://iot.eclipse.org:1883/', username=None, password=None)
yield from asyncio.sleep(18)
yield from C.disconnect()
if __name__ == '__main__':
formatter = "[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
logging.basicConfig(level=logging.DEBUG, format=formatter)
asyncio.get_event_loop().run_until_complete(test_coro())

Wyświetl plik

@ -9,12 +9,14 @@ C = MQTTClient()
@asyncio.coroutine
def test_coro():
yield from C.connect(uri='mqtt://iot.eclipse.org:1883/', username=None, password=None)
yield from C.subscribe([
{'filter': '$SYS/broker/uptime', 'qos': 0x00},
ret = yield from C.subscribe([
{'filter': '$SYS/broker/uptime', 'qos': 0x01},
])
logger.info("Subscribed")
yield from asyncio.sleep(60)
logger.info(repr(ret))
yield from asyncio.sleep(10)
yield from C.unsubscribe(['$SYS/broker/uptime'])
logger.info("UnSubscribed")
yield from C.disconnect()

Wyświetl plik

@ -10,37 +10,12 @@ C = MQTTClient()
def test_coro():
yield from C.connect(uri='mqtt://iot.eclipse.org:1883/', username=None, password=None)
tasks = [
asyncio.async(C.publish('a/b', b'0123456789')),
asyncio.async(C.publish('a/b', b'0', qos=0x01)),
asyncio.async(C.publish('a/b', b'1', qos=0x01)),
asyncio.async(C.publish('a/b', b'2', qos=0x01)),
asyncio.async(C.publish('a/b', b'3', qos=0x01)),
asyncio.async(C.publish('a/b', b'4', qos=0x01)),
asyncio.async(C.publish('a/b', b'5', qos=0x01)),
asyncio.async(C.publish('a/b', b'6', qos=0x01)),
asyncio.async(C.publish('a/b', b'7', qos=0x01)),
asyncio.async(C.publish('a/b', b'8', qos=0x01)),
asyncio.async(C.publish('a/b', b'9', qos=0x01)),
asyncio.async(C.publish('a/b', b'0', qos=0x02)),
asyncio.async(C.publish('a/b', b'1', qos=0x02)),
asyncio.async(C.publish('a/b', b'2', qos=0x02)),
asyncio.async(C.publish('a/b', b'3', qos=0x02)),
asyncio.async(C.publish('a/b', b'4', qos=0x02)),
asyncio.async(C.publish('a/b', b'5', qos=0x02)),
asyncio.async(C.publish('a/b', b'6', qos=0x02)),
asyncio.async(C.publish('a/b', b'7', qos=0x02)),
asyncio.async(C.publish('a/b', b'8', qos=0x02)),
asyncio.async(C.publish('a/b', b'9', qos=0x02)),
asyncio.async(C.publish('a/b', b'TEST MESSAGE WITH QOS_0')),
asyncio.async(C.publish('a/b', b'TEST MESSAGE WITH QOS_1', qos=0x01)),
asyncio.async(C.publish('a/b', b'TEST MESSAGE WITH QOS_2', qos=0x02)),
]
yield from asyncio.wait(tasks)
logger.info("messages published")
yield from C.subscribe([
{'filter': '$SYS/broker/connections/*', 'qos': 0x01},
])
logger.info("Subscribed")
#yield from C.unsubscribe(['a/b', 'c/d'])
#logger.info("Unsubscribed")
yield from C.disconnect()