Repository: Yakifo/amqtt (language: python)

Issue:
rename module and create import hook
rename the module and create an import hook to allow importing the old name.

- [x] Decide on module name
- [x] rename repo
- [ ] rename package to match module name (blocked by https://github.com/Yakifo/amqtt/issues/7)
- [ ] Implement import hook & deprecation warning when importing the old name


Discussion / hints:
Possible names

 - `aio-hbmqtt`
 - `aiohbmqtt`
 - `aiomqtt` (taken)
 - `amqtt`
I like `amqtt`.
Not to be confused with `amqp` :]
Related discussion here: https://discuss.python.org/t/packaging-forks/7502
We need to decide on an "official writing".
Currently both `aMQTT` and `AMQTT` are used.

@HerrMuellerluedenscheid your take?
> We need to decide on an "official writing".
> Currently both `aMQTT` and `AMQTT` are used.
> 
> @HerrMuellerluedenscheid your take?

Good point. I like `aMQTT`.
Okay, lets go with `aMQTT` 
I'd suggest following the convention here https://github.com/aio-libs to indicate similar intent.
@shipmints that is not possible as `aiomqtt` is already taken :(
Perhaps, then, aiomqttd (in the Unix world, d implies server vs. client). That would suit me! If the person who owns aiomqtt as a client side implementation agrees, we could attempt to ensure compatibility.
aiomqtt is a wrapper around paho, for details see: https://github.com/sbtinstruments/asyncio-mqtt/discussions/50

Since this repo does also provide a client implementation I would prefer to stay away from the daemon naming.
@FlorianLudwig I took a look at possibilities to setup import hooks and show deprecation warnings but then wasn't sure about the best strategy. I feel like this requires a deeper understanding of the `importlib` and import mechanism than I currently have. But I'm eager to understand how this should be done right.

You have something in mind like if I run
```
import hbmqtt
```
I see something like:
```
>>> UserWarning: HBMQTT is deprecated. Use aMQTT as drop-in replacement
```
right? `DeprecationWarning` or `PendingDeprecationWarning` would be nicer but those are ignored by default.

Well, I'm not sure how to approach the hooks. Wouldn't that require to register `aMQTT` and `hbmqtt` at installation e.g. by somehow hijacking `setup.py` logic? Do you happen to have some reference where I could have a look how that should be done?
@HerrMuellerluedenscheid I also played around and my conclusion is the same, there needs to be some hack to make it work.

So my thought was to generate python files, "KISS":

for example the `hbmqtt/errors.py` would be:
```
import warnings
from amqtt.errors import *

warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
```

I made a small script to generate these files and works as expected.

The `DeprecationWarning` being ignored by default is something I thought about as well... I would use it in the next release and maybe use `FutureWarning` in a later release?

Fix patch:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 9fde5db1..a44f7ab0 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -14,6 +14,7 @@ jobs:
 
     runs-on: ubuntu-latest
     strategy:
+      fail-fast: false
       matrix:
         python-version: [3.6, 3.7, 3.8, 3.9]
 
@@ -29,11 +30,11 @@ jobs:
         $HOME/.poetry/bin/poetry install --extras ci
     - name: Lint with flake8 and black
       run: |
-        $HOME/.poetry/bin/poetry run flake8 . --count --statistics --show-source --ignore=E501,W503,W605,E722
+        $HOME/.poetry/bin/poetry run flake8 tests/ amqtt/ --count --statistics --show-source --ignore=E501,W503,W605,E722
         $HOME/.poetry/bin/poetry run  black . --check
     - name: Test with pytest
       run: |
-        $HOME/.poetry/bin/poetry run  pytest --cov hbmqtt --cov tests --cov-report=term --cov-report=xml
+        $HOME/.poetry/bin/poetry run  pytest --cov amqtt --cov tests --cov-report=term --cov-report=xml
 
     - name: Upload coverage data to coveralls.io
       run: |
diff --git a/amqtt/__init__.py b/amqtt/__init__.py
new file mode 100644
index 00000000..e4e0261e
--- /dev/null
+++ b/amqtt/__init__.py
@@ -0,0 +1,3 @@
+# See the file license.txt for copying permission.
+
+__version__ = "0.10.0-alpha.1"
diff --git a/amqtt/adapters.py b/amqtt/adapters.py
new file mode 100644
index 00000000..bfc10dc5
--- /dev/null
+++ b/amqtt/adapters.py
@@ -0,0 +1,227 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+
+import io
+from websockets.protocol import WebSocketCommonProtocol
+from websockets.exceptions import ConnectionClosed
+from asyncio import StreamReader, StreamWriter
+import logging
+
+
+class ReaderAdapter:
+    """
+    Base class for all network protocol reader adapter.
+
+    Reader adapters are used to adapt read operations on the network depending on the
+    protocol used
+    """
+
+    async def read(self, n=-1) -> bytes:
+        """
+        Read up to n bytes. If n is not provided, or set to -1, read until EOF and
+        return all read bytes. If the EOF was received and the internal buffer is
+        empty, return an empty bytes object. :return: packet read as bytes data
+        """
+
+    def feed_eof(self):
+        """
+        Acknowledge EOF
+        """
+
+
+class WriterAdapter:
+    """
+    Base class for all network protocol writer adapter.
+
+    Writer adapters are used to adapt write operations on the network depending on
+    the protocol used
+    """
+
+    def write(self, data):
+        """
+        write some data to the protocol layer
+        """
+
+    async def drain(self):
+        """
+        Let the write buffer of the underlying transport a chance to be flushed.
+        """
+
+    def get_peer_info(self):
+        """
+        Return peer socket info (remote address and remote port as tuple
+        """
+
+    async def close(self):
+        """
+        Close the protocol connection
+        """
+
+
+class WebSocketsReader(ReaderAdapter):
+    """
+    WebSockets API reader adapter
+    This adapter relies on WebSocketCommonProtocol to read from a WebSocket.
+    """
+
+    def __init__(self, protocol: WebSocketCommonProtocol):
+        self._protocol = protocol
+        self._stream = io.BytesIO(b"")
+
+    async def read(self, n=-1) -> bytes:
+        await self._feed_buffer(n)
+        data = self._stream.read(n)
+        return data
+
+    async def _feed_buffer(self, n=1):
+        """
+        Feed the data buffer by reading a Websocket message.
+        :param n: if given, feed buffer until it contains at least n bytes
+        """
+        buffer = bytearray(self._stream.read())
+        while len(buffer) < n:
+            try:
+                message = await self._protocol.recv()
+            except ConnectionClosed:
+                message = None
+            if message is None:
+                break
+            if not isinstance(message, bytes):
+                raise TypeError("message must be bytes")
+            buffer.extend(message)
+        self._stream = io.BytesIO(buffer)
+
+
+class WebSocketsWriter(WriterAdapter):
+    """
+    WebSockets API writer adapter
+    This adapter relies on WebSocketCommonProtocol to read from a WebSocket.
+    """
+
+    def __init__(self, protocol: WebSocketCommonProtocol):
+        self._protocol = protocol
+        self._stream = io.BytesIO(b"")
+
+    def write(self, data):
+        """
+        write some data to the protocol layer
+        """
+        self._stream.write(data)
+
+    async def drain(self):
+        """
+        Let the write buffer of the underlying transport a chance to be flushed.
+        """
+        data = self._stream.getvalue()
+        if len(data):
+            await self._protocol.send(data)
+        self._stream = io.BytesIO(b"")
+
+    def get_peer_info(self):
+        # remote_address can be either a 4-tuple or 2-tuple depending on whether
+        # it is an IPv6 or IPv4 address, so we take their shared (host, port)
+        # prefix here to present a uniform return value.
+        return self._protocol.remote_address[:2]
+
+    async def close(self):
+        await self._protocol.close()
+
+
+class StreamReaderAdapter(ReaderAdapter):
+    """
+    Asyncio Streams API protocol adapter
+    This adapter relies on StreamReader to read from a TCP socket.
+    Because API is very close, this class is trivial
+    """
+
+    def __init__(self, reader: StreamReader):
+        self._reader = reader
+
+    async def read(self, n=-1) -> bytes:
+        if n == -1:
+            data = await self._reader.read(n)
+        else:
+            data = await self._reader.readexactly(n)
+        return data
+
+    def feed_eof(self):
+        return self._reader.feed_eof()
+
+
+class StreamWriterAdapter(WriterAdapter):
+    """
+    Asyncio Streams API protocol adapter
+    This adapter relies on StreamWriter to write to a TCP socket.
+    Because API is very close, this class is trivial
+    """
+
+    def __init__(self, writer: StreamWriter):
+        self.logger = logging.getLogger(__name__)
+        self._writer = writer
+        self.is_closed = False  # StreamWriter has no test for closed...we use our own
+
+    def write(self, data):
+        if not self.is_closed:
+            self._writer.write(data)
+
+    async def drain(self):
+        if not self.is_closed:
+            await self._writer.drain()
+
+    def get_peer_info(self):
+        extra_info = self._writer.get_extra_info("peername")
+        return extra_info[0], extra_info[1]
+
+    async def close(self):
+        if not self.is_closed:
+            self.is_closed = True  # we first mark this closed so yields below don't cause races with waiting writes
+            await self._writer.drain()
+            if self._writer.can_write_eof():
+                self._writer.write_eof()
+            self._writer.close()
+            try:
+                await self._writer.wait_closed()  # py37+
+            except AttributeError:
+                pass
+
+
+class BufferReader(ReaderAdapter):
+    """
+    Byte Buffer reader adapter
+    This adapter simply adapt reading a byte buffer.
+    """
+
+    def __init__(self, buffer: bytes):
+        self._stream = io.BytesIO(buffer)
+
+    async def read(self, n=-1) -> bytes:
+        return self._stream.read(n)
+
+
+class BufferWriter(WriterAdapter):
+    """
+    ByteBuffer writer adapter
+    This adapter simply adapt writing to a byte buffer
+    """
+
+    def __init__(self, buffer=b""):
+        self._stream = io.BytesIO(buffer)
+
+    def write(self, data):
+        """
+        write some data to the protocol layer
+        """
+        self._stream.write(data)
+
+    async def drain(self):
+        pass
+
+    def get_buffer(self):
+        return self._stream.getvalue()
+
+    def get_peer_info(self):
+        return "BufferWriter", 0
+
+    async def close(self):
+        self._stream.close()
diff --git a/amqtt/broker.py b/amqtt/broker.py
new file mode 100644
index 00000000..fb25bf76
--- /dev/null
+++ b/amqtt/broker.py
@@ -0,0 +1,1019 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from typing import Optional
+import logging
+import ssl
+import websockets
+import asyncio
+import re
+from asyncio import CancelledError
+from collections import deque
+
+from functools import partial
+from transitions import Machine, MachineError
+from amqtt.session import Session
+from amqtt.mqtt.protocol.broker_handler import BrokerProtocolHandler
+from amqtt.errors import HBMQTTException, MQTTException
+from amqtt.utils import format_client_message, gen_client_id
+from amqtt.adapters import (
+    StreamReaderAdapter,
+    StreamWriterAdapter,
+    ReaderAdapter,
+    WriterAdapter,
+    WebSocketsReader,
+    WebSocketsWriter,
+)
+from .plugins.manager import PluginManager, BaseContext
+
+
+_defaults = {
+    "timeout-disconnect-delay": 2,
+    "auth": {"allow-anonymous": True, "password-file": None},
+}
+
+EVENT_BROKER_PRE_START = "broker_pre_start"
+EVENT_BROKER_POST_START = "broker_post_start"
+EVENT_BROKER_PRE_SHUTDOWN = "broker_pre_shutdown"
+EVENT_BROKER_POST_SHUTDOWN = "broker_post_shutdown"
+EVENT_BROKER_CLIENT_CONNECTED = "broker_client_connected"
+EVENT_BROKER_CLIENT_DISCONNECTED = "broker_client_disconnected"
+EVENT_BROKER_CLIENT_SUBSCRIBED = "broker_client_subscribed"
+EVENT_BROKER_CLIENT_UNSUBSCRIBED = "broker_client_unsubscribed"
+EVENT_BROKER_MESSAGE_RECEIVED = "broker_message_received"
+
+
+class BrokerException(Exception):
+    pass
+
+
+class RetainedApplicationMessage:
+
+    __slots__ = ("source_session", "topic", "data", "qos")
+
+    def __init__(self, source_session, topic, data, qos=None):
+        self.source_session = source_session
+        self.topic = topic
+        self.data = data
+        self.qos = qos
+
+
+class Server:
+    def __init__(self, listener_name, server_instance, max_connections=-1, loop=None):
+        self.logger = logging.getLogger(__name__)
+        self.instance = server_instance
+        self.conn_count = 0
+        self.listener_name = listener_name
+        if loop is not None:
+            self._loop = loop
+        else:
+            self._loop = asyncio.get_event_loop()
+
+        self.max_connections = max_connections
+        if self.max_connections > 0:
+            self.semaphore = asyncio.Semaphore(self.max_connections, loop=self._loop)
+        else:
+            self.semaphore = None
+
+    async def acquire_connection(self):
+        if self.semaphore:
+            await self.semaphore.acquire()
+        self.conn_count += 1
+        if self.max_connections > 0:
+            self.logger.info(
+                "Listener '%s': %d/%d connections acquired"
+                % (self.listener_name, self.conn_count, self.max_connections)
+            )
+        else:
+            self.logger.info(
+                "Listener '%s': %d connections acquired"
+                % (self.listener_name, self.conn_count)
+            )
+
+    def release_connection(self) -> None:
+        if self.semaphore:
+            self.semaphore.release()
+        self.conn_count -= 1
+        if self.max_connections > 0:
+            self.logger.info(
+                "Listener '%s': %d/%d connections acquired"
+                % (self.listener_name, self.conn_count, self.max_connections)
+            )
+        else:
+            self.logger.info(
+                "Listener '%s': %d connections acquired"
+                % (self.listener_name, self.conn_count)
+            )
+
+    async def close_instance(self):
+        if self.instance:
+            self.instance.close()
+            await self.instance.wait_closed()
+
+
+class BrokerContext(BaseContext):
+    """
+    BrokerContext is used as the context passed to plugins interacting with the broker.
+    It act as an adapter to broker services from plugins developed for HBMQTT broker
+    """
+
+    def __init__(self, broker: "Broker") -> None:
+        super().__init__()
+        self.config = None
+        self._broker_instance = broker
+
+    async def broadcast_message(self, topic, data, qos=None):
+        await self._broker_instance.internal_message_broadcast(topic, data, qos)
+
+    def retain_message(self, topic_name, data, qos=None):
+        self._broker_instance.retain_message(None, topic_name, data, qos)
+
+    @property
+    def sessions(self):
+        for session in self._broker_instance._sessions.values():
+            yield session[0]
+
+    @property
+    def retained_messages(self):
+        return self._broker_instance._retained_messages
+
+    @property
+    def subscriptions(self):
+        return self._broker_instance._subscriptions
+
+
+class Broker:
+    """
+    MQTT 3.1.1 compliant broker implementation
+
+    :param config: Example Yaml config
+    :param loop: asyncio loop to use. Defaults to ``asyncio.get_event_loop()``.
+    :param plugin_namespace: Plugin namespace to use when loading plugin entry_points. Defaults to ``amqtt.broker.plugins``
+
+    """
+
+    states = [
+        "new",
+        "starting",
+        "started",
+        "not_started",
+        "stopping",
+        "stopped",
+        "not_stopped",
+        "stopped",
+    ]
+
+    def __init__(self, config=None, loop=None, plugin_namespace=None):
+        self.logger = logging.getLogger(__name__)
+        self.config = _defaults
+        if config is not None:
+            self.config.update(config)
+        self._build_listeners_config(self.config)
+
+        if loop is not None:
+            self._loop = loop
+        else:
+            self._loop = asyncio.get_event_loop()
+
+        self._servers = dict()
+        self._init_states()
+        self._sessions = dict()
+        self._subscriptions = dict()
+        self._retained_messages = dict()
+        self._broadcast_queue = asyncio.Queue(loop=self._loop)
+
+        self._broadcast_task = None
+
+        # Init plugins manager
+        context = BrokerContext(self)
+        context.config = self.config
+        if plugin_namespace:
+            namespace = plugin_namespace
+        else:
+            namespace = "amqtt.broker.plugins"
+        self.plugins_manager = PluginManager(namespace, context, self._loop)
+
+    def _build_listeners_config(self, broker_config):
+        self.listeners_config = dict()
+        try:
+            listeners_config = broker_config["listeners"]
+            defaults = listeners_config["default"]
+            for listener in listeners_config:
+                config = dict(defaults)
+                config.update(listeners_config[listener])
+                self.listeners_config[listener] = config
+        except KeyError as ke:
+            raise BrokerException("Listener config not found invalid: %s" % ke)
+
+    def _init_states(self) -> None:
+        self.transitions = Machine(states=Broker.states, initial="new")
+        self.transitions.add_transition(trigger="start", source="new", dest="starting")
+        self.transitions.add_transition(
+            trigger="starting_fail", source="starting", dest="not_started"
+        )
+        self.transitions.add_transition(
+            trigger="starting_success", source="starting", dest="started"
+        )
+        self.transitions.add_transition(
+            trigger="shutdown", source="started", dest="stopping"
+        )
+        self.transitions.add_transition(
+            trigger="stopping_success", source="stopping", dest="stopped"
+        )
+        self.transitions.add_transition(
+            trigger="stopping_failure", source="stopping", dest="not_stopped"
+        )
+        self.transitions.add_transition(
+            trigger="start", source="stopped", dest="starting"
+        )
+
+    async def start(self) -> None:
+        """
+        Start the broker to serve with the given configuration
+
+        Start method opens network sockets and will start listening for incoming connections.
+
+        This method is a *coroutine*.
+        """
+        try:
+            self._sessions = dict()
+            self._subscriptions = dict()
+            self._retained_messages = dict()
+            self.transitions.start()
+            self.logger.debug("Broker starting")
+        except (MachineError, ValueError) as exc:
+            # Backwards compat: MachineError is raised by transitions < 0.5.0.
+            self.logger.warning(
+                "[WARN-0001] Invalid method call at this moment: %s" % exc
+            )
+            raise BrokerException("Broker instance can't be started: %s" % exc)
+
+        await self.plugins_manager.fire_event(EVENT_BROKER_PRE_START)
+        try:
+            # Start network listeners
+            for listener_name in self.listeners_config:
+                listener = self.listeners_config[listener_name]
+
+                if "bind" not in listener:
+                    self.logger.debug(
+                        "Listener configuration '%s' is not bound" % listener_name
+                    )
+                    continue
+
+                max_connections = listener.get("max_connections", -1)
+
+                # SSL Context
+                sc = None
+
+                # accept string "on" / "off" or boolean
+                ssl_active = listener.get("ssl", False)
+                if isinstance(ssl_active, str):
+                    ssl_active = ssl_active.upper() == "ON"
+
+                if ssl_active:
+                    try:
+                        sc = ssl.create_default_context(
+                            ssl.Purpose.CLIENT_AUTH,
+                            cafile=listener.get("cafile"),
+                            capath=listener.get("capath"),
+                            cadata=listener.get("cadata"),
+                        )
+                        sc.load_cert_chain(listener["certfile"], listener["keyfile"])
+                        sc.verify_mode = ssl.CERT_OPTIONAL
+                    except KeyError as ke:
+                        raise BrokerException(
+                            "'certfile' or 'keyfile' configuration parameter missing: %s"
+                            % ke
+                        )
+                    except FileNotFoundError as fnfe:
+                        raise BrokerException(
+                            "Can't read cert files '%s' or '%s' : %s"
+                            % (listener["certfile"], listener["keyfile"], fnfe)
+                        )
+
+                address, s_port = listener["bind"].split(":")
+                port = 0
+                try:
+                    port = int(s_port)
+                except ValueError:
+                    raise BrokerException(
+                        "Invalid port value in bind value: %s" % listener["bind"]
+                    )
+
+                if listener["type"] == "tcp":
+                    cb_partial = partial(
+                        self.stream_connected, listener_name=listener_name
+                    )
+                    instance = await asyncio.start_server(
+                        cb_partial,
+                        address,
+                        port,
+                        reuse_address=True,
+                        ssl=sc,
+                        loop=self._loop,
+                    )
+                    self._servers[listener_name] = Server(
+                        listener_name, instance, max_connections, self._loop
+                    )
+                elif listener["type"] == "ws":
+                    cb_partial = partial(self.ws_connected, listener_name=listener_name)
+                    instance = await websockets.serve(
+                        cb_partial,
+                        address,
+                        port,
+                        ssl=sc,
+                        loop=self._loop,
+                        subprotocols=["mqtt"],
+                    )
+                    self._servers[listener_name] = Server(
+                        listener_name, instance, max_connections, self._loop
+                    )
+
+                self.logger.info(
+                    "Listener '%s' bind to %s (max_connections=%d)"
+                    % (listener_name, listener["bind"], max_connections)
+                )
+
+            self.transitions.starting_success()
+            await self.plugins_manager.fire_event(EVENT_BROKER_POST_START)
+
+            # Start broadcast loop
+            self._broadcast_task = asyncio.ensure_future(
+                self._broadcast_loop(), loop=self._loop
+            )
+
+            self.logger.debug("Broker started")
+        except Exception as e:
+            self.logger.error("Broker startup failed: %s" % e)
+            self.transitions.starting_fail()
+            raise BrokerException("Broker instance can't be started: %s" % e)
+
+    async def shutdown(self):
+        """
+        Stop broker instance.
+
+        Closes all connected session, stop listening on network socket and free resources.
+        """
+        try:
+            self._sessions = dict()
+            self._subscriptions = dict()
+            self._retained_messages = dict()
+            self.transitions.shutdown()
+        except (MachineError, ValueError) as exc:
+            # Backwards compat: MachineError is raised by transitions < 0.5.0.
+            self.logger.debug("Invalid method call at this moment: %s" % exc)
+            raise BrokerException("Broker instance can't be stopped: %s" % exc)
+
+        # Fire broker_shutdown event to plugins
+        await self.plugins_manager.fire_event(EVENT_BROKER_PRE_SHUTDOWN)
+
+        # Stop broadcast loop
+        if self._broadcast_task:
+            self._broadcast_task.cancel()
+        if self._broadcast_queue.qsize() > 0:
+            self.logger.warning(
+                "%d messages not broadcasted" % self._broadcast_queue.qsize()
+            )
+
+        for listener_name in self._servers:
+            server = self._servers[listener_name]
+            await server.close_instance()
+        self.logger.debug("Broker closing")
+        self.logger.info("Broker closed")
+        await self.plugins_manager.fire_event(EVENT_BROKER_POST_SHUTDOWN)
+        self.transitions.stopping_success()
+
+    async def internal_message_broadcast(self, topic, data, qos=None):
+        return await self._broadcast_message(None, topic, data)
+
+    async def ws_connected(self, websocket, uri, listener_name):
+        await self.client_connected(
+            listener_name, WebSocketsReader(websocket), WebSocketsWriter(websocket)
+        )
+
+    async def stream_connected(self, reader, writer, listener_name):
+        await self.client_connected(
+            listener_name, StreamReaderAdapter(reader), StreamWriterAdapter(writer)
+        )
+
+    async def client_connected(
+        self, listener_name, reader: ReaderAdapter, writer: WriterAdapter
+    ):
+        # Wait for connection available on listener
+        server = self._servers.get(listener_name, None)
+        if not server:
+            raise BrokerException("Invalid listener name '%s'" % listener_name)
+        await server.acquire_connection()
+
+        remote_address, remote_port = writer.get_peer_info()
+        self.logger.info(
+            "Connection from %s:%d on listener '%s'"
+            % (remote_address, remote_port, listener_name)
+        )
+
+        # Wait for first packet and expect a CONNECT
+        try:
+            handler, client_session = await BrokerProtocolHandler.init_from_connect(
+                reader, writer, self.plugins_manager, loop=self._loop
+            )
+        except HBMQTTException as exc:
+            self.logger.warning(
+                "[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s"
+                % (format_client_message(address=remote_address, port=remote_port), exc)
+            )
+            # await writer.close()
+            self.logger.debug("Connection closed")
+            return
+        except MQTTException as me:
+            self.logger.error(
+                "Invalid connection from %s : %s"
+                % (format_client_message(address=remote_address, port=remote_port), me)
+            )
+            await writer.close()
+            self.logger.debug("Connection closed")
+            return
+
+        if client_session.clean_session:
+            # Delete existing session and create a new one
+            if client_session.client_id is not None and client_session.client_id != "":
+                self.delete_session(client_session.client_id)
+            else:
+                client_session.client_id = gen_client_id()
+            client_session.parent = 0
+        else:
+            # Get session from cache
+            if client_session.client_id in self._sessions:
+                self.logger.debug(
+                    "Found old session %s"
+                    % repr(self._sessions[client_session.client_id])
+                )
+                (client_session, h) = self._sessions[client_session.client_id]
+                client_session.parent = 1
+            else:
+                client_session.parent = 0
+        if client_session.keep_alive > 0:
+            client_session.keep_alive += self.config["timeout-disconnect-delay"]
+        self.logger.debug("Keep-alive timeout=%d" % client_session.keep_alive)
+
+        handler.attach(client_session, reader, writer)
+        self._sessions[client_session.client_id] = (client_session, handler)
+
+        authenticated = await self.authenticate(
+            client_session, self.listeners_config[listener_name]
+        )
+        if not authenticated:
+            await writer.close()
+            server.release_connection()  # Delete client from connections list
+            return
+
+        while True:
+            try:
+                client_session.transitions.connect()
+                break
+            except (MachineError, ValueError):
+                # Backwards compat: MachineError is raised by transitions < 0.5.0.
+                self.logger.warning(
+                    "Client %s is reconnecting too quickly, make it wait"
+                    % client_session.client_id
+                )
+                # Wait a bit may be client is reconnecting too fast
+                await asyncio.sleep(1, loop=self._loop)
+        await handler.mqtt_connack_authorize(authenticated)
+
+        await self.plugins_manager.fire_event(
+            EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id
+        )
+
+        self.logger.debug("%s Start messages handling" % client_session.client_id)
+        await handler.start()
+        self.logger.debug(
+            "Retained messages queue size: %d"
+            % client_session.retained_messages.qsize()
+        )
+        await self.publish_session_retained_messages(client_session)
+
+        # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect)
+        disconnect_waiter = asyncio.ensure_future(
+            handler.wait_disconnect(), loop=self._loop
+        )
+        subscribe_waiter = asyncio.ensure_future(
+            handler.get_next_pending_subscription(), loop=self._loop
+        )
+        unsubscribe_waiter = asyncio.ensure_future(
+            handler.get_next_pending_unsubscription(), loop=self._loop
+        )
+        wait_deliver = asyncio.ensure_future(
+            handler.mqtt_deliver_next_message(), loop=self._loop
+        )
+        connected = True
+        while connected:
+            try:
+                done, pending = await asyncio.wait(
+                    [
+                        disconnect_waiter,
+                        subscribe_waiter,
+                        unsubscribe_waiter,
+                        wait_deliver,
+                    ],
+                    return_when=asyncio.FIRST_COMPLETED,
+                    loop=self._loop,
+                )
+                if disconnect_waiter in done:
+                    result = disconnect_waiter.result()
+                    self.logger.debug(
+                        "%s Result from wait_diconnect: %s"
+                        % (client_session.client_id, result)
+                    )
+                    if result is None:
+                        self.logger.debug("Will flag: %s" % client_session.will_flag)
+                        # Connection closed anormally, send will message
+                        if client_session.will_flag:
+                            self.logger.debug(
+                                "Client %s disconnected abnormally, sending will message"
+                                % format_client_message(client_session)
+                            )
+                            await self._broadcast_message(
+                                client_session,
+                                client_session.will_topic,
+                                client_session.will_message,
+                                client_session.will_qos,
+                            )
+                            if client_session.will_retain:
+                                self.retain_message(
+                                    client_session,
+                                    client_session.will_topic,
+                                    client_session.will_message,
+                                    client_session.will_qos,
+                                )
+                    self.logger.debug(
+                        "%s Disconnecting session" % client_session.client_id
+                    )
+                    await self._stop_handler(handler)
+                    client_session.transitions.disconnect()
+                    await self.plugins_manager.fire_event(
+                        EVENT_BROKER_CLIENT_DISCONNECTED,
+                        client_id=client_session.client_id,
+                    )
+                    connected = False
+                if unsubscribe_waiter in done:
+                    self.logger.debug(
+                        "%s handling unsubscription" % client_session.client_id
+                    )
+                    unsubscription = unsubscribe_waiter.result()
+                    for topic in unsubscription["topics"]:
+                        self._del_subscription(topic, client_session)
+                        await self.plugins_manager.fire_event(
+                            EVENT_BROKER_CLIENT_UNSUBSCRIBED,
+                            client_id=client_session.client_id,
+                            topic=topic,
+                        )
+                    await handler.mqtt_acknowledge_unsubscription(
+                        unsubscription["packet_id"]
+                    )
+                    unsubscribe_waiter = asyncio.Task(
+                        handler.get_next_pending_unsubscription(), loop=self._loop
+                    )
+                if subscribe_waiter in done:
+                    self.logger.debug(
+                        "%s handling subscription" % client_session.client_id
+                    )
+                    subscriptions = subscribe_waiter.result()
+                    return_codes = []
+                    for subscription in subscriptions["topics"]:
+                        result = await self.add_subscription(
+                            subscription, client_session
+                        )
+                        return_codes.append(result)
+                    await handler.mqtt_acknowledge_subscription(
+                        subscriptions["packet_id"], return_codes
+                    )
+                    for index, subscription in enumerate(subscriptions["topics"]):
+                        if return_codes[index] != 0x80:
+                            await self.plugins_manager.fire_event(
+                                EVENT_BROKER_CLIENT_SUBSCRIBED,
+                                client_id=client_session.client_id,
+                                topic=subscription[0],
+                                qos=subscription[1],
+                            )
+                            await self.publish_retained_messages_for_subscription(
+                                subscription, client_session
+                            )
+                    subscribe_waiter = asyncio.Task(
+                        handler.get_next_pending_subscription(), loop=self._loop
+                    )
+                    self.logger.debug(repr(self._subscriptions))
+                if wait_deliver in done:
+                    if self.logger.isEnabledFor(logging.DEBUG):
+                        self.logger.debug(
+                            "%s handling message delivery" % client_session.client_id
+                        )
+                    app_message = wait_deliver.result()
+                    if not app_message.topic:
+                        self.logger.warning(
+                            "[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message, closing connection"
+                            % client_session.client_id
+                        )
+                        break
+                    if "#" in app_message.topic or "+" in app_message.topic:
+                        self.logger.warning(
+                            "[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection"
+                            % client_session.client_id
+                        )
+                        break
+                    await self.plugins_manager.fire_event(
+                        EVENT_BROKER_MESSAGE_RECEIVED,
+                        client_id=client_session.client_id,
+                        message=app_message,
+                    )
+                    await self._broadcast_message(
+                        client_session, app_message.topic, app_message.data
+                    )
+                    if app_message.publish_packet.retain_flag:
+                        self.retain_message(
+                            client_session,
+                            app_message.topic,
+                            app_message.data,
+                            app_message.qos,
+                        )
+                    wait_deliver = asyncio.Task(
+                        handler.mqtt_deliver_next_message(), loop=self._loop
+                    )
+            except asyncio.CancelledError:
+                self.logger.debug("Client loop cancelled")
+                break
+        disconnect_waiter.cancel()
+        subscribe_waiter.cancel()
+        unsubscribe_waiter.cancel()
+        wait_deliver.cancel()
+
+        self.logger.debug("%s Client disconnected" % client_session.client_id)
+        server.release_connection()
+
+    def _init_handler(self, session, reader, writer):
+        """
+        Create a BrokerProtocolHandler and attach to a session
+        :return:
+        """
+        handler = BrokerProtocolHandler(self.plugins_manager, self._loop)
+        handler.attach(session, reader, writer)
+        return handler
+
+    async def _stop_handler(self, handler):
+        """
+        Stop a running handler and detach if from the session
+        :param handler:
+        :return:
+        """
+        try:
+            await handler.stop()
+        except Exception as e:
+            self.logger.error(e)
+
+    async def authenticate(self, session: Session, listener):
+        """
+        This method call the authenticate method on registered plugins to test user authentication.
+        User is considered authenticated if all plugins called returns True.
+        Plugins authenticate() method are supposed to return :
+         - True if user is authentication succeed
+         - False if user authentication fails
+         - None if authentication can't be achieved (then plugin result is then ignored)
+        :param session:
+        :param listener:
+        :return:
+        """
+        auth_plugins = None
+        auth_config = self.config.get("auth", None)
+        if auth_config:
+            auth_plugins = auth_config.get("plugins", None)
+        returns = await self.plugins_manager.map_plugin_coro(
+            "authenticate", session=session, filter_plugins=auth_plugins
+        )
+        auth_result = True
+        if returns:
+            for plugin in returns:
+                res = returns[plugin]
+                if res is False:
+                    auth_result = False
+                    self.logger.debug(
+                        "Authentication failed due to '%s' plugin result: %s"
+                        % (plugin.name, res)
+                    )
+                else:
+                    self.logger.debug("'%s' plugin result: %s" % (plugin.name, res))
+        # If all plugins returned True, authentication is success
+        return auth_result
+
+    async def topic_filtering(self, session: Session, topic):
+        """
+        This method call the topic_filtering method on registered plugins to check that the subscription is allowed.
+        User is considered allowed if all plugins called return True.
+        Plugins topic_filtering() method are supposed to return :
+         - True if MQTT client can be subscribed to the topic
+         - False if MQTT client is not allowed to subscribe to the topic
+         - None if topic filtering can't be achieved (then plugin result is then ignored)
+        :param session:
+        :param listener:
+        :param topic: Topic in which the client wants to subscribe
+        :return:
+        """
+        topic_plugins = None
+        topic_config = self.config.get("topic-check", None)
+        if topic_config and topic_config.get("enabled", False):
+            topic_plugins = topic_config.get("plugins", None)
+        returns = await self.plugins_manager.map_plugin_coro(
+            "topic_filtering",
+            session=session,
+            topic=topic,
+            filter_plugins=topic_plugins,
+        )
+        topic_result = True
+        if returns:
+            for plugin in returns:
+                res = returns[plugin]
+                if res is False:
+                    topic_result = False
+                    self.logger.debug(
+                        "Topic filtering failed due to '%s' plugin result: %s"
+                        % (plugin.name, res)
+                    )
+                else:
+                    self.logger.debug("'%s' plugin result: %s" % (plugin.name, res))
+        # If all plugins returned True, authentication is success
+        return topic_result
+
+    def retain_message(
+        self,
+        source_session: Session,
+        topic_name: str,
+        data: bytearray,
+        qos: Optional[int] = None,
+    ) -> None:
+        if data is not None and data != b"":
+            # If retained flag set, store the message for further subscriptions
+            self.logger.debug("Retaining message on topic %s" % topic_name)
+            retained_message = RetainedApplicationMessage(
+                source_session, topic_name, data, qos
+            )
+            self._retained_messages[topic_name] = retained_message
+        else:
+            # [MQTT-3.3.1-10]
+            if topic_name in self._retained_messages:
+                self.logger.debug("Clear retained messages for topic '%s'" % topic_name)
+                del self._retained_messages[topic_name]
+
+    async def add_subscription(self, subscription, session):
+        try:
+            a_filter = subscription[0]
+            if "#" in a_filter and not a_filter.endswith("#"):
+                # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter
+                return 0x80
+            if a_filter != "+":
+                if "+" in a_filter:
+                    if "/+" not in a_filter and "+/" not in a_filter:
+                        # [MQTT-4.7.1-3] + wildcard character must occupy entire level
+                        return 0x80
+            # Check if the client is authorised to connect to the topic
+            permitted = await self.topic_filtering(session, topic=a_filter)
+            if not permitted:
+                return 0x80
+            qos = subscription[1]
+            if "max-qos" in self.config and qos > self.config["max-qos"]:
+                qos = self.config["max-qos"]
+            if a_filter not in self._subscriptions:
+                self._subscriptions[a_filter] = []
+            already_subscribed = next(
+                (
+                    s
+                    for (s, qos) in self._subscriptions[a_filter]
+                    if s.client_id == session.client_id
+                ),
+                None,
+            )
+            if not already_subscribed:
+                self._subscriptions[a_filter].append((session, qos))
+            else:
+                self.logger.debug(
+                    "Client %s has already subscribed to %s"
+                    % (format_client_message(session=session), a_filter)
+                )
+            return qos
+        except KeyError:
+            return 0x80
+
+    def _del_subscription(self, a_filter: str, session: Session) -> int:
+        """
+        Delete a session subscription on a given topic
+        :param a_filter:
+        :param session:
+        :return:
+        """
+        deleted = 0
+        try:
+            subscriptions = self._subscriptions[a_filter]
+            for index, (sub_session, qos) in enumerate(subscriptions):
+                if sub_session.client_id == session.client_id:
+                    self.logger.debug(
+                        "Removing subscription on topic '%s' for client %s"
+                        % (a_filter, format_client_message(session=session))
+                    )
+                    subscriptions.pop(index)
+                    deleted += 1
+                    break
+        except KeyError:
+            # Unsubscribe topic not found in current subscribed topics
+            pass
+        finally:
+            return deleted
+
+    def _del_all_subscriptions(self, session: Session) -> None:
+        """
+        Delete all topic subscriptions for a given session
+        :param session:
+        :return:
+        """
+        filter_queue = deque()
+        for topic in self._subscriptions:
+            if self._del_subscription(topic, session):
+                filter_queue.append(topic)
+        for topic in filter_queue:
+            if not self._subscriptions[topic]:
+                del self._subscriptions[topic]
+
+    def matches(self, topic, a_filter):
+        if "#" not in a_filter and "+" not in a_filter:
+            # if filter doesn't contain wildcard, return exact match
+            return a_filter == topic
+        else:
+            # else use regex
+            match_pattern = re.compile(
+                re.escape(a_filter)
+                .replace("\\#", "?.*")
+                .replace("\\+", "[^/]*")
+                .lstrip("?")
+            )
+            return match_pattern.fullmatch(topic)
+
+    async def _broadcast_loop(self):
+        running_tasks = deque()
+        try:
+            while True:
+                while running_tasks and running_tasks[0].done():
+                    task = running_tasks.popleft()
+                    try:
+                        task.result()  # make asyncio happy and collect results
+                    except Exception:
+                        pass
+                broadcast = await self._broadcast_queue.get()
+                if self.logger.isEnabledFor(logging.DEBUG):
+                    self.logger.debug("broadcasting %r" % broadcast)
+                for k_filter in self._subscriptions:
+                    if broadcast["topic"].startswith("$") and (
+                        k_filter.startswith("+") or k_filter.startswith("#")
+                    ):
+                        self.logger.debug(
+                            "[MQTT-4.7.2-1] - ignoring brodcasting $ topic to subscriptions starting with + or #"
+                        )
+                    elif self.matches(broadcast["topic"], k_filter):
+                        subscriptions = self._subscriptions[k_filter]
+                        for (target_session, qos) in subscriptions:
+                            if "qos" in broadcast:
+                                qos = broadcast["qos"]
+                            if target_session.transitions.state == "connected":
+                                self.logger.debug(
+                                    "broadcasting application message from %s on topic '%s' to %s"
+                                    % (
+                                        format_client_message(
+                                            session=broadcast["session"]
+                                        ),
+                                        broadcast["topic"],
+                                        format_client_message(session=target_session),
+                                    )
+                                )
+                                handler = self._get_handler(target_session)
+                                task = asyncio.ensure_future(
+                                    handler.mqtt_publish(
+                                        broadcast["topic"],
+                                        broadcast["data"],
+                                        qos,
+                                        retain=False,
+                                    ),
+                                    loop=self._loop,
+                                )
+                                running_tasks.append(task)
+                            else:
+                                self.logger.debug(
+                                    "retaining application message from %s on topic '%s' to client '%s'"
+                                    % (
+                                        format_client_message(
+                                            session=broadcast["session"]
+                                        ),
+                                        broadcast["topic"],
+                                        format_client_message(session=target_session),
+                                    )
+                                )
+                                retained_message = RetainedApplicationMessage(
+                                    broadcast["session"],
+                                    broadcast["topic"],
+                                    broadcast["data"],
+                                    qos,
+                                )
+                                await target_session.retained_messages.put(
+                                    retained_message
+                                )
+                                if self.logger.isEnabledFor(logging.DEBUG):
+                                    self.logger.debug(
+                                        f"target_session.retained_messages={target_session.retained_messages.qsize()}"
+                                    )
+        except CancelledError:
+            # Wait until current broadcasting tasks end
+            if running_tasks:
+                await asyncio.wait(running_tasks, loop=self._loop)
+            raise  # reraise per CancelledError semantics
+
+    async def _broadcast_message(self, session, topic, data, force_qos=None):
+        broadcast = {"session": session, "topic": topic, "data": data}
+        if force_qos:
+            broadcast["qos"] = force_qos
+        await self._broadcast_queue.put(broadcast)
+
+    async def publish_session_retained_messages(self, session):
+        self.logger.debug(
+            "Publishing %d messages retained for session %s"
+            % (
+                session.retained_messages.qsize(),
+                format_client_message(session=session),
+            )
+        )
+        publish_tasks = []
+        handler = self._get_handler(session)
+        while not session.retained_messages.empty():
+            retained = await session.retained_messages.get()
+            publish_tasks.append(
+                asyncio.ensure_future(
+                    handler.mqtt_publish(
+                        retained.topic, retained.data, retained.qos, True
+                    ),
+                    loop=self._loop,
+                )
+            )
+        if publish_tasks:
+            await asyncio.wait(publish_tasks, loop=self._loop)
+
+    async def publish_retained_messages_for_subscription(self, subscription, session):
+        self.logger.debug(
+            "Begin broadcasting messages retained due to subscription on '%s' from %s"
+            % (subscription[0], format_client_message(session=session))
+        )
+        publish_tasks = []
+        handler = self._get_handler(session)
+        for d_topic in self._retained_messages:
+            self.logger.debug("matching : %s %s" % (d_topic, subscription[0]))
+            if self.matches(d_topic, subscription[0]):
+                self.logger.debug("%s and %s match" % (d_topic, subscription[0]))
+                retained = self._retained_messages[d_topic]
+                publish_tasks.append(
+                    asyncio.Task(
+                        handler.mqtt_publish(
+                            retained.topic, retained.data, subscription[1], True
+                        ),
+                        loop=self._loop,
+                    )
+                )
+        if publish_tasks:
+            await asyncio.wait(publish_tasks, loop=self._loop)
+        self.logger.debug(
+            "End broadcasting messages retained due to subscription on '%s' from %s"
+            % (subscription[0], format_client_message(session=session))
+        )
+
+    def delete_session(self, client_id: str) -> None:
+        """
+        Delete an existing session data, for example due to clean session set in CONNECT
+        :param client_id:
+        :return:
+        """
+        try:
+            session = self._sessions[client_id][0]
+        except KeyError:
+            session = None
+        if session is None:
+            self.logger.debug("Delete session : session %s doesn't exist" % client_id)
+            return
+
+        # Delete subscriptions
+        self.logger.debug("deleting session %s subscriptions" % repr(session))
+        self._del_all_subscriptions(session)
+
+        self.logger.debug(
+            "deleting existing session %s" % repr(self._sessions[client_id])
+        )
+        del self._sessions[client_id]
+
+    def _get_handler(self, session):
+        client_id = session.client_id
+        if client_id:
+            try:
+                return self._sessions[client_id][1]
+            except KeyError:
+                pass
+        return None
diff --git a/amqtt/client.py b/amqtt/client.py
new file mode 100644
index 00000000..f07653ea
--- /dev/null
+++ b/amqtt/client.py
@@ -0,0 +1,585 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+
+import asyncio
+import logging
+import ssl
+import copy
+from urllib.parse import urlparse, urlunparse
+from functools import wraps
+
+from amqtt.session import Session
+from amqtt.mqtt.connack import CONNECTION_ACCEPTED
+from amqtt.mqtt.protocol.client_handler import ClientProtocolHandler
+from amqtt.adapters import (
+    StreamReaderAdapter,
+    StreamWriterAdapter,
+    WebSocketsReader,
+    WebSocketsWriter,
+)
+from amqtt.plugins.manager import PluginManager, BaseContext
+from amqtt.mqtt.protocol.handler import ProtocolHandlerException
+from amqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
+import websockets
+from websockets.uri import InvalidURI
+from websockets.exceptions import InvalidHandshake
+from collections import deque
+
+
+_defaults = {
+    "keep_alive": 10,
+    "ping_delay": 1,
+    "default_qos": 0,
+    "default_retain": False,
+    "auto_reconnect": True,
+    "reconnect_max_interval": 10,
+    "reconnect_retries": 2,
+}
+
+
+class ClientException(Exception):
+    pass
+
+
+class ConnectException(ClientException):
+    pass
+
+
+class ClientContext(BaseContext):
+    """
+    ClientContext is used as the context passed to plugins interacting with the client.
+    It act as an adapter to client services from plugins
+    """
+
+    def __init__(self):
+        super().__init__()
+        self.config = None
+
+
+base_logger = logging.getLogger(__name__)
+
+
+def mqtt_connected(func):
+    """
+    MQTTClient coroutines decorator which will wait until connection before calling the decorated method.
+    :param func: coroutine to be called once connected
+    :return: coroutine result
+    """
+    wraps(func)
+
+    async def wrapper(self, *args, **kwargs):
+        if not self._connected_state.is_set():
+            base_logger.warning("Client not connected, waiting for it")
+            _, pending = await asyncio.wait(
+                [self._connected_state.wait(), self._no_more_connections.wait()],
+                return_when=asyncio.FIRST_COMPLETED,
+            )
+            for t in pending:
+                t.cancel()
+            if self._no_more_connections.is_set():
+                raise ClientException("Will not reconnect")
+        return await func(self, *args, **kwargs)
+
+    return wrapper
+
+
+class MQTTClient:
+    """
+    MQTT client implementation.
+
+    MQTTClient instances provides API for connecting to a broker and send/receive messages using the MQTT protocol.
+
+    :param client_id: MQTT client ID to use when connecting to the broker. If none, it will generated randomly by :func:`amqtt.utils.gen_client_id`
+    :param config: Client configuration
+    :param loop: asynio loop to use
+    :return: class instance
+    """
+
+    def __init__(self, client_id=None, config=None, loop=None):
+        self.logger = logging.getLogger(__name__)
+        self.config = copy.deepcopy(_defaults)
+        if config is not None:
+            self.config.update(config)
+        if client_id is not None:
+            self.client_id = client_id
+        else:
+            from amqtt.utils import gen_client_id
+
+            self.client_id = gen_client_id()
+            self.logger.debug("Using generated client ID : %s" % self.client_id)
+
+        if loop is not None:
+            self._loop = loop
+        else:
+            self._loop = asyncio.get_event_loop()
+        self.session = None
+        self._handler = None
+        self._disconnect_task = None
+        self._connected_state = asyncio.Event(loop=self._loop)
+        self._no_more_connections = asyncio.Event(loop=self._loop)
+        self.extra_headers = {}
+
+        # Init plugins manager
+        context = ClientContext()
+        context.config = self.config
+        self.plugins_manager = PluginManager(
+            "amqtt.client.plugins", context, loop=self._loop
+        )
+        self.client_tasks = deque()
+
+    async def connect(
+        self,
+        uri=None,
+        cleansession=None,
+        cafile=None,
+        capath=None,
+        cadata=None,
+        extra_headers=None,
+    ):
+        """
+        Connect to a remote broker.
+
+        At first, a network connection is established with the server using the given protocol (``mqtt``, ``mqtts``, ``ws`` or ``wss``). Once the socket is connected, a `CONNECT <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028>`_ message is sent with the requested informations.
+
+        This method is a *coroutine*.
+
+        :param uri: Broker URI connection, conforming to `MQTT URI scheme <https://github.com/mqtt/mqtt.github.io/wiki/URI-Scheme>`_. Uses ``uri`` config attribute by default.
+        :param cleansession: MQTT CONNECT clean session flag
+        :param cafile: server certificate authority file (optional, used for secured connection)
+        :param capath: server certificate authority path (optional, used for secured connection)
+        :param cadata: server certificate authority data (optional, used for secured connection)
+        :param extra_headers: a dictionary with additional http headers that should be sent on the initial connection (optional, used only with websocket connections)
+        :return: `CONNACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033>`_ return code
+        :raise: :class:`amqtt.client.ConnectException` if connection fails
+        """
+
+        if extra_headers is None:
+            extra_headers = {}
+
+        self.session = self._initsession(uri, cleansession, cafile, capath, cadata)
+        self.extra_headers = extra_headers
+        self.logger.debug("Connect to: %s" % uri)
+
+        try:
+            return await self._do_connect()
+        except asyncio.CancelledError:
+            raise
+        except BaseException as be:
+            self.logger.warning("Connection failed: %r" % be)
+            auto_reconnect = self.config.get("auto_reconnect", False)
+            if not auto_reconnect:
+                raise
+            else:
+                return await self.reconnect()
+
+    async def disconnect(self):
+        """
+        Disconnect from the connected broker.
+
+        This method sends a `DISCONNECT <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718090>`_ message and closes the network socket.
+
+        This method is a *coroutine*.
+        """
+        await self.cancel_tasks()
+        if self.session.transitions.is_connected():
+            if not self._disconnect_task.done():
+                self._disconnect_task.cancel()
+            await self._handler.mqtt_disconnect()
+            self._connected_state.clear()
+            await self._handler.stop()
+            self.session.transitions.disconnect()
+        else:
+            self.logger.warning(
+                "Client session is not currently connected, ignoring call"
+            )
+
+    async def cancel_tasks(self):
+        """
+        Before disconnection need to cancel all pending tasks
+        :return:
+        """
+        try:
+            while self.client_tasks:
+                task = self.client_tasks.pop()
+                task.cancel()
+        except IndexError:
+            pass
+
+    async def reconnect(self, cleansession=None):
+        """
+        Reconnect a previously connected broker.
+
+        Reconnection tries to establish a network connection and send a `CONNECT <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028>`_ message.
+        Retries interval and attempts can be controlled with the ``reconnect_max_interval`` and ``reconnect_retries`` configuration parameters.
+
+        This method is a *coroutine*.
+
+        :param cleansession: clean session flag used in MQTT CONNECT messages sent for reconnections.
+        :return: `CONNACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033>`_ return code
+        :raise: :class:`amqtt.client.ConnectException` if re-connection fails after max retries.
+        """
+
+        if self.session.transitions.is_connected():
+            self.logger.warning("Client already connected")
+            return CONNECTION_ACCEPTED
+
+        if cleansession:
+            self.session.clean_session = cleansession
+        self.logger.debug("Reconnecting with session parameters: %s" % self.session)
+        reconnect_max_interval = self.config.get("reconnect_max_interval", 10)
+        reconnect_retries = self.config.get("reconnect_retries", 5)
+        nb_attempt = 1
+        await asyncio.sleep(1, loop=self._loop)
+        while True:
+            try:
+                self.logger.debug("Reconnect attempt %d ..." % nb_attempt)
+                return await self._do_connect()
+            except asyncio.CancelledError:
+                raise
+            except BaseException as e:
+                self.logger.warning("Reconnection attempt failed: %r" % e)
+                if reconnect_retries >= 0 and nb_attempt > reconnect_retries:
+                    self.logger.error(
+                        "Maximum number of connection attempts reached. Reconnection aborted"
+                    )
+                    raise ConnectException("Too many connection attempts failed")
+                exp = 2 ** nb_attempt
+                delay = exp if exp < reconnect_max_interval else reconnect_max_interval
+                self.logger.debug("Waiting %d second before next attempt" % delay)
+                await asyncio.sleep(delay, loop=self._loop)
+                nb_attempt += 1
+
+    async def _do_connect(self):
+        return_code = await self._connect_coro()
+        self._disconnect_task = asyncio.ensure_future(
+            self.handle_connection_close(), loop=self._loop
+        )
+        return return_code
+
+    @mqtt_connected
+    async def ping(self):
+        """
+        Ping the broker.
+
+        Send a MQTT `PINGREQ <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718081>`_ message for response.
+
+        This method is a *coroutine*.
+        """
+
+        if self.session.transitions.is_connected():
+            await self._handler.mqtt_ping()
+        else:
+            self.logger.warning(
+                "MQTT PING request incompatible with current session state '%s'"
+                % self.session.transitions.state
+            )
+
+    @mqtt_connected
+    async def publish(self, topic, message, qos=None, retain=None, ack_timeout=None):
+        """
+        Publish a message to the broker.
+
+        Send a MQTT `PUBLISH <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037>`_ message and wait for acknowledgment depending on Quality Of Service
+
+        This method is a *coroutine*.
+
+        :param topic: topic name to which message data is published
+        :param message: payload message (as bytes) to send.
+        :param qos: requested publish quality of service : QOS_0, QOS_1 or QOS_2. Defaults to ``default_qos`` config parameter or QOS_0.
+        :param retain: retain flag. Defaults to ``default_retain`` config parameter or False.
+        """
+
+        def get_retain_and_qos():
+            if qos:
+                assert qos in (QOS_0, QOS_1, QOS_2)
+                _qos = qos
+            else:
+                _qos = self.config["default_qos"]
+                try:
+                    _qos = self.config["topics"][topic]["qos"]
+                except KeyError:
+                    pass
+            if retain:
+                _retain = retain
+            else:
+                _retain = self.config["default_retain"]
+                try:
+                    _retain = self.config["topics"][topic]["retain"]
+                except KeyError:
+                    pass
+            return _qos, _retain
+
+        (app_qos, app_retain) = get_retain_and_qos()
+        return await self._handler.mqtt_publish(
+            topic, message, app_qos, app_retain, ack_timeout
+        )
+
+    @mqtt_connected
+    async def subscribe(self, topics):
+        """
+        Subscribe to some topics.
+
+        Send a MQTT `SUBSCRIBE <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718063>`_ message and wait for broker acknowledgment.
+
+        This method is a *coroutine*.
+
+        :param topics: array of topics pattern to subscribe with associated QoS.
+        :return: `SUBACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718068>`_ message return code.
+
+        Example of ``topics`` argument expected structure:
+        ::
+
+            [
+                ('$SYS/broker/uptime', QOS_1),
+                ('$SYS/broker/load/#', QOS_2),
+            ]
+        """
+        return await self._handler.mqtt_subscribe(topics, self.session.next_packet_id)
+
+    @mqtt_connected
+    async def unsubscribe(self, topics):
+        """
+        Unsubscribe from some topics.
+
+        Send a MQTT `UNSUBSCRIBE <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718072>`_ message and wait for broker `UNSUBACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718077>`_ message.
+
+        This method is a *coroutine*.
+
+        :param topics: array of topics to unsubscribe from.
+
+        Example of ``topics`` argument expected structure:
+        ::
+
+            ['$SYS/broker/uptime', '$SYS/broker/load/#']
+        """
+        await self._handler.mqtt_unsubscribe(topics, self.session.next_packet_id)
+
+    async def deliver_message(self, timeout=None):
+        """
+        Deliver next received message.
+
+        Deliver next message received from the broker. If no message is available, this methods waits until next message arrives or ``timeout`` occurs.
+
+        This method is a *coroutine*.
+
+        :param timeout: maximum number of seconds to wait before returning. If timeout is not specified or None, there is no limit to the wait time until next message arrives.
+        :return: instance of :class:`amqtt.session.ApplicationMessage` containing received message information flow.
+        :raises: :class:`asyncio.TimeoutError` if timeout occurs before a message is delivered
+        """
+        deliver_task = asyncio.ensure_future(
+            self._handler.mqtt_deliver_next_message(), loop=self._loop
+        )
+        self.client_tasks.append(deliver_task)
+        self.logger.debug("Waiting message delivery")
+        done, pending = await asyncio.wait(
+            [deliver_task],
+            loop=self._loop,
+            return_when=asyncio.FIRST_EXCEPTION,
+            timeout=timeout,
+        )
+        if self.client_tasks:
+            self.client_tasks.pop()
+        if deliver_task in done:
+            if deliver_task.exception() is not None:
+                # deliver_task raised an exception, pass it on to our caller
+                raise deliver_task.exception()
+            return deliver_task.result()
+        else:
+            # timeout occured before message received
+            deliver_task.cancel()
+            raise asyncio.TimeoutError
+
+    async def _connect_coro(self):
+        kwargs = dict()
+
+        # Decode URI attributes
+        uri_attributes = urlparse(self.session.broker_uri)
+        scheme = uri_attributes.scheme
+        secure = True if scheme in ("mqtts", "wss") else False
+        self.session.username = (
+            self.session.username if self.session.username else uri_attributes.username
+        )
+        self.session.password = (
+            self.session.password if self.session.password else uri_attributes.password
+        )
+        self.session.remote_address = uri_attributes.hostname
+        self.session.remote_port = uri_attributes.port
+        if scheme in ("mqtt", "mqtts") and not self.session.remote_port:
+            self.session.remote_port = 8883 if scheme == "mqtts" else 1883
+        if scheme in ("ws", "wss") and not self.session.remote_port:
+            self.session.remote_port = 443 if scheme == "wss" else 80
+        if scheme in ("ws", "wss"):
+            # Rewrite URI to conform to https://tools.ietf.org/html/rfc6455#section-3
+            uri = (
+                scheme,
+                self.session.remote_address + ":" + str(self.session.remote_port),
+                uri_attributes[2],
+                uri_attributes[3],
+                uri_attributes[4],
+                uri_attributes[5],
+            )
+            self.session.broker_uri = urlunparse(uri)
+        # Init protocol handler
+        # if not self._handler:
+        self._handler = ClientProtocolHandler(self.plugins_manager, loop=self._loop)
+
+        if secure:
+            sc = ssl.create_default_context(
+                ssl.Purpose.SERVER_AUTH,
+                cafile=self.session.cafile,
+                capath=self.session.capath,
+                cadata=self.session.cadata,
+            )
+            if "certfile" in self.config and "keyfile" in self.config:
+                sc.load_cert_chain(self.config["certfile"], self.config["keyfile"])
+            if "check_hostname" in self.config and isinstance(
+                self.config["check_hostname"], bool
+            ):
+                sc.check_hostname = self.config["check_hostname"]
+            kwargs["ssl"] = sc
+
+        try:
+            reader = None
+            writer = None
+            self._connected_state.clear()
+            # Open connection
+            if scheme in ("mqtt", "mqtts"):
+                conn_reader, conn_writer = await asyncio.open_connection(
+                    self.session.remote_address,
+                    self.session.remote_port,
+                    loop=self._loop,
+                    **kwargs
+                )
+                reader = StreamReaderAdapter(conn_reader)
+                writer = StreamWriterAdapter(conn_writer)
+            elif scheme in ("ws", "wss"):
+                websocket = await websockets.connect(
+                    self.session.broker_uri,
+                    subprotocols=["mqtt"],
+                    loop=self._loop,
+                    extra_headers=self.extra_headers,
+                    **kwargs
+                )
+                reader = WebSocketsReader(websocket)
+                writer = WebSocketsWriter(websocket)
+            # Start MQTT protocol
+            self._handler.attach(self.session, reader, writer)
+            return_code = await self._handler.mqtt_connect()
+            if return_code is not CONNECTION_ACCEPTED:
+                self.session.transitions.disconnect()
+                self.logger.warning("Connection rejected with code '%s'" % return_code)
+                exc = ConnectException("Connection rejected by broker")
+                exc.return_code = return_code
+                raise exc
+            else:
+                # Handle MQTT protocol
+                await self._handler.start()
+                self.session.transitions.connect()
+                self._connected_state.set()
+                self.logger.debug(
+                    "connected to %s:%s"
+                    % (self.session.remote_address, self.session.remote_port)
+                )
+            return return_code
+        except InvalidURI as iuri:
+            self.logger.warning(
+                "connection failed: invalid URI '%s'" % self.session.broker_uri
+            )
+            self.session.transitions.disconnect()
+            raise ConnectException(
+                "connection failed: invalid URI '%s'" % self.session.broker_uri, iuri
+            )
+        except InvalidHandshake as ihs:
+            self.logger.warning("connection failed: invalid websocket handshake")
+            self.session.transitions.disconnect()
+            raise ConnectException(
+                "connection failed: invalid websocket handshake", ihs
+            )
+        except (ProtocolHandlerException, ConnectionError, OSError) as e:
+            self.logger.warning("MQTT connection failed: %r" % e)
+            self.session.transitions.disconnect()
+            raise ConnectException(e)
+
+    async def handle_connection_close(self):
+        def cancel_tasks():
+            self._no_more_connections.set()
+            while self.client_tasks:
+                task = self.client_tasks.popleft()
+                if not task.done():
+                    task.set_exception(ClientException("Connection lost"))
+
+        self.logger.debug("Watch broker disconnection")
+        # Wait for disconnection from broker (like connection lost)
+        await self._handler.wait_disconnect()
+        self.logger.warning("Disconnected from broker")
+
+        # Block client API
+        self._connected_state.clear()
+
+        # stop an clean handler
+        # await self._handler.stop()
+        self._handler.detach()
+        self.session.transitions.disconnect()
+
+        if self.config.get("auto_reconnect", False):
+            # Try reconnection
+            self.logger.debug("Auto-reconnecting")
+            try:
+                await self.reconnect()
+            except ConnectException:
+                # Cancel client pending tasks
+                cancel_tasks()
+        else:
+            # Cancel client pending tasks
+            cancel_tasks()
+
+    def _initsession(
+        self, uri=None, cleansession=None, cafile=None, capath=None, cadata=None
+    ) -> Session:
+        # Load config
+        broker_conf = self.config.get("broker", dict()).copy()
+        if uri:
+            broker_conf["uri"] = uri
+        if cafile:
+            broker_conf["cafile"] = cafile
+        elif "cafile" not in broker_conf:
+            broker_conf["cafile"] = None
+        if capath:
+            broker_conf["capath"] = capath
+        elif "capath" not in broker_conf:
+            broker_conf["capath"] = None
+        if cadata:
+            broker_conf["cadata"] = cadata
+        elif "cadata" not in broker_conf:
+            broker_conf["cadata"] = None
+
+        if cleansession is not None:
+            broker_conf["cleansession"] = cleansession
+
+        if not broker_conf.get("uri"):
+            raise ClientException("Missing connection parameter 'uri'")
+
+        s = Session()
+        s.broker_uri = broker_conf["uri"]
+        s.client_id = self.client_id
+        s.cafile = broker_conf["cafile"]
+        s.capath = broker_conf["capath"]
+        s.cadata = broker_conf["cadata"]
+        if cleansession is not None:
+            s.clean_session = cleansession
+        else:
+            s.clean_session = self.config.get("cleansession", True)
+        s.keep_alive = self.config["keep_alive"] - self.config["ping_delay"]
+        if "will" in self.config:
+            s.will_flag = True
+            s.will_retain = self.config["will"]["retain"]
+            s.will_topic = self.config["will"]["topic"]
+            s.will_message = self.config["will"]["message"]
+            s.will_qos = self.config["will"]["qos"]
+        else:
+            s.will_flag = False
+            s.will_retain = False
+            s.will_topic = None
+            s.will_message = None
+        return s
diff --git a/amqtt/codecs.py b/amqtt/codecs.py
new file mode 100644
index 00000000..45cb7c52
--- /dev/null
+++ b/amqtt/codecs.py
@@ -0,0 +1,119 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import asyncio
+from struct import pack, unpack
+from amqtt.errors import NoDataException
+
+
+def bytes_to_hex_str(data):
+    """
+    converts a sequence of bytes into its displayable hex representation, ie: 0x??????
+    :param data: byte sequence
+    :return: Hexadecimal displayable representation
+    """
+    return "0x" + "".join(format(b, "02x") for b in data)
+
+
+def bytes_to_int(data):
+    """
+    convert a sequence of bytes to an integer using big endian byte ordering
+    :param data: byte sequence
+    :return: integer value
+    """
+    try:
+        return int.from_bytes(data, byteorder="big")
+    except:
+        return data
+
+
+def int_to_bytes(int_value: int, length: int) -> bytes:
+    """
+    convert an integer to a sequence of bytes using big endian byte ordering
+    :param int_value: integer value to convert
+    :param length: (optional) byte length
+    :return: byte sequence
+    """
+    if length == 1:
+        fmt = "!B"
+    elif length == 2:
+        fmt = "!H"
+    return pack(fmt, int_value)
+
+
+async def read_or_raise(reader, n=-1):
+    """
+    Read a given byte number from Stream. NoDataException is raised if read gives no data
+    :param reader: reader adapter
+    :param n: number of bytes to read
+    :return: bytes read
+    """
+    try:
+        data = await reader.read(n)
+    except (asyncio.IncompleteReadError, ConnectionResetError, BrokenPipeError):
+        data = None
+    if not data:
+        raise NoDataException("No more data")
+    return data
+
+
+async def decode_string(reader) -> str:
+    """
+    Read a string from a reader and decode it according to MQTT string specification
+    :param reader: Stream reader
+    :return: string read from stream
+    """
+    length_bytes = await read_or_raise(reader, 2)
+    str_length = unpack("!H", length_bytes)
+    if str_length[0]:
+        byte_str = await read_or_raise(reader, str_length[0])
+        try:
+            return byte_str.decode(encoding="utf-8")
+        except:
+            return str(byte_str)
+    else:
+        return ""
+
+
+async def decode_data_with_length(reader) -> bytes:
+    """
+    Read data from a reader. Data is prefixed with 2 bytes length
+    :param reader: Stream reader
+    :return: bytes read from stream (without length)
+    """
+    length_bytes = await read_or_raise(reader, 2)
+    bytes_length = unpack("!H", length_bytes)
+    data = await read_or_raise(reader, bytes_length[0])
+    return data
+
+
+def encode_string(string: str) -> bytes:
+    data = string.encode(encoding="utf-8")
+    data_length = len(data)
+    return int_to_bytes(data_length, 2) + data
+
+
+def encode_data_with_length(data: bytes) -> bytes:
+    data_length = len(data)
+    return int_to_bytes(data_length, 2) + data
+
+
+async def decode_packet_id(reader) -> int:
+    """
+    Read a packet ID as 2-bytes int from stream according to MQTT specification (2.3.1)
+    :param reader: Stream reader
+    :return: Packet ID
+    """
+    packet_id_bytes = await read_or_raise(reader, 2)
+    packet_id = unpack("!H", packet_id_bytes)
+    return packet_id[0]
+
+
+def int_to_bytes_str(value: int) -> bytes:
+    """
+    Converts a int value to a bytes array containing the numeric character.
+    Ex: 123 -> b'123'
+    :param value: int value to convert
+    :return: bytes array
+    """
+    return str(value).encode("utf-8")
diff --git a/amqtt/errors.py b/amqtt/errors.py
new file mode 100644
index 00000000..c36d7458
--- /dev/null
+++ b/amqtt/errors.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+
+
+class HBMQTTException(Exception):
+    """
+    HBMQTT base exception
+    """
+
+    pass
+
+
+class MQTTException(Exception):
+    """
+    Base class for all errors refering to MQTT specifications
+    """
+
+    pass
+
+
+class CodecException(Exception):
+    """
+    Exceptions thrown by packet encode/decode functions
+    """
+
+    pass
+
+
+class NoDataException(Exception):
+    """
+    Exceptions thrown by packet encode/decode functions
+    """
+
+    pass
diff --git a/amqtt/mqtt/__init__.py b/amqtt/mqtt/__init__.py
new file mode 100644
index 00000000..808c9716
--- /dev/null
+++ b/amqtt/mqtt/__init__.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.errors import HBMQTTException
+from amqtt.mqtt.packet import (
+    CONNECT,
+    CONNACK,
+    PUBLISH,
+    PUBACK,
+    PUBREC,
+    PUBREL,
+    PUBCOMP,
+    SUBSCRIBE,
+    SUBACK,
+    UNSUBSCRIBE,
+    UNSUBACK,
+    PINGREQ,
+    PINGRESP,
+    DISCONNECT,
+    MQTTFixedHeader,
+)
+from amqtt.mqtt.connect import ConnectPacket
+from amqtt.mqtt.connack import ConnackPacket
+from amqtt.mqtt.disconnect import DisconnectPacket
+from amqtt.mqtt.pingreq import PingReqPacket
+from amqtt.mqtt.pingresp import PingRespPacket
+from amqtt.mqtt.publish import PublishPacket
+from amqtt.mqtt.puback import PubackPacket
+from amqtt.mqtt.pubrec import PubrecPacket
+from amqtt.mqtt.pubrel import PubrelPacket
+from amqtt.mqtt.pubcomp import PubcompPacket
+from amqtt.mqtt.subscribe import SubscribePacket
+from amqtt.mqtt.suback import SubackPacket
+from amqtt.mqtt.unsubscribe import UnsubscribePacket
+from amqtt.mqtt.unsuback import UnsubackPacket
+
+packet_dict = {
+    CONNECT: ConnectPacket,
+    CONNACK: ConnackPacket,
+    PUBLISH: PublishPacket,
+    PUBACK: PubackPacket,
+    PUBREC: PubrecPacket,
+    PUBREL: PubrelPacket,
+    PUBCOMP: PubcompPacket,
+    SUBSCRIBE: SubscribePacket,
+    SUBACK: SubackPacket,
+    UNSUBSCRIBE: UnsubscribePacket,
+    UNSUBACK: UnsubackPacket,
+    PINGREQ: PingReqPacket,
+    PINGRESP: PingRespPacket,
+    DISCONNECT: DisconnectPacket,
+}
+
+
+def packet_class(fixed_header: MQTTFixedHeader):
+    try:
+        cls = packet_dict[fixed_header.packet_type]
+        return cls
+    except KeyError:
+        raise HBMQTTException("Unexpected packet Type '%s'" % fixed_header.packet_type)
diff --git a/amqtt/mqtt/connack.py b/amqtt/mqtt/connack.py
new file mode 100644
index 00000000..dc1d0355
--- /dev/null
+++ b/amqtt/mqtt/connack.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import CONNACK, MQTTPacket, MQTTFixedHeader, MQTTVariableHeader
+from amqtt.codecs import read_or_raise, bytes_to_int
+from amqtt.errors import HBMQTTException
+from amqtt.adapters import ReaderAdapter
+
+CONNECTION_ACCEPTED = 0x00
+UNACCEPTABLE_PROTOCOL_VERSION = 0x01
+IDENTIFIER_REJECTED = 0x02
+SERVER_UNAVAILABLE = 0x03
+BAD_USERNAME_PASSWORD = 0x04
+NOT_AUTHORIZED = 0x05
+
+
+class ConnackVariableHeader(MQTTVariableHeader):
+
+    __slots__ = ("session_parent", "return_code")
+
+    def __init__(self, session_parent=None, return_code=None):
+        super().__init__()
+        self.session_parent = session_parent
+        self.return_code = return_code
+
+    @classmethod
+    async def from_stream(cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader):
+        data = await read_or_raise(reader, 2)
+        session_parent = data[0] & 0x01
+        return_code = bytes_to_int(data[1])
+        return cls(session_parent, return_code)
+
+    def to_bytes(self):
+        out = bytearray(2)
+        # Connect acknowledge flags
+        if self.session_parent:
+            out[0] = 1
+        else:
+            out[0] = 0
+        # return code
+        out[1] = self.return_code
+
+        return out
+
+    def __repr__(self):
+        return type(self).__name__ + "(session_parent={0}, return_code={1})".format(
+            hex(self.session_parent), hex(self.return_code)
+        )
+
+
+class ConnackPacket(MQTTPacket):
+    VARIABLE_HEADER = ConnackVariableHeader
+    PAYLOAD = None
+
+    @property
+    def return_code(self):
+        return self.variable_header.return_code
+
+    @return_code.setter
+    def return_code(self, return_code):
+        self.variable_header.return_code = return_code
+
+    @property
+    def session_parent(self):
+        return self.variable_header.session_parent
+
+    @session_parent.setter
+    def session_parent(self, session_parent):
+        self.variable_header.session_parent = session_parent
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: ConnackVariableHeader = None,
+        payload=None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(CONNACK, 0x00)
+        else:
+            if fixed.packet_type is not CONNACK:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for ConnackPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = None
+
+    @classmethod
+    def build(cls, session_parent=None, return_code=None):
+        v_header = ConnackVariableHeader(session_parent, return_code)
+        packet = ConnackPacket(variable_header=v_header)
+        return packet
diff --git a/amqtt/mqtt/connect.py b/amqtt/mqtt/connect.py
new file mode 100644
index 00000000..380f29a0
--- /dev/null
+++ b/amqtt/mqtt/connect.py
@@ -0,0 +1,400 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+
+from amqtt.codecs import (
+    bytes_to_int,
+    decode_data_with_length,
+    decode_string,
+    encode_data_with_length,
+    encode_string,
+    int_to_bytes,
+    read_or_raise,
+)
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    CONNECT,
+    MQTTVariableHeader,
+    MQTTPayload,
+)
+from amqtt.errors import HBMQTTException, NoDataException
+from amqtt.adapters import ReaderAdapter
+from amqtt.utils import gen_client_id
+
+
+class ConnectVariableHeader(MQTTVariableHeader):
+
+    __slots__ = ("proto_name", "proto_level", "flags", "keep_alive")
+
+    USERNAME_FLAG = 0x80
+    PASSWORD_FLAG = 0x40
+    WILL_RETAIN_FLAG = 0x20
+    WILL_FLAG = 0x04
+    WILL_QOS_MASK = 0x18
+    CLEAN_SESSION_FLAG = 0x02
+    RESERVED_FLAG = 0x01
+
+    def __init__(
+        self, connect_flags=0x00, keep_alive=0, proto_name="MQTT", proto_level=0x04
+    ):
+        super().__init__()
+        self.proto_name = proto_name
+        self.proto_level = proto_level
+        self.flags = connect_flags
+        self.keep_alive = keep_alive
+
+    def __repr__(self):
+        return "ConnectVariableHeader(proto_name={0}, proto_level={1}, flags={2}, keepalive={3})".format(
+            self.proto_name, self.proto_level, hex(self.flags), self.keep_alive
+        )
+
+    def _set_flag(self, val, mask):
+        if val:
+            self.flags |= mask
+        else:
+            self.flags &= ~mask
+
+    def _get_flag(self, mask):
+        if self.flags & mask:
+            return True
+        else:
+            return False
+
+    @property
+    def username_flag(self) -> bool:
+        return self._get_flag(self.USERNAME_FLAG)
+
+    @username_flag.setter
+    def username_flag(self, val: bool):
+        self._set_flag(val, self.USERNAME_FLAG)
+
+    @property
+    def password_flag(self) -> bool:
+        return self._get_flag(self.PASSWORD_FLAG)
+
+    @password_flag.setter
+    def password_flag(self, val: bool):
+        self._set_flag(val, self.PASSWORD_FLAG)
+
+    @property
+    def will_retain_flag(self) -> bool:
+        return self._get_flag(self.WILL_RETAIN_FLAG)
+
+    @will_retain_flag.setter
+    def will_retain_flag(self, val: bool):
+        self._set_flag(val, self.WILL_RETAIN_FLAG)
+
+    @property
+    def will_flag(self) -> bool:
+        return self._get_flag(self.WILL_FLAG)
+
+    @will_flag.setter
+    def will_flag(self, val: bool):
+        self._set_flag(val, self.WILL_FLAG)
+
+    @property
+    def clean_session_flag(self) -> bool:
+        return self._get_flag(self.CLEAN_SESSION_FLAG)
+
+    @clean_session_flag.setter
+    def clean_session_flag(self, val: bool):
+        self._set_flag(val, self.CLEAN_SESSION_FLAG)
+
+    @property
+    def reserved_flag(self) -> bool:
+        return self._get_flag(self.RESERVED_FLAG)
+
+    @property
+    def will_qos(self):
+        return (self.flags & 0x18) >> 3
+
+    @will_qos.setter
+    def will_qos(self, val: int):
+        self.flags &= 0xE7  # Reset QOS flags
+        self.flags |= val << 3
+
+    @classmethod
+    async def from_stream(cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader):
+        #  protocol name
+        protocol_name = await decode_string(reader)
+
+        # protocol level
+        protocol_level_byte = await read_or_raise(reader, 1)
+        protocol_level = bytes_to_int(protocol_level_byte)
+
+        # flags
+        flags_byte = await read_or_raise(reader, 1)
+        flags = bytes_to_int(flags_byte)
+
+        # keep-alive
+        keep_alive_byte = await read_or_raise(reader, 2)
+        keep_alive = bytes_to_int(keep_alive_byte)
+
+        return cls(flags, keep_alive, protocol_name, protocol_level)
+
+    def to_bytes(self):
+        out = bytearray()
+
+        # Protocol name
+        out.extend(encode_string(self.proto_name))
+        # Protocol level
+        out.append(self.proto_level)
+        # flags
+        out.append(self.flags)
+        # keep alive
+        out.extend(int_to_bytes(self.keep_alive, 2))
+
+        return out
+
+
+class ConnectPayload(MQTTPayload):
+
+    __slots__ = (
+        "client_id",
+        "will_topic",
+        "will_message",
+        "username",
+        "password",
+        "client_id_is_random",
+    )
+
+    def __init__(
+        self,
+        client_id=None,
+        will_topic=None,
+        will_message=None,
+        username=None,
+        password=None,
+    ):
+        super().__init__()
+        self.client_id_is_random = False
+        self.client_id = client_id
+        self.will_topic = will_topic
+        self.will_message = will_message
+        self.username = username
+        self.password = password
+
+    def __repr__(self):
+        return "ConnectVariableHeader(client_id={0}, will_topic={1}, will_message={2}, username={3}, password={4})".format(
+            self.client_id,
+            self.will_topic,
+            self.will_message,
+            self.username,
+            self.password,
+        )
+
+    @classmethod
+    async def from_stream(
+        cls,
+        reader: ReaderAdapter,
+        fixed_header: MQTTFixedHeader,
+        variable_header: ConnectVariableHeader,
+    ):
+        payload = cls()
+        #  Client identifier
+        try:
+            payload.client_id = await decode_string(reader)
+        except NoDataException:
+            payload.client_id = None
+
+        if payload.client_id is None or payload.client_id == "":
+            # A Server MAY allow a Client to supply a ClientId that has a length of zero bytes
+            # [MQTT-3.1.3-6]
+            payload.client_id = gen_client_id()
+            # indicator to trow exception in case CLEAN_SESSION_FLAG is set to False
+            payload.client_id_is_random = True
+
+        # Read will topic, username and password
+        if variable_header.will_flag:
+            try:
+                payload.will_topic = await decode_string(reader)
+                payload.will_message = await decode_data_with_length(reader)
+            except NoDataException:
+                payload.will_topic = None
+                payload.will_message = None
+
+        if variable_header.username_flag:
+            try:
+                payload.username = await decode_string(reader)
+            except NoDataException:
+                payload.username = None
+
+        if variable_header.password_flag:
+            try:
+                payload.password = await decode_string(reader)
+            except NoDataException:
+                payload.password = None
+
+        return payload
+
+    def to_bytes(
+        self, fixed_header: MQTTFixedHeader, variable_header: ConnectVariableHeader
+    ):
+        out = bytearray()
+        # Client identifier
+        out.extend(encode_string(self.client_id))
+        # Will topic / message
+        if variable_header.will_flag:
+            out.extend(encode_string(self.will_topic))
+            out.extend(encode_data_with_length(self.will_message))
+        # username
+        if variable_header.username_flag:
+            out.extend(encode_string(self.username))
+        # password
+        if variable_header.password_flag:
+            out.extend(encode_string(self.password))
+
+        return out
+
+
+class ConnectPacket(MQTTPacket):
+    VARIABLE_HEADER = ConnectVariableHeader
+    PAYLOAD = ConnectPayload
+
+    @property
+    def proto_name(self):
+        return self.variable_header.proto_name
+
+    @proto_name.setter
+    def proto_name(self, name: str):
+        self.variable_header.proto_name = name
+
+    @property
+    def proto_level(self):
+        return self.variable_header.proto_level
+
+    @proto_level.setter
+    def proto_level(self, level):
+        self.variable_header.proto_level = level
+
+    @property
+    def username_flag(self):
+        return self.variable_header.username_flag
+
+    @username_flag.setter
+    def username_flag(self, flag):
+        self.variable_header.username_flag = flag
+
+    @property
+    def password_flag(self):
+        return self.variable_header.password_flag
+
+    @password_flag.setter
+    def password_flag(self, flag):
+        self.variable_header.password_flag = flag
+
+    @property
+    def clean_session_flag(self):
+        return self.variable_header.clean_session_flag
+
+    @clean_session_flag.setter
+    def clean_session_flag(self, flag):
+        self.variable_header.clean_session_flag = flag
+
+    @property
+    def will_retain_flag(self):
+        return self.variable_header.will_retain_flag
+
+    @will_retain_flag.setter
+    def will_retain_flag(self, flag):
+        self.variable_header.will_retain_flag = flag
+
+    @property
+    def will_qos(self):
+        return self.variable_header.will_qos
+
+    @will_qos.setter
+    def will_qos(self, flag):
+        self.variable_header.will_qos = flag
+
+    @property
+    def will_flag(self):
+        return self.variable_header.will_flag
+
+    @will_flag.setter
+    def will_flag(self, flag):
+        self.variable_header.will_flag = flag
+
+    @property
+    def reserved_flag(self):
+        return self.variable_header.reserved_flag
+
+    @reserved_flag.setter
+    def reserved_flag(self, flag):
+        self.variable_header.reserved_flag = flag
+
+    @property
+    def client_id(self):
+        return self.payload.client_id
+
+    @client_id.setter
+    def client_id(self, client_id):
+        self.payload.client_id = client_id
+
+    @property
+    def client_id_is_random(self) -> bool:
+        return self.payload.client_id_is_random
+
+    @client_id_is_random.setter
+    def client_id_is_random(self, client_id_is_random: bool):
+        self.payload.client_id_is_random = client_id_is_random
+
+    @property
+    def will_topic(self):
+        return self.payload.will_topic
+
+    @will_topic.setter
+    def will_topic(self, will_topic):
+        self.payload.will_topic = will_topic
+
+    @property
+    def will_message(self):
+        return self.payload.will_message
+
+    @will_message.setter
+    def will_message(self, will_message):
+        self.payload.will_message = will_message
+
+    @property
+    def username(self):
+        return self.payload.username
+
+    @username.setter
+    def username(self, username):
+        self.payload.username = username
+
+    @property
+    def password(self):
+        return self.payload.password
+
+    @password.setter
+    def password(self, password):
+        self.payload.password = password
+
+    @property
+    def keep_alive(self):
+        return self.variable_header.keep_alive
+
+    @keep_alive.setter
+    def keep_alive(self, keep_alive):
+        self.variable_header.keep_alive = keep_alive
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        vh: ConnectVariableHeader = None,
+        payload: ConnectPayload = None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(CONNECT, 0x00)
+        else:
+            if fixed.packet_type is not CONNECT:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for ConnectPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = vh
+        self.payload = payload
diff --git a/amqtt/mqtt/constants.py b/amqtt/mqtt/constants.py
new file mode 100644
index 00000000..841d2819
--- /dev/null
+++ b/amqtt/mqtt/constants.py
@@ -0,0 +1,7 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+
+QOS_0 = 0x00
+QOS_1 = 0x01
+QOS_2 = 0x02
diff --git a/amqtt/mqtt/disconnect.py b/amqtt/mqtt/disconnect.py
new file mode 100644
index 00000000..686caa96
--- /dev/null
+++ b/amqtt/mqtt/disconnect.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import MQTTPacket, MQTTFixedHeader, DISCONNECT
+from amqtt.errors import HBMQTTException
+
+
+class DisconnectPacket(MQTTPacket):
+    VARIABLE_HEADER = None
+    PAYLOAD = None
+
+    def __init__(self, fixed: MQTTFixedHeader = None):
+        if fixed is None:
+            header = MQTTFixedHeader(DISCONNECT, 0x00)
+        else:
+            if fixed.packet_type is not DISCONNECT:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for DisconnectPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = None
+        self.payload = None
diff --git a/amqtt/mqtt/packet.py b/amqtt/mqtt/packet.py
new file mode 100644
index 00000000..e5561c7a
--- /dev/null
+++ b/amqtt/mqtt/packet.py
@@ -0,0 +1,279 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import asyncio
+
+from amqtt.codecs import (
+    bytes_to_hex_str,
+    decode_packet_id,
+    int_to_bytes,
+    read_or_raise,
+)
+from amqtt.errors import CodecException, MQTTException, NoDataException
+from amqtt.adapters import ReaderAdapter, WriterAdapter
+from datetime import datetime
+from struct import unpack
+
+
+RESERVED_0 = 0x00
+CONNECT = 0x01
+CONNACK = 0x02
+PUBLISH = 0x03
+PUBACK = 0x04
+PUBREC = 0x05
+PUBREL = 0x06
+PUBCOMP = 0x07
+SUBSCRIBE = 0x08
+SUBACK = 0x09
+UNSUBSCRIBE = 0x0A
+UNSUBACK = 0x0B
+PINGREQ = 0x0C
+PINGRESP = 0x0D
+DISCONNECT = 0x0E
+RESERVED_15 = 0x0F
+
+
+class MQTTFixedHeader:
+
+    __slots__ = ("packet_type", "remaining_length", "flags")
+
+    def __init__(self, packet_type, flags=0, length=0):
+        self.packet_type = packet_type
+        self.remaining_length = length
+        self.flags = flags
+
+    def to_bytes(self):
+        def encode_remaining_length(length: int):
+            encoded = bytearray()
+            while True:
+                length_byte = length % 0x80
+                length //= 0x80
+                if length > 0:
+                    length_byte |= 0x80
+                encoded.append(length_byte)
+                if length <= 0:
+                    break
+            return encoded
+
+        out = bytearray()
+        packet_type = 0
+        try:
+            packet_type = (self.packet_type << 4) | self.flags
+            out.append(packet_type)
+        except OverflowError:
+            raise CodecException(
+                "packet_type encoding exceed 1 byte length: value=%d", packet_type
+            )
+
+        encoded_length = encode_remaining_length(self.remaining_length)
+        out.extend(encoded_length)
+
+        return out
+
+    async def to_stream(self, writer: WriterAdapter):
+        writer.write(self.to_bytes())
+
+    @property
+    def bytes_length(self):
+        return len(self.to_bytes())
+
+    @classmethod
+    async def from_stream(cls, reader: ReaderAdapter):
+        """
+        Read and decode MQTT message fixed header from stream
+        :return: FixedHeader instance
+        """
+
+        async def decode_remaining_length():
+            """
+            Decode message length according to MQTT specifications
+            :return:
+            """
+            multiplier = 1
+            value = 0
+            buffer = bytearray()
+            while True:
+                encoded_byte = await reader.read(1)
+                int_byte = unpack("!B", encoded_byte)
+                buffer.append(int_byte[0])
+                value += (int_byte[0] & 0x7F) * multiplier
+                if (int_byte[0] & 0x80) == 0:
+                    break
+                else:
+                    multiplier *= 128
+                    if multiplier > 128 * 128 * 128:
+                        raise MQTTException(
+                            "Invalid remaining length bytes:%s, packet_type=%d"
+                            % (bytes_to_hex_str(buffer), msg_type)
+                        )
+            return value
+
+        try:
+            byte1 = await read_or_raise(reader, 1)
+            int1 = unpack("!B", byte1)
+            msg_type = (int1[0] & 0xF0) >> 4
+            flags = int1[0] & 0x0F
+            remain_length = await decode_remaining_length()
+
+            return cls(msg_type, flags, remain_length)
+        except NoDataException:
+            return None
+
+    def __repr__(self):
+        return type(self).__name__ + "(length={0}, flags={1})".format(
+            self.remaining_length, hex(self.flags)
+        )
+
+
+class MQTTVariableHeader:
+    def __init__(self):
+        pass
+
+    async def to_stream(self, writer: asyncio.StreamWriter):
+        writer.write(self.to_bytes())
+        await writer.drain()
+
+    def to_bytes(self) -> bytes:
+        """
+        Serialize header data to a byte array conforming to MQTT protocol
+        :return: serialized data
+        """
+
+    @property
+    def bytes_length(self):
+        return len(self.to_bytes())
+
+    @classmethod
+    async def from_stream(
+        cls, reader: asyncio.StreamReader, fixed_header: MQTTFixedHeader
+    ):
+        pass
+
+
+class PacketIdVariableHeader(MQTTVariableHeader):
+
+    __slots__ = ("packet_id",)
+
+    def __init__(self, packet_id):
+        super().__init__()
+        self.packet_id = packet_id
+
+    def to_bytes(self):
+        out = b""
+        out += int_to_bytes(self.packet_id, 2)
+        return out
+
+    @classmethod
+    async def from_stream(cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader):
+        packet_id = await decode_packet_id(reader)
+        return cls(packet_id)
+
+    def __repr__(self):
+        return type(self).__name__ + "(packet_id={0})".format(self.packet_id)
+
+
+class MQTTPayload:
+    def __init__(self):
+        pass
+
+    async def to_stream(self, writer: asyncio.StreamWriter):
+        writer.write(self.to_bytes())
+        await writer.drain()
+
+    def to_bytes(
+        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
+    ):
+        pass
+
+    @classmethod
+    async def from_stream(
+        cls,
+        reader: asyncio.StreamReader,
+        fixed_header: MQTTFixedHeader,
+        variable_header: MQTTVariableHeader,
+    ):
+        pass
+
+
+class MQTTPacket:
+
+    __slots__ = ("fixed_header", "variable_header", "payload", "protocol_ts")
+
+    FIXED_HEADER = MQTTFixedHeader
+    VARIABLE_HEADER = None
+    PAYLOAD = None
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader,
+        variable_header: MQTTVariableHeader = None,
+        payload: MQTTPayload = None,
+    ):
+        self.fixed_header = fixed
+        self.variable_header = variable_header
+        self.payload = payload
+        self.protocol_ts = None
+
+    async def to_stream(self, writer: asyncio.StreamWriter):
+        writer.write(self.to_bytes())
+        await writer.drain()
+        self.protocol_ts = datetime.now()
+
+    def to_bytes(self) -> bytes:
+        if self.variable_header:
+            variable_header_bytes = self.variable_header.to_bytes()
+        else:
+            variable_header_bytes = b""
+        if self.payload:
+            payload_bytes = self.payload.to_bytes(
+                self.fixed_header, self.variable_header
+            )
+        else:
+            payload_bytes = b""
+
+        self.fixed_header.remaining_length = len(variable_header_bytes) + len(
+            payload_bytes
+        )
+        fixed_header_bytes = self.fixed_header.to_bytes()
+
+        return fixed_header_bytes + variable_header_bytes + payload_bytes
+
+    @classmethod
+    async def from_stream(
+        cls, reader: ReaderAdapter, fixed_header=None, variable_header=None
+    ):
+        if fixed_header is None:
+            fixed_header = await cls.FIXED_HEADER.from_stream(reader)
+        if cls.VARIABLE_HEADER:
+            if variable_header is None:
+                variable_header = await cls.VARIABLE_HEADER.from_stream(
+                    reader, fixed_header
+                )
+        else:
+            variable_header = None
+        if cls.PAYLOAD:
+            payload = await cls.PAYLOAD.from_stream(
+                reader, fixed_header, variable_header
+            )
+        else:
+            payload = None
+
+        if fixed_header and not variable_header and not payload:
+            instance = cls(fixed_header)
+        elif fixed_header and not payload:
+            instance = cls(fixed_header, variable_header)
+        else:
+            instance = cls(fixed_header, variable_header, payload)
+        instance.protocol_ts = datetime.now()
+        return instance
+
+    @property
+    def bytes_length(self):
+        return len(self.to_bytes())
+
+    def __repr__(self):
+        return type(
+            self
+        ).__name__ + "(ts={0!s}, fixed={1!r}, variable={2!r}, payload={3!r})".format(
+            self.protocol_ts, self.fixed_header, self.variable_header, self.payload
+        )
diff --git a/amqtt/mqtt/pingreq.py b/amqtt/mqtt/pingreq.py
new file mode 100644
index 00000000..a1f1ab9a
--- /dev/null
+++ b/amqtt/mqtt/pingreq.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import MQTTPacket, MQTTFixedHeader, PINGREQ
+from amqtt.errors import HBMQTTException
+
+
+class PingReqPacket(MQTTPacket):
+    VARIABLE_HEADER = None
+    PAYLOAD = None
+
+    def __init__(self, fixed: MQTTFixedHeader = None):
+        if fixed is None:
+            header = MQTTFixedHeader(PINGREQ, 0x00)
+        else:
+            if fixed.packet_type is not PINGREQ:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for PingReqPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = None
+        self.payload = None
diff --git a/amqtt/mqtt/pingresp.py b/amqtt/mqtt/pingresp.py
new file mode 100644
index 00000000..c2d56970
--- /dev/null
+++ b/amqtt/mqtt/pingresp.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import MQTTPacket, MQTTFixedHeader, PINGRESP
+from amqtt.errors import HBMQTTException
+
+
+class PingRespPacket(MQTTPacket):
+    VARIABLE_HEADER = None
+    PAYLOAD = None
+
+    def __init__(self, fixed: MQTTFixedHeader = None):
+        if fixed is None:
+            header = MQTTFixedHeader(PINGRESP, 0x00)
+        else:
+            if fixed.packet_type is not PINGRESP:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for PingRespPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = None
+        self.payload = None
+
+    @classmethod
+    def build(cls):
+        return cls()
diff --git a/amqtt/mqtt/protocol/__init__.py b/amqtt/mqtt/protocol/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/amqtt/mqtt/protocol/broker_handler.py b/amqtt/mqtt/protocol/broker_handler.py
new file mode 100644
index 00000000..11ee484e
--- /dev/null
+++ b/amqtt/mqtt/protocol/broker_handler.py
@@ -0,0 +1,212 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from asyncio import futures, Queue
+from amqtt.mqtt.protocol.handler import ProtocolHandler
+from amqtt.mqtt.connack import (
+    CONNECTION_ACCEPTED,
+    UNACCEPTABLE_PROTOCOL_VERSION,
+    IDENTIFIER_REJECTED,
+    BAD_USERNAME_PASSWORD,
+    NOT_AUTHORIZED,
+    ConnackPacket,
+)
+from amqtt.mqtt.connect import ConnectPacket
+from amqtt.mqtt.pingreq import PingReqPacket
+from amqtt.mqtt.pingresp import PingRespPacket
+from amqtt.mqtt.subscribe import SubscribePacket
+from amqtt.mqtt.suback import SubackPacket
+from amqtt.mqtt.unsubscribe import UnsubscribePacket
+from amqtt.mqtt.unsuback import UnsubackPacket
+from amqtt.utils import format_client_message
+from amqtt.session import Session
+from amqtt.plugins.manager import PluginManager
+from amqtt.adapters import ReaderAdapter, WriterAdapter
+from amqtt.errors import MQTTException
+from .handler import EVENT_MQTT_PACKET_RECEIVED, EVENT_MQTT_PACKET_SENT
+
+
+class BrokerProtocolHandler(ProtocolHandler):
+    def __init__(
+        self, plugins_manager: PluginManager, session: Session = None, loop=None
+    ):
+        super().__init__(plugins_manager, session, loop)
+        self._disconnect_waiter = None
+        self._pending_subscriptions = Queue(loop=self._loop)
+        self._pending_unsubscriptions = Queue(loop=self._loop)
+
+    async def start(self):
+        await super().start()
+        if self._disconnect_waiter is None:
+            self._disconnect_waiter = futures.Future(loop=self._loop)
+
+    async def stop(self):
+        await super().stop()
+        if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
+            self._disconnect_waiter.set_result(None)
+
+    async def wait_disconnect(self):
+        return await self._disconnect_waiter
+
+    def handle_write_timeout(self):
+        pass
+
+    def handle_read_timeout(self):
+        if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
+            self._disconnect_waiter.set_result(None)
+
+    async def handle_disconnect(self, disconnect):
+        self.logger.debug("Client disconnecting")
+        if self._disconnect_waiter and not self._disconnect_waiter.done():
+            self.logger.debug("Setting waiter result to %r" % disconnect)
+            self._disconnect_waiter.set_result(disconnect)
+
+    async def handle_connection_closed(self):
+        await self.handle_disconnect(None)
+
+    async def handle_connect(self, connect: ConnectPacket):
+        # Broker handler shouldn't received CONNECT message during messages handling
+        # as CONNECT messages are managed by the broker on client connection
+        self.logger.error(
+            "%s [MQTT-3.1.0-2] %s : CONNECT message received during messages handling"
+            % (self.session.client_id, format_client_message(self.session))
+        )
+        if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
+            self._disconnect_waiter.set_result(None)
+
+    async def handle_pingreq(self, pingreq: PingReqPacket):
+        await self._send_packet(PingRespPacket.build())
+
+    async def handle_subscribe(self, subscribe: SubscribePacket):
+        subscription = {
+            "packet_id": subscribe.variable_header.packet_id,
+            "topics": subscribe.payload.topics,
+        }
+        await self._pending_subscriptions.put(subscription)
+
+    async def handle_unsubscribe(self, unsubscribe: UnsubscribePacket):
+        unsubscription = {
+            "packet_id": unsubscribe.variable_header.packet_id,
+            "topics": unsubscribe.payload.topics,
+        }
+        await self._pending_unsubscriptions.put(unsubscription)
+
+    async def get_next_pending_subscription(self):
+        subscription = await self._pending_subscriptions.get()
+        return subscription
+
+    async def get_next_pending_unsubscription(self):
+        unsubscription = await self._pending_unsubscriptions.get()
+        return unsubscription
+
+    async def mqtt_acknowledge_subscription(self, packet_id, return_codes):
+        suback = SubackPacket.build(packet_id, return_codes)
+        await self._send_packet(suback)
+
+    async def mqtt_acknowledge_unsubscription(self, packet_id):
+        unsuback = UnsubackPacket.build(packet_id)
+        await self._send_packet(unsuback)
+
+    async def mqtt_connack_authorize(self, authorize: bool):
+        if authorize:
+            connack = ConnackPacket.build(self.session.parent, CONNECTION_ACCEPTED)
+        else:
+            connack = ConnackPacket.build(self.session.parent, NOT_AUTHORIZED)
+        await self._send_packet(connack)
+
+    @classmethod
+    async def init_from_connect(
+        cls, reader: ReaderAdapter, writer: WriterAdapter, plugins_manager, loop=None
+    ):
+        """
+
+        :param reader:
+        :param writer:
+        :param plugins_manager:
+        :param loop:
+        :return:
+        """
+        remote_address, remote_port = writer.get_peer_info()
+        connect = await ConnectPacket.from_stream(reader)
+        await plugins_manager.fire_event(EVENT_MQTT_PACKET_RECEIVED, packet=connect)
+        # this shouldn't be required anymore since broker generates for each client a random client_id if not provided
+        # [MQTT-3.1.3-6]
+        if connect.payload.client_id is None:
+            raise MQTTException("[[MQTT-3.1.3-3]] : Client identifier must be present")
+
+        if connect.variable_header.will_flag:
+            if (
+                connect.payload.will_topic is None
+                or connect.payload.will_message is None
+            ):
+                raise MQTTException(
+                    "will flag set, but will topic/message not present in payload"
+                )
+
+        if connect.variable_header.reserved_flag:
+            raise MQTTException("[MQTT-3.1.2-3] CONNECT reserved flag must be set to 0")
+        if connect.proto_name != "MQTT":
+            raise MQTTException(
+                '[MQTT-3.1.2-1] Incorrect protocol name: "%s"' % connect.proto_name
+            )
+
+        connack = None
+        error_msg = None
+        if connect.proto_level != 4:
+            # only MQTT 3.1.1 supported
+            error_msg = "Invalid protocol from %s: %d" % (
+                format_client_message(address=remote_address, port=remote_port),
+                connect.proto_level,
+            )
+            connack = ConnackPacket.build(
+                0, UNACCEPTABLE_PROTOCOL_VERSION
+            )  # [MQTT-3.2.2-4] session_parent=0
+        elif not connect.username_flag and connect.password_flag:
+            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
+        elif connect.username_flag and not connect.password_flag:
+            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
+        elif connect.username_flag and connect.username is None:
+            error_msg = "Invalid username from %s" % (
+                format_client_message(address=remote_address, port=remote_port)
+            )
+            connack = ConnackPacket.build(
+                0, BAD_USERNAME_PASSWORD
+            )  # [MQTT-3.2.2-4] session_parent=0
+        elif connect.password_flag and connect.password is None:
+            error_msg = "Invalid password %s" % (
+                format_client_message(address=remote_address, port=remote_port)
+            )
+            connack = ConnackPacket.build(
+                0, BAD_USERNAME_PASSWORD
+            )  # [MQTT-3.2.2-4] session_parent=0
+        elif connect.clean_session_flag is False and (
+            connect.payload.client_id_is_random
+        ):
+            error_msg = (
+                "[MQTT-3.1.3-8] [MQTT-3.1.3-9] %s: No client Id provided (cleansession=0)"
+                % (format_client_message(address=remote_address, port=remote_port))
+            )
+            connack = ConnackPacket.build(0, IDENTIFIER_REJECTED)
+        if connack is not None:
+            await plugins_manager.fire_event(EVENT_MQTT_PACKET_SENT, packet=connack)
+            await connack.to_stream(writer)
+            await writer.close()
+            raise MQTTException(error_msg)
+
+        incoming_session = Session(loop)
+        incoming_session.client_id = connect.client_id
+        incoming_session.clean_session = connect.clean_session_flag
+        incoming_session.will_flag = connect.will_flag
+        incoming_session.will_retain = connect.will_retain_flag
+        incoming_session.will_qos = connect.will_qos
+        incoming_session.will_topic = connect.will_topic
+        incoming_session.will_message = connect.will_message
+        incoming_session.username = connect.username
+        incoming_session.password = connect.password
+        if connect.keep_alive > 0:
+            incoming_session.keep_alive = connect.keep_alive
+        else:
+            incoming_session.keep_alive = 0
+
+        handler = cls(plugins_manager, loop=loop)
+        return handler, incoming_session
diff --git a/amqtt/mqtt/protocol/client_handler.py b/amqtt/mqtt/protocol/client_handler.py
new file mode 100644
index 00000000..8f4468b7
--- /dev/null
+++ b/amqtt/mqtt/protocol/client_handler.py
@@ -0,0 +1,173 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import asyncio
+from asyncio import futures
+from amqtt.mqtt.protocol.handler import ProtocolHandler, EVENT_MQTT_PACKET_RECEIVED
+from amqtt.mqtt.disconnect import DisconnectPacket
+from amqtt.mqtt.pingreq import PingReqPacket
+from amqtt.mqtt.pingresp import PingRespPacket
+from amqtt.mqtt.subscribe import SubscribePacket
+from amqtt.mqtt.suback import SubackPacket
+from amqtt.mqtt.unsubscribe import UnsubscribePacket
+from amqtt.mqtt.unsuback import UnsubackPacket
+from amqtt.mqtt.connect import ConnectVariableHeader, ConnectPayload, ConnectPacket
+from amqtt.mqtt.connack import ConnackPacket
+from amqtt.session import Session
+from amqtt.plugins.manager import PluginManager
+
+
+class ClientProtocolHandler(ProtocolHandler):
+    def __init__(
+        self, plugins_manager: PluginManager, session: Session = None, loop=None
+    ):
+        super().__init__(plugins_manager, session, loop=loop)
+        self._ping_task = None
+        self._pingresp_queue = asyncio.Queue(loop=self._loop)
+        self._subscriptions_waiter = dict()
+        self._unsubscriptions_waiter = dict()
+        self._disconnect_waiter = None
+
+    async def start(self):
+        await super().start()
+        if self._disconnect_waiter is None:
+            self._disconnect_waiter = futures.Future(loop=self._loop)
+
+    async def stop(self):
+        await super().stop()
+        if self._ping_task:
+            try:
+                self.logger.debug("Cancel ping task")
+                self._ping_task.cancel()
+            except BaseException:
+                pass
+        if not self._disconnect_waiter.done():
+            self._disconnect_waiter.cancel()
+        self._disconnect_waiter = None
+
+    def _build_connect_packet(self):
+        vh = ConnectVariableHeader()
+        payload = ConnectPayload()
+
+        vh.keep_alive = self.session.keep_alive
+        vh.clean_session_flag = self.session.clean_session
+        vh.will_retain_flag = self.session.will_retain
+        payload.client_id = self.session.client_id
+
+        if self.session.username:
+            vh.username_flag = True
+            payload.username = self.session.username
+        else:
+            vh.username_flag = False
+
+        if self.session.password:
+            vh.password_flag = True
+            payload.password = self.session.password
+        else:
+            vh.password_flag = False
+        if self.session.will_flag:
+            vh.will_flag = True
+            vh.will_qos = self.session.will_qos
+            payload.will_message = self.session.will_message
+            payload.will_topic = self.session.will_topic
+        else:
+            vh.will_flag = False
+
+        packet = ConnectPacket(vh=vh, payload=payload)
+        return packet
+
+    async def mqtt_connect(self):
+        connect_packet = self._build_connect_packet()
+        await self._send_packet(connect_packet)
+        connack = await ConnackPacket.from_stream(self.reader)
+        await self.plugins_manager.fire_event(
+            EVENT_MQTT_PACKET_RECEIVED, packet=connack, session=self.session
+        )
+        return connack.return_code
+
+    def handle_write_timeout(self):
+        try:
+            if not self._ping_task:
+                self.logger.debug("Scheduling Ping")
+                self._ping_task = asyncio.ensure_future(self.mqtt_ping())
+        except BaseException as be:
+            self.logger.debug("Exception ignored in ping task: %r" % be)
+
+    def handle_read_timeout(self):
+        pass
+
+    async def mqtt_subscribe(self, topics, packet_id):
+        """
+        :param topics: array of topics [{'filter':'/a/b', 'qos': 0x00}, ...]
+        :return:
+        """
+
+        # Build and send SUBSCRIBE message
+        subscribe = SubscribePacket.build(topics, packet_id)
+        await self._send_packet(subscribe)
+
+        # Wait for SUBACK is received
+        waiter = futures.Future(loop=self._loop)
+        self._subscriptions_waiter[subscribe.variable_header.packet_id] = waiter
+        return_codes = await waiter
+
+        del self._subscriptions_waiter[subscribe.variable_header.packet_id]
+        return return_codes
+
+    async 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:
+            self.logger.warning(
+                "Received SUBACK for unknown pending subscription with Id: %s"
+                % packet_id
+            )
+
+    async def mqtt_unsubscribe(self, topics, packet_id):
+        """
+
+        :param topics: array of topics ['/a/b', ...]
+        :return:
+        """
+        unsubscribe = UnsubscribePacket.build(topics, packet_id)
+        await self._send_packet(unsubscribe)
+        waiter = futures.Future(loop=self._loop)
+        self._unsubscriptions_waiter[unsubscribe.variable_header.packet_id] = waiter
+        await waiter
+        del self._unsubscriptions_waiter[unsubscribe.variable_header.packet_id]
+
+    async 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:
+            self.logger.warning(
+                "Received UNSUBACK for unknown pending subscription with Id: %s"
+                % packet_id
+            )
+
+    async def mqtt_disconnect(self):
+        disconnect_packet = DisconnectPacket()
+        await self._send_packet(disconnect_packet)
+
+    async def mqtt_ping(self):
+        ping_packet = PingReqPacket()
+        await self._send_packet(ping_packet)
+        resp = await self._pingresp_queue.get()
+        if self._ping_task:
+            self._ping_task = None
+        return resp
+
+    async def handle_pingresp(self, pingresp: PingRespPacket):
+        await self._pingresp_queue.put(pingresp)
+
+    async def handle_connection_closed(self):
+        self.logger.debug("Broker closed connection")
+        if not self._disconnect_waiter.done():
+            self._disconnect_waiter.set_result(None)
+
+    async def wait_disconnect(self):
+        await self._disconnect_waiter
diff --git a/amqtt/mqtt/protocol/handler.py b/amqtt/mqtt/protocol/handler.py
new file mode 100644
index 00000000..3c037abf
--- /dev/null
+++ b/amqtt/mqtt/protocol/handler.py
@@ -0,0 +1,676 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import logging
+import collections
+import itertools
+
+import asyncio
+from asyncio import InvalidStateError
+
+from amqtt.mqtt import packet_class
+from amqtt.mqtt.connack import ConnackPacket
+from amqtt.mqtt.connect import ConnectPacket
+from amqtt.mqtt.packet import (
+    RESERVED_0,
+    CONNECT,
+    CONNACK,
+    PUBLISH,
+    PUBACK,
+    PUBREC,
+    PUBREL,
+    PUBCOMP,
+    SUBSCRIBE,
+    SUBACK,
+    UNSUBSCRIBE,
+    UNSUBACK,
+    PINGREQ,
+    PINGRESP,
+    DISCONNECT,
+    RESERVED_15,
+    MQTTFixedHeader,
+)
+from amqtt.mqtt.pingresp import PingRespPacket
+from amqtt.mqtt.pingreq import PingReqPacket
+from amqtt.mqtt.publish import PublishPacket
+from amqtt.mqtt.pubrel import PubrelPacket
+from amqtt.mqtt.puback import PubackPacket
+from amqtt.mqtt.pubrec import PubrecPacket
+from amqtt.mqtt.pubcomp import PubcompPacket
+from amqtt.mqtt.suback import SubackPacket
+from amqtt.mqtt.subscribe import SubscribePacket
+from amqtt.mqtt.unsubscribe import UnsubscribePacket
+from amqtt.mqtt.unsuback import UnsubackPacket
+from amqtt.mqtt.disconnect import DisconnectPacket
+from amqtt.adapters import ReaderAdapter, WriterAdapter
+from amqtt.session import (
+    Session,
+    OutgoingApplicationMessage,
+    IncomingApplicationMessage,
+    INCOMING,
+    OUTGOING,
+)
+from amqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
+from amqtt.plugins.manager import PluginManager
+from amqtt.errors import HBMQTTException, MQTTException, NoDataException
+
+
+EVENT_MQTT_PACKET_SENT = "mqtt_packet_sent"
+EVENT_MQTT_PACKET_RECEIVED = "mqtt_packet_received"
+
+
+class ProtocolHandlerException(BaseException):
+    pass
+
+
+class ProtocolHandler:
+    """
+    Class implementing the MQTT communication protocol using asyncio features
+    """
+
+    def __init__(
+        self, plugins_manager: PluginManager, session: Session = None, loop=None
+    ):
+        self.logger = logging.getLogger(__name__)
+        if session:
+            self._init_session(session)
+        else:
+            self.session = None
+        self.reader = None
+        self.writer = None
+        self.plugins_manager = plugins_manager
+
+        if loop is None:
+            self._loop = asyncio.get_event_loop()
+        else:
+            self._loop = loop
+        self._reader_task = None
+        self._keepalive_task = None
+        self._reader_ready = None
+        self._reader_stopped = asyncio.Event(loop=self._loop)
+
+        self._puback_waiters = dict()
+        self._pubrec_waiters = dict()
+        self._pubrel_waiters = dict()
+        self._pubcomp_waiters = dict()
+
+        self._write_lock = asyncio.Lock(loop=self._loop)
+
+    def _init_session(self, session: Session):
+        assert session
+        log = logging.getLogger(__name__)
+        self.session = session
+        self.logger = logging.LoggerAdapter(log, {"client_id": self.session.client_id})
+        self.keepalive_timeout = self.session.keep_alive
+        if self.keepalive_timeout <= 0:
+            self.keepalive_timeout = None
+
+    def attach(self, session, reader: ReaderAdapter, writer: WriterAdapter):
+        if self.session:
+            raise ProtocolHandlerException("Handler is already attached to a session")
+        self._init_session(session)
+        self.reader = reader
+        self.writer = writer
+
+    def detach(self):
+        self.session = None
+        self.reader = None
+        self.writer = None
+
+    def _is_attached(self):
+        if self.session:
+            return True
+        else:
+            return False
+
+    async def start(self):
+        if not self._is_attached():
+            raise ProtocolHandlerException("Handler is not attached to a stream")
+        self._reader_ready = asyncio.Event(loop=self._loop)
+        self._reader_task = asyncio.Task(self._reader_loop(), loop=self._loop)
+        await asyncio.wait([self._reader_ready.wait()], loop=self._loop)
+        if self.keepalive_timeout:
+            self._keepalive_task = self._loop.call_later(
+                self.keepalive_timeout, self.handle_write_timeout
+            )
+
+        self.logger.debug("Handler tasks started")
+        await self._retry_deliveries()
+        self.logger.debug("Handler ready")
+
+    async def stop(self):
+        # Stop messages flow waiter
+        self._stop_waiters()
+        if self._keepalive_task:
+            self._keepalive_task.cancel()
+        self.logger.debug("waiting for tasks to be stopped")
+        if not self._reader_task.done():
+            self._reader_task.cancel()
+            await asyncio.wait([self._reader_stopped.wait()], loop=self._loop)
+        self.logger.debug("closing writer")
+        try:
+            await self.writer.close()
+        except Exception as e:
+            self.logger.debug("Handler writer close failed: %s" % e)
+
+    def _stop_waiters(self):
+        self.logger.debug("Stopping %d puback waiters" % len(self._puback_waiters))
+        self.logger.debug("Stopping %d pucomp waiters" % len(self._pubcomp_waiters))
+        self.logger.debug("Stopping %d purec waiters" % len(self._pubrec_waiters))
+        self.logger.debug("Stopping %d purel waiters" % len(self._pubrel_waiters))
+        for waiter in itertools.chain(
+            self._puback_waiters.values(),
+            self._pubcomp_waiters.values(),
+            self._pubrec_waiters.values(),
+            self._pubrel_waiters.values(),
+        ):
+            waiter.cancel()
+
+    async def _retry_deliveries(self):
+        """
+        Handle [MQTT-4.4.0-1] by resending PUBLISH and PUBREL messages for pending out messages
+        :return:
+        """
+        self.logger.debug("Begin messages delivery retries")
+        tasks = []
+        for message in itertools.chain(
+            self.session.inflight_in.values(), self.session.inflight_out.values()
+        ):
+            tasks.append(
+                asyncio.wait_for(
+                    self._handle_message_flow(message), 10, loop=self._loop
+                )
+            )
+        if tasks:
+            done, pending = await asyncio.wait(tasks, loop=self._loop)
+            self.logger.debug("%d messages redelivered" % len(done))
+            self.logger.debug(
+                "%d messages not redelivered due to timeout" % len(pending)
+            )
+        self.logger.debug("End messages delivery retries")
+
+    async def mqtt_publish(self, topic, data, qos, retain, ack_timeout=None):
+        """
+        Sends a MQTT publish message and manages messages flows.
+        This methods doesn't return until the message has been acknowledged by receiver or timeout occur
+        :param topic: MQTT topic to publish
+        :param data:  data to send on topic
+        :param qos: quality of service to use for message flow. Can be QOS_0, QOS_1 or QOS_2
+        :param retain: retain message flag
+        :param ack_timeout: acknowledge timeout. If set, this method will return a TimeOut error if the acknowledgment
+        is not completed before ack_timeout second
+        :return: ApplicationMessage used during inflight operations
+        """
+        if qos in (QOS_1, QOS_2):
+            packet_id = self.session.next_packet_id
+            if packet_id in self.session.inflight_out:
+                raise HBMQTTException(
+                    "A message with the same packet ID '%d' is already in flight"
+                    % packet_id
+                )
+        else:
+            packet_id = None
+
+        message = OutgoingApplicationMessage(packet_id, topic, qos, data, retain)
+        # Handle message flow
+        if ack_timeout is not None and ack_timeout > 0:
+            await asyncio.wait_for(
+                self._handle_message_flow(message), ack_timeout, loop=self._loop
+            )
+        else:
+            await self._handle_message_flow(message)
+
+        return message
+
+    async def _handle_message_flow(self, app_message):
+        """
+        Handle protocol flow for incoming and outgoing messages, depending on service level and according to MQTT
+        spec. paragraph 4.3-Quality of Service levels and protocol flows
+        :param app_message: PublishMessage to handle
+        :return: nothing.
+        """
+        if app_message.qos == QOS_0:
+            await self._handle_qos0_message_flow(app_message)
+        elif app_message.qos == QOS_1:
+            await self._handle_qos1_message_flow(app_message)
+        elif app_message.qos == QOS_2:
+            await self._handle_qos2_message_flow(app_message)
+        else:
+            raise HBMQTTException("Unexcepted QOS value '%d" % str(app_message.qos))
+
+    async def _handle_qos0_message_flow(self, app_message):
+        """
+        Handle QOS_0 application message acknowledgment
+        For incoming messages, this method stores the message
+        For outgoing messages, this methods sends PUBLISH
+        :param app_message:
+        :return:
+        """
+        assert app_message.qos == QOS_0
+        if app_message.direction == OUTGOING:
+            packet = app_message.build_publish_packet()
+            # Send PUBLISH packet
+            await self._send_packet(packet)
+            app_message.publish_packet = packet
+        elif app_message.direction == INCOMING:
+            if app_message.publish_packet.dup_flag:
+                self.logger.warning(
+                    "[MQTT-3.3.1-2] DUP flag must set to 0 for QOS 0 message. Message ignored: %s"
+                    % repr(app_message.publish_packet)
+                )
+            else:
+                try:
+                    self.session.delivered_message_queue.put_nowait(app_message)
+                except:
+                    self.logger.warning(
+                        "delivered messages queue full. QOS_0 message discarded"
+                    )
+
+    async def _handle_qos1_message_flow(self, app_message):
+        """
+        Handle QOS_1 application message acknowledgment
+        For incoming messages, this method stores the message and reply with PUBACK
+        For outgoing messages, this methods sends PUBLISH and waits for the corresponding PUBACK
+        :param app_message:
+        :return:
+        """
+        assert app_message.qos == QOS_1
+        if app_message.puback_packet:
+            raise HBMQTTException(
+                "Message '%d' has already been acknowledged" % app_message.packet_id
+            )
+        if app_message.direction == OUTGOING:
+            if app_message.packet_id not in self.session.inflight_out:
+                # Store message in session
+                self.session.inflight_out[app_message.packet_id] = app_message
+            if app_message.publish_packet is not None:
+                # A Publish packet has already been sent, this is a retry
+                publish_packet = app_message.build_publish_packet(dup=True)
+            else:
+                publish_packet = app_message.build_publish_packet()
+            # Send PUBLISH packet
+            await self._send_packet(publish_packet)
+            app_message.publish_packet = publish_packet
+
+            # Wait for puback
+            waiter = asyncio.Future(loop=self._loop)
+            self._puback_waiters[app_message.packet_id] = waiter
+            await waiter
+            del self._puback_waiters[app_message.packet_id]
+            app_message.puback_packet = waiter.result()
+
+            # Discard inflight message
+            del self.session.inflight_out[app_message.packet_id]
+        elif app_message.direction == INCOMING:
+            # Initiate delivery
+            self.logger.debug("Add message to delivery")
+            await self.session.delivered_message_queue.put(app_message)
+            # Send PUBACK
+            puback = PubackPacket.build(app_message.packet_id)
+            await self._send_packet(puback)
+            app_message.puback_packet = puback
+
+    async def _handle_qos2_message_flow(self, app_message):
+        """
+        Handle QOS_2 application message acknowledgment
+        For incoming messages, this method stores the message, sends PUBREC, waits for PUBREL, initiate delivery
+        and send PUBCOMP
+        For outgoing messages, this methods sends PUBLISH, waits for PUBREC, discards messages and wait for PUBCOMP
+        :param app_message:
+        :return:
+        """
+        assert app_message.qos == QOS_2
+        if app_message.direction == OUTGOING:
+            if app_message.pubrel_packet and app_message.pubcomp_packet:
+                raise HBMQTTException(
+                    "Message '%d' has already been acknowledged" % app_message.packet_id
+                )
+            if not app_message.pubrel_packet:
+                # Store message
+                if app_message.publish_packet is not None:
+                    # This is a retry flow, no need to store just check the message exists in session
+                    if app_message.packet_id not in self.session.inflight_out:
+                        raise HBMQTTException(
+                            "Unknown inflight message '%d' in session"
+                            % app_message.packet_id
+                        )
+                    publish_packet = app_message.build_publish_packet(dup=True)
+                else:
+                    # Store message in session
+                    self.session.inflight_out[app_message.packet_id] = app_message
+                    publish_packet = app_message.build_publish_packet()
+                # Send PUBLISH packet
+                await self._send_packet(publish_packet)
+                app_message.publish_packet = publish_packet
+                # Wait PUBREC
+                if app_message.packet_id in self._pubrec_waiters:
+                    # PUBREC waiter already exists for this packet ID
+                    message = (
+                        "Can't add PUBREC waiter, a waiter already exists for message Id '%s'"
+                        % app_message.packet_id
+                    )
+                    self.logger.warning(message)
+                    raise HBMQTTException(message)
+                waiter = asyncio.Future(loop=self._loop)
+                self._pubrec_waiters[app_message.packet_id] = waiter
+                await waiter
+                del self._pubrec_waiters[app_message.packet_id]
+                app_message.pubrec_packet = waiter.result()
+            if not app_message.pubcomp_packet:
+                # Send pubrel
+                app_message.pubrel_packet = PubrelPacket.build(app_message.packet_id)
+                await self._send_packet(app_message.pubrel_packet)
+                # Wait for PUBCOMP
+                waiter = asyncio.Future(loop=self._loop)
+                self._pubcomp_waiters[app_message.packet_id] = waiter
+                await waiter
+                del self._pubcomp_waiters[app_message.packet_id]
+                app_message.pubcomp_packet = waiter.result()
+            # Discard inflight message
+            del self.session.inflight_out[app_message.packet_id]
+        elif app_message.direction == INCOMING:
+            self.session.inflight_in[app_message.packet_id] = app_message
+            # Send pubrec
+            pubrec_packet = PubrecPacket.build(app_message.packet_id)
+            await self._send_packet(pubrec_packet)
+            app_message.pubrec_packet = pubrec_packet
+            # Wait PUBREL
+            if (
+                app_message.packet_id in self._pubrel_waiters
+                and not self._pubrel_waiters[app_message.packet_id].done()
+            ):
+                # PUBREL waiter already exists for this packet ID
+                message = (
+                    "A waiter already exists for message Id '%s', canceling it"
+                    % app_message.packet_id
+                )
+                self.logger.warning(message)
+                self._pubrel_waiters[app_message.packet_id].cancel()
+            try:
+                waiter = asyncio.Future(loop=self._loop)
+                self._pubrel_waiters[app_message.packet_id] = waiter
+                await waiter
+                del self._pubrel_waiters[app_message.packet_id]
+                app_message.pubrel_packet = waiter.result()
+                # Initiate delivery and discard message
+                await self.session.delivered_message_queue.put(app_message)
+                del self.session.inflight_in[app_message.packet_id]
+                # Send pubcomp
+                pubcomp_packet = PubcompPacket.build(app_message.packet_id)
+                await self._send_packet(pubcomp_packet)
+                app_message.pubcomp_packet = pubcomp_packet
+            except asyncio.CancelledError:
+                self.logger.debug("Message flow cancelled")
+
+    async def _reader_loop(self):
+        self.logger.debug("%s Starting reader coro" % self.session.client_id)
+        running_tasks = collections.deque()
+        keepalive_timeout = self.session.keep_alive
+        if keepalive_timeout <= 0:
+            keepalive_timeout = None
+        while True:
+            try:
+                self._reader_ready.set()
+                while running_tasks and running_tasks[0].done():
+                    running_tasks.popleft()
+                if len(running_tasks) > 1:
+                    self.logger.debug("handler running tasks: %d" % len(running_tasks))
+
+                fixed_header = await asyncio.wait_for(
+                    MQTTFixedHeader.from_stream(self.reader),
+                    keepalive_timeout,
+                    loop=self._loop,
+                )
+                if fixed_header:
+                    if (
+                        fixed_header.packet_type == RESERVED_0
+                        or fixed_header.packet_type == RESERVED_15
+                    ):
+                        self.logger.warning(
+                            "%s Received reserved packet, which is forbidden: closing connection"
+                            % (self.session.client_id)
+                        )
+                        await self.handle_connection_closed()
+                    else:
+                        cls = packet_class(fixed_header)
+                        packet = await cls.from_stream(
+                            self.reader, fixed_header=fixed_header
+                        )
+                        await self.plugins_manager.fire_event(
+                            EVENT_MQTT_PACKET_RECEIVED,
+                            packet=packet,
+                            session=self.session,
+                        )
+                        task = None
+                        if packet.fixed_header.packet_type == CONNACK:
+                            task = asyncio.ensure_future(
+                                self.handle_connack(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == SUBSCRIBE:
+                            task = asyncio.ensure_future(
+                                self.handle_subscribe(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == UNSUBSCRIBE:
+                            task = asyncio.ensure_future(
+                                self.handle_unsubscribe(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == SUBACK:
+                            task = asyncio.ensure_future(
+                                self.handle_suback(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == UNSUBACK:
+                            task = asyncio.ensure_future(
+                                self.handle_unsuback(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == PUBACK:
+                            task = asyncio.ensure_future(
+                                self.handle_puback(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == PUBREC:
+                            task = asyncio.ensure_future(
+                                self.handle_pubrec(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == PUBREL:
+                            task = asyncio.ensure_future(
+                                self.handle_pubrel(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == PUBCOMP:
+                            task = asyncio.ensure_future(
+                                self.handle_pubcomp(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == PINGREQ:
+                            task = asyncio.ensure_future(
+                                self.handle_pingreq(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == PINGRESP:
+                            task = asyncio.ensure_future(
+                                self.handle_pingresp(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == PUBLISH:
+                            task = asyncio.ensure_future(
+                                self.handle_publish(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == DISCONNECT:
+                            task = asyncio.ensure_future(
+                                self.handle_disconnect(packet), loop=self._loop
+                            )
+                        elif packet.fixed_header.packet_type == CONNECT:
+                            self.handle_connect(packet)
+                        else:
+                            self.logger.warning(
+                                "%s Unhandled packet type: %s"
+                                % (
+                                    self.session.client_id,
+                                    packet.fixed_header.packet_type,
+                                )
+                            )
+                        if task:
+                            running_tasks.append(task)
+                else:
+                    self.logger.debug(
+                        "%s No more data (EOF received), stopping reader coro"
+                        % self.session.client_id
+                    )
+                    break
+            except MQTTException:
+                self.logger.debug("Message discarded")
+            except asyncio.CancelledError:
+                self.logger.debug("Task cancelled, reader loop ending")
+                break
+            except asyncio.TimeoutError:
+                self.logger.debug(
+                    "%s Input stream read timeout" % self.session.client_id
+                )
+                self.handle_read_timeout()
+            except NoDataException:
+                self.logger.debug("%s No data available" % self.session.client_id)
+            except BaseException as e:
+                self.logger.warning(
+                    "%s Unhandled exception in reader coro: %r"
+                    % (type(self).__name__, e)
+                )
+                break
+        while running_tasks:
+            running_tasks.popleft().cancel()
+        await self.handle_connection_closed()
+        self._reader_stopped.set()
+        self.logger.debug("Reader coro stopped")
+        await self.stop()
+
+    async def _send_packet(self, packet):
+        try:
+            async with self._write_lock:
+                await packet.to_stream(self.writer)
+            if self._keepalive_task:
+                self._keepalive_task.cancel()
+                self._keepalive_task = self._loop.call_later(
+                    self.keepalive_timeout, self.handle_write_timeout
+                )
+
+            await self.plugins_manager.fire_event(
+                EVENT_MQTT_PACKET_SENT, packet=packet, session=self.session
+            )
+        except (ConnectionResetError, BrokenPipeError):
+            await self.handle_connection_closed()
+        except asyncio.CancelledError:
+            raise
+        except BaseException as e:
+            self.logger.warning("Unhandled exception: %s" % e)
+            raise
+
+    async def mqtt_deliver_next_message(self):
+        if not self._is_attached():
+            return None
+        if self.logger.isEnabledFor(logging.DEBUG):
+            self.logger.debug(
+                "%d message(s) available for delivery"
+                % self.session.delivered_message_queue.qsize()
+            )
+        try:
+            message = await self.session.delivered_message_queue.get()
+        except asyncio.CancelledError:
+            message = None
+        if self.logger.isEnabledFor(logging.DEBUG):
+            self.logger.debug("Delivering message %s" % message)
+        return message
+
+    def handle_write_timeout(self):
+        self.logger.debug("%s write timeout unhandled" % self.session.client_id)
+
+    def handle_read_timeout(self):
+        self.logger.debug("%s read timeout unhandled" % self.session.client_id)
+
+    async def handle_connack(self, connack: ConnackPacket):
+        self.logger.debug("%s CONNACK unhandled" % self.session.client_id)
+
+    async def handle_connect(self, connect: ConnectPacket):
+        self.logger.debug("%s CONNECT unhandled" % self.session.client_id)
+
+    async def handle_subscribe(self, subscribe: SubscribePacket):
+        self.logger.debug("%s SUBSCRIBE unhandled" % self.session.client_id)
+
+    async def handle_unsubscribe(self, subscribe: UnsubscribePacket):
+        self.logger.debug("%s UNSUBSCRIBE unhandled" % self.session.client_id)
+
+    async def handle_suback(self, suback: SubackPacket):
+        self.logger.debug("%s SUBACK unhandled" % self.session.client_id)
+
+    async def handle_unsuback(self, unsuback: UnsubackPacket):
+        self.logger.debug("%s UNSUBACK unhandled" % self.session.client_id)
+
+    async def handle_pingresp(self, pingresp: PingRespPacket):
+        self.logger.debug("%s PINGRESP unhandled" % self.session.client_id)
+
+    async def handle_pingreq(self, pingreq: PingReqPacket):
+        self.logger.debug("%s PINGREQ unhandled" % self.session.client_id)
+
+    async def handle_disconnect(self, disconnect: DisconnectPacket):
+        self.logger.debug("%s DISCONNECT unhandled" % self.session.client_id)
+
+    async def handle_connection_closed(self):
+        self.logger.debug("%s Connection closed unhandled" % self.session.client_id)
+
+    async def handle_puback(self, puback: PubackPacket):
+        packet_id = puback.variable_header.packet_id
+        try:
+            waiter = self._puback_waiters[packet_id]
+            waiter.set_result(puback)
+        except KeyError:
+            self.logger.warning(
+                "Received PUBACK for unknown pending message Id: '%d'" % packet_id
+            )
+        except InvalidStateError:
+            self.logger.warning("PUBACK waiter with Id '%d' already done" % packet_id)
+
+    async def handle_pubrec(self, pubrec: PubrecPacket):
+        packet_id = pubrec.packet_id
+        try:
+            waiter = self._pubrec_waiters[packet_id]
+            waiter.set_result(pubrec)
+        except KeyError:
+            self.logger.warning(
+                "Received PUBREC for unknown pending message with Id: %d" % packet_id
+            )
+        except InvalidStateError:
+            self.logger.warning("PUBREC waiter with Id '%d' already done" % packet_id)
+
+    async def handle_pubcomp(self, pubcomp: PubcompPacket):
+        packet_id = pubcomp.packet_id
+        try:
+            waiter = self._pubcomp_waiters[packet_id]
+            waiter.set_result(pubcomp)
+        except KeyError:
+            self.logger.warning(
+                "Received PUBCOMP for unknown pending message with Id: %d" % packet_id
+            )
+        except InvalidStateError:
+            self.logger.warning("PUBCOMP waiter with Id '%d' already done" % packet_id)
+
+    async def handle_pubrel(self, pubrel: PubrelPacket):
+        packet_id = pubrel.packet_id
+        try:
+            waiter = self._pubrel_waiters[packet_id]
+            waiter.set_result(pubrel)
+        except KeyError:
+            self.logger.warning(
+                "Received PUBREL for unknown pending message with Id: %d" % packet_id
+            )
+        except InvalidStateError:
+            self.logger.warning("PUBREL waiter with Id '%d' already done" % packet_id)
+
+    async def handle_publish(self, publish_packet: PublishPacket):
+        packet_id = publish_packet.variable_header.packet_id
+        qos = publish_packet.qos
+
+        incoming_message = IncomingApplicationMessage(
+            packet_id,
+            publish_packet.topic_name,
+            qos,
+            publish_packet.data,
+            publish_packet.retain_flag,
+        )
+        incoming_message.publish_packet = publish_packet
+        await self._handle_message_flow(incoming_message)
+        self.logger.debug(
+            "Message queue size: %d" % self.session.delivered_message_queue.qsize()
+        )
diff --git a/amqtt/mqtt/puback.py b/amqtt/mqtt/puback.py
new file mode 100644
index 00000000..809cedf8
--- /dev/null
+++ b/amqtt/mqtt/puback.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    PUBACK,
+    PacketIdVariableHeader,
+)
+from amqtt.errors import HBMQTTException
+
+
+class PubackPacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = None
+
+    @property
+    def packet_id(self):
+        return self.variable_header.packet_id
+
+    @packet_id.setter
+    def packet_id(self, val: int):
+        self.variable_header.packet_id = val
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(PUBACK, 0x00)
+        else:
+            if fixed.packet_type is not PUBACK:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for PubackPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = None
+
+    @classmethod
+    def build(cls, packet_id: int):
+        v_header = PacketIdVariableHeader(packet_id)
+        packet = PubackPacket(variable_header=v_header)
+        return packet
diff --git a/amqtt/mqtt/pubcomp.py b/amqtt/mqtt/pubcomp.py
new file mode 100644
index 00000000..d5bc49b4
--- /dev/null
+++ b/amqtt/mqtt/pubcomp.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    PUBCOMP,
+    PacketIdVariableHeader,
+)
+from amqtt.errors import HBMQTTException
+
+
+class PubcompPacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = None
+
+    @property
+    def packet_id(self):
+        return self.variable_header.packet_id
+
+    @packet_id.setter
+    def packet_id(self, val: int):
+        self.variable_header.packet_id = val
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(PUBCOMP, 0x00)
+        else:
+            if fixed.packet_type is not PUBCOMP:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for PubcompPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = None
+
+    @classmethod
+    def build(cls, packet_id: int):
+        v_header = PacketIdVariableHeader(packet_id)
+        packet = PubcompPacket(variable_header=v_header)
+        return packet
diff --git a/amqtt/mqtt/publish.py b/amqtt/mqtt/publish.py
new file mode 100644
index 00000000..fa378541
--- /dev/null
+++ b/amqtt/mqtt/publish.py
@@ -0,0 +1,192 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import asyncio
+
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    PUBLISH,
+    MQTTVariableHeader,
+    MQTTPayload,
+)
+from amqtt.errors import HBMQTTException, MQTTException
+from amqtt.codecs import decode_packet_id, decode_string, encode_string, int_to_bytes
+
+
+class PublishVariableHeader(MQTTVariableHeader):
+
+    __slots__ = ("topic_name", "packet_id")
+
+    def __init__(self, topic_name: str, packet_id: int = None):
+        super().__init__()
+        if "*" in topic_name:
+            raise MQTTException(
+                "[MQTT-3.3.2-2] Topic name in the PUBLISH Packet MUST NOT contain wildcard characters."
+            )
+        self.topic_name = topic_name
+        self.packet_id = packet_id
+
+    def __repr__(self):
+        return type(self).__name__ + "(topic={0}, packet_id={1})".format(
+            self.topic_name, self.packet_id
+        )
+
+    def to_bytes(self):
+        out = bytearray()
+        out.extend(encode_string(self.topic_name))
+        if self.packet_id is not None:
+            out.extend(int_to_bytes(self.packet_id, 2))
+        return out
+
+    @classmethod
+    async def from_stream(
+        cls, reader: asyncio.StreamReader, fixed_header: MQTTFixedHeader
+    ):
+        topic_name = await decode_string(reader)
+        has_qos = (fixed_header.flags >> 1) & 0x03
+        if has_qos:
+            packet_id = await decode_packet_id(reader)
+        else:
+            packet_id = None
+        return cls(topic_name, packet_id)
+
+
+class PublishPayload(MQTTPayload):
+
+    __slots__ = ("data",)
+
+    def __init__(self, data: bytes = None):
+        super().__init__()
+        self.data = data
+
+    def to_bytes(
+        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
+    ):
+        return self.data
+
+    @classmethod
+    async def from_stream(
+        cls,
+        reader: asyncio.StreamReader,
+        fixed_header: MQTTFixedHeader,
+        variable_header: MQTTVariableHeader,
+    ):
+        data = bytearray()
+        data_length = fixed_header.remaining_length - variable_header.bytes_length
+        length_read = 0
+        while length_read < data_length:
+            buffer = await reader.read(data_length - length_read)
+            data.extend(buffer)
+            length_read = len(data)
+        return cls(data)
+
+    def __repr__(self):
+        return type(self).__name__ + "(data={0!r})".format(repr(self.data))
+
+
+class PublishPacket(MQTTPacket):
+    VARIABLE_HEADER = PublishVariableHeader
+    PAYLOAD = PublishPayload
+
+    DUP_FLAG = 0x08
+    RETAIN_FLAG = 0x01
+    QOS_FLAG = 0x06
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PublishVariableHeader = None,
+        payload=None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(PUBLISH, 0x00)
+        else:
+            if fixed.packet_type is not PUBLISH:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for PublishPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = payload
+
+    def set_flags(self, dup_flag=False, qos=0, retain_flag=False):
+        self.dup_flag = dup_flag
+        self.retain_flag = retain_flag
+        self.qos = qos
+
+    def _set_header_flag(self, val, mask):
+        if val:
+            self.fixed_header.flags |= mask
+        else:
+            self.fixed_header.flags &= ~mask
+
+    def _get_header_flag(self, mask):
+        if self.fixed_header.flags & mask:
+            return True
+        else:
+            return False
+
+    @property
+    def dup_flag(self) -> bool:
+        return self._get_header_flag(self.DUP_FLAG)
+
+    @dup_flag.setter
+    def dup_flag(self, val: bool):
+        self._set_header_flag(val, self.DUP_FLAG)
+
+    @property
+    def retain_flag(self) -> bool:
+        return self._get_header_flag(self.RETAIN_FLAG)
+
+    @retain_flag.setter
+    def retain_flag(self, val: bool):
+        self._set_header_flag(val, self.RETAIN_FLAG)
+
+    @property
+    def qos(self):
+        return (self.fixed_header.flags & self.QOS_FLAG) >> 1
+
+    @qos.setter
+    def qos(self, val: int):
+        self.fixed_header.flags &= 0xF9
+        self.fixed_header.flags |= val << 1
+
+    @property
+    def packet_id(self):
+        return self.variable_header.packet_id
+
+    @packet_id.setter
+    def packet_id(self, val: int):
+        self.variable_header.packet_id = val
+
+    @property
+    def data(self):
+        return self.payload.data
+
+    @data.setter
+    def data(self, data: bytes):
+        self.payload.data = data
+
+    @property
+    def topic_name(self):
+        return self.variable_header.topic_name
+
+    @topic_name.setter
+    def topic_name(self, name: str):
+        self.variable_header.topic_name = name
+
+    @classmethod
+    def build(
+        cls, topic_name: str, message: bytes, packet_id: int, dup_flag, qos, retain
+    ):
+        v_header = PublishVariableHeader(topic_name, packet_id)
+        payload = PublishPayload(message)
+        packet = PublishPacket(variable_header=v_header, payload=payload)
+        packet.dup_flag = dup_flag
+        packet.retain_flag = retain
+        packet.qos = qos
+        return packet
diff --git a/amqtt/mqtt/pubrec.py b/amqtt/mqtt/pubrec.py
new file mode 100644
index 00000000..5a292004
--- /dev/null
+++ b/amqtt/mqtt/pubrec.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    PUBREC,
+    PacketIdVariableHeader,
+)
+from amqtt.errors import HBMQTTException
+
+
+class PubrecPacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = None
+
+    @property
+    def packet_id(self):
+        return self.variable_header.packet_id
+
+    @packet_id.setter
+    def packet_id(self, val: int):
+        self.variable_header.packet_id = val
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(PUBREC, 0x00)
+        else:
+            if fixed.packet_type is not PUBREC:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for PubrecPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = None
+
+    @classmethod
+    def build(cls, packet_id: int):
+        v_header = PacketIdVariableHeader(packet_id)
+        packet = PubrecPacket(variable_header=v_header)
+        return packet
diff --git a/amqtt/mqtt/pubrel.py b/amqtt/mqtt/pubrel.py
new file mode 100644
index 00000000..af4f0f17
--- /dev/null
+++ b/amqtt/mqtt/pubrel.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    PUBREL,
+    PacketIdVariableHeader,
+)
+from amqtt.errors import HBMQTTException
+
+
+class PubrelPacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = None
+
+    @property
+    def packet_id(self):
+        return self.variable_header.packet_id
+
+    @packet_id.setter
+    def packet_id(self, val: int):
+        self.variable_header.packet_id = val
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(PUBREL, 0x02)  # [MQTT-3.6.1-1]
+        else:
+            if fixed.packet_type is not PUBREL:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for PubrelPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = None
+
+    @classmethod
+    def build(cls, packet_id):
+        variable_header = PacketIdVariableHeader(packet_id)
+        return PubrelPacket(variable_header=variable_header)
diff --git a/amqtt/mqtt/suback.py b/amqtt/mqtt/suback.py
new file mode 100644
index 00000000..5439aa36
--- /dev/null
+++ b/amqtt/mqtt/suback.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    SUBACK,
+    PacketIdVariableHeader,
+    MQTTPayload,
+    MQTTVariableHeader,
+)
+from amqtt.errors import HBMQTTException, NoDataException
+from amqtt.adapters import ReaderAdapter
+from amqtt.codecs import bytes_to_int, int_to_bytes, read_or_raise
+
+
+class SubackPayload(MQTTPayload):
+
+    __slots__ = ("return_codes",)
+
+    RETURN_CODE_00 = 0x00
+    RETURN_CODE_01 = 0x01
+    RETURN_CODE_02 = 0x02
+    RETURN_CODE_80 = 0x80
+
+    def __init__(self, return_codes=None):
+        super().__init__()
+        self.return_codes = return_codes or []
+
+    def __repr__(self):
+        return type(self).__name__ + "(return_codes={0})".format(
+            repr(self.return_codes)
+        )
+
+    def to_bytes(
+        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
+    ):
+        out = b""
+        for return_code in self.return_codes:
+            out += int_to_bytes(return_code, 1)
+        return out
+
+    @classmethod
+    async def from_stream(
+        cls,
+        reader: ReaderAdapter,
+        fixed_header: MQTTFixedHeader,
+        variable_header: MQTTVariableHeader,
+    ):
+        return_codes = []
+        bytes_to_read = fixed_header.remaining_length - variable_header.bytes_length
+        for i in range(0, bytes_to_read):
+            try:
+                return_code_byte = await read_or_raise(reader, 1)
+                return_code = bytes_to_int(return_code_byte)
+                return_codes.append(return_code)
+            except NoDataException:
+                break
+        return cls(return_codes)
+
+
+class SubackPacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = SubackPayload
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+        payload=None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(SUBACK, 0x00)
+        else:
+            if fixed.packet_type is not SUBACK:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for SubackPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = payload
+
+    @classmethod
+    def build(cls, packet_id, return_codes):
+        variable_header = cls.VARIABLE_HEADER(packet_id)
+        payload = cls.PAYLOAD(return_codes)
+        return cls(variable_header=variable_header, payload=payload)
diff --git a/amqtt/mqtt/subscribe.py b/amqtt/mqtt/subscribe.py
new file mode 100644
index 00000000..14725eb8
--- /dev/null
+++ b/amqtt/mqtt/subscribe.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import asyncio
+
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    SUBSCRIBE,
+    PacketIdVariableHeader,
+    MQTTPayload,
+    MQTTVariableHeader,
+)
+from amqtt.errors import HBMQTTException, NoDataException
+from amqtt.codecs import (
+    bytes_to_int,
+    decode_string,
+    encode_string,
+    int_to_bytes,
+    read_or_raise,
+)
+
+
+class SubscribePayload(MQTTPayload):
+
+    __slots__ = ("topics",)
+
+    def __init__(self, topics=None):
+        super().__init__()
+        self.topics = topics or []
+
+    def to_bytes(
+        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
+    ):
+        out = b""
+        for topic in self.topics:
+            out += encode_string(topic[0])
+            out += int_to_bytes(topic[1], 1)
+        return out
+
+    @classmethod
+    async def from_stream(
+        cls,
+        reader: asyncio.StreamReader,
+        fixed_header: MQTTFixedHeader,
+        variable_header: MQTTVariableHeader,
+    ):
+        topics = []
+        payload_length = fixed_header.remaining_length - variable_header.bytes_length
+        read_bytes = 0
+        while read_bytes < payload_length:
+            try:
+                topic = await decode_string(reader)
+                qos_byte = await read_or_raise(reader, 1)
+                qos = bytes_to_int(qos_byte)
+                topics.append((topic, qos))
+                read_bytes += 2 + len(topic.encode("utf-8")) + 1
+            except NoDataException:
+                break
+        return cls(topics)
+
+    def __repr__(self):
+        return type(self).__name__ + "(topics={0!r})".format(self.topics)
+
+
+class SubscribePacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = SubscribePayload
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+        payload=None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(SUBSCRIBE, 0x02)  # [MQTT-3.8.1-1]
+        else:
+            if fixed.packet_type is not SUBSCRIBE:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for SubscribePacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = payload
+
+    @classmethod
+    def build(cls, topics, packet_id):
+        v_header = PacketIdVariableHeader(packet_id)
+        payload = SubscribePayload(topics)
+        return SubscribePacket(variable_header=v_header, payload=payload)
diff --git a/amqtt/mqtt/unsuback.py b/amqtt/mqtt/unsuback.py
new file mode 100644
index 00000000..0484509c
--- /dev/null
+++ b/amqtt/mqtt/unsuback.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    UNSUBACK,
+    PacketIdVariableHeader,
+)
+from amqtt.errors import HBMQTTException
+
+
+class UnsubackPacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = None
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+        payload=None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(UNSUBACK, 0x00)
+        else:
+            if fixed.packet_type is not UNSUBACK:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for UnsubackPacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = payload
+
+    @classmethod
+    def build(cls, packet_id):
+        variable_header = PacketIdVariableHeader(packet_id)
+        return cls(variable_header=variable_header)
diff --git a/amqtt/mqtt/unsubscribe.py b/amqtt/mqtt/unsubscribe.py
new file mode 100644
index 00000000..1600c6f6
--- /dev/null
+++ b/amqtt/mqtt/unsubscribe.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import asyncio
+
+from amqtt.mqtt.packet import (
+    MQTTPacket,
+    MQTTFixedHeader,
+    UNSUBSCRIBE,
+    PacketIdVariableHeader,
+    MQTTPayload,
+    MQTTVariableHeader,
+)
+from amqtt.errors import HBMQTTException, NoDataException
+from amqtt.codecs import decode_string, encode_string
+
+
+class UnubscribePayload(MQTTPayload):
+
+    __slots__ = ("topics",)
+
+    def __init__(self, topics=None):
+        super().__init__()
+        self.topics = topics or []
+
+    def to_bytes(
+        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
+    ):
+        out = b""
+        for topic in self.topics:
+            out += encode_string(topic)
+        return out
+
+    @classmethod
+    async def from_stream(
+        cls,
+        reader: asyncio.StreamReader,
+        fixed_header: MQTTFixedHeader,
+        variable_header: MQTTVariableHeader,
+    ):
+        topics = []
+        payload_length = fixed_header.remaining_length - variable_header.bytes_length
+        read_bytes = 0
+        while read_bytes < payload_length:
+            try:
+                topic = await decode_string(reader)
+                topics.append(topic)
+                read_bytes += 2 + len(topic.encode("utf-8"))
+            except NoDataException:
+                break
+        return cls(topics)
+
+
+class UnsubscribePacket(MQTTPacket):
+    VARIABLE_HEADER = PacketIdVariableHeader
+    PAYLOAD = UnubscribePayload
+
+    def __init__(
+        self,
+        fixed: MQTTFixedHeader = None,
+        variable_header: PacketIdVariableHeader = None,
+        payload=None,
+    ):
+        if fixed is None:
+            header = MQTTFixedHeader(UNSUBSCRIBE, 0x02)  # [MQTT-3.10.1-1]
+        else:
+            if fixed.packet_type is not UNSUBSCRIBE:
+                raise HBMQTTException(
+                    "Invalid fixed packet type %s for UnsubscribePacket init"
+                    % fixed.packet_type
+                )
+            header = fixed
+
+        super().__init__(header)
+        self.variable_header = variable_header
+        self.payload = payload
+
+    @classmethod
+    def build(cls, topics, packet_id):
+        v_header = PacketIdVariableHeader(packet_id)
+        payload = UnubscribePayload(topics)
+        return UnsubscribePacket(variable_header=v_header, payload=payload)
diff --git a/amqtt/plugins/__init__.py b/amqtt/plugins/__init__.py
new file mode 100644
index 00000000..86831baf
--- /dev/null
+++ b/amqtt/plugins/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
diff --git a/amqtt/plugins/authentication.py b/amqtt/plugins/authentication.py
new file mode 100644
index 00000000..a4f92dde
--- /dev/null
+++ b/amqtt/plugins/authentication.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import logging
+from passlib.apps import custom_app_context as pwd_context
+
+
+class BaseAuthPlugin:
+    def __init__(self, context):
+        self.context = context
+        try:
+            self.auth_config = self.context.config["auth"]
+        except KeyError:
+            self.context.logger.warning(
+                "'auth' section not found in context configuration"
+            )
+
+    def authenticate(self, *args, **kwargs):
+        if not self.auth_config:
+            # auth config section not found
+            self.context.logger.warning(
+                "'auth' section not found in context configuration"
+            )
+            return False
+        return True
+
+
+class AnonymousAuthPlugin(BaseAuthPlugin):
+    def __init__(self, context):
+        super().__init__(context)
+
+    async def authenticate(self, *args, **kwargs):
+        authenticated = super().authenticate(*args, **kwargs)
+        if authenticated:
+            allow_anonymous = self.auth_config.get(
+                "allow-anonymous", True
+            )  # allow anonymous by default
+            if allow_anonymous:
+                authenticated = True
+                self.context.logger.debug(
+                    "Authentication success: config allows anonymous"
+                )
+            else:
+                try:
+                    session = kwargs.get("session", None)
+                    authenticated = True if session.username else False
+                    if self.context.logger.isEnabledFor(logging.DEBUG):
+                        if authenticated:
+                            self.context.logger.debug(
+                                "Authentication success: session has a non empty username"
+                            )
+                        else:
+                            self.context.logger.debug(
+                                "Authentication failure: session has an empty username"
+                            )
+                except KeyError:
+                    self.context.logger.warning("Session information not available")
+                    authenticated = False
+        return authenticated
+
+
+class FileAuthPlugin(BaseAuthPlugin):
+    def __init__(self, context):
+        super().__init__(context)
+        self._users = dict()
+        self._read_password_file()
+
+    def _read_password_file(self):
+        password_file = self.auth_config.get("password-file", None)
+        if password_file:
+            try:
+                with open(password_file) as f:
+                    self.context.logger.debug(
+                        "Reading user database from %s" % password_file
+                    )
+                    for line in f:
+                        line = line.strip()
+                        if not line.startswith("#"):  # Allow comments in files
+                            (username, pwd_hash) = line.split(sep=":", maxsplit=3)
+                            if username:
+                                self._users[username] = pwd_hash
+                                self.context.logger.debug(
+                                    "user %s , hash=%s" % (username, pwd_hash)
+                                )
+                self.context.logger.debug(
+                    "%d user(s) read from file %s" % (len(self._users), password_file)
+                )
+            except FileNotFoundError:
+                self.context.logger.warning(
+                    "Password file %s not found" % password_file
+                )
+        else:
+            self.context.logger.debug(
+                "Configuration parameter 'password_file' not found"
+            )
+
+    async def authenticate(self, *args, **kwargs):
+        authenticated = super().authenticate(*args, **kwargs)
+        if authenticated:
+            session = kwargs.get("session", None)
+            if session.username:
+                hash = self._users.get(session.username, None)
+                if not hash:
+                    authenticated = False
+                    self.context.logger.debug(
+                        "No hash found for user '%s'" % session.username
+                    )
+                else:
+                    authenticated = pwd_context.verify(session.password, hash)
+            else:
+                return None
+        return authenticated
diff --git a/amqtt/plugins/logging.py b/amqtt/plugins/logging.py
new file mode 100644
index 00000000..4b76d1b6
--- /dev/null
+++ b/amqtt/plugins/logging.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+
+
+import logging
+from functools import partial
+
+
+class EventLoggerPlugin:
+    def __init__(self, context):
+        self.context = context
+
+    async def log_event(self, *args, **kwargs):
+        self.context.logger.info(
+            "### '%s' EVENT FIRED ###" % kwargs["event_name"].replace("old", "")
+        )
+
+    def __getattr__(self, name):
+        if name.startswith("on_"):
+            return partial(self.log_event, event_name=name)
+
+
+class PacketLoggerPlugin:
+    def __init__(self, context):
+        self.context = context
+
+    async def on_mqtt_packet_received(self, *args, **kwargs):
+        packet = kwargs.get("packet")
+        session = kwargs.get("session", None)
+        if self.context.logger.isEnabledFor(logging.DEBUG):
+            if session:
+                self.context.logger.debug(
+                    "%s <-in-- %s" % (session.client_id, repr(packet))
+                )
+            else:
+                self.context.logger.debug("<-in-- %s" % repr(packet))
+
+    async def on_mqtt_packet_sent(self, *args, **kwargs):
+        packet = kwargs.get("packet")
+        session = kwargs.get("session", None)
+        if self.context.logger.isEnabledFor(logging.DEBUG):
+            if session:
+                self.context.logger.debug(
+                    "%s -out-> %s" % (session.client_id, repr(packet))
+                )
+            else:
+                self.context.logger.debug("-out-> %s" % repr(packet))
diff --git a/amqtt/plugins/manager.py b/amqtt/plugins/manager.py
new file mode 100644
index 00000000..de553d1c
--- /dev/null
+++ b/amqtt/plugins/manager.py
@@ -0,0 +1,214 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+
+__all__ = ["get_plugin_manager", "BaseContext", "PluginManager"]
+
+import pkg_resources
+import logging
+import asyncio
+import copy
+
+from collections import namedtuple
+
+
+Plugin = namedtuple("Plugin", ["name", "ep", "object"])
+
+plugins_manager = dict()
+
+
+def get_plugin_manager(namespace):
+    global plugins_manager
+    return plugins_manager.get(namespace, None)
+
+
+class BaseContext:
+    def __init__(self):
+        self.loop = None
+        self.logger = None
+
+
+class PluginManager:
+    """
+    Wraps setuptools Entry point mechanism to provide a basic plugin system. Plugins
+    are loaded for a given namespace (group). This plugin manager uses coroutines to
+    run plugin call asynchronously in an event queue
+    """
+
+    def __init__(self, namespace, context, loop=None):
+        global plugins_manager
+        if loop is not None:
+            self._loop = loop
+        else:
+            self._loop = asyncio.get_event_loop()
+
+        self.logger = logging.getLogger(namespace)
+        if context is None:
+            self.context = BaseContext()
+        else:
+            self.context = context
+        self.context.loop = self._loop
+        self._plugins = []
+        self._load_plugins(namespace)
+        self._fired_events = []
+        plugins_manager[namespace] = self
+
+    @property
+    def app_context(self):
+        return self.context
+
+    def _load_plugins(self, namespace):
+        self.logger.debug("Loading plugins for namespace %s" % namespace)
+        for ep in pkg_resources.iter_entry_points(group=namespace):
+            plugin = self._load_plugin(ep)
+            self._plugins.append(plugin)
+            self.logger.debug(" Plugin %s ready" % ep.name)
+
+        if namespace.startswith("hbmqtt."):
+            self._load_plugins("amqtt" + namespace[6:])
+
+    def _load_plugin(self, ep: pkg_resources.EntryPoint):
+        try:
+            self.logger.debug(" Loading plugin %s" % ep)
+            plugin = ep.load(require=True)
+            self.logger.debug(" Initializing plugin %s" % ep)
+            plugin_context = copy.copy(self.app_context)
+            plugin_context.logger = self.logger.getChild(ep.name)
+            obj = plugin(plugin_context)
+            return Plugin(ep.name, ep, obj)
+        except ImportError as ie:
+            self.logger.warning("Plugin %r import failed: %s" % (ep, ie))
+        except pkg_resources.UnknownExtra as ue:
+            self.logger.warning(
+                "Plugin %r dependencies resolution failed: %s" % (ep, ue)
+            )
+
+    def get_plugin(self, name):
+        """
+        Get a plugin by its name from the plugins loaded for the current namespace
+        :param name:
+        :return:
+        """
+        for p in self._plugins:
+            if p.name == name:
+                return p
+        return None
+
+    async def close(self):
+        """
+        Free PluginManager resources and cancel pending event methods
+        This method call a close() coroutine for each plugin, allowing plugins to close
+        and free resources
+        :return:
+        """
+        await self.map_plugin_coro("close")
+        for task in self._fired_events:
+            task.cancel()
+
+    @property
+    def plugins(self):
+        """
+        Get the loaded plugins list
+        :return:
+        """
+        return self._plugins
+
+    def _schedule_coro(self, coro):
+        return asyncio.ensure_future(coro, loop=self._loop)
+
+    async def fire_event(self, event_name, wait=False, *args, **kwargs):
+        """
+        Fire an event to plugins.
+        PluginManager schedule async calls for each plugin on method called "on_" + event_name
+        For example, on_connect will be called on event 'connect'
+        Method calls are schedule in the async loop. wait parameter must be set to true
+        to wait until all methods are completed.
+        :param event_name:
+        :param args:
+        :param kwargs:
+        :param wait: indicates if fire_event should wait for plugin calls completion (True), or not
+        :return:
+        """
+        tasks = []
+        event_method_name = "on_" + event_name
+        for plugin in self._plugins:
+            event_method = getattr(plugin.object, event_method_name, None)
+            if event_method:
+                try:
+                    task = self._schedule_coro(event_method(*args, **kwargs))
+                    tasks.append(task)
+
+                    def clean_fired_events(future):
+                        try:
+                            self._fired_events.remove(future)
+                        except (KeyError, ValueError):
+                            pass
+
+                    task.add_done_callback(clean_fired_events)
+                except AssertionError:
+                    self.logger.error(
+                        "Method '%s' on plugin '%s' is not a coroutine"
+                        % (event_method_name, plugin.name)
+                    )
+
+        self._fired_events.extend(tasks)
+        if wait:
+            if tasks:
+                await asyncio.wait(tasks, loop=self._loop)
+        self.logger.debug("Plugins len(_fired_events)=%d" % (len(self._fired_events)))
+
+    async def map(self, coro, *args, **kwargs):
+        """
+        Schedule a given coroutine call for each plugin.
+        The coro called get the Plugin instance as first argument of its method call
+        :param coro: coro to call on each plugin
+        :param filter_plugins: list of plugin names to filter (only plugin whose name is
+            in filter are called).
+        None will call all plugins. [] will call None.
+        :param args: arguments to pass to coro
+        :param kwargs: arguments to pass to coro
+        :return: dict containing return from coro call for each plugin
+        """
+        p_list = kwargs.pop("filter_plugins", None)
+        if p_list is None:
+            p_list = [p.name for p in self.plugins]
+        tasks = []
+        plugins_list = []
+        for plugin in self._plugins:
+            if plugin.name in p_list:
+                coro_instance = coro(plugin, *args, **kwargs)
+                if coro_instance:
+                    try:
+                        tasks.append(self._schedule_coro(coro_instance))
+                        plugins_list.append(plugin)
+                    except AssertionError:
+                        self.logger.error(
+                            "Method '%r' on plugin '%s' is not a coroutine"
+                            % (coro, plugin.name)
+                        )
+        if tasks:
+            ret_list = await asyncio.gather(*tasks, loop=self._loop)
+            # Create result map plugin=>ret
+            ret_dict = {k: v for k, v in zip(plugins_list, ret_list)}
+        else:
+            ret_dict = {}
+        return ret_dict
+
+    @staticmethod
+    async def _call_coro(plugin, coro_name, *args, **kwargs):
+        try:
+            coro = getattr(plugin.object, coro_name, None)(*args, **kwargs)
+            return await coro
+        except TypeError:
+            # Plugin doesn't implement coro_name
+            return None
+
+    async def map_plugin_coro(self, coro_name, *args, **kwargs):
+        """
+        Call a plugin declared by plugin by its name
+        :param coro_name:
+        :param args:
+        :param kwargs:
+        :return:
+        """
+        return await self.map(self._call_coro, coro_name, *args, **kwargs)
diff --git a/amqtt/plugins/persistence.py b/amqtt/plugins/persistence.py
new file mode 100644
index 00000000..e61e70ac
--- /dev/null
+++ b/amqtt/plugins/persistence.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import sqlite3
+import pickle
+
+
+class SQLitePlugin:
+    def __init__(self, context):
+        self.context = context
+        self.conn = None
+        self.cursor = None
+        self.db_file = None
+        try:
+            self.persistence_config = self.context.config["persistence"]
+            self.init_db()
+        except KeyError:
+            self.context.logger.warning(
+                "'persistence' section not found in context configuration"
+            )
+
+    def init_db(self):
+        self.db_file = self.persistence_config.get("file", None)
+        if not self.db_file:
+            self.context.logger.warning("'file' persistence parameter not found")
+        else:
+            try:
+                self.conn = sqlite3.connect(self.db_file)
+                self.cursor = self.conn.cursor()
+                self.context.logger.info("Database file '%s' opened" % self.db_file)
+            except Exception as e:
+                self.context.logger.error(
+                    "Error while initializing database '%s' : %s" % (self.db_file, e)
+                )
+        if self.cursor:
+            self.cursor.execute(
+                "CREATE TABLE IF NOT EXISTS session(client_id TEXT PRIMARY KEY, data BLOB)"
+            )
+
+    async def save_session(self, session):
+        if self.cursor:
+            dump = pickle.dumps(session)
+            try:
+                self.cursor.execute(
+                    "INSERT OR REPLACE INTO session (client_id, data) VALUES (?,?)",
+                    (session.client_id, dump),
+                )
+                self.conn.commit()
+            except Exception as e:
+                self.context.logger.error(
+                    "Failed saving session '%s': %s" % (session, e)
+                )
+
+    async def find_session(self, client_id):
+        if self.cursor:
+            row = self.cursor.execute(
+                "SELECT data FROM session where client_id=?", (client_id,)
+            ).fetchone()
+            if row:
+                return pickle.loads(row[0])
+            else:
+                return None
+
+    async def del_session(self, client_id):
+        if self.cursor:
+            self.cursor.execute("DELETE FROM session where client_id=?", (client_id,))
+            self.conn.commit()
+
+    async def on_broker_post_shutdown(self):
+        if self.conn:
+            self.conn.close()
+            self.context.logger.info("Database file '%s' closed" % self.db_file)
diff --git a/amqtt/plugins/sys/__init__.py b/amqtt/plugins/sys/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/amqtt/plugins/sys/broker.py b/amqtt/plugins/sys/broker.py
new file mode 100644
index 00000000..01fedd73
--- /dev/null
+++ b/amqtt/plugins/sys/broker.py
@@ -0,0 +1,249 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+from datetime import datetime
+from collections import deque
+import asyncio
+
+import amqtt
+from amqtt.mqtt.packet import PUBLISH
+from amqtt.codecs import int_to_bytes_str
+
+
+DOLLAR_SYS_ROOT = "$SYS/broker/"
+STAT_BYTES_SENT = "bytes_sent"
+STAT_BYTES_RECEIVED = "bytes_received"
+STAT_MSG_SENT = "messages_sent"
+STAT_MSG_RECEIVED = "messages_received"
+STAT_PUBLISH_SENT = "publish_sent"
+STAT_PUBLISH_RECEIVED = "publish_received"
+STAT_START_TIME = "start_time"
+STAT_CLIENTS_MAXIMUM = "clients_maximum"
+STAT_CLIENTS_CONNECTED = "clients_connected"
+STAT_CLIENTS_DISCONNECTED = "clients_disconnected"
+
+
+class BrokerSysPlugin:
+    def __init__(self, context):
+        self.context = context
+        # Broker statistics initialization
+        self._stats = dict()
+        self._sys_handle = None
+
+    def _clear_stats(self):
+        """
+        Initializes broker statistics data structures
+        """
+        for stat in (
+            STAT_BYTES_RECEIVED,
+            STAT_BYTES_SENT,
+            STAT_MSG_RECEIVED,
+            STAT_MSG_SENT,
+            STAT_CLIENTS_MAXIMUM,
+            STAT_CLIENTS_CONNECTED,
+            STAT_CLIENTS_DISCONNECTED,
+            STAT_PUBLISH_RECEIVED,
+            STAT_PUBLISH_SENT,
+        ):
+            self._stats[stat] = 0
+
+    async def _broadcast_sys_topic(self, topic_basename, data):
+        return await self.context.broadcast_message(topic_basename, data)
+
+    def schedule_broadcast_sys_topic(self, topic_basename, data):
+        return asyncio.ensure_future(
+            self._broadcast_sys_topic(DOLLAR_SYS_ROOT + topic_basename, data),
+            loop=self.context.loop,
+        )
+
+    async def on_broker_pre_start(self, *args, **kwargs):
+        self._clear_stats()
+
+    async def on_broker_post_start(self, *args, **kwargs):
+        self._stats[STAT_START_TIME] = datetime.now()
+        version = f"HBMQTT version {amqtt.__version__}"
+        self.context.retain_message(DOLLAR_SYS_ROOT + "version", version.encode())
+
+        # Start $SYS topics management
+        try:
+            sys_interval = int(self.context.config.get("sys_interval", 0))
+            if sys_interval > 0:
+                self.context.logger.debug(
+                    "Setup $SYS broadcasting every %d seconds" % sys_interval
+                )
+                self.sys_handle = self.context.loop.call_later(
+                    sys_interval, self.broadcast_dollar_sys_topics
+                )
+            else:
+                self.context.logger.debug("$SYS disabled")
+        except KeyError:
+            pass
+            # 'sys_internal' config parameter not found
+
+    async def on_broker_pre_stop(self, *args, **kwargs):
+        # Stop $SYS topics broadcasting
+        if self.sys_handle:
+            self.sys_handle.cancel()
+
+    def broadcast_dollar_sys_topics(self):
+        """
+        Broadcast dynamic $SYS topics updates and reschedule next execution depending on 'sys_interval' config
+        parameter.
+        """
+
+        # Update stats
+        uptime = datetime.now() - self._stats[STAT_START_TIME]
+        client_connected = self._stats[STAT_CLIENTS_CONNECTED]
+        client_disconnected = self._stats[STAT_CLIENTS_DISCONNECTED]
+        inflight_in = 0
+        inflight_out = 0
+        messages_stored = 0
+        for session in self.context.sessions:
+            inflight_in += session.inflight_in_count
+            inflight_out += session.inflight_out_count
+            messages_stored += session.retained_messages_count
+        messages_stored += len(self.context.retained_messages)
+        subscriptions_count = 0
+        for topic in self.context.subscriptions:
+            subscriptions_count += len(self.context.subscriptions[topic])
+
+        # Broadcast updates
+        tasks = deque()
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "load/bytes/received",
+                int_to_bytes_str(self._stats[STAT_BYTES_RECEIVED]),
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "load/bytes/sent", int_to_bytes_str(self._stats[STAT_BYTES_SENT])
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/received", int_to_bytes_str(self._stats[STAT_MSG_RECEIVED])
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/sent", int_to_bytes_str(self._stats[STAT_MSG_SENT])
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "time", str(datetime.now()).encode("utf-8")
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "uptime", int_to_bytes_str(int(uptime.total_seconds()))
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "uptime/formated", str(uptime).encode("utf-8")
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "clients/connected", int_to_bytes_str(client_connected)
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "clients/disconnected", int_to_bytes_str(client_disconnected)
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "clients/maximum", int_to_bytes_str(self._stats[STAT_CLIENTS_MAXIMUM])
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "clients/total",
+                int_to_bytes_str(client_connected + client_disconnected),
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/inflight", int_to_bytes_str(inflight_in + inflight_out)
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/inflight/in", int_to_bytes_str(inflight_in)
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/inflight/out", int_to_bytes_str(inflight_out)
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/inflight/stored", int_to_bytes_str(messages_stored)
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/publish/received",
+                int_to_bytes_str(self._stats[STAT_PUBLISH_RECEIVED]),
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/publish/sent",
+                int_to_bytes_str(self._stats[STAT_PUBLISH_SENT]),
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/retained/count",
+                int_to_bytes_str(len(self.context.retained_messages)),
+            )
+        )
+        tasks.append(
+            self.schedule_broadcast_sys_topic(
+                "messages/subscriptions/count", int_to_bytes_str(subscriptions_count)
+            )
+        )
+
+        # Wait until broadcasting tasks end
+        while tasks and tasks[0].done():
+            tasks.popleft()
+        # Reschedule
+        sys_interval = int(self.context.config["sys_interval"])
+        self.context.logger.debug("Broadcasting $SYS topics")
+        self.sys_handle = self.context.loop.call_later(
+            sys_interval, self.broadcast_dollar_sys_topics
+        )
+
+    async def on_mqtt_packet_received(self, *args, **kwargs):
+        packet = kwargs.get("packet")
+        if packet:
+            packet_size = packet.bytes_length
+            self._stats[STAT_BYTES_RECEIVED] += packet_size
+            self._stats[STAT_MSG_RECEIVED] += 1
+            if packet.fixed_header.packet_type == PUBLISH:
+                self._stats[STAT_PUBLISH_RECEIVED] += 1
+
+    async def on_mqtt_packet_sent(self, *args, **kwargs):
+        packet = kwargs.get("packet")
+        if packet:
+            packet_size = packet.bytes_length
+            self._stats[STAT_BYTES_SENT] += packet_size
+            self._stats[STAT_MSG_SENT] += 1
+            if packet.fixed_header.packet_type == PUBLISH:
+                self._stats[STAT_PUBLISH_SENT] += 1
+
+    async def on_broker_client_connected(self, *args, **kwargs):
+        self._stats[STAT_CLIENTS_CONNECTED] += 1
+        self._stats[STAT_CLIENTS_MAXIMUM] = max(
+            self._stats[STAT_CLIENTS_MAXIMUM], self._stats[STAT_CLIENTS_CONNECTED]
+        )
+
+    async def on_broker_client_disconnected(self, *args, **kwargs):
+        self._stats[STAT_CLIENTS_CONNECTED] -= 1
+        self._stats[STAT_CLIENTS_DISCONNECTED] += 1
diff --git a/amqtt/plugins/topic_checking.py b/amqtt/plugins/topic_checking.py
new file mode 100644
index 00000000..b52a156f
--- /dev/null
+++ b/amqtt/plugins/topic_checking.py
@@ -0,0 +1,82 @@
+class BaseTopicPlugin:
+    def __init__(self, context):
+        self.context = context
+        try:
+            self.topic_config = self.context.config["topic-check"]
+        except KeyError:
+            self.context.logger.warning(
+                "'topic-check' section not found in context configuration"
+            )
+
+    def topic_filtering(self, *args, **kwargs):
+        if not self.topic_config:
+            # auth config section not found
+            self.context.logger.warning(
+                "'auth' section not found in context configuration"
+            )
+            return False
+        return True
+
+
+class TopicTabooPlugin(BaseTopicPlugin):
+    def __init__(self, context):
+        super().__init__(context)
+        self._taboo = ["prohibited", "top-secret", "data/classified"]
+
+    async def topic_filtering(self, *args, **kwargs):
+        filter_result = super().topic_filtering(*args, **kwargs)
+        if filter_result:
+            session = kwargs.get("session", None)
+            topic = kwargs.get("topic", None)
+            if session.username and session.username == "admin":
+                return True
+            if topic and topic in self._taboo:
+                return False
+            return True
+        return filter_result
+
+
+class TopicAccessControlListPlugin(BaseTopicPlugin):
+    def __init__(self, context):
+        super().__init__(context)
+
+    @staticmethod
+    def topic_ac(topic_requested, topic_allowed):
+        req_split = topic_requested.split("/")
+        allowed_split = topic_allowed.split("/")
+        ret = True
+        for i in range(max(len(req_split), len(allowed_split))):
+            try:
+                a_aux = req_split[i]
+                b_aux = allowed_split[i]
+            except IndexError:
+                ret = False
+                break
+            if b_aux == "#":
+                break
+            elif (b_aux == "+") or (b_aux == a_aux):
+                continue
+            else:
+                ret = False
+                break
+        return ret
+
+    async def topic_filtering(self, *args, **kwargs):
+        filter_result = super().topic_filtering(*args, **kwargs)
+        if filter_result:
+            session = kwargs.get("session", None)
+            req_topic = kwargs.get("topic", None)
+            if req_topic:
+                username = session.username
+                if username is None:
+                    username = "anonymous"
+                allowed_topics = self.topic_config["acl"].get(username, None)
+                if allowed_topics:
+                    for allowed_topic in allowed_topics:
+                        if self.topic_ac(req_topic, allowed_topic):
+                            return True
+                    return False
+                else:
+                    return False
+            else:
+                return False
diff --git a/amqtt/scripts/__init__.py b/amqtt/scripts/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/amqtt/scripts/broker_script.py b/amqtt/scripts/broker_script.py
new file mode 100644
index 00000000..b96e1b6a
--- /dev/null
+++ b/amqtt/scripts/broker_script.py
@@ -0,0 +1,87 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+"""
+HBMQTT - MQTT 3.1.1 broker
+
+Usage:
+    hbmqtt --version
+    hbmqtt (-h | --help)
+    hbmqtt [-c <config_file> ] [-d]
+
+Options:
+    -h --help           Show this screen.
+    --version           Show version.
+    -c <config_file>    Broker configuration file (YAML format)
+    -d                  Enable debug messages
+"""
+
+import sys
+import logging
+import asyncio
+import os
+
+import hbmqtt
+from hbmqtt.broker import Broker
+from docopt import docopt
+from hbmqtt.utils import read_yaml_config
+
+
+default_config = {
+    "listeners": {
+        "default": {
+            "type": "tcp",
+            "bind": "0.0.0.0:1883",
+        },
+    },
+    "sys_interval": 10,
+    "auth": {
+        "allow-anonymous": True,
+        "password-file": os.path.join(
+            os.path.dirname(os.path.realpath(__file__)), "passwd"
+        ),
+        "plugins": ["auth_file", "auth_anonymous"],
+    },
+    "topic-check": {"enabled": False},
+}
+
+logger = logging.getLogger(__name__)
+
+
+def main(*args, **kwargs):
+    if sys.version_info[:2] < (3, 4):
+        logger.fatal("Error: Python 3.4+ is required")
+        sys.exit(-1)
+
+    arguments = docopt(__doc__, version=hbmqtt.__version__)
+    formatter = "[%(asctime)s] :: %(levelname)s - %(message)s"
+
+    if arguments["-d"]:
+        level = logging.DEBUG
+    else:
+        level = logging.INFO
+    logging.basicConfig(level=level, format=formatter)
+
+    config = None
+    if arguments["-c"]:
+        config = read_yaml_config(arguments["-c"])
+    else:
+        config = read_yaml_config(
+            os.path.join(
+                os.path.dirname(os.path.realpath(__file__)), "default_broker.yaml"
+            )
+        )
+        logger.debug("Using default configuration")
+    loop = asyncio.get_event_loop()
+    broker = Broker(config)
+    try:
+        loop.run_until_complete(broker.start())
+        loop.run_forever()
+    except KeyboardInterrupt:
+        loop.run_until_complete(broker.shutdown())
+    finally:
+        loop.close()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/hbmqtt/scripts/default_broker.yaml b/amqtt/scripts/default_broker.yaml
similarity index 100%
rename from hbmqtt/scripts/default_broker.yaml
rename to amqtt/scripts/default_broker.yaml
diff --git a/hbmqtt/scripts/default_client.yaml b/amqtt/scripts/default_client.yaml
similarity index 100%
rename from hbmqtt/scripts/default_client.yaml
rename to amqtt/scripts/default_client.yaml
diff --git a/amqtt/scripts/pub_script.py b/amqtt/scripts/pub_script.py
new file mode 100644
index 00000000..1a58d3a6
--- /dev/null
+++ b/amqtt/scripts/pub_script.py
@@ -0,0 +1,186 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+"""
+hbmqtt_pub - MQTT 3.1.1 publisher
+
+Usage:
+    hbmqtt_pub --version
+    hbmqtt_pub (-h | --help)
+    hbmqtt_pub --url BROKER_URL -t TOPIC (-f FILE | -l | -m MESSAGE | -n | -s) [-c CONFIG_FILE] [-i CLIENT_ID] [-q | --qos QOS] [-d] [-k KEEP_ALIVE] [--clean-session] [--ca-file CAFILE] [--ca-path CAPATH] [--ca-data CADATA] [ --will-topic WILL_TOPIC [--will-message WILL_MESSAGE] [--will-qos WILL_QOS] [--will-retain] ] [--extra-headers HEADER] [-r]
+
+Options:
+    -h --help           Show this screen.
+    --version           Show version.
+    --url BROKER_URL    Broker connection URL (musr conform to MQTT URI scheme (see https://github.com/mqtt/mqtt.github.io/wiki/URI-Scheme>)
+    -c CONFIG_FILE      Broker configuration file (YAML format)
+    -i CLIENT_ID        Id to use as client ID.
+    -q | --qos QOS      Quality of service to use for the message, from 0, 1 and 2. Defaults to 0.
+    -r                  Set retain flag on connect
+    -t TOPIC            Message topic
+    -m MESSAGE          Message data to send
+    -f FILE             Read file by line and publish message for each line
+    -s                  Read from stdin and publish message for each line
+    -k KEEP_ALIVE       Keep alive timeout in second
+    --clean-session     Clean session on connect (defaults to False)
+    --ca-file CAFILE]   CA file
+    --ca-path CAPATH]   CA Path
+    --ca-data CADATA    CA data
+    --will-topic WILL_TOPIC
+    --will-message WILL_MESSAGE
+    --will-qos WILL_QOS
+    --will-retain
+    --extra-headers EXTRA_HEADERS      JSON object with key-value pairs of additional headers for websocket connections
+    -d                  Enable debug messages
+"""
+
+import sys
+import logging
+import asyncio
+import os
+import json
+
+import hbmqtt
+from hbmqtt.client import MQTTClient, ConnectException
+from docopt import docopt
+from hbmqtt.utils import read_yaml_config
+
+
+logger = logging.getLogger(__name__)
+
+
+def _gen_client_id():
+    import os
+    import socket
+
+    pid = os.getpid()
+    hostname = socket.gethostname()
+    return "hbmqtt_pub/%d-%s" % (pid, hostname)
+
+
+def _get_qos(arguments):
+    try:
+        return int(arguments["--qos"][0])
+    except:
+        return None
+
+
+def _get_extra_headers(arguments):
+    try:
+        return json.loads(arguments["--extra-headers"])
+    except:
+        return {}
+
+
+def _get_message(arguments):
+    if arguments["-n"]:
+        yield b""
+    if arguments["-m"]:
+        yield arguments["-m"].encode(encoding="utf-8")
+    if arguments["-f"]:
+        try:
+            with open(arguments["-f"], "r") as f:
+                for line in f:
+                    yield line.encode(encoding="utf-8")
+        except:
+            logger.error("Failed to read file '%s'" % arguments["-f"])
+    if arguments["-l"]:
+        import sys
+
+        for line in sys.stdin:
+            if line:
+                yield line.encode(encoding="utf-8")
+    if arguments["-s"]:
+        import sys
+
+        message = bytearray()
+        for line in sys.stdin:
+            message.extend(line.encode(encoding="utf-8"))
+        yield message
+
+
+async def do_pub(client, arguments):
+    running_tasks = []
+
+    try:
+        logger.info("%s Connecting to broker" % client.client_id)
+
+        await client.connect(
+            uri=arguments["--url"],
+            cleansession=arguments["--clean-session"],
+            cafile=arguments["--ca-file"],
+            capath=arguments["--ca-path"],
+            cadata=arguments["--ca-data"],
+            extra_headers=_get_extra_headers(arguments),
+        )
+        qos = _get_qos(arguments)
+        topic = arguments["-t"]
+        retain = arguments["-r"]
+        for message in _get_message(arguments):
+            logger.info("%s Publishing to '%s'" % (client.client_id, topic))
+            task = asyncio.ensure_future(client.publish(topic, message, qos, retain))
+            running_tasks.append(task)
+        if running_tasks:
+            await asyncio.wait(running_tasks)
+        await client.disconnect()
+        logger.info("%s Disconnected from broker" % client.client_id)
+    except KeyboardInterrupt:
+        await client.disconnect()
+        logger.info("%s Disconnected from broker" % client.client_id)
+    except ConnectException as ce:
+        logger.fatal("connection to '%s' failed: %r" % (arguments["--url"], ce))
+    except asyncio.CancelledError:
+        logger.fatal("Publish canceled due to previous error")
+
+
+def main(*args, **kwargs):
+    if sys.version_info[:2] < (3, 6):
+        logger.fatal("Error: Python 3.6+ is required")
+        sys.exit(-1)
+
+    arguments = docopt(__doc__, version=hbmqtt.__version__)
+    # print(arguments)
+    formatter = "[%(asctime)s] :: %(levelname)s - %(message)s"
+
+    if arguments["-d"]:
+        level = logging.DEBUG
+    else:
+        level = logging.INFO
+    logging.basicConfig(level=level, format=formatter)
+
+    if arguments["-c"]:
+        config = read_yaml_config(arguments["-c"])
+    else:
+        config = read_yaml_config(
+            os.path.join(
+                os.path.dirname(os.path.realpath(__file__)), "default_client.yaml"
+            )
+        )
+        logger.debug("Using default configuration")
+    loop = asyncio.get_event_loop()
+
+    client_id = arguments.get("-i", None)
+    if not client_id:
+        client_id = _gen_client_id()
+
+    if arguments["-k"]:
+        config["keep_alive"] = int(arguments["-k"])
+
+    if (
+        arguments["--will-topic"]
+        and arguments["--will-message"]
+        and arguments["--will-qos"]
+    ):
+        config["will"] = dict()
+        config["will"]["topic"] = arguments["--will-topic"]
+        config["will"]["message"] = arguments["--will-message"].encode("utf-8")
+        config["will"]["qos"] = int(arguments["--will-qos"])
+        config["will"]["retain"] = arguments["--will-retain"]
+
+    client = MQTTClient(client_id=client_id, config=config, loop=loop)
+    loop.run_until_complete(do_pub(client, arguments))
+    loop.close()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/amqtt/scripts/sub_script.py b/amqtt/scripts/sub_script.py
new file mode 100644
index 00000000..7d3057d6
--- /dev/null
+++ b/amqtt/scripts/sub_script.py
@@ -0,0 +1,168 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+"""
+hbmqtt_sub - MQTT 3.1.1 publisher
+
+Usage:
+    hbmqtt_sub --version
+    hbmqtt_sub (-h | --help)
+    hbmqtt_sub --url BROKER_URL -t TOPIC... [-n COUNT] [-c CONFIG_FILE] [-i CLIENT_ID] [-q | --qos QOS] [-d] [-k KEEP_ALIVE] [--clean-session] [--ca-file CAFILE] [--ca-path CAPATH] [--ca-data CADATA] [ --will-topic WILL_TOPIC [--will-message WILL_MESSAGE] [--will-qos WILL_QOS] [--will-retain] ] [--extra-headers HEADER]
+
+Options:
+    -h --help           Show this screen.
+    --version           Show version.
+    --url BROKER_URL    Broker connection URL (musr conform to MQTT URI scheme (see https://github.com/mqtt/mqtt.github.io/wiki/URI-Scheme>)
+    -c CONFIG_FILE      Broker configuration file (YAML format)
+    -i CLIENT_ID        Id to use as client ID.
+    -n COUNT            Number of messages to read before ending.
+    -q | --qos QOS      Quality of service desired to receive messages, from 0, 1 and 2. Defaults to 0.
+    -t TOPIC...         Topic filter to subcribe
+    -k KEEP_ALIVE       Keep alive timeout in second
+    --clean-session     Clean session on connect (defaults to False)
+    --ca-file CAFILE]   CA file
+    --ca-path CAPATH]   CA Path
+    --ca-data CADATA    CA data
+    --will-topic WILL_TOPIC
+    --will-message WILL_MESSAGE
+    --will-qos WILL_QOS
+    --will-retain
+    --extra-headers EXTRA_HEADERS      JSON object with key-value pairs of additional headers for websocket connections
+    -d                  Enable debug messages
+"""
+
+import sys
+import logging
+import asyncio
+import os
+import json
+
+import hbmqtt
+from hbmqtt.client import MQTTClient, ConnectException
+from hbmqtt.errors import MQTTException
+from docopt import docopt
+from hbmqtt.mqtt.constants import QOS_0
+from hbmqtt.utils import read_yaml_config
+
+logger = logging.getLogger(__name__)
+
+
+def _gen_client_id():
+    import os
+    import socket
+
+    pid = os.getpid()
+    hostname = socket.gethostname()
+    return "hbmqtt_sub/%d-%s" % (pid, hostname)
+
+
+def _get_qos(arguments):
+    try:
+        return int(arguments["--qos"][0])
+    except:
+        return QOS_0
+
+
+def _get_extra_headers(arguments):
+    try:
+        return json.loads(arguments["--extra-headers"])
+    except:
+        return {}
+
+
+async def do_sub(client, arguments):
+
+    try:
+        await client.connect(
+            uri=arguments["--url"],
+            cleansession=arguments["--clean-session"],
+            cafile=arguments["--ca-file"],
+            capath=arguments["--ca-path"],
+            cadata=arguments["--ca-data"],
+            extra_headers=_get_extra_headers(arguments),
+        )
+        qos = _get_qos(arguments)
+        filters = []
+        for topic in arguments["-t"]:
+            filters.append((topic, qos))
+        await client.subscribe(filters)
+        if arguments["-n"]:
+            max_count = int(arguments["-n"])
+        else:
+            max_count = None
+        count = 0
+        while True:
+            if max_count and count >= max_count:
+                break
+            try:
+                message = await client.deliver_message()
+                count += 1
+                sys.stdout.buffer.write(message.publish_packet.data)
+                sys.stdout.write("\n")
+            except MQTTException:
+                logger.debug("Error reading packet")
+        await client.disconnect()
+    except KeyboardInterrupt:
+        await client.disconnect()
+    except ConnectException as ce:
+        logger.fatal("connection to '%s' failed: %r" % (arguments["--url"], ce))
+    except asyncio.CancelledError:
+        logger.fatal("Publish canceled due to previous error")
+
+
+def main(*args, **kwargs):
+    if sys.version_info[:2] < (3, 6):
+        logger.fatal("Error: Python 3.6+ is required")
+        sys.exit(-1)
+
+    arguments = docopt(__doc__, version=hbmqtt.__version__)
+    formatter = "[%(asctime)s] :: %(levelname)s - %(message)s"
+
+    if arguments["-d"]:
+        level = logging.DEBUG
+    else:
+        level = logging.INFO
+    logging.basicConfig(level=level, format=formatter)
+
+    config = None
+    if arguments["-c"]:
+        config = read_yaml_config(arguments["-c"])
+    else:
+        config = read_yaml_config(
+            os.path.join(
+                os.path.dirname(os.path.realpath(__file__)), "default_client.yaml"
+            )
+        )
+        logger.debug(
+            os.path.join(
+                os.path.dirname(os.path.realpath(__file__)), "default_client.yaml"
+            )
+        )
+        logger.debug("Using default configuration")
+    loop = asyncio.get_event_loop()
+
+    client_id = arguments.get("-i", None)
+    if not client_id:
+        client_id = _gen_client_id()
+
+    if arguments["-k"]:
+        config["keep_alive"] = int(arguments["-k"])
+
+    if (
+        arguments["--will-topic"]
+        and arguments["--will-message"]
+        and arguments["--will-qos"]
+    ):
+        config["will"] = dict()
+        config["will"]["topic"] = arguments["--will-topic"]
+        config["will"]["message"] = arguments["--will-message"].encode("utf-8")
+        config["will"]["qos"] = int(arguments["--will-qos"])
+        config["will"]["retain"] = arguments["--will-retain"]
+
+    client = MQTTClient(client_id=client_id, config=config, loop=loop)
+    loop.run_until_complete(do_sub(client, arguments))
+    loop.close()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/amqtt/session.py b/amqtt/session.py
new file mode 100644
index 00000000..be8580b2
--- /dev/null
+++ b/amqtt/session.py
@@ -0,0 +1,212 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import asyncio
+from transitions import Machine
+from asyncio import Queue
+from collections import OrderedDict
+from amqtt.mqtt.publish import PublishPacket
+from amqtt.errors import HBMQTTException
+
+OUTGOING = 0
+INCOMING = 1
+
+
+class ApplicationMessage:
+
+    """
+    ApplicationMessage and subclasses are used to store published message information flow. These objects can contain different information depending on the way they were created (incoming or outgoing) and the quality of service used between peers.
+    """
+
+    __slots__ = (
+        "packet_id",
+        "topic",
+        "qos",
+        "data",
+        "retain",
+        "publish_packet",
+        "puback_packet",
+        "pubrec_packet",
+        "pubrel_packet",
+        "pubcomp_packet",
+    )
+
+    def __init__(self, packet_id, topic, qos, data, retain):
+        self.packet_id = packet_id
+        """ Publish message `packet identifier <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718025>`_"""
+
+        self.topic = topic
+        """ Publish message topic"""
+
+        self.qos = qos
+        """ Publish message Quality of Service"""
+
+        self.data = data
+        """ Publish message payload data"""
+
+        self.retain = retain
+        """ Publish message retain flag"""
+
+        self.publish_packet = None
+        """ :class:`amqtt.mqtt.publish.PublishPacket` instance corresponding to the `PUBLISH <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037>`_ packet in the messages flow. ``None`` if the PUBLISH packet has not already been received or sent."""
+
+        self.puback_packet = None
+        """ :class:`amqtt.mqtt.puback.PubackPacket` instance corresponding to the `PUBACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718043>`_ packet in the messages flow. ``None`` if QoS != QOS_1 or if the PUBACK packet has not already been received or sent."""
+
+        self.pubrec_packet = None
+        """ :class:`amqtt.mqtt.puback.PubrecPacket` instance corresponding to the `PUBREC <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718048>`_ packet in the messages flow. ``None`` if QoS != QOS_2 or if the PUBREC packet has not already been received or sent."""
+
+        self.pubrel_packet = None
+        """ :class:`amqtt.mqtt.puback.PubrelPacket` instance corresponding to the `PUBREL <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718053>`_ packet in the messages flow. ``None`` if QoS != QOS_2 or if the PUBREL packet has not already been received or sent."""
+
+        self.pubcomp_packet = None
+        """ :class:`amqtt.mqtt.puback.PubrelPacket` instance corresponding to the `PUBCOMP <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718058>`_ packet in the messages flow. ``None`` if QoS != QOS_2 or if the PUBCOMP packet has not already been received or sent."""
+
+    def build_publish_packet(self, dup=False):
+        """
+            Build :class:`amqtt.mqtt.publish.PublishPacket` from attributes
+
+        :param dup: force dup flag
+        :return: :class:`amqtt.mqtt.publish.PublishPacket` built from ApplicationMessage instance attributes
+        """
+        return PublishPacket.build(
+            self.topic, self.data, self.packet_id, dup, self.qos, self.retain
+        )
+
+    def __eq__(self, other):
+        return self.packet_id == other.packet_id
+
+
+class IncomingApplicationMessage(ApplicationMessage):
+
+    """
+    Incoming :class:`~amqtt.session.ApplicationMessage`.
+    """
+
+    __slots__ = ("direction",)
+
+    def __init__(self, packet_id, topic, qos, data, retain):
+        super().__init__(packet_id, topic, qos, data, retain)
+        self.direction = INCOMING
+
+
+class OutgoingApplicationMessage(ApplicationMessage):
+
+    """
+    Outgoing :class:`~amqtt.session.ApplicationMessage`.
+    """
+
+    __slots__ = ("direction",)
+
+    def __init__(self, packet_id, topic, qos, data, retain):
+        super().__init__(packet_id, topic, qos, data, retain)
+        self.direction = OUTGOING
+
+
+class Session:
+    states = ["new", "connected", "disconnected"]
+
+    def __init__(self, loop=None):
+        self._init_states()
+        self.remote_address = None
+        self.remote_port = None
+        self.client_id = None
+        self.clean_session = None
+        self.will_flag = False
+        self.will_message = None
+        self.will_qos = None
+        self.will_retain = None
+        self.will_topic = None
+        self.keep_alive = 0
+        self.publish_retry_delay = 0
+        self.broker_uri = None
+        self.username = None
+        self.password = None
+        self.cafile = None
+        self.capath = None
+        self.cadata = None
+        self._packet_id = 0
+        self.parent = 0
+        if loop is not None:
+            self._loop = loop
+        else:
+            self._loop = asyncio.get_event_loop()
+
+        # Used to store outgoing ApplicationMessage while publish protocol flows
+        self.inflight_out = OrderedDict()
+
+        # Used to store incoming ApplicationMessage while publish protocol flows
+        self.inflight_in = OrderedDict()
+
+        # Stores messages retained for this session
+        self.retained_messages = Queue(loop=self._loop)
+
+        # Stores PUBLISH messages ID received in order and ready for application process
+        self.delivered_message_queue = Queue(loop=self._loop)
+
+    def _init_states(self):
+        self.transitions = Machine(states=Session.states, initial="new")
+        self.transitions.add_transition(
+            trigger="connect", source="new", dest="connected"
+        )
+        self.transitions.add_transition(
+            trigger="connect", source="disconnected", dest="connected"
+        )
+        self.transitions.add_transition(
+            trigger="disconnect", source="connected", dest="disconnected"
+        )
+        self.transitions.add_transition(
+            trigger="disconnect", source="new", dest="disconnected"
+        )
+        self.transitions.add_transition(
+            trigger="disconnect", source="disconnected", dest="disconnected"
+        )
+
+    @property
+    def next_packet_id(self):
+        self._packet_id += 1
+        if self._packet_id > 65535:
+            self._packet_id = 1
+        while (
+            self._packet_id in self.inflight_in or self._packet_id in self.inflight_out
+        ):
+            self._packet_id += 1
+            if self._packet_id > 65535:
+                raise HBMQTTException(
+                    "More than 65525 messages pending. No free packet ID"
+                )
+
+        return self._packet_id
+
+    @property
+    def inflight_in_count(self):
+        return len(self.inflight_in)
+
+    @property
+    def inflight_out_count(self):
+        return len(self.inflight_out)
+
+    @property
+    def retained_messages_count(self):
+        return self.retained_messages.qsize()
+
+    def __repr__(self):
+        return type(self).__name__ + "(clientId={0}, state={1})".format(
+            self.client_id, self.transitions.state
+        )
+
+    def __getstate__(self):
+        state = self.__dict__.copy()
+        # Remove the unpicklable entries.
+        # del state['transitions']
+        del state["retained_messages"]
+        del state["delivered_message_queue"]
+        return state
+
+    def __setstate(self, state):
+        self.__dict__.update(state)
+        self.retained_messages = Queue()
+        self.delivered_message_queue = Queue()
+
+    def __eq__(self, other):
+        return self.client_id == other.client_id
diff --git a/amqtt/utils.py b/amqtt/utils.py
new file mode 100644
index 00000000..07a595e2
--- /dev/null
+++ b/amqtt/utils.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2015 Nicolas JOUANIN
+#
+# See the file license.txt for copying permission.
+import logging
+import random
+
+import yaml
+
+
+logger = logging.getLogger(__name__)
+
+
+def format_client_message(session=None, address=None, port=None):
+    if session:
+        return "(client id=%s)" % session.client_id
+    elif address is not None and port is not None:
+        return "(client @=%s:%d)" % (address, port)
+    else:
+        return "(unknown client)"
+
+
+def gen_client_id() -> str:
+    """Generates random client ID"""
+    gen_id = "amqtt/"
+
+    for i in range(7, 23):
+        gen_id += chr(random.randint(0, 74) + 48)
+    return gen_id
+
+
+def read_yaml_config(config_file):
+    config = None
+    try:
+        with open(config_file, "r") as stream:
+            config = (
+                yaml.full_load(stream)
+                if hasattr(yaml, "full_load")
+                else yaml.load(stream)
+            )
+    except yaml.YAMLError as exc:
+        logger.error("Invalid config_file %s: %s" % (config_file, exc))
+    return config
diff --git a/amqtt/version.py b/amqtt/version.py
new file mode 100644
index 00000000..6d2241a5
--- /dev/null
+++ b/amqtt/version.py
@@ -0,0 +1,37 @@
+# See the file license.txt for copying permission.
+import datetime
+import os
+import subprocess
+import warnings
+
+import amqtt
+
+
+def get_version():
+    warnings.warn(
+        "amqtt.version.get_version() is deprecated, use amqtt.__version__ instead"
+    )
+    return amqtt.__version__
+
+
+def get_git_changeset():
+    """Returns a numeric identifier of the latest git changeset.
+    The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format.
+    This value isn't guaranteed to be unique, but collisions are very unlikely,
+    so it's sufficient for generating the development version numbers.
+    """
+    repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    git_log = subprocess.Popen(
+        "git log --pretty=format:%ct --quiet -1 HEAD",
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        shell=True,
+        cwd=repo_dir,
+        universal_newlines=True,
+    )
+    timestamp = git_log.communicate()[0]
+    try:
+        timestamp = datetime.datetime.utcfromtimestamp(int(timestamp))
+    except ValueError:
+        return None
+    return timestamp.strftime("%Y%m%d%H%M%S")
diff --git a/docs/references/broker.rst b/docs/references/broker.rst
index b7aa3d8a..9dfd228a 100644
--- a/docs/references/broker.rst
+++ b/docs/references/broker.rst
@@ -96,14 +96,14 @@ The ``listeners`` section allows to define network listeners which must be start
 
 The ``auth`` section setup authentication behaviour:
 
-* ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``hbmqtt.broker.plugins`` `entry point <https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins>`_.
+* ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``amqtt.broker.plugins`` `entry point <https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins>`_.
 * ``allow-anonymous`` : used by the internal :class:`hbmqtt.plugins.authentication.AnonymousAuthPlugin` plugin. This parameter enables (``on``) or disable anonymous connection, ie. connection without username.
 * ``password-file`` : used by the internal :class:`hbmqtt.plugins.authentication.FileAuthPlugin` plugin. This parameter gives to path of the password file to load for authenticating users.
 
 The ``topic-check`` section setup access control policies for publishing and subscribing to topics:
 
 * ``enabled``: set to true if you want to impose an access control policy. Otherwise, set it to false.
-* ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``hbmqtt.broker.plugins`` `entry point <https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins>`_.
+* ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``amqtt.broker.plugins`` `entry point <https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins>`_.
 * additional parameters: depending on the plugin used for access control, additional parameters should be added.
     * In case of ``topic_acl`` plugin, the Access Control List (ACL) must be defined in the parameter ``acl``.
         * For each username, a list with the allowed topics must be defined.
diff --git a/gen_transition_code.py b/gen_transition_code.py
new file mode 100644
index 00000000..f5340471
--- /dev/null
+++ b/gen_transition_code.py
@@ -0,0 +1,32 @@
+import pathlib
+
+
+template = """import warnings
+from {module_name} import *
+
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
+"""
+
+
+def main():
+    src = pathlib.Path("amqtt")
+    dst = pathlib.Path("hbmqtt")
+
+    for py_file in src.glob("**/*.py"):
+
+        file_path = py_file.parent.relative_to(src)
+        dst_file = dst / file_path / py_file.name
+        module_name = "amqtt"
+        sub_modue = str(file_path).replace("/", ".").strip(".")
+        if sub_modue:
+            module_name += "." + sub_modue
+
+        if py_file.name != "__init__.py":
+            module_name += "." + py_file.name[:-3]
+
+        dst_file.parent.mkdir(parents=True, exist_ok=True)
+        dst_file.write_text(template.format(module_name=module_name))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/hbmqtt/__init__.py b/hbmqtt/__init__.py
index e4e0261e..7db2425f 100644
--- a/hbmqtt/__init__.py
+++ b/hbmqtt/__init__.py
@@ -1,3 +1,4 @@
-# See the file license.txt for copying permission.
+import warnings
+from amqtt import *
 
-__version__ = "0.10.0-alpha.1"
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/adapters.py b/hbmqtt/adapters.py
index bfc10dc5..f80f3a30 100644
--- a/hbmqtt/adapters.py
+++ b/hbmqtt/adapters.py
@@ -1,227 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.adapters import *
 
-import io
-from websockets.protocol import WebSocketCommonProtocol
-from websockets.exceptions import ConnectionClosed
-from asyncio import StreamReader, StreamWriter
-import logging
-
-
-class ReaderAdapter:
-    """
-    Base class for all network protocol reader adapter.
-
-    Reader adapters are used to adapt read operations on the network depending on the
-    protocol used
-    """
-
-    async def read(self, n=-1) -> bytes:
-        """
-        Read up to n bytes. If n is not provided, or set to -1, read until EOF and
-        return all read bytes. If the EOF was received and the internal buffer is
-        empty, return an empty bytes object. :return: packet read as bytes data
-        """
-
-    def feed_eof(self):
-        """
-        Acknowledge EOF
-        """
-
-
-class WriterAdapter:
-    """
-    Base class for all network protocol writer adapter.
-
-    Writer adapters are used to adapt write operations on the network depending on
-    the protocol used
-    """
-
-    def write(self, data):
-        """
-        write some data to the protocol layer
-        """
-
-    async def drain(self):
-        """
-        Let the write buffer of the underlying transport a chance to be flushed.
-        """
-
-    def get_peer_info(self):
-        """
-        Return peer socket info (remote address and remote port as tuple
-        """
-
-    async def close(self):
-        """
-        Close the protocol connection
-        """
-
-
-class WebSocketsReader(ReaderAdapter):
-    """
-    WebSockets API reader adapter
-    This adapter relies on WebSocketCommonProtocol to read from a WebSocket.
-    """
-
-    def __init__(self, protocol: WebSocketCommonProtocol):
-        self._protocol = protocol
-        self._stream = io.BytesIO(b"")
-
-    async def read(self, n=-1) -> bytes:
-        await self._feed_buffer(n)
-        data = self._stream.read(n)
-        return data
-
-    async def _feed_buffer(self, n=1):
-        """
-        Feed the data buffer by reading a Websocket message.
-        :param n: if given, feed buffer until it contains at least n bytes
-        """
-        buffer = bytearray(self._stream.read())
-        while len(buffer) < n:
-            try:
-                message = await self._protocol.recv()
-            except ConnectionClosed:
-                message = None
-            if message is None:
-                break
-            if not isinstance(message, bytes):
-                raise TypeError("message must be bytes")
-            buffer.extend(message)
-        self._stream = io.BytesIO(buffer)
-
-
-class WebSocketsWriter(WriterAdapter):
-    """
-    WebSockets API writer adapter
-    This adapter relies on WebSocketCommonProtocol to read from a WebSocket.
-    """
-
-    def __init__(self, protocol: WebSocketCommonProtocol):
-        self._protocol = protocol
-        self._stream = io.BytesIO(b"")
-
-    def write(self, data):
-        """
-        write some data to the protocol layer
-        """
-        self._stream.write(data)
-
-    async def drain(self):
-        """
-        Let the write buffer of the underlying transport a chance to be flushed.
-        """
-        data = self._stream.getvalue()
-        if len(data):
-            await self._protocol.send(data)
-        self._stream = io.BytesIO(b"")
-
-    def get_peer_info(self):
-        # remote_address can be either a 4-tuple or 2-tuple depending on whether
-        # it is an IPv6 or IPv4 address, so we take their shared (host, port)
-        # prefix here to present a uniform return value.
-        return self._protocol.remote_address[:2]
-
-    async def close(self):
-        await self._protocol.close()
-
-
-class StreamReaderAdapter(ReaderAdapter):
-    """
-    Asyncio Streams API protocol adapter
-    This adapter relies on StreamReader to read from a TCP socket.
-    Because API is very close, this class is trivial
-    """
-
-    def __init__(self, reader: StreamReader):
-        self._reader = reader
-
-    async def read(self, n=-1) -> bytes:
-        if n == -1:
-            data = await self._reader.read(n)
-        else:
-            data = await self._reader.readexactly(n)
-        return data
-
-    def feed_eof(self):
-        return self._reader.feed_eof()
-
-
-class StreamWriterAdapter(WriterAdapter):
-    """
-    Asyncio Streams API protocol adapter
-    This adapter relies on StreamWriter to write to a TCP socket.
-    Because API is very close, this class is trivial
-    """
-
-    def __init__(self, writer: StreamWriter):
-        self.logger = logging.getLogger(__name__)
-        self._writer = writer
-        self.is_closed = False  # StreamWriter has no test for closed...we use our own
-
-    def write(self, data):
-        if not self.is_closed:
-            self._writer.write(data)
-
-    async def drain(self):
-        if not self.is_closed:
-            await self._writer.drain()
-
-    def get_peer_info(self):
-        extra_info = self._writer.get_extra_info("peername")
-        return extra_info[0], extra_info[1]
-
-    async def close(self):
-        if not self.is_closed:
-            self.is_closed = True  # we first mark this closed so yields below don't cause races with waiting writes
-            await self._writer.drain()
-            if self._writer.can_write_eof():
-                self._writer.write_eof()
-            self._writer.close()
-            try:
-                await self._writer.wait_closed()  # py37+
-            except AttributeError:
-                pass
-
-
-class BufferReader(ReaderAdapter):
-    """
-    Byte Buffer reader adapter
-    This adapter simply adapt reading a byte buffer.
-    """
-
-    def __init__(self, buffer: bytes):
-        self._stream = io.BytesIO(buffer)
-
-    async def read(self, n=-1) -> bytes:
-        return self._stream.read(n)
-
-
-class BufferWriter(WriterAdapter):
-    """
-    ByteBuffer writer adapter
-    This adapter simply adapt writing to a byte buffer
-    """
-
-    def __init__(self, buffer=b""):
-        self._stream = io.BytesIO(buffer)
-
-    def write(self, data):
-        """
-        write some data to the protocol layer
-        """
-        self._stream.write(data)
-
-    async def drain(self):
-        pass
-
-    def get_buffer(self):
-        return self._stream.getvalue()
-
-    def get_peer_info(self):
-        return "BufferWriter", 0
-
-    async def close(self):
-        self._stream.close()
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/broker.py b/hbmqtt/broker.py
index 61d8de8b..470d6470 100644
--- a/hbmqtt/broker.py
+++ b/hbmqtt/broker.py
@@ -1,1019 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from typing import Optional
-import logging
-import ssl
-import websockets
-import asyncio
-import re
-from asyncio import CancelledError
-from collections import deque
+import warnings
+from amqtt.broker import *
 
-from functools import partial
-from transitions import Machine, MachineError
-from hbmqtt.session import Session
-from hbmqtt.mqtt.protocol.broker_handler import BrokerProtocolHandler
-from hbmqtt.errors import HBMQTTException, MQTTException
-from hbmqtt.utils import format_client_message, gen_client_id
-from hbmqtt.adapters import (
-    StreamReaderAdapter,
-    StreamWriterAdapter,
-    ReaderAdapter,
-    WriterAdapter,
-    WebSocketsReader,
-    WebSocketsWriter,
-)
-from .plugins.manager import PluginManager, BaseContext
-
-
-_defaults = {
-    "timeout-disconnect-delay": 2,
-    "auth": {"allow-anonymous": True, "password-file": None},
-}
-
-EVENT_BROKER_PRE_START = "broker_pre_start"
-EVENT_BROKER_POST_START = "broker_post_start"
-EVENT_BROKER_PRE_SHUTDOWN = "broker_pre_shutdown"
-EVENT_BROKER_POST_SHUTDOWN = "broker_post_shutdown"
-EVENT_BROKER_CLIENT_CONNECTED = "broker_client_connected"
-EVENT_BROKER_CLIENT_DISCONNECTED = "broker_client_disconnected"
-EVENT_BROKER_CLIENT_SUBSCRIBED = "broker_client_subscribed"
-EVENT_BROKER_CLIENT_UNSUBSCRIBED = "broker_client_unsubscribed"
-EVENT_BROKER_MESSAGE_RECEIVED = "broker_message_received"
-
-
-class BrokerException(Exception):
-    pass
-
-
-class RetainedApplicationMessage:
-
-    __slots__ = ("source_session", "topic", "data", "qos")
-
-    def __init__(self, source_session, topic, data, qos=None):
-        self.source_session = source_session
-        self.topic = topic
-        self.data = data
-        self.qos = qos
-
-
-class Server:
-    def __init__(self, listener_name, server_instance, max_connections=-1, loop=None):
-        self.logger = logging.getLogger(__name__)
-        self.instance = server_instance
-        self.conn_count = 0
-        self.listener_name = listener_name
-        if loop is not None:
-            self._loop = loop
-        else:
-            self._loop = asyncio.get_event_loop()
-
-        self.max_connections = max_connections
-        if self.max_connections > 0:
-            self.semaphore = asyncio.Semaphore(self.max_connections, loop=self._loop)
-        else:
-            self.semaphore = None
-
-    async def acquire_connection(self):
-        if self.semaphore:
-            await self.semaphore.acquire()
-        self.conn_count += 1
-        if self.max_connections > 0:
-            self.logger.info(
-                "Listener '%s': %d/%d connections acquired"
-                % (self.listener_name, self.conn_count, self.max_connections)
-            )
-        else:
-            self.logger.info(
-                "Listener '%s': %d connections acquired"
-                % (self.listener_name, self.conn_count)
-            )
-
-    def release_connection(self) -> None:
-        if self.semaphore:
-            self.semaphore.release()
-        self.conn_count -= 1
-        if self.max_connections > 0:
-            self.logger.info(
-                "Listener '%s': %d/%d connections acquired"
-                % (self.listener_name, self.conn_count, self.max_connections)
-            )
-        else:
-            self.logger.info(
-                "Listener '%s': %d connections acquired"
-                % (self.listener_name, self.conn_count)
-            )
-
-    async def close_instance(self):
-        if self.instance:
-            self.instance.close()
-            await self.instance.wait_closed()
-
-
-class BrokerContext(BaseContext):
-    """
-    BrokerContext is used as the context passed to plugins interacting with the broker.
-    It act as an adapter to broker services from plugins developed for HBMQTT broker
-    """
-
-    def __init__(self, broker: "Broker") -> None:
-        super().__init__()
-        self.config = None
-        self._broker_instance = broker
-
-    async def broadcast_message(self, topic, data, qos=None):
-        await self._broker_instance.internal_message_broadcast(topic, data, qos)
-
-    def retain_message(self, topic_name, data, qos=None):
-        self._broker_instance.retain_message(None, topic_name, data, qos)
-
-    @property
-    def sessions(self):
-        for session in self._broker_instance._sessions.values():
-            yield session[0]
-
-    @property
-    def retained_messages(self):
-        return self._broker_instance._retained_messages
-
-    @property
-    def subscriptions(self):
-        return self._broker_instance._subscriptions
-
-
-class Broker:
-    """
-    MQTT 3.1.1 compliant broker implementation
-
-    :param config: Example Yaml config
-    :param loop: asyncio loop to use. Defaults to ``asyncio.get_event_loop()``.
-    :param plugin_namespace: Plugin namespace to use when loading plugin entry_points. Defaults to ``hbmqtt.broker.plugins``
-
-    """
-
-    states = [
-        "new",
-        "starting",
-        "started",
-        "not_started",
-        "stopping",
-        "stopped",
-        "not_stopped",
-        "stopped",
-    ]
-
-    def __init__(self, config=None, loop=None, plugin_namespace=None):
-        self.logger = logging.getLogger(__name__)
-        self.config = _defaults
-        if config is not None:
-            self.config.update(config)
-        self._build_listeners_config(self.config)
-
-        if loop is not None:
-            self._loop = loop
-        else:
-            self._loop = asyncio.get_event_loop()
-
-        self._servers = dict()
-        self._init_states()
-        self._sessions = dict()
-        self._subscriptions = dict()
-        self._retained_messages = dict()
-        self._broadcast_queue = asyncio.Queue(loop=self._loop)
-
-        self._broadcast_task = None
-
-        # Init plugins manager
-        context = BrokerContext(self)
-        context.config = self.config
-        if plugin_namespace:
-            namespace = plugin_namespace
-        else:
-            namespace = "hbmqtt.broker.plugins"
-        self.plugins_manager = PluginManager(namespace, context, self._loop)
-
-    def _build_listeners_config(self, broker_config):
-        self.listeners_config = dict()
-        try:
-            listeners_config = broker_config["listeners"]
-            defaults = listeners_config["default"]
-            for listener in listeners_config:
-                config = dict(defaults)
-                config.update(listeners_config[listener])
-                self.listeners_config[listener] = config
-        except KeyError as ke:
-            raise BrokerException("Listener config not found invalid: %s" % ke)
-
-    def _init_states(self) -> None:
-        self.transitions = Machine(states=Broker.states, initial="new")
-        self.transitions.add_transition(trigger="start", source="new", dest="starting")
-        self.transitions.add_transition(
-            trigger="starting_fail", source="starting", dest="not_started"
-        )
-        self.transitions.add_transition(
-            trigger="starting_success", source="starting", dest="started"
-        )
-        self.transitions.add_transition(
-            trigger="shutdown", source="started", dest="stopping"
-        )
-        self.transitions.add_transition(
-            trigger="stopping_success", source="stopping", dest="stopped"
-        )
-        self.transitions.add_transition(
-            trigger="stopping_failure", source="stopping", dest="not_stopped"
-        )
-        self.transitions.add_transition(
-            trigger="start", source="stopped", dest="starting"
-        )
-
-    async def start(self) -> None:
-        """
-        Start the broker to serve with the given configuration
-
-        Start method opens network sockets and will start listening for incoming connections.
-
-        This method is a *coroutine*.
-        """
-        try:
-            self._sessions = dict()
-            self._subscriptions = dict()
-            self._retained_messages = dict()
-            self.transitions.start()
-            self.logger.debug("Broker starting")
-        except (MachineError, ValueError) as exc:
-            # Backwards compat: MachineError is raised by transitions < 0.5.0.
-            self.logger.warning(
-                "[WARN-0001] Invalid method call at this moment: %s" % exc
-            )
-            raise BrokerException("Broker instance can't be started: %s" % exc)
-
-        await self.plugins_manager.fire_event(EVENT_BROKER_PRE_START)
-        try:
-            # Start network listeners
-            for listener_name in self.listeners_config:
-                listener = self.listeners_config[listener_name]
-
-                if "bind" not in listener:
-                    self.logger.debug(
-                        "Listener configuration '%s' is not bound" % listener_name
-                    )
-                    continue
-
-                max_connections = listener.get("max_connections", -1)
-
-                # SSL Context
-                sc = None
-
-                # accept string "on" / "off" or boolean
-                ssl_active = listener.get("ssl", False)
-                if isinstance(ssl_active, str):
-                    ssl_active = ssl_active.upper() == "ON"
-
-                if ssl_active:
-                    try:
-                        sc = ssl.create_default_context(
-                            ssl.Purpose.CLIENT_AUTH,
-                            cafile=listener.get("cafile"),
-                            capath=listener.get("capath"),
-                            cadata=listener.get("cadata"),
-                        )
-                        sc.load_cert_chain(listener["certfile"], listener["keyfile"])
-                        sc.verify_mode = ssl.CERT_OPTIONAL
-                    except KeyError as ke:
-                        raise BrokerException(
-                            "'certfile' or 'keyfile' configuration parameter missing: %s"
-                            % ke
-                        )
-                    except FileNotFoundError as fnfe:
-                        raise BrokerException(
-                            "Can't read cert files '%s' or '%s' : %s"
-                            % (listener["certfile"], listener["keyfile"], fnfe)
-                        )
-
-                address, s_port = listener["bind"].split(":")
-                port = 0
-                try:
-                    port = int(s_port)
-                except ValueError:
-                    raise BrokerException(
-                        "Invalid port value in bind value: %s" % listener["bind"]
-                    )
-
-                if listener["type"] == "tcp":
-                    cb_partial = partial(
-                        self.stream_connected, listener_name=listener_name
-                    )
-                    instance = await asyncio.start_server(
-                        cb_partial,
-                        address,
-                        port,
-                        reuse_address=True,
-                        ssl=sc,
-                        loop=self._loop,
-                    )
-                    self._servers[listener_name] = Server(
-                        listener_name, instance, max_connections, self._loop
-                    )
-                elif listener["type"] == "ws":
-                    cb_partial = partial(self.ws_connected, listener_name=listener_name)
-                    instance = await websockets.serve(
-                        cb_partial,
-                        address,
-                        port,
-                        ssl=sc,
-                        loop=self._loop,
-                        subprotocols=["mqtt"],
-                    )
-                    self._servers[listener_name] = Server(
-                        listener_name, instance, max_connections, self._loop
-                    )
-
-                self.logger.info(
-                    "Listener '%s' bind to %s (max_connections=%d)"
-                    % (listener_name, listener["bind"], max_connections)
-                )
-
-            self.transitions.starting_success()
-            await self.plugins_manager.fire_event(EVENT_BROKER_POST_START)
-
-            # Start broadcast loop
-            self._broadcast_task = asyncio.ensure_future(
-                self._broadcast_loop(), loop=self._loop
-            )
-
-            self.logger.debug("Broker started")
-        except Exception as e:
-            self.logger.error("Broker startup failed: %s" % e)
-            self.transitions.starting_fail()
-            raise BrokerException("Broker instance can't be started: %s" % e)
-
-    async def shutdown(self):
-        """
-        Stop broker instance.
-
-        Closes all connected session, stop listening on network socket and free resources.
-        """
-        try:
-            self._sessions = dict()
-            self._subscriptions = dict()
-            self._retained_messages = dict()
-            self.transitions.shutdown()
-        except (MachineError, ValueError) as exc:
-            # Backwards compat: MachineError is raised by transitions < 0.5.0.
-            self.logger.debug("Invalid method call at this moment: %s" % exc)
-            raise BrokerException("Broker instance can't be stopped: %s" % exc)
-
-        # Fire broker_shutdown event to plugins
-        await self.plugins_manager.fire_event(EVENT_BROKER_PRE_SHUTDOWN)
-
-        # Stop broadcast loop
-        if self._broadcast_task:
-            self._broadcast_task.cancel()
-        if self._broadcast_queue.qsize() > 0:
-            self.logger.warning(
-                "%d messages not broadcasted" % self._broadcast_queue.qsize()
-            )
-
-        for listener_name in self._servers:
-            server = self._servers[listener_name]
-            await server.close_instance()
-        self.logger.debug("Broker closing")
-        self.logger.info("Broker closed")
-        await self.plugins_manager.fire_event(EVENT_BROKER_POST_SHUTDOWN)
-        self.transitions.stopping_success()
-
-    async def internal_message_broadcast(self, topic, data, qos=None):
-        return await self._broadcast_message(None, topic, data)
-
-    async def ws_connected(self, websocket, uri, listener_name):
-        await self.client_connected(
-            listener_name, WebSocketsReader(websocket), WebSocketsWriter(websocket)
-        )
-
-    async def stream_connected(self, reader, writer, listener_name):
-        await self.client_connected(
-            listener_name, StreamReaderAdapter(reader), StreamWriterAdapter(writer)
-        )
-
-    async def client_connected(
-        self, listener_name, reader: ReaderAdapter, writer: WriterAdapter
-    ):
-        # Wait for connection available on listener
-        server = self._servers.get(listener_name, None)
-        if not server:
-            raise BrokerException("Invalid listener name '%s'" % listener_name)
-        await server.acquire_connection()
-
-        remote_address, remote_port = writer.get_peer_info()
-        self.logger.info(
-            "Connection from %s:%d on listener '%s'"
-            % (remote_address, remote_port, listener_name)
-        )
-
-        # Wait for first packet and expect a CONNECT
-        try:
-            handler, client_session = await BrokerProtocolHandler.init_from_connect(
-                reader, writer, self.plugins_manager, loop=self._loop
-            )
-        except HBMQTTException as exc:
-            self.logger.warning(
-                "[MQTT-3.1.0-1] %s: Can't read first packet an CONNECT: %s"
-                % (format_client_message(address=remote_address, port=remote_port), exc)
-            )
-            # await writer.close()
-            self.logger.debug("Connection closed")
-            return
-        except MQTTException as me:
-            self.logger.error(
-                "Invalid connection from %s : %s"
-                % (format_client_message(address=remote_address, port=remote_port), me)
-            )
-            await writer.close()
-            self.logger.debug("Connection closed")
-            return
-
-        if client_session.clean_session:
-            # Delete existing session and create a new one
-            if client_session.client_id is not None and client_session.client_id != "":
-                self.delete_session(client_session.client_id)
-            else:
-                client_session.client_id = gen_client_id()
-            client_session.parent = 0
-        else:
-            # Get session from cache
-            if client_session.client_id in self._sessions:
-                self.logger.debug(
-                    "Found old session %s"
-                    % repr(self._sessions[client_session.client_id])
-                )
-                (client_session, h) = self._sessions[client_session.client_id]
-                client_session.parent = 1
-            else:
-                client_session.parent = 0
-        if client_session.keep_alive > 0:
-            client_session.keep_alive += self.config["timeout-disconnect-delay"]
-        self.logger.debug("Keep-alive timeout=%d" % client_session.keep_alive)
-
-        handler.attach(client_session, reader, writer)
-        self._sessions[client_session.client_id] = (client_session, handler)
-
-        authenticated = await self.authenticate(
-            client_session, self.listeners_config[listener_name]
-        )
-        if not authenticated:
-            await writer.close()
-            server.release_connection()  # Delete client from connections list
-            return
-
-        while True:
-            try:
-                client_session.transitions.connect()
-                break
-            except (MachineError, ValueError):
-                # Backwards compat: MachineError is raised by transitions < 0.5.0.
-                self.logger.warning(
-                    "Client %s is reconnecting too quickly, make it wait"
-                    % client_session.client_id
-                )
-                # Wait a bit may be client is reconnecting too fast
-                await asyncio.sleep(1, loop=self._loop)
-        await handler.mqtt_connack_authorize(authenticated)
-
-        await self.plugins_manager.fire_event(
-            EVENT_BROKER_CLIENT_CONNECTED, client_id=client_session.client_id
-        )
-
-        self.logger.debug("%s Start messages handling" % client_session.client_id)
-        await handler.start()
-        self.logger.debug(
-            "Retained messages queue size: %d"
-            % client_session.retained_messages.qsize()
-        )
-        await self.publish_session_retained_messages(client_session)
-
-        # Init and start loop for handling client messages (publish, subscribe/unsubscribe, disconnect)
-        disconnect_waiter = asyncio.ensure_future(
-            handler.wait_disconnect(), loop=self._loop
-        )
-        subscribe_waiter = asyncio.ensure_future(
-            handler.get_next_pending_subscription(), loop=self._loop
-        )
-        unsubscribe_waiter = asyncio.ensure_future(
-            handler.get_next_pending_unsubscription(), loop=self._loop
-        )
-        wait_deliver = asyncio.ensure_future(
-            handler.mqtt_deliver_next_message(), loop=self._loop
-        )
-        connected = True
-        while connected:
-            try:
-                done, pending = await asyncio.wait(
-                    [
-                        disconnect_waiter,
-                        subscribe_waiter,
-                        unsubscribe_waiter,
-                        wait_deliver,
-                    ],
-                    return_when=asyncio.FIRST_COMPLETED,
-                    loop=self._loop,
-                )
-                if disconnect_waiter in done:
-                    result = disconnect_waiter.result()
-                    self.logger.debug(
-                        "%s Result from wait_diconnect: %s"
-                        % (client_session.client_id, result)
-                    )
-                    if result is None:
-                        self.logger.debug("Will flag: %s" % client_session.will_flag)
-                        # Connection closed anormally, send will message
-                        if client_session.will_flag:
-                            self.logger.debug(
-                                "Client %s disconnected abnormally, sending will message"
-                                % format_client_message(client_session)
-                            )
-                            await self._broadcast_message(
-                                client_session,
-                                client_session.will_topic,
-                                client_session.will_message,
-                                client_session.will_qos,
-                            )
-                            if client_session.will_retain:
-                                self.retain_message(
-                                    client_session,
-                                    client_session.will_topic,
-                                    client_session.will_message,
-                                    client_session.will_qos,
-                                )
-                    self.logger.debug(
-                        "%s Disconnecting session" % client_session.client_id
-                    )
-                    await self._stop_handler(handler)
-                    client_session.transitions.disconnect()
-                    await self.plugins_manager.fire_event(
-                        EVENT_BROKER_CLIENT_DISCONNECTED,
-                        client_id=client_session.client_id,
-                    )
-                    connected = False
-                if unsubscribe_waiter in done:
-                    self.logger.debug(
-                        "%s handling unsubscription" % client_session.client_id
-                    )
-                    unsubscription = unsubscribe_waiter.result()
-                    for topic in unsubscription["topics"]:
-                        self._del_subscription(topic, client_session)
-                        await self.plugins_manager.fire_event(
-                            EVENT_BROKER_CLIENT_UNSUBSCRIBED,
-                            client_id=client_session.client_id,
-                            topic=topic,
-                        )
-                    await handler.mqtt_acknowledge_unsubscription(
-                        unsubscription["packet_id"]
-                    )
-                    unsubscribe_waiter = asyncio.Task(
-                        handler.get_next_pending_unsubscription(), loop=self._loop
-                    )
-                if subscribe_waiter in done:
-                    self.logger.debug(
-                        "%s handling subscription" % client_session.client_id
-                    )
-                    subscriptions = subscribe_waiter.result()
-                    return_codes = []
-                    for subscription in subscriptions["topics"]:
-                        result = await self.add_subscription(
-                            subscription, client_session
-                        )
-                        return_codes.append(result)
-                    await handler.mqtt_acknowledge_subscription(
-                        subscriptions["packet_id"], return_codes
-                    )
-                    for index, subscription in enumerate(subscriptions["topics"]):
-                        if return_codes[index] != 0x80:
-                            await self.plugins_manager.fire_event(
-                                EVENT_BROKER_CLIENT_SUBSCRIBED,
-                                client_id=client_session.client_id,
-                                topic=subscription[0],
-                                qos=subscription[1],
-                            )
-                            await self.publish_retained_messages_for_subscription(
-                                subscription, client_session
-                            )
-                    subscribe_waiter = asyncio.Task(
-                        handler.get_next_pending_subscription(), loop=self._loop
-                    )
-                    self.logger.debug(repr(self._subscriptions))
-                if wait_deliver in done:
-                    if self.logger.isEnabledFor(logging.DEBUG):
-                        self.logger.debug(
-                            "%s handling message delivery" % client_session.client_id
-                        )
-                    app_message = wait_deliver.result()
-                    if not app_message.topic:
-                        self.logger.warning(
-                            "[MQTT-4.7.3-1] - %s invalid TOPIC sent in PUBLISH message, closing connection"
-                            % client_session.client_id
-                        )
-                        break
-                    if "#" in app_message.topic or "+" in app_message.topic:
-                        self.logger.warning(
-                            "[MQTT-3.3.2-2] - %s invalid TOPIC sent in PUBLISH message, closing connection"
-                            % client_session.client_id
-                        )
-                        break
-                    await self.plugins_manager.fire_event(
-                        EVENT_BROKER_MESSAGE_RECEIVED,
-                        client_id=client_session.client_id,
-                        message=app_message,
-                    )
-                    await self._broadcast_message(
-                        client_session, app_message.topic, app_message.data
-                    )
-                    if app_message.publish_packet.retain_flag:
-                        self.retain_message(
-                            client_session,
-                            app_message.topic,
-                            app_message.data,
-                            app_message.qos,
-                        )
-                    wait_deliver = asyncio.Task(
-                        handler.mqtt_deliver_next_message(), loop=self._loop
-                    )
-            except asyncio.CancelledError:
-                self.logger.debug("Client loop cancelled")
-                break
-        disconnect_waiter.cancel()
-        subscribe_waiter.cancel()
-        unsubscribe_waiter.cancel()
-        wait_deliver.cancel()
-
-        self.logger.debug("%s Client disconnected" % client_session.client_id)
-        server.release_connection()
-
-    def _init_handler(self, session, reader, writer):
-        """
-        Create a BrokerProtocolHandler and attach to a session
-        :return:
-        """
-        handler = BrokerProtocolHandler(self.plugins_manager, self._loop)
-        handler.attach(session, reader, writer)
-        return handler
-
-    async def _stop_handler(self, handler):
-        """
-        Stop a running handler and detach if from the session
-        :param handler:
-        :return:
-        """
-        try:
-            await handler.stop()
-        except Exception as e:
-            self.logger.error(e)
-
-    async def authenticate(self, session: Session, listener):
-        """
-        This method call the authenticate method on registered plugins to test user authentication.
-        User is considered authenticated if all plugins called returns True.
-        Plugins authenticate() method are supposed to return :
-         - True if user is authentication succeed
-         - False if user authentication fails
-         - None if authentication can't be achieved (then plugin result is then ignored)
-        :param session:
-        :param listener:
-        :return:
-        """
-        auth_plugins = None
-        auth_config = self.config.get("auth", None)
-        if auth_config:
-            auth_plugins = auth_config.get("plugins", None)
-        returns = await self.plugins_manager.map_plugin_coro(
-            "authenticate", session=session, filter_plugins=auth_plugins
-        )
-        auth_result = True
-        if returns:
-            for plugin in returns:
-                res = returns[plugin]
-                if res is False:
-                    auth_result = False
-                    self.logger.debug(
-                        "Authentication failed due to '%s' plugin result: %s"
-                        % (plugin.name, res)
-                    )
-                else:
-                    self.logger.debug("'%s' plugin result: %s" % (plugin.name, res))
-        # If all plugins returned True, authentication is success
-        return auth_result
-
-    async def topic_filtering(self, session: Session, topic):
-        """
-        This method call the topic_filtering method on registered plugins to check that the subscription is allowed.
-        User is considered allowed if all plugins called return True.
-        Plugins topic_filtering() method are supposed to return :
-         - True if MQTT client can be subscribed to the topic
-         - False if MQTT client is not allowed to subscribe to the topic
-         - None if topic filtering can't be achieved (then plugin result is then ignored)
-        :param session:
-        :param listener:
-        :param topic: Topic in which the client wants to subscribe
-        :return:
-        """
-        topic_plugins = None
-        topic_config = self.config.get("topic-check", None)
-        if topic_config and topic_config.get("enabled", False):
-            topic_plugins = topic_config.get("plugins", None)
-        returns = await self.plugins_manager.map_plugin_coro(
-            "topic_filtering",
-            session=session,
-            topic=topic,
-            filter_plugins=topic_plugins,
-        )
-        topic_result = True
-        if returns:
-            for plugin in returns:
-                res = returns[plugin]
-                if res is False:
-                    topic_result = False
-                    self.logger.debug(
-                        "Topic filtering failed due to '%s' plugin result: %s"
-                        % (plugin.name, res)
-                    )
-                else:
-                    self.logger.debug("'%s' plugin result: %s" % (plugin.name, res))
-        # If all plugins returned True, authentication is success
-        return topic_result
-
-    def retain_message(
-        self,
-        source_session: Session,
-        topic_name: str,
-        data: bytearray,
-        qos: Optional[int] = None,
-    ) -> None:
-        if data is not None and data != b"":
-            # If retained flag set, store the message for further subscriptions
-            self.logger.debug("Retaining message on topic %s" % topic_name)
-            retained_message = RetainedApplicationMessage(
-                source_session, topic_name, data, qos
-            )
-            self._retained_messages[topic_name] = retained_message
-        else:
-            # [MQTT-3.3.1-10]
-            if topic_name in self._retained_messages:
-                self.logger.debug("Clear retained messages for topic '%s'" % topic_name)
-                del self._retained_messages[topic_name]
-
-    async def add_subscription(self, subscription, session):
-        try:
-            a_filter = subscription[0]
-            if "#" in a_filter and not a_filter.endswith("#"):
-                # [MQTT-4.7.1-2] Wildcard character '#' is only allowed as last character in filter
-                return 0x80
-            if a_filter != "+":
-                if "+" in a_filter:
-                    if "/+" not in a_filter and "+/" not in a_filter:
-                        # [MQTT-4.7.1-3] + wildcard character must occupy entire level
-                        return 0x80
-            # Check if the client is authorised to connect to the topic
-            permitted = await self.topic_filtering(session, topic=a_filter)
-            if not permitted:
-                return 0x80
-            qos = subscription[1]
-            if "max-qos" in self.config and qos > self.config["max-qos"]:
-                qos = self.config["max-qos"]
-            if a_filter not in self._subscriptions:
-                self._subscriptions[a_filter] = []
-            already_subscribed = next(
-                (
-                    s
-                    for (s, qos) in self._subscriptions[a_filter]
-                    if s.client_id == session.client_id
-                ),
-                None,
-            )
-            if not already_subscribed:
-                self._subscriptions[a_filter].append((session, qos))
-            else:
-                self.logger.debug(
-                    "Client %s has already subscribed to %s"
-                    % (format_client_message(session=session), a_filter)
-                )
-            return qos
-        except KeyError:
-            return 0x80
-
-    def _del_subscription(self, a_filter: str, session: Session) -> int:
-        """
-        Delete a session subscription on a given topic
-        :param a_filter:
-        :param session:
-        :return:
-        """
-        deleted = 0
-        try:
-            subscriptions = self._subscriptions[a_filter]
-            for index, (sub_session, qos) in enumerate(subscriptions):
-                if sub_session.client_id == session.client_id:
-                    self.logger.debug(
-                        "Removing subscription on topic '%s' for client %s"
-                        % (a_filter, format_client_message(session=session))
-                    )
-                    subscriptions.pop(index)
-                    deleted += 1
-                    break
-        except KeyError:
-            # Unsubscribe topic not found in current subscribed topics
-            pass
-        finally:
-            return deleted
-
-    def _del_all_subscriptions(self, session: Session) -> None:
-        """
-        Delete all topic subscriptions for a given session
-        :param session:
-        :return:
-        """
-        filter_queue = deque()
-        for topic in self._subscriptions:
-            if self._del_subscription(topic, session):
-                filter_queue.append(topic)
-        for topic in filter_queue:
-            if not self._subscriptions[topic]:
-                del self._subscriptions[topic]
-
-    def matches(self, topic, a_filter):
-        if "#" not in a_filter and "+" not in a_filter:
-            # if filter doesn't contain wildcard, return exact match
-            return a_filter == topic
-        else:
-            # else use regex
-            match_pattern = re.compile(
-                re.escape(a_filter)
-                .replace("\\#", "?.*")
-                .replace("\\+", "[^/]*")
-                .lstrip("?")
-            )
-            return match_pattern.fullmatch(topic)
-
-    async def _broadcast_loop(self):
-        running_tasks = deque()
-        try:
-            while True:
-                while running_tasks and running_tasks[0].done():
-                    task = running_tasks.popleft()
-                    try:
-                        task.result()  # make asyncio happy and collect results
-                    except Exception:
-                        pass
-                broadcast = await self._broadcast_queue.get()
-                if self.logger.isEnabledFor(logging.DEBUG):
-                    self.logger.debug("broadcasting %r" % broadcast)
-                for k_filter in self._subscriptions:
-                    if broadcast["topic"].startswith("$") and (
-                        k_filter.startswith("+") or k_filter.startswith("#")
-                    ):
-                        self.logger.debug(
-                            "[MQTT-4.7.2-1] - ignoring brodcasting $ topic to subscriptions starting with + or #"
-                        )
-                    elif self.matches(broadcast["topic"], k_filter):
-                        subscriptions = self._subscriptions[k_filter]
-                        for (target_session, qos) in subscriptions:
-                            if "qos" in broadcast:
-                                qos = broadcast["qos"]
-                            if target_session.transitions.state == "connected":
-                                self.logger.debug(
-                                    "broadcasting application message from %s on topic '%s' to %s"
-                                    % (
-                                        format_client_message(
-                                            session=broadcast["session"]
-                                        ),
-                                        broadcast["topic"],
-                                        format_client_message(session=target_session),
-                                    )
-                                )
-                                handler = self._get_handler(target_session)
-                                task = asyncio.ensure_future(
-                                    handler.mqtt_publish(
-                                        broadcast["topic"],
-                                        broadcast["data"],
-                                        qos,
-                                        retain=False,
-                                    ),
-                                    loop=self._loop,
-                                )
-                                running_tasks.append(task)
-                            else:
-                                self.logger.debug(
-                                    "retaining application message from %s on topic '%s' to client '%s'"
-                                    % (
-                                        format_client_message(
-                                            session=broadcast["session"]
-                                        ),
-                                        broadcast["topic"],
-                                        format_client_message(session=target_session),
-                                    )
-                                )
-                                retained_message = RetainedApplicationMessage(
-                                    broadcast["session"],
-                                    broadcast["topic"],
-                                    broadcast["data"],
-                                    qos,
-                                )
-                                await target_session.retained_messages.put(
-                                    retained_message
-                                )
-                                if self.logger.isEnabledFor(logging.DEBUG):
-                                    self.logger.debug(
-                                        f"target_session.retained_messages={target_session.retained_messages.qsize()}"
-                                    )
-        except CancelledError:
-            # Wait until current broadcasting tasks end
-            if running_tasks:
-                await asyncio.wait(running_tasks, loop=self._loop)
-            raise  # reraise per CancelledError semantics
-
-    async def _broadcast_message(self, session, topic, data, force_qos=None):
-        broadcast = {"session": session, "topic": topic, "data": data}
-        if force_qos:
-            broadcast["qos"] = force_qos
-        await self._broadcast_queue.put(broadcast)
-
-    async def publish_session_retained_messages(self, session):
-        self.logger.debug(
-            "Publishing %d messages retained for session %s"
-            % (
-                session.retained_messages.qsize(),
-                format_client_message(session=session),
-            )
-        )
-        publish_tasks = []
-        handler = self._get_handler(session)
-        while not session.retained_messages.empty():
-            retained = await session.retained_messages.get()
-            publish_tasks.append(
-                asyncio.ensure_future(
-                    handler.mqtt_publish(
-                        retained.topic, retained.data, retained.qos, True
-                    ),
-                    loop=self._loop,
-                )
-            )
-        if publish_tasks:
-            await asyncio.wait(publish_tasks, loop=self._loop)
-
-    async def publish_retained_messages_for_subscription(self, subscription, session):
-        self.logger.debug(
-            "Begin broadcasting messages retained due to subscription on '%s' from %s"
-            % (subscription[0], format_client_message(session=session))
-        )
-        publish_tasks = []
-        handler = self._get_handler(session)
-        for d_topic in self._retained_messages:
-            self.logger.debug("matching : %s %s" % (d_topic, subscription[0]))
-            if self.matches(d_topic, subscription[0]):
-                self.logger.debug("%s and %s match" % (d_topic, subscription[0]))
-                retained = self._retained_messages[d_topic]
-                publish_tasks.append(
-                    asyncio.Task(
-                        handler.mqtt_publish(
-                            retained.topic, retained.data, subscription[1], True
-                        ),
-                        loop=self._loop,
-                    )
-                )
-        if publish_tasks:
-            await asyncio.wait(publish_tasks, loop=self._loop)
-        self.logger.debug(
-            "End broadcasting messages retained due to subscription on '%s' from %s"
-            % (subscription[0], format_client_message(session=session))
-        )
-
-    def delete_session(self, client_id: str) -> None:
-        """
-        Delete an existing session data, for example due to clean session set in CONNECT
-        :param client_id:
-        :return:
-        """
-        try:
-            session = self._sessions[client_id][0]
-        except KeyError:
-            session = None
-        if session is None:
-            self.logger.debug("Delete session : session %s doesn't exist" % client_id)
-            return
-
-        # Delete subscriptions
-        self.logger.debug("deleting session %s subscriptions" % repr(session))
-        self._del_all_subscriptions(session)
-
-        self.logger.debug(
-            "deleting existing session %s" % repr(self._sessions[client_id])
-        )
-        del self._sessions[client_id]
-
-    def _get_handler(self, session):
-        client_id = session.client_id
-        if client_id:
-            try:
-                return self._sessions[client_id][1]
-            except KeyError:
-                pass
-        return None
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/client.py b/hbmqtt/client.py
index 3f54bba4..091cf782 100644
--- a/hbmqtt/client.py
+++ b/hbmqtt/client.py
@@ -1,585 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.client import *
 
-import asyncio
-import logging
-import ssl
-import copy
-from urllib.parse import urlparse, urlunparse
-from functools import wraps
-
-from hbmqtt.session import Session
-from hbmqtt.mqtt.connack import CONNECTION_ACCEPTED
-from hbmqtt.mqtt.protocol.client_handler import ClientProtocolHandler
-from hbmqtt.adapters import (
-    StreamReaderAdapter,
-    StreamWriterAdapter,
-    WebSocketsReader,
-    WebSocketsWriter,
-)
-from hbmqtt.plugins.manager import PluginManager, BaseContext
-from hbmqtt.mqtt.protocol.handler import ProtocolHandlerException
-from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
-import websockets
-from websockets.uri import InvalidURI
-from websockets.exceptions import InvalidHandshake
-from collections import deque
-
-
-_defaults = {
-    "keep_alive": 10,
-    "ping_delay": 1,
-    "default_qos": 0,
-    "default_retain": False,
-    "auto_reconnect": True,
-    "reconnect_max_interval": 10,
-    "reconnect_retries": 2,
-}
-
-
-class ClientException(Exception):
-    pass
-
-
-class ConnectException(ClientException):
-    pass
-
-
-class ClientContext(BaseContext):
-    """
-    ClientContext is used as the context passed to plugins interacting with the client.
-    It act as an adapter to client services from plugins
-    """
-
-    def __init__(self):
-        super().__init__()
-        self.config = None
-
-
-base_logger = logging.getLogger(__name__)
-
-
-def mqtt_connected(func):
-    """
-    MQTTClient coroutines decorator which will wait until connection before calling the decorated method.
-    :param func: coroutine to be called once connected
-    :return: coroutine result
-    """
-    wraps(func)
-
-    async def wrapper(self, *args, **kwargs):
-        if not self._connected_state.is_set():
-            base_logger.warning("Client not connected, waiting for it")
-            _, pending = await asyncio.wait(
-                [self._connected_state.wait(), self._no_more_connections.wait()],
-                return_when=asyncio.FIRST_COMPLETED,
-            )
-            for t in pending:
-                t.cancel()
-            if self._no_more_connections.is_set():
-                raise ClientException("Will not reconnect")
-        return await func(self, *args, **kwargs)
-
-    return wrapper
-
-
-class MQTTClient:
-    """
-    MQTT client implementation.
-
-    MQTTClient instances provides API for connecting to a broker and send/receive messages using the MQTT protocol.
-
-    :param client_id: MQTT client ID to use when connecting to the broker. If none, it will generated randomly by :func:`hbmqtt.utils.gen_client_id`
-    :param config: Client configuration
-    :param loop: asynio loop to use
-    :return: class instance
-    """
-
-    def __init__(self, client_id=None, config=None, loop=None):
-        self.logger = logging.getLogger(__name__)
-        self.config = copy.deepcopy(_defaults)
-        if config is not None:
-            self.config.update(config)
-        if client_id is not None:
-            self.client_id = client_id
-        else:
-            from hbmqtt.utils import gen_client_id
-
-            self.client_id = gen_client_id()
-            self.logger.debug("Using generated client ID : %s" % self.client_id)
-
-        if loop is not None:
-            self._loop = loop
-        else:
-            self._loop = asyncio.get_event_loop()
-        self.session = None
-        self._handler = None
-        self._disconnect_task = None
-        self._connected_state = asyncio.Event(loop=self._loop)
-        self._no_more_connections = asyncio.Event(loop=self._loop)
-        self.extra_headers = {}
-
-        # Init plugins manager
-        context = ClientContext()
-        context.config = self.config
-        self.plugins_manager = PluginManager(
-            "hbmqtt.client.plugins", context, loop=self._loop
-        )
-        self.client_tasks = deque()
-
-    async def connect(
-        self,
-        uri=None,
-        cleansession=None,
-        cafile=None,
-        capath=None,
-        cadata=None,
-        extra_headers=None,
-    ):
-        """
-        Connect to a remote broker.
-
-        At first, a network connection is established with the server using the given protocol (``mqtt``, ``mqtts``, ``ws`` or ``wss``). Once the socket is connected, a `CONNECT <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028>`_ message is sent with the requested informations.
-
-        This method is a *coroutine*.
-
-        :param uri: Broker URI connection, conforming to `MQTT URI scheme <https://github.com/mqtt/mqtt.github.io/wiki/URI-Scheme>`_. Uses ``uri`` config attribute by default.
-        :param cleansession: MQTT CONNECT clean session flag
-        :param cafile: server certificate authority file (optional, used for secured connection)
-        :param capath: server certificate authority path (optional, used for secured connection)
-        :param cadata: server certificate authority data (optional, used for secured connection)
-        :param extra_headers: a dictionary with additional http headers that should be sent on the initial connection (optional, used only with websocket connections)
-        :return: `CONNACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033>`_ return code
-        :raise: :class:`hbmqtt.client.ConnectException` if connection fails
-        """
-
-        if extra_headers is None:
-            extra_headers = {}
-
-        self.session = self._initsession(uri, cleansession, cafile, capath, cadata)
-        self.extra_headers = extra_headers
-        self.logger.debug("Connect to: %s" % uri)
-
-        try:
-            return await self._do_connect()
-        except asyncio.CancelledError:
-            raise
-        except BaseException as be:
-            self.logger.warning("Connection failed: %r" % be)
-            auto_reconnect = self.config.get("auto_reconnect", False)
-            if not auto_reconnect:
-                raise
-            else:
-                return await self.reconnect()
-
-    async def disconnect(self):
-        """
-        Disconnect from the connected broker.
-
-        This method sends a `DISCONNECT <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718090>`_ message and closes the network socket.
-
-        This method is a *coroutine*.
-        """
-        await self.cancel_tasks()
-        if self.session.transitions.is_connected():
-            if not self._disconnect_task.done():
-                self._disconnect_task.cancel()
-            await self._handler.mqtt_disconnect()
-            self._connected_state.clear()
-            await self._handler.stop()
-            self.session.transitions.disconnect()
-        else:
-            self.logger.warning(
-                "Client session is not currently connected, ignoring call"
-            )
-
-    async def cancel_tasks(self):
-        """
-        Before disconnection need to cancel all pending tasks
-        :return:
-        """
-        try:
-            while self.client_tasks:
-                task = self.client_tasks.pop()
-                task.cancel()
-        except IndexError:
-            pass
-
-    async def reconnect(self, cleansession=None):
-        """
-        Reconnect a previously connected broker.
-
-        Reconnection tries to establish a network connection and send a `CONNECT <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028>`_ message.
-        Retries interval and attempts can be controlled with the ``reconnect_max_interval`` and ``reconnect_retries`` configuration parameters.
-
-        This method is a *coroutine*.
-
-        :param cleansession: clean session flag used in MQTT CONNECT messages sent for reconnections.
-        :return: `CONNACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033>`_ return code
-        :raise: :class:`hbmqtt.client.ConnectException` if re-connection fails after max retries.
-        """
-
-        if self.session.transitions.is_connected():
-            self.logger.warning("Client already connected")
-            return CONNECTION_ACCEPTED
-
-        if cleansession:
-            self.session.clean_session = cleansession
-        self.logger.debug("Reconnecting with session parameters: %s" % self.session)
-        reconnect_max_interval = self.config.get("reconnect_max_interval", 10)
-        reconnect_retries = self.config.get("reconnect_retries", 5)
-        nb_attempt = 1
-        await asyncio.sleep(1, loop=self._loop)
-        while True:
-            try:
-                self.logger.debug("Reconnect attempt %d ..." % nb_attempt)
-                return await self._do_connect()
-            except asyncio.CancelledError:
-                raise
-            except BaseException as e:
-                self.logger.warning("Reconnection attempt failed: %r" % e)
-                if reconnect_retries >= 0 and nb_attempt > reconnect_retries:
-                    self.logger.error(
-                        "Maximum number of connection attempts reached. Reconnection aborted"
-                    )
-                    raise ConnectException("Too many connection attempts failed")
-                exp = 2 ** nb_attempt
-                delay = exp if exp < reconnect_max_interval else reconnect_max_interval
-                self.logger.debug("Waiting %d second before next attempt" % delay)
-                await asyncio.sleep(delay, loop=self._loop)
-                nb_attempt += 1
-
-    async def _do_connect(self):
-        return_code = await self._connect_coro()
-        self._disconnect_task = asyncio.ensure_future(
-            self.handle_connection_close(), loop=self._loop
-        )
-        return return_code
-
-    @mqtt_connected
-    async def ping(self):
-        """
-        Ping the broker.
-
-        Send a MQTT `PINGREQ <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718081>`_ message for response.
-
-        This method is a *coroutine*.
-        """
-
-        if self.session.transitions.is_connected():
-            await self._handler.mqtt_ping()
-        else:
-            self.logger.warning(
-                "MQTT PING request incompatible with current session state '%s'"
-                % self.session.transitions.state
-            )
-
-    @mqtt_connected
-    async def publish(self, topic, message, qos=None, retain=None, ack_timeout=None):
-        """
-        Publish a message to the broker.
-
-        Send a MQTT `PUBLISH <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037>`_ message and wait for acknowledgment depending on Quality Of Service
-
-        This method is a *coroutine*.
-
-        :param topic: topic name to which message data is published
-        :param message: payload message (as bytes) to send.
-        :param qos: requested publish quality of service : QOS_0, QOS_1 or QOS_2. Defaults to ``default_qos`` config parameter or QOS_0.
-        :param retain: retain flag. Defaults to ``default_retain`` config parameter or False.
-        """
-
-        def get_retain_and_qos():
-            if qos:
-                assert qos in (QOS_0, QOS_1, QOS_2)
-                _qos = qos
-            else:
-                _qos = self.config["default_qos"]
-                try:
-                    _qos = self.config["topics"][topic]["qos"]
-                except KeyError:
-                    pass
-            if retain:
-                _retain = retain
-            else:
-                _retain = self.config["default_retain"]
-                try:
-                    _retain = self.config["topics"][topic]["retain"]
-                except KeyError:
-                    pass
-            return _qos, _retain
-
-        (app_qos, app_retain) = get_retain_and_qos()
-        return await self._handler.mqtt_publish(
-            topic, message, app_qos, app_retain, ack_timeout
-        )
-
-    @mqtt_connected
-    async def subscribe(self, topics):
-        """
-        Subscribe to some topics.
-
-        Send a MQTT `SUBSCRIBE <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718063>`_ message and wait for broker acknowledgment.
-
-        This method is a *coroutine*.
-
-        :param topics: array of topics pattern to subscribe with associated QoS.
-        :return: `SUBACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718068>`_ message return code.
-
-        Example of ``topics`` argument expected structure:
-        ::
-
-            [
-                ('$SYS/broker/uptime', QOS_1),
-                ('$SYS/broker/load/#', QOS_2),
-            ]
-        """
-        return await self._handler.mqtt_subscribe(topics, self.session.next_packet_id)
-
-    @mqtt_connected
-    async def unsubscribe(self, topics):
-        """
-        Unsubscribe from some topics.
-
-        Send a MQTT `UNSUBSCRIBE <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718072>`_ message and wait for broker `UNSUBACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718077>`_ message.
-
-        This method is a *coroutine*.
-
-        :param topics: array of topics to unsubscribe from.
-
-        Example of ``topics`` argument expected structure:
-        ::
-
-            ['$SYS/broker/uptime', '$SYS/broker/load/#']
-        """
-        await self._handler.mqtt_unsubscribe(topics, self.session.next_packet_id)
-
-    async def deliver_message(self, timeout=None):
-        """
-        Deliver next received message.
-
-        Deliver next message received from the broker. If no message is available, this methods waits until next message arrives or ``timeout`` occurs.
-
-        This method is a *coroutine*.
-
-        :param timeout: maximum number of seconds to wait before returning. If timeout is not specified or None, there is no limit to the wait time until next message arrives.
-        :return: instance of :class:`hbmqtt.session.ApplicationMessage` containing received message information flow.
-        :raises: :class:`asyncio.TimeoutError` if timeout occurs before a message is delivered
-        """
-        deliver_task = asyncio.ensure_future(
-            self._handler.mqtt_deliver_next_message(), loop=self._loop
-        )
-        self.client_tasks.append(deliver_task)
-        self.logger.debug("Waiting message delivery")
-        done, pending = await asyncio.wait(
-            [deliver_task],
-            loop=self._loop,
-            return_when=asyncio.FIRST_EXCEPTION,
-            timeout=timeout,
-        )
-        if self.client_tasks:
-            self.client_tasks.pop()
-        if deliver_task in done:
-            if deliver_task.exception() is not None:
-                # deliver_task raised an exception, pass it on to our caller
-                raise deliver_task.exception()
-            return deliver_task.result()
-        else:
-            # timeout occured before message received
-            deliver_task.cancel()
-            raise asyncio.TimeoutError
-
-    async def _connect_coro(self):
-        kwargs = dict()
-
-        # Decode URI attributes
-        uri_attributes = urlparse(self.session.broker_uri)
-        scheme = uri_attributes.scheme
-        secure = True if scheme in ("mqtts", "wss") else False
-        self.session.username = (
-            self.session.username if self.session.username else uri_attributes.username
-        )
-        self.session.password = (
-            self.session.password if self.session.password else uri_attributes.password
-        )
-        self.session.remote_address = uri_attributes.hostname
-        self.session.remote_port = uri_attributes.port
-        if scheme in ("mqtt", "mqtts") and not self.session.remote_port:
-            self.session.remote_port = 8883 if scheme == "mqtts" else 1883
-        if scheme in ("ws", "wss") and not self.session.remote_port:
-            self.session.remote_port = 443 if scheme == "wss" else 80
-        if scheme in ("ws", "wss"):
-            # Rewrite URI to conform to https://tools.ietf.org/html/rfc6455#section-3
-            uri = (
-                scheme,
-                self.session.remote_address + ":" + str(self.session.remote_port),
-                uri_attributes[2],
-                uri_attributes[3],
-                uri_attributes[4],
-                uri_attributes[5],
-            )
-            self.session.broker_uri = urlunparse(uri)
-        # Init protocol handler
-        # if not self._handler:
-        self._handler = ClientProtocolHandler(self.plugins_manager, loop=self._loop)
-
-        if secure:
-            sc = ssl.create_default_context(
-                ssl.Purpose.SERVER_AUTH,
-                cafile=self.session.cafile,
-                capath=self.session.capath,
-                cadata=self.session.cadata,
-            )
-            if "certfile" in self.config and "keyfile" in self.config:
-                sc.load_cert_chain(self.config["certfile"], self.config["keyfile"])
-            if "check_hostname" in self.config and isinstance(
-                self.config["check_hostname"], bool
-            ):
-                sc.check_hostname = self.config["check_hostname"]
-            kwargs["ssl"] = sc
-
-        try:
-            reader = None
-            writer = None
-            self._connected_state.clear()
-            # Open connection
-            if scheme in ("mqtt", "mqtts"):
-                conn_reader, conn_writer = await asyncio.open_connection(
-                    self.session.remote_address,
-                    self.session.remote_port,
-                    loop=self._loop,
-                    **kwargs
-                )
-                reader = StreamReaderAdapter(conn_reader)
-                writer = StreamWriterAdapter(conn_writer)
-            elif scheme in ("ws", "wss"):
-                websocket = await websockets.connect(
-                    self.session.broker_uri,
-                    subprotocols=["mqtt"],
-                    loop=self._loop,
-                    extra_headers=self.extra_headers,
-                    **kwargs
-                )
-                reader = WebSocketsReader(websocket)
-                writer = WebSocketsWriter(websocket)
-            # Start MQTT protocol
-            self._handler.attach(self.session, reader, writer)
-            return_code = await self._handler.mqtt_connect()
-            if return_code is not CONNECTION_ACCEPTED:
-                self.session.transitions.disconnect()
-                self.logger.warning("Connection rejected with code '%s'" % return_code)
-                exc = ConnectException("Connection rejected by broker")
-                exc.return_code = return_code
-                raise exc
-            else:
-                # Handle MQTT protocol
-                await self._handler.start()
-                self.session.transitions.connect()
-                self._connected_state.set()
-                self.logger.debug(
-                    "connected to %s:%s"
-                    % (self.session.remote_address, self.session.remote_port)
-                )
-            return return_code
-        except InvalidURI as iuri:
-            self.logger.warning(
-                "connection failed: invalid URI '%s'" % self.session.broker_uri
-            )
-            self.session.transitions.disconnect()
-            raise ConnectException(
-                "connection failed: invalid URI '%s'" % self.session.broker_uri, iuri
-            )
-        except InvalidHandshake as ihs:
-            self.logger.warning("connection failed: invalid websocket handshake")
-            self.session.transitions.disconnect()
-            raise ConnectException(
-                "connection failed: invalid websocket handshake", ihs
-            )
-        except (ProtocolHandlerException, ConnectionError, OSError) as e:
-            self.logger.warning("MQTT connection failed: %r" % e)
-            self.session.transitions.disconnect()
-            raise ConnectException(e)
-
-    async def handle_connection_close(self):
-        def cancel_tasks():
-            self._no_more_connections.set()
-            while self.client_tasks:
-                task = self.client_tasks.popleft()
-                if not task.done():
-                    task.set_exception(ClientException("Connection lost"))
-
-        self.logger.debug("Watch broker disconnection")
-        # Wait for disconnection from broker (like connection lost)
-        await self._handler.wait_disconnect()
-        self.logger.warning("Disconnected from broker")
-
-        # Block client API
-        self._connected_state.clear()
-
-        # stop an clean handler
-        # await self._handler.stop()
-        self._handler.detach()
-        self.session.transitions.disconnect()
-
-        if self.config.get("auto_reconnect", False):
-            # Try reconnection
-            self.logger.debug("Auto-reconnecting")
-            try:
-                await self.reconnect()
-            except ConnectException:
-                # Cancel client pending tasks
-                cancel_tasks()
-        else:
-            # Cancel client pending tasks
-            cancel_tasks()
-
-    def _initsession(
-        self, uri=None, cleansession=None, cafile=None, capath=None, cadata=None
-    ) -> Session:
-        # Load config
-        broker_conf = self.config.get("broker", dict()).copy()
-        if uri:
-            broker_conf["uri"] = uri
-        if cafile:
-            broker_conf["cafile"] = cafile
-        elif "cafile" not in broker_conf:
-            broker_conf["cafile"] = None
-        if capath:
-            broker_conf["capath"] = capath
-        elif "capath" not in broker_conf:
-            broker_conf["capath"] = None
-        if cadata:
-            broker_conf["cadata"] = cadata
-        elif "cadata" not in broker_conf:
-            broker_conf["cadata"] = None
-
-        if cleansession is not None:
-            broker_conf["cleansession"] = cleansession
-
-        if not broker_conf.get("uri"):
-            raise ClientException("Missing connection parameter 'uri'")
-
-        s = Session()
-        s.broker_uri = broker_conf["uri"]
-        s.client_id = self.client_id
-        s.cafile = broker_conf["cafile"]
-        s.capath = broker_conf["capath"]
-        s.cadata = broker_conf["cadata"]
-        if cleansession is not None:
-            s.clean_session = cleansession
-        else:
-            s.clean_session = self.config.get("cleansession", True)
-        s.keep_alive = self.config["keep_alive"] - self.config["ping_delay"]
-        if "will" in self.config:
-            s.will_flag = True
-            s.will_retain = self.config["will"]["retain"]
-            s.will_topic = self.config["will"]["topic"]
-            s.will_message = self.config["will"]["message"]
-            s.will_qos = self.config["will"]["qos"]
-        else:
-            s.will_flag = False
-            s.will_retain = False
-            s.will_topic = None
-            s.will_message = None
-        return s
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/codecs.py b/hbmqtt/codecs.py
index bde80bdd..ea74b63b 100644
--- a/hbmqtt/codecs.py
+++ b/hbmqtt/codecs.py
@@ -1,119 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import asyncio
-from struct import pack, unpack
-from hbmqtt.errors import NoDataException
+import warnings
+from amqtt.codecs import *
 
-
-def bytes_to_hex_str(data):
-    """
-    converts a sequence of bytes into its displayable hex representation, ie: 0x??????
-    :param data: byte sequence
-    :return: Hexadecimal displayable representation
-    """
-    return "0x" + "".join(format(b, "02x") for b in data)
-
-
-def bytes_to_int(data):
-    """
-    convert a sequence of bytes to an integer using big endian byte ordering
-    :param data: byte sequence
-    :return: integer value
-    """
-    try:
-        return int.from_bytes(data, byteorder="big")
-    except:
-        return data
-
-
-def int_to_bytes(int_value: int, length: int) -> bytes:
-    """
-    convert an integer to a sequence of bytes using big endian byte ordering
-    :param int_value: integer value to convert
-    :param length: (optional) byte length
-    :return: byte sequence
-    """
-    if length == 1:
-        fmt = "!B"
-    elif length == 2:
-        fmt = "!H"
-    return pack(fmt, int_value)
-
-
-async def read_or_raise(reader, n=-1):
-    """
-    Read a given byte number from Stream. NoDataException is raised if read gives no data
-    :param reader: reader adapter
-    :param n: number of bytes to read
-    :return: bytes read
-    """
-    try:
-        data = await reader.read(n)
-    except (asyncio.IncompleteReadError, ConnectionResetError, BrokenPipeError):
-        data = None
-    if not data:
-        raise NoDataException("No more data")
-    return data
-
-
-async def decode_string(reader) -> str:
-    """
-    Read a string from a reader and decode it according to MQTT string specification
-    :param reader: Stream reader
-    :return: string read from stream
-    """
-    length_bytes = await read_or_raise(reader, 2)
-    str_length = unpack("!H", length_bytes)
-    if str_length[0]:
-        byte_str = await read_or_raise(reader, str_length[0])
-        try:
-            return byte_str.decode(encoding="utf-8")
-        except:
-            return str(byte_str)
-    else:
-        return ""
-
-
-async def decode_data_with_length(reader) -> bytes:
-    """
-    Read data from a reader. Data is prefixed with 2 bytes length
-    :param reader: Stream reader
-    :return: bytes read from stream (without length)
-    """
-    length_bytes = await read_or_raise(reader, 2)
-    bytes_length = unpack("!H", length_bytes)
-    data = await read_or_raise(reader, bytes_length[0])
-    return data
-
-
-def encode_string(string: str) -> bytes:
-    data = string.encode(encoding="utf-8")
-    data_length = len(data)
-    return int_to_bytes(data_length, 2) + data
-
-
-def encode_data_with_length(data: bytes) -> bytes:
-    data_length = len(data)
-    return int_to_bytes(data_length, 2) + data
-
-
-async def decode_packet_id(reader) -> int:
-    """
-    Read a packet ID as 2-bytes int from stream according to MQTT specification (2.3.1)
-    :param reader: Stream reader
-    :return: Packet ID
-    """
-    packet_id_bytes = await read_or_raise(reader, 2)
-    packet_id = unpack("!H", packet_id_bytes)
-    return packet_id[0]
-
-
-def int_to_bytes_str(value: int) -> bytes:
-    """
-    Converts a int value to a bytes array containing the numeric character.
-    Ex: 123 -> b'123'
-    :param value: int value to convert
-    :return: bytes array
-    """
-    return str(value).encode("utf-8")
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/errors.py b/hbmqtt/errors.py
index c36d7458..52b32e22 100644
--- a/hbmqtt/errors.py
+++ b/hbmqtt/errors.py
@@ -1,35 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.errors import *
 
-
-class HBMQTTException(Exception):
-    """
-    HBMQTT base exception
-    """
-
-    pass
-
-
-class MQTTException(Exception):
-    """
-    Base class for all errors refering to MQTT specifications
-    """
-
-    pass
-
-
-class CodecException(Exception):
-    """
-    Exceptions thrown by packet encode/decode functions
-    """
-
-    pass
-
-
-class NoDataException(Exception):
-    """
-    Exceptions thrown by packet encode/decode functions
-    """
-
-    pass
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/__init__.py b/hbmqtt/mqtt/__init__.py
index da97d44c..adcea054 100644
--- a/hbmqtt/mqtt/__init__.py
+++ b/hbmqtt/mqtt/__init__.py
@@ -1,60 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.errors import HBMQTTException
-from hbmqtt.mqtt.packet import (
-    CONNECT,
-    CONNACK,
-    PUBLISH,
-    PUBACK,
-    PUBREC,
-    PUBREL,
-    PUBCOMP,
-    SUBSCRIBE,
-    SUBACK,
-    UNSUBSCRIBE,
-    UNSUBACK,
-    PINGREQ,
-    PINGRESP,
-    DISCONNECT,
-    MQTTFixedHeader,
-)
-from hbmqtt.mqtt.connect import ConnectPacket
-from hbmqtt.mqtt.connack import ConnackPacket
-from hbmqtt.mqtt.disconnect import DisconnectPacket
-from hbmqtt.mqtt.pingreq import PingReqPacket
-from hbmqtt.mqtt.pingresp import PingRespPacket
-from hbmqtt.mqtt.publish import PublishPacket
-from hbmqtt.mqtt.puback import PubackPacket
-from hbmqtt.mqtt.pubrec import PubrecPacket
-from hbmqtt.mqtt.pubrel import PubrelPacket
-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
+import warnings
+from amqtt.mqtt import *
 
-packet_dict = {
-    CONNECT: ConnectPacket,
-    CONNACK: ConnackPacket,
-    PUBLISH: PublishPacket,
-    PUBACK: PubackPacket,
-    PUBREC: PubrecPacket,
-    PUBREL: PubrelPacket,
-    PUBCOMP: PubcompPacket,
-    SUBSCRIBE: SubscribePacket,
-    SUBACK: SubackPacket,
-    UNSUBSCRIBE: UnsubscribePacket,
-    UNSUBACK: UnsubackPacket,
-    PINGREQ: PingReqPacket,
-    PINGRESP: PingRespPacket,
-    DISCONNECT: DisconnectPacket,
-}
-
-
-def packet_class(fixed_header: MQTTFixedHeader):
-    try:
-        cls = packet_dict[fixed_header.packet_type]
-        return cls
-    except KeyError:
-        raise HBMQTTException("Unexpected packet Type '%s'" % fixed_header.packet_type)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/connack.py b/hbmqtt/mqtt/connack.py
index ad9a4ed6..3f8ce586 100644
--- a/hbmqtt/mqtt/connack.py
+++ b/hbmqtt/mqtt/connack.py
@@ -1,94 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import CONNACK, MQTTPacket, MQTTFixedHeader, MQTTVariableHeader
-from hbmqtt.codecs import read_or_raise, bytes_to_int
-from hbmqtt.errors import HBMQTTException
-from hbmqtt.adapters import ReaderAdapter
+import warnings
+from amqtt.mqtt.connack import *
 
-CONNECTION_ACCEPTED = 0x00
-UNACCEPTABLE_PROTOCOL_VERSION = 0x01
-IDENTIFIER_REJECTED = 0x02
-SERVER_UNAVAILABLE = 0x03
-BAD_USERNAME_PASSWORD = 0x04
-NOT_AUTHORIZED = 0x05
-
-
-class ConnackVariableHeader(MQTTVariableHeader):
-
-    __slots__ = ("session_parent", "return_code")
-
-    def __init__(self, session_parent=None, return_code=None):
-        super().__init__()
-        self.session_parent = session_parent
-        self.return_code = return_code
-
-    @classmethod
-    async def from_stream(cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader):
-        data = await read_or_raise(reader, 2)
-        session_parent = data[0] & 0x01
-        return_code = bytes_to_int(data[1])
-        return cls(session_parent, return_code)
-
-    def to_bytes(self):
-        out = bytearray(2)
-        # Connect acknowledge flags
-        if self.session_parent:
-            out[0] = 1
-        else:
-            out[0] = 0
-        # return code
-        out[1] = self.return_code
-
-        return out
-
-    def __repr__(self):
-        return type(self).__name__ + "(session_parent={0}, return_code={1})".format(
-            hex(self.session_parent), hex(self.return_code)
-        )
-
-
-class ConnackPacket(MQTTPacket):
-    VARIABLE_HEADER = ConnackVariableHeader
-    PAYLOAD = None
-
-    @property
-    def return_code(self):
-        return self.variable_header.return_code
-
-    @return_code.setter
-    def return_code(self, return_code):
-        self.variable_header.return_code = return_code
-
-    @property
-    def session_parent(self):
-        return self.variable_header.session_parent
-
-    @session_parent.setter
-    def session_parent(self, session_parent):
-        self.variable_header.session_parent = session_parent
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: ConnackVariableHeader = None,
-        payload=None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(CONNACK, 0x00)
-        else:
-            if fixed.packet_type is not CONNACK:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for ConnackPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = None
-
-    @classmethod
-    def build(cls, session_parent=None, return_code=None):
-        v_header = ConnackVariableHeader(session_parent, return_code)
-        packet = ConnackPacket(variable_header=v_header)
-        return packet
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/connect.py b/hbmqtt/mqtt/connect.py
index cbd1d9bd..e6bf880f 100644
--- a/hbmqtt/mqtt/connect.py
+++ b/hbmqtt/mqtt/connect.py
@@ -1,400 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.mqtt.connect import *
 
-from hbmqtt.codecs import (
-    bytes_to_int,
-    decode_data_with_length,
-    decode_string,
-    encode_data_with_length,
-    encode_string,
-    int_to_bytes,
-    read_or_raise,
-)
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    CONNECT,
-    MQTTVariableHeader,
-    MQTTPayload,
-)
-from hbmqtt.errors import HBMQTTException, NoDataException
-from hbmqtt.adapters import ReaderAdapter
-from hbmqtt.utils import gen_client_id
-
-
-class ConnectVariableHeader(MQTTVariableHeader):
-
-    __slots__ = ("proto_name", "proto_level", "flags", "keep_alive")
-
-    USERNAME_FLAG = 0x80
-    PASSWORD_FLAG = 0x40
-    WILL_RETAIN_FLAG = 0x20
-    WILL_FLAG = 0x04
-    WILL_QOS_MASK = 0x18
-    CLEAN_SESSION_FLAG = 0x02
-    RESERVED_FLAG = 0x01
-
-    def __init__(
-        self, connect_flags=0x00, keep_alive=0, proto_name="MQTT", proto_level=0x04
-    ):
-        super().__init__()
-        self.proto_name = proto_name
-        self.proto_level = proto_level
-        self.flags = connect_flags
-        self.keep_alive = keep_alive
-
-    def __repr__(self):
-        return "ConnectVariableHeader(proto_name={0}, proto_level={1}, flags={2}, keepalive={3})".format(
-            self.proto_name, self.proto_level, hex(self.flags), self.keep_alive
-        )
-
-    def _set_flag(self, val, mask):
-        if val:
-            self.flags |= mask
-        else:
-            self.flags &= ~mask
-
-    def _get_flag(self, mask):
-        if self.flags & mask:
-            return True
-        else:
-            return False
-
-    @property
-    def username_flag(self) -> bool:
-        return self._get_flag(self.USERNAME_FLAG)
-
-    @username_flag.setter
-    def username_flag(self, val: bool):
-        self._set_flag(val, self.USERNAME_FLAG)
-
-    @property
-    def password_flag(self) -> bool:
-        return self._get_flag(self.PASSWORD_FLAG)
-
-    @password_flag.setter
-    def password_flag(self, val: bool):
-        self._set_flag(val, self.PASSWORD_FLAG)
-
-    @property
-    def will_retain_flag(self) -> bool:
-        return self._get_flag(self.WILL_RETAIN_FLAG)
-
-    @will_retain_flag.setter
-    def will_retain_flag(self, val: bool):
-        self._set_flag(val, self.WILL_RETAIN_FLAG)
-
-    @property
-    def will_flag(self) -> bool:
-        return self._get_flag(self.WILL_FLAG)
-
-    @will_flag.setter
-    def will_flag(self, val: bool):
-        self._set_flag(val, self.WILL_FLAG)
-
-    @property
-    def clean_session_flag(self) -> bool:
-        return self._get_flag(self.CLEAN_SESSION_FLAG)
-
-    @clean_session_flag.setter
-    def clean_session_flag(self, val: bool):
-        self._set_flag(val, self.CLEAN_SESSION_FLAG)
-
-    @property
-    def reserved_flag(self) -> bool:
-        return self._get_flag(self.RESERVED_FLAG)
-
-    @property
-    def will_qos(self):
-        return (self.flags & 0x18) >> 3
-
-    @will_qos.setter
-    def will_qos(self, val: int):
-        self.flags &= 0xE7  # Reset QOS flags
-        self.flags |= val << 3
-
-    @classmethod
-    async def from_stream(cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader):
-        #  protocol name
-        protocol_name = await decode_string(reader)
-
-        # protocol level
-        protocol_level_byte = await read_or_raise(reader, 1)
-        protocol_level = bytes_to_int(protocol_level_byte)
-
-        # flags
-        flags_byte = await read_or_raise(reader, 1)
-        flags = bytes_to_int(flags_byte)
-
-        # keep-alive
-        keep_alive_byte = await read_or_raise(reader, 2)
-        keep_alive = bytes_to_int(keep_alive_byte)
-
-        return cls(flags, keep_alive, protocol_name, protocol_level)
-
-    def to_bytes(self):
-        out = bytearray()
-
-        # Protocol name
-        out.extend(encode_string(self.proto_name))
-        # Protocol level
-        out.append(self.proto_level)
-        # flags
-        out.append(self.flags)
-        # keep alive
-        out.extend(int_to_bytes(self.keep_alive, 2))
-
-        return out
-
-
-class ConnectPayload(MQTTPayload):
-
-    __slots__ = (
-        "client_id",
-        "will_topic",
-        "will_message",
-        "username",
-        "password",
-        "client_id_is_random",
-    )
-
-    def __init__(
-        self,
-        client_id=None,
-        will_topic=None,
-        will_message=None,
-        username=None,
-        password=None,
-    ):
-        super().__init__()
-        self.client_id_is_random = False
-        self.client_id = client_id
-        self.will_topic = will_topic
-        self.will_message = will_message
-        self.username = username
-        self.password = password
-
-    def __repr__(self):
-        return "ConnectVariableHeader(client_id={0}, will_topic={1}, will_message={2}, username={3}, password={4})".format(
-            self.client_id,
-            self.will_topic,
-            self.will_message,
-            self.username,
-            self.password,
-        )
-
-    @classmethod
-    async def from_stream(
-        cls,
-        reader: ReaderAdapter,
-        fixed_header: MQTTFixedHeader,
-        variable_header: ConnectVariableHeader,
-    ):
-        payload = cls()
-        #  Client identifier
-        try:
-            payload.client_id = await decode_string(reader)
-        except NoDataException:
-            payload.client_id = None
-
-        if payload.client_id is None or payload.client_id == "":
-            # A Server MAY allow a Client to supply a ClientId that has a length of zero bytes
-            # [MQTT-3.1.3-6]
-            payload.client_id = gen_client_id()
-            # indicator to trow exception in case CLEAN_SESSION_FLAG is set to False
-            payload.client_id_is_random = True
-
-        # Read will topic, username and password
-        if variable_header.will_flag:
-            try:
-                payload.will_topic = await decode_string(reader)
-                payload.will_message = await decode_data_with_length(reader)
-            except NoDataException:
-                payload.will_topic = None
-                payload.will_message = None
-
-        if variable_header.username_flag:
-            try:
-                payload.username = await decode_string(reader)
-            except NoDataException:
-                payload.username = None
-
-        if variable_header.password_flag:
-            try:
-                payload.password = await decode_string(reader)
-            except NoDataException:
-                payload.password = None
-
-        return payload
-
-    def to_bytes(
-        self, fixed_header: MQTTFixedHeader, variable_header: ConnectVariableHeader
-    ):
-        out = bytearray()
-        # Client identifier
-        out.extend(encode_string(self.client_id))
-        # Will topic / message
-        if variable_header.will_flag:
-            out.extend(encode_string(self.will_topic))
-            out.extend(encode_data_with_length(self.will_message))
-        # username
-        if variable_header.username_flag:
-            out.extend(encode_string(self.username))
-        # password
-        if variable_header.password_flag:
-            out.extend(encode_string(self.password))
-
-        return out
-
-
-class ConnectPacket(MQTTPacket):
-    VARIABLE_HEADER = ConnectVariableHeader
-    PAYLOAD = ConnectPayload
-
-    @property
-    def proto_name(self):
-        return self.variable_header.proto_name
-
-    @proto_name.setter
-    def proto_name(self, name: str):
-        self.variable_header.proto_name = name
-
-    @property
-    def proto_level(self):
-        return self.variable_header.proto_level
-
-    @proto_level.setter
-    def proto_level(self, level):
-        self.variable_header.proto_level = level
-
-    @property
-    def username_flag(self):
-        return self.variable_header.username_flag
-
-    @username_flag.setter
-    def username_flag(self, flag):
-        self.variable_header.username_flag = flag
-
-    @property
-    def password_flag(self):
-        return self.variable_header.password_flag
-
-    @password_flag.setter
-    def password_flag(self, flag):
-        self.variable_header.password_flag = flag
-
-    @property
-    def clean_session_flag(self):
-        return self.variable_header.clean_session_flag
-
-    @clean_session_flag.setter
-    def clean_session_flag(self, flag):
-        self.variable_header.clean_session_flag = flag
-
-    @property
-    def will_retain_flag(self):
-        return self.variable_header.will_retain_flag
-
-    @will_retain_flag.setter
-    def will_retain_flag(self, flag):
-        self.variable_header.will_retain_flag = flag
-
-    @property
-    def will_qos(self):
-        return self.variable_header.will_qos
-
-    @will_qos.setter
-    def will_qos(self, flag):
-        self.variable_header.will_qos = flag
-
-    @property
-    def will_flag(self):
-        return self.variable_header.will_flag
-
-    @will_flag.setter
-    def will_flag(self, flag):
-        self.variable_header.will_flag = flag
-
-    @property
-    def reserved_flag(self):
-        return self.variable_header.reserved_flag
-
-    @reserved_flag.setter
-    def reserved_flag(self, flag):
-        self.variable_header.reserved_flag = flag
-
-    @property
-    def client_id(self):
-        return self.payload.client_id
-
-    @client_id.setter
-    def client_id(self, client_id):
-        self.payload.client_id = client_id
-
-    @property
-    def client_id_is_random(self) -> bool:
-        return self.payload.client_id_is_random
-
-    @client_id_is_random.setter
-    def client_id_is_random(self, client_id_is_random: bool):
-        self.payload.client_id_is_random = client_id_is_random
-
-    @property
-    def will_topic(self):
-        return self.payload.will_topic
-
-    @will_topic.setter
-    def will_topic(self, will_topic):
-        self.payload.will_topic = will_topic
-
-    @property
-    def will_message(self):
-        return self.payload.will_message
-
-    @will_message.setter
-    def will_message(self, will_message):
-        self.payload.will_message = will_message
-
-    @property
-    def username(self):
-        return self.payload.username
-
-    @username.setter
-    def username(self, username):
-        self.payload.username = username
-
-    @property
-    def password(self):
-        return self.payload.password
-
-    @password.setter
-    def password(self, password):
-        self.payload.password = password
-
-    @property
-    def keep_alive(self):
-        return self.variable_header.keep_alive
-
-    @keep_alive.setter
-    def keep_alive(self, keep_alive):
-        self.variable_header.keep_alive = keep_alive
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        vh: ConnectVariableHeader = None,
-        payload: ConnectPayload = None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(CONNECT, 0x00)
-        else:
-            if fixed.packet_type is not CONNECT:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for ConnectPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = vh
-        self.payload = payload
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/constants.py b/hbmqtt/mqtt/constants.py
index 841d2819..b97a19f7 100644
--- a/hbmqtt/mqtt/constants.py
+++ b/hbmqtt/mqtt/constants.py
@@ -1,7 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.mqtt.constants import *
 
-QOS_0 = 0x00
-QOS_1 = 0x01
-QOS_2 = 0x02
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/disconnect.py b/hbmqtt/mqtt/disconnect.py
index b5bfad87..92929680 100644
--- a/hbmqtt/mqtt/disconnect.py
+++ b/hbmqtt/mqtt/disconnect.py
@@ -1,24 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import MQTTPacket, MQTTFixedHeader, DISCONNECT
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.disconnect import *
 
-
-class DisconnectPacket(MQTTPacket):
-    VARIABLE_HEADER = None
-    PAYLOAD = None
-
-    def __init__(self, fixed: MQTTFixedHeader = None):
-        if fixed is None:
-            header = MQTTFixedHeader(DISCONNECT, 0x00)
-        else:
-            if fixed.packet_type is not DISCONNECT:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for DisconnectPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = None
-        self.payload = None
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/packet.py b/hbmqtt/mqtt/packet.py
index 046fe01f..75fc483b 100644
--- a/hbmqtt/mqtt/packet.py
+++ b/hbmqtt/mqtt/packet.py
@@ -1,279 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import asyncio
+import warnings
+from amqtt.mqtt.packet import *
 
-from hbmqtt.codecs import (
-    bytes_to_hex_str,
-    decode_packet_id,
-    int_to_bytes,
-    read_or_raise,
-)
-from hbmqtt.errors import CodecException, MQTTException, NoDataException
-from hbmqtt.adapters import ReaderAdapter, WriterAdapter
-from datetime import datetime
-from struct import unpack
-
-
-RESERVED_0 = 0x00
-CONNECT = 0x01
-CONNACK = 0x02
-PUBLISH = 0x03
-PUBACK = 0x04
-PUBREC = 0x05
-PUBREL = 0x06
-PUBCOMP = 0x07
-SUBSCRIBE = 0x08
-SUBACK = 0x09
-UNSUBSCRIBE = 0x0A
-UNSUBACK = 0x0B
-PINGREQ = 0x0C
-PINGRESP = 0x0D
-DISCONNECT = 0x0E
-RESERVED_15 = 0x0F
-
-
-class MQTTFixedHeader:
-
-    __slots__ = ("packet_type", "remaining_length", "flags")
-
-    def __init__(self, packet_type, flags=0, length=0):
-        self.packet_type = packet_type
-        self.remaining_length = length
-        self.flags = flags
-
-    def to_bytes(self):
-        def encode_remaining_length(length: int):
-            encoded = bytearray()
-            while True:
-                length_byte = length % 0x80
-                length //= 0x80
-                if length > 0:
-                    length_byte |= 0x80
-                encoded.append(length_byte)
-                if length <= 0:
-                    break
-            return encoded
-
-        out = bytearray()
-        packet_type = 0
-        try:
-            packet_type = (self.packet_type << 4) | self.flags
-            out.append(packet_type)
-        except OverflowError:
-            raise CodecException(
-                "packet_type encoding exceed 1 byte length: value=%d", packet_type
-            )
-
-        encoded_length = encode_remaining_length(self.remaining_length)
-        out.extend(encoded_length)
-
-        return out
-
-    async def to_stream(self, writer: WriterAdapter):
-        writer.write(self.to_bytes())
-
-    @property
-    def bytes_length(self):
-        return len(self.to_bytes())
-
-    @classmethod
-    async def from_stream(cls, reader: ReaderAdapter):
-        """
-        Read and decode MQTT message fixed header from stream
-        :return: FixedHeader instance
-        """
-
-        async def decode_remaining_length():
-            """
-            Decode message length according to MQTT specifications
-            :return:
-            """
-            multiplier = 1
-            value = 0
-            buffer = bytearray()
-            while True:
-                encoded_byte = await reader.read(1)
-                int_byte = unpack("!B", encoded_byte)
-                buffer.append(int_byte[0])
-                value += (int_byte[0] & 0x7F) * multiplier
-                if (int_byte[0] & 0x80) == 0:
-                    break
-                else:
-                    multiplier *= 128
-                    if multiplier > 128 * 128 * 128:
-                        raise MQTTException(
-                            "Invalid remaining length bytes:%s, packet_type=%d"
-                            % (bytes_to_hex_str(buffer), msg_type)
-                        )
-            return value
-
-        try:
-            byte1 = await read_or_raise(reader, 1)
-            int1 = unpack("!B", byte1)
-            msg_type = (int1[0] & 0xF0) >> 4
-            flags = int1[0] & 0x0F
-            remain_length = await decode_remaining_length()
-
-            return cls(msg_type, flags, remain_length)
-        except NoDataException:
-            return None
-
-    def __repr__(self):
-        return type(self).__name__ + "(length={0}, flags={1})".format(
-            self.remaining_length, hex(self.flags)
-        )
-
-
-class MQTTVariableHeader:
-    def __init__(self):
-        pass
-
-    async def to_stream(self, writer: asyncio.StreamWriter):
-        writer.write(self.to_bytes())
-        await writer.drain()
-
-    def to_bytes(self) -> bytes:
-        """
-        Serialize header data to a byte array conforming to MQTT protocol
-        :return: serialized data
-        """
-
-    @property
-    def bytes_length(self):
-        return len(self.to_bytes())
-
-    @classmethod
-    async def from_stream(
-        cls, reader: asyncio.StreamReader, fixed_header: MQTTFixedHeader
-    ):
-        pass
-
-
-class PacketIdVariableHeader(MQTTVariableHeader):
-
-    __slots__ = ("packet_id",)
-
-    def __init__(self, packet_id):
-        super().__init__()
-        self.packet_id = packet_id
-
-    def to_bytes(self):
-        out = b""
-        out += int_to_bytes(self.packet_id, 2)
-        return out
-
-    @classmethod
-    async def from_stream(cls, reader: ReaderAdapter, fixed_header: MQTTFixedHeader):
-        packet_id = await decode_packet_id(reader)
-        return cls(packet_id)
-
-    def __repr__(self):
-        return type(self).__name__ + "(packet_id={0})".format(self.packet_id)
-
-
-class MQTTPayload:
-    def __init__(self):
-        pass
-
-    async def to_stream(self, writer: asyncio.StreamWriter):
-        writer.write(self.to_bytes())
-        await writer.drain()
-
-    def to_bytes(
-        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
-    ):
-        pass
-
-    @classmethod
-    async def from_stream(
-        cls,
-        reader: asyncio.StreamReader,
-        fixed_header: MQTTFixedHeader,
-        variable_header: MQTTVariableHeader,
-    ):
-        pass
-
-
-class MQTTPacket:
-
-    __slots__ = ("fixed_header", "variable_header", "payload", "protocol_ts")
-
-    FIXED_HEADER = MQTTFixedHeader
-    VARIABLE_HEADER = None
-    PAYLOAD = None
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader,
-        variable_header: MQTTVariableHeader = None,
-        payload: MQTTPayload = None,
-    ):
-        self.fixed_header = fixed
-        self.variable_header = variable_header
-        self.payload = payload
-        self.protocol_ts = None
-
-    async def to_stream(self, writer: asyncio.StreamWriter):
-        writer.write(self.to_bytes())
-        await writer.drain()
-        self.protocol_ts = datetime.now()
-
-    def to_bytes(self) -> bytes:
-        if self.variable_header:
-            variable_header_bytes = self.variable_header.to_bytes()
-        else:
-            variable_header_bytes = b""
-        if self.payload:
-            payload_bytes = self.payload.to_bytes(
-                self.fixed_header, self.variable_header
-            )
-        else:
-            payload_bytes = b""
-
-        self.fixed_header.remaining_length = len(variable_header_bytes) + len(
-            payload_bytes
-        )
-        fixed_header_bytes = self.fixed_header.to_bytes()
-
-        return fixed_header_bytes + variable_header_bytes + payload_bytes
-
-    @classmethod
-    async def from_stream(
-        cls, reader: ReaderAdapter, fixed_header=None, variable_header=None
-    ):
-        if fixed_header is None:
-            fixed_header = await cls.FIXED_HEADER.from_stream(reader)
-        if cls.VARIABLE_HEADER:
-            if variable_header is None:
-                variable_header = await cls.VARIABLE_HEADER.from_stream(
-                    reader, fixed_header
-                )
-        else:
-            variable_header = None
-        if cls.PAYLOAD:
-            payload = await cls.PAYLOAD.from_stream(
-                reader, fixed_header, variable_header
-            )
-        else:
-            payload = None
-
-        if fixed_header and not variable_header and not payload:
-            instance = cls(fixed_header)
-        elif fixed_header and not payload:
-            instance = cls(fixed_header, variable_header)
-        else:
-            instance = cls(fixed_header, variable_header, payload)
-        instance.protocol_ts = datetime.now()
-        return instance
-
-    @property
-    def bytes_length(self):
-        return len(self.to_bytes())
-
-    def __repr__(self):
-        return type(
-            self
-        ).__name__ + "(ts={0!s}, fixed={1!r}, variable={2!r}, payload={3!r})".format(
-            self.protocol_ts, self.fixed_header, self.variable_header, self.payload
-        )
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/pingreq.py b/hbmqtt/mqtt/pingreq.py
index d99c9256..1e93531a 100644
--- a/hbmqtt/mqtt/pingreq.py
+++ b/hbmqtt/mqtt/pingreq.py
@@ -1,24 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import MQTTPacket, MQTTFixedHeader, PINGREQ
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.pingreq import *
 
-
-class PingReqPacket(MQTTPacket):
-    VARIABLE_HEADER = None
-    PAYLOAD = None
-
-    def __init__(self, fixed: MQTTFixedHeader = None):
-        if fixed is None:
-            header = MQTTFixedHeader(PINGREQ, 0x00)
-        else:
-            if fixed.packet_type is not PINGREQ:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for PingReqPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = None
-        self.payload = None
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/pingresp.py b/hbmqtt/mqtt/pingresp.py
index bd36cdac..57fa01e4 100644
--- a/hbmqtt/mqtt/pingresp.py
+++ b/hbmqtt/mqtt/pingresp.py
@@ -1,28 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import MQTTPacket, MQTTFixedHeader, PINGRESP
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.pingresp import *
 
-
-class PingRespPacket(MQTTPacket):
-    VARIABLE_HEADER = None
-    PAYLOAD = None
-
-    def __init__(self, fixed: MQTTFixedHeader = None):
-        if fixed is None:
-            header = MQTTFixedHeader(PINGRESP, 0x00)
-        else:
-            if fixed.packet_type is not PINGRESP:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for PingRespPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = None
-        self.payload = None
-
-    @classmethod
-    def build(cls):
-        return cls()
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/protocol/__init__.py b/hbmqtt/mqtt/protocol/__init__.py
index e69de29b..237ce6da 100644
--- a/hbmqtt/mqtt/protocol/__init__.py
+++ b/hbmqtt/mqtt/protocol/__init__.py
@@ -0,0 +1,4 @@
+import warnings
+from amqtt.mqtt.protocol import *
+
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/protocol/broker_handler.py b/hbmqtt/mqtt/protocol/broker_handler.py
index bda3eb1c..728575d1 100644
--- a/hbmqtt/mqtt/protocol/broker_handler.py
+++ b/hbmqtt/mqtt/protocol/broker_handler.py
@@ -1,212 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from asyncio import futures, Queue
-from hbmqtt.mqtt.protocol.handler import ProtocolHandler
-from hbmqtt.mqtt.connack import (
-    CONNECTION_ACCEPTED,
-    UNACCEPTABLE_PROTOCOL_VERSION,
-    IDENTIFIER_REJECTED,
-    BAD_USERNAME_PASSWORD,
-    NOT_AUTHORIZED,
-    ConnackPacket,
-)
-from hbmqtt.mqtt.connect import ConnectPacket
-from hbmqtt.mqtt.pingreq import PingReqPacket
-from hbmqtt.mqtt.pingresp import PingRespPacket
-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.utils import format_client_message
-from hbmqtt.session import Session
-from hbmqtt.plugins.manager import PluginManager
-from hbmqtt.adapters import ReaderAdapter, WriterAdapter
-from hbmqtt.errors import MQTTException
-from .handler import EVENT_MQTT_PACKET_RECEIVED, EVENT_MQTT_PACKET_SENT
+import warnings
+from amqtt.mqtt.protocol.broker_handler import *
 
-
-class BrokerProtocolHandler(ProtocolHandler):
-    def __init__(
-        self, plugins_manager: PluginManager, session: Session = None, loop=None
-    ):
-        super().__init__(plugins_manager, session, loop)
-        self._disconnect_waiter = None
-        self._pending_subscriptions = Queue(loop=self._loop)
-        self._pending_unsubscriptions = Queue(loop=self._loop)
-
-    async def start(self):
-        await super().start()
-        if self._disconnect_waiter is None:
-            self._disconnect_waiter = futures.Future(loop=self._loop)
-
-    async def stop(self):
-        await super().stop()
-        if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
-            self._disconnect_waiter.set_result(None)
-
-    async def wait_disconnect(self):
-        return await self._disconnect_waiter
-
-    def handle_write_timeout(self):
-        pass
-
-    def handle_read_timeout(self):
-        if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
-            self._disconnect_waiter.set_result(None)
-
-    async def handle_disconnect(self, disconnect):
-        self.logger.debug("Client disconnecting")
-        if self._disconnect_waiter and not self._disconnect_waiter.done():
-            self.logger.debug("Setting waiter result to %r" % disconnect)
-            self._disconnect_waiter.set_result(disconnect)
-
-    async def handle_connection_closed(self):
-        await self.handle_disconnect(None)
-
-    async def handle_connect(self, connect: ConnectPacket):
-        # Broker handler shouldn't received CONNECT message during messages handling
-        # as CONNECT messages are managed by the broker on client connection
-        self.logger.error(
-            "%s [MQTT-3.1.0-2] %s : CONNECT message received during messages handling"
-            % (self.session.client_id, format_client_message(self.session))
-        )
-        if self._disconnect_waiter is not None and not self._disconnect_waiter.done():
-            self._disconnect_waiter.set_result(None)
-
-    async def handle_pingreq(self, pingreq: PingReqPacket):
-        await self._send_packet(PingRespPacket.build())
-
-    async def handle_subscribe(self, subscribe: SubscribePacket):
-        subscription = {
-            "packet_id": subscribe.variable_header.packet_id,
-            "topics": subscribe.payload.topics,
-        }
-        await self._pending_subscriptions.put(subscription)
-
-    async def handle_unsubscribe(self, unsubscribe: UnsubscribePacket):
-        unsubscription = {
-            "packet_id": unsubscribe.variable_header.packet_id,
-            "topics": unsubscribe.payload.topics,
-        }
-        await self._pending_unsubscriptions.put(unsubscription)
-
-    async def get_next_pending_subscription(self):
-        subscription = await self._pending_subscriptions.get()
-        return subscription
-
-    async def get_next_pending_unsubscription(self):
-        unsubscription = await self._pending_unsubscriptions.get()
-        return unsubscription
-
-    async def mqtt_acknowledge_subscription(self, packet_id, return_codes):
-        suback = SubackPacket.build(packet_id, return_codes)
-        await self._send_packet(suback)
-
-    async def mqtt_acknowledge_unsubscription(self, packet_id):
-        unsuback = UnsubackPacket.build(packet_id)
-        await self._send_packet(unsuback)
-
-    async def mqtt_connack_authorize(self, authorize: bool):
-        if authorize:
-            connack = ConnackPacket.build(self.session.parent, CONNECTION_ACCEPTED)
-        else:
-            connack = ConnackPacket.build(self.session.parent, NOT_AUTHORIZED)
-        await self._send_packet(connack)
-
-    @classmethod
-    async def init_from_connect(
-        cls, reader: ReaderAdapter, writer: WriterAdapter, plugins_manager, loop=None
-    ):
-        """
-
-        :param reader:
-        :param writer:
-        :param plugins_manager:
-        :param loop:
-        :return:
-        """
-        remote_address, remote_port = writer.get_peer_info()
-        connect = await ConnectPacket.from_stream(reader)
-        await plugins_manager.fire_event(EVENT_MQTT_PACKET_RECEIVED, packet=connect)
-        # this shouldn't be required anymore since broker generates for each client a random client_id if not provided
-        # [MQTT-3.1.3-6]
-        if connect.payload.client_id is None:
-            raise MQTTException("[[MQTT-3.1.3-3]] : Client identifier must be present")
-
-        if connect.variable_header.will_flag:
-            if (
-                connect.payload.will_topic is None
-                or connect.payload.will_message is None
-            ):
-                raise MQTTException(
-                    "will flag set, but will topic/message not present in payload"
-                )
-
-        if connect.variable_header.reserved_flag:
-            raise MQTTException("[MQTT-3.1.2-3] CONNECT reserved flag must be set to 0")
-        if connect.proto_name != "MQTT":
-            raise MQTTException(
-                '[MQTT-3.1.2-1] Incorrect protocol name: "%s"' % connect.proto_name
-            )
-
-        connack = None
-        error_msg = None
-        if connect.proto_level != 4:
-            # only MQTT 3.1.1 supported
-            error_msg = "Invalid protocol from %s: %d" % (
-                format_client_message(address=remote_address, port=remote_port),
-                connect.proto_level,
-            )
-            connack = ConnackPacket.build(
-                0, UNACCEPTABLE_PROTOCOL_VERSION
-            )  # [MQTT-3.2.2-4] session_parent=0
-        elif not connect.username_flag and connect.password_flag:
-            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
-        elif connect.username_flag and not connect.password_flag:
-            connack = ConnackPacket.build(0, BAD_USERNAME_PASSWORD)  # [MQTT-3.1.2-22]
-        elif connect.username_flag and connect.username is None:
-            error_msg = "Invalid username from %s" % (
-                format_client_message(address=remote_address, port=remote_port)
-            )
-            connack = ConnackPacket.build(
-                0, BAD_USERNAME_PASSWORD
-            )  # [MQTT-3.2.2-4] session_parent=0
-        elif connect.password_flag and connect.password is None:
-            error_msg = "Invalid password %s" % (
-                format_client_message(address=remote_address, port=remote_port)
-            )
-            connack = ConnackPacket.build(
-                0, BAD_USERNAME_PASSWORD
-            )  # [MQTT-3.2.2-4] session_parent=0
-        elif connect.clean_session_flag is False and (
-            connect.payload.client_id_is_random
-        ):
-            error_msg = (
-                "[MQTT-3.1.3-8] [MQTT-3.1.3-9] %s: No client Id provided (cleansession=0)"
-                % (format_client_message(address=remote_address, port=remote_port))
-            )
-            connack = ConnackPacket.build(0, IDENTIFIER_REJECTED)
-        if connack is not None:
-            await plugins_manager.fire_event(EVENT_MQTT_PACKET_SENT, packet=connack)
-            await connack.to_stream(writer)
-            await writer.close()
-            raise MQTTException(error_msg)
-
-        incoming_session = Session(loop)
-        incoming_session.client_id = connect.client_id
-        incoming_session.clean_session = connect.clean_session_flag
-        incoming_session.will_flag = connect.will_flag
-        incoming_session.will_retain = connect.will_retain_flag
-        incoming_session.will_qos = connect.will_qos
-        incoming_session.will_topic = connect.will_topic
-        incoming_session.will_message = connect.will_message
-        incoming_session.username = connect.username
-        incoming_session.password = connect.password
-        if connect.keep_alive > 0:
-            incoming_session.keep_alive = connect.keep_alive
-        else:
-            incoming_session.keep_alive = 0
-
-        handler = cls(plugins_manager, loop=loop)
-        return handler, incoming_session
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/protocol/client_handler.py b/hbmqtt/mqtt/protocol/client_handler.py
index e4eaa915..5da752fa 100644
--- a/hbmqtt/mqtt/protocol/client_handler.py
+++ b/hbmqtt/mqtt/protocol/client_handler.py
@@ -1,173 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import asyncio
-from asyncio import futures
-from hbmqtt.mqtt.protocol.handler import ProtocolHandler, EVENT_MQTT_PACKET_RECEIVED
-from hbmqtt.mqtt.disconnect import DisconnectPacket
-from hbmqtt.mqtt.pingreq import PingReqPacket
-from hbmqtt.mqtt.pingresp import PingRespPacket
-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.mqtt.connect import ConnectVariableHeader, ConnectPayload, ConnectPacket
-from hbmqtt.mqtt.connack import ConnackPacket
-from hbmqtt.session import Session
-from hbmqtt.plugins.manager import PluginManager
+import warnings
+from amqtt.mqtt.protocol.client_handler import *
 
-
-class ClientProtocolHandler(ProtocolHandler):
-    def __init__(
-        self, plugins_manager: PluginManager, session: Session = None, loop=None
-    ):
-        super().__init__(plugins_manager, session, loop=loop)
-        self._ping_task = None
-        self._pingresp_queue = asyncio.Queue(loop=self._loop)
-        self._subscriptions_waiter = dict()
-        self._unsubscriptions_waiter = dict()
-        self._disconnect_waiter = None
-
-    async def start(self):
-        await super().start()
-        if self._disconnect_waiter is None:
-            self._disconnect_waiter = futures.Future(loop=self._loop)
-
-    async def stop(self):
-        await super().stop()
-        if self._ping_task:
-            try:
-                self.logger.debug("Cancel ping task")
-                self._ping_task.cancel()
-            except BaseException:
-                pass
-        if not self._disconnect_waiter.done():
-            self._disconnect_waiter.cancel()
-        self._disconnect_waiter = None
-
-    def _build_connect_packet(self):
-        vh = ConnectVariableHeader()
-        payload = ConnectPayload()
-
-        vh.keep_alive = self.session.keep_alive
-        vh.clean_session_flag = self.session.clean_session
-        vh.will_retain_flag = self.session.will_retain
-        payload.client_id = self.session.client_id
-
-        if self.session.username:
-            vh.username_flag = True
-            payload.username = self.session.username
-        else:
-            vh.username_flag = False
-
-        if self.session.password:
-            vh.password_flag = True
-            payload.password = self.session.password
-        else:
-            vh.password_flag = False
-        if self.session.will_flag:
-            vh.will_flag = True
-            vh.will_qos = self.session.will_qos
-            payload.will_message = self.session.will_message
-            payload.will_topic = self.session.will_topic
-        else:
-            vh.will_flag = False
-
-        packet = ConnectPacket(vh=vh, payload=payload)
-        return packet
-
-    async def mqtt_connect(self):
-        connect_packet = self._build_connect_packet()
-        await self._send_packet(connect_packet)
-        connack = await ConnackPacket.from_stream(self.reader)
-        await self.plugins_manager.fire_event(
-            EVENT_MQTT_PACKET_RECEIVED, packet=connack, session=self.session
-        )
-        return connack.return_code
-
-    def handle_write_timeout(self):
-        try:
-            if not self._ping_task:
-                self.logger.debug("Scheduling Ping")
-                self._ping_task = asyncio.ensure_future(self.mqtt_ping())
-        except BaseException as be:
-            self.logger.debug("Exception ignored in ping task: %r" % be)
-
-    def handle_read_timeout(self):
-        pass
-
-    async def mqtt_subscribe(self, topics, packet_id):
-        """
-        :param topics: array of topics [{'filter':'/a/b', 'qos': 0x00}, ...]
-        :return:
-        """
-
-        # Build and send SUBSCRIBE message
-        subscribe = SubscribePacket.build(topics, packet_id)
-        await self._send_packet(subscribe)
-
-        # Wait for SUBACK is received
-        waiter = futures.Future(loop=self._loop)
-        self._subscriptions_waiter[subscribe.variable_header.packet_id] = waiter
-        return_codes = await waiter
-
-        del self._subscriptions_waiter[subscribe.variable_header.packet_id]
-        return return_codes
-
-    async 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:
-            self.logger.warning(
-                "Received SUBACK for unknown pending subscription with Id: %s"
-                % packet_id
-            )
-
-    async def mqtt_unsubscribe(self, topics, packet_id):
-        """
-
-        :param topics: array of topics ['/a/b', ...]
-        :return:
-        """
-        unsubscribe = UnsubscribePacket.build(topics, packet_id)
-        await self._send_packet(unsubscribe)
-        waiter = futures.Future(loop=self._loop)
-        self._unsubscriptions_waiter[unsubscribe.variable_header.packet_id] = waiter
-        await waiter
-        del self._unsubscriptions_waiter[unsubscribe.variable_header.packet_id]
-
-    async 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:
-            self.logger.warning(
-                "Received UNSUBACK for unknown pending subscription with Id: %s"
-                % packet_id
-            )
-
-    async def mqtt_disconnect(self):
-        disconnect_packet = DisconnectPacket()
-        await self._send_packet(disconnect_packet)
-
-    async def mqtt_ping(self):
-        ping_packet = PingReqPacket()
-        await self._send_packet(ping_packet)
-        resp = await self._pingresp_queue.get()
-        if self._ping_task:
-            self._ping_task = None
-        return resp
-
-    async def handle_pingresp(self, pingresp: PingRespPacket):
-        await self._pingresp_queue.put(pingresp)
-
-    async def handle_connection_closed(self):
-        self.logger.debug("Broker closed connection")
-        if not self._disconnect_waiter.done():
-            self._disconnect_waiter.set_result(None)
-
-    async def wait_disconnect(self):
-        await self._disconnect_waiter
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/protocol/handler.py b/hbmqtt/mqtt/protocol/handler.py
index 0145903c..547a42ec 100644
--- a/hbmqtt/mqtt/protocol/handler.py
+++ b/hbmqtt/mqtt/protocol/handler.py
@@ -1,676 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import logging
-import collections
-import itertools
+import warnings
+from amqtt.mqtt.protocol.handler import *
 
-import asyncio
-from asyncio import InvalidStateError
-
-from hbmqtt.mqtt import packet_class
-from hbmqtt.mqtt.connack import ConnackPacket
-from hbmqtt.mqtt.connect import ConnectPacket
-from hbmqtt.mqtt.packet import (
-    RESERVED_0,
-    CONNECT,
-    CONNACK,
-    PUBLISH,
-    PUBACK,
-    PUBREC,
-    PUBREL,
-    PUBCOMP,
-    SUBSCRIBE,
-    SUBACK,
-    UNSUBSCRIBE,
-    UNSUBACK,
-    PINGREQ,
-    PINGRESP,
-    DISCONNECT,
-    RESERVED_15,
-    MQTTFixedHeader,
-)
-from hbmqtt.mqtt.pingresp import PingRespPacket
-from hbmqtt.mqtt.pingreq import PingReqPacket
-from hbmqtt.mqtt.publish import PublishPacket
-from hbmqtt.mqtt.pubrel import PubrelPacket
-from hbmqtt.mqtt.puback import PubackPacket
-from hbmqtt.mqtt.pubrec import PubrecPacket
-from hbmqtt.mqtt.pubcomp import PubcompPacket
-from hbmqtt.mqtt.suback import SubackPacket
-from hbmqtt.mqtt.subscribe import SubscribePacket
-from hbmqtt.mqtt.unsubscribe import UnsubscribePacket
-from hbmqtt.mqtt.unsuback import UnsubackPacket
-from hbmqtt.mqtt.disconnect import DisconnectPacket
-from hbmqtt.adapters import ReaderAdapter, WriterAdapter
-from hbmqtt.session import (
-    Session,
-    OutgoingApplicationMessage,
-    IncomingApplicationMessage,
-    INCOMING,
-    OUTGOING,
-)
-from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
-from hbmqtt.plugins.manager import PluginManager
-from hbmqtt.errors import HBMQTTException, MQTTException, NoDataException
-
-
-EVENT_MQTT_PACKET_SENT = "mqtt_packet_sent"
-EVENT_MQTT_PACKET_RECEIVED = "mqtt_packet_received"
-
-
-class ProtocolHandlerException(BaseException):
-    pass
-
-
-class ProtocolHandler:
-    """
-    Class implementing the MQTT communication protocol using asyncio features
-    """
-
-    def __init__(
-        self, plugins_manager: PluginManager, session: Session = None, loop=None
-    ):
-        self.logger = logging.getLogger(__name__)
-        if session:
-            self._init_session(session)
-        else:
-            self.session = None
-        self.reader = None
-        self.writer = None
-        self.plugins_manager = plugins_manager
-
-        if loop is None:
-            self._loop = asyncio.get_event_loop()
-        else:
-            self._loop = loop
-        self._reader_task = None
-        self._keepalive_task = None
-        self._reader_ready = None
-        self._reader_stopped = asyncio.Event(loop=self._loop)
-
-        self._puback_waiters = dict()
-        self._pubrec_waiters = dict()
-        self._pubrel_waiters = dict()
-        self._pubcomp_waiters = dict()
-
-        self._write_lock = asyncio.Lock(loop=self._loop)
-
-    def _init_session(self, session: Session):
-        assert session
-        log = logging.getLogger(__name__)
-        self.session = session
-        self.logger = logging.LoggerAdapter(log, {"client_id": self.session.client_id})
-        self.keepalive_timeout = self.session.keep_alive
-        if self.keepalive_timeout <= 0:
-            self.keepalive_timeout = None
-
-    def attach(self, session, reader: ReaderAdapter, writer: WriterAdapter):
-        if self.session:
-            raise ProtocolHandlerException("Handler is already attached to a session")
-        self._init_session(session)
-        self.reader = reader
-        self.writer = writer
-
-    def detach(self):
-        self.session = None
-        self.reader = None
-        self.writer = None
-
-    def _is_attached(self):
-        if self.session:
-            return True
-        else:
-            return False
-
-    async def start(self):
-        if not self._is_attached():
-            raise ProtocolHandlerException("Handler is not attached to a stream")
-        self._reader_ready = asyncio.Event(loop=self._loop)
-        self._reader_task = asyncio.Task(self._reader_loop(), loop=self._loop)
-        await asyncio.wait([self._reader_ready.wait()], loop=self._loop)
-        if self.keepalive_timeout:
-            self._keepalive_task = self._loop.call_later(
-                self.keepalive_timeout, self.handle_write_timeout
-            )
-
-        self.logger.debug("Handler tasks started")
-        await self._retry_deliveries()
-        self.logger.debug("Handler ready")
-
-    async def stop(self):
-        # Stop messages flow waiter
-        self._stop_waiters()
-        if self._keepalive_task:
-            self._keepalive_task.cancel()
-        self.logger.debug("waiting for tasks to be stopped")
-        if not self._reader_task.done():
-            self._reader_task.cancel()
-            await asyncio.wait([self._reader_stopped.wait()], loop=self._loop)
-        self.logger.debug("closing writer")
-        try:
-            await self.writer.close()
-        except Exception as e:
-            self.logger.debug("Handler writer close failed: %s" % e)
-
-    def _stop_waiters(self):
-        self.logger.debug("Stopping %d puback waiters" % len(self._puback_waiters))
-        self.logger.debug("Stopping %d pucomp waiters" % len(self._pubcomp_waiters))
-        self.logger.debug("Stopping %d purec waiters" % len(self._pubrec_waiters))
-        self.logger.debug("Stopping %d purel waiters" % len(self._pubrel_waiters))
-        for waiter in itertools.chain(
-            self._puback_waiters.values(),
-            self._pubcomp_waiters.values(),
-            self._pubrec_waiters.values(),
-            self._pubrel_waiters.values(),
-        ):
-            waiter.cancel()
-
-    async def _retry_deliveries(self):
-        """
-        Handle [MQTT-4.4.0-1] by resending PUBLISH and PUBREL messages for pending out messages
-        :return:
-        """
-        self.logger.debug("Begin messages delivery retries")
-        tasks = []
-        for message in itertools.chain(
-            self.session.inflight_in.values(), self.session.inflight_out.values()
-        ):
-            tasks.append(
-                asyncio.wait_for(
-                    self._handle_message_flow(message), 10, loop=self._loop
-                )
-            )
-        if tasks:
-            done, pending = await asyncio.wait(tasks, loop=self._loop)
-            self.logger.debug("%d messages redelivered" % len(done))
-            self.logger.debug(
-                "%d messages not redelivered due to timeout" % len(pending)
-            )
-        self.logger.debug("End messages delivery retries")
-
-    async def mqtt_publish(self, topic, data, qos, retain, ack_timeout=None):
-        """
-        Sends a MQTT publish message and manages messages flows.
-        This methods doesn't return until the message has been acknowledged by receiver or timeout occur
-        :param topic: MQTT topic to publish
-        :param data:  data to send on topic
-        :param qos: quality of service to use for message flow. Can be QOS_0, QOS_1 or QOS_2
-        :param retain: retain message flag
-        :param ack_timeout: acknowledge timeout. If set, this method will return a TimeOut error if the acknowledgment
-        is not completed before ack_timeout second
-        :return: ApplicationMessage used during inflight operations
-        """
-        if qos in (QOS_1, QOS_2):
-            packet_id = self.session.next_packet_id
-            if packet_id in self.session.inflight_out:
-                raise HBMQTTException(
-                    "A message with the same packet ID '%d' is already in flight"
-                    % packet_id
-                )
-        else:
-            packet_id = None
-
-        message = OutgoingApplicationMessage(packet_id, topic, qos, data, retain)
-        # Handle message flow
-        if ack_timeout is not None and ack_timeout > 0:
-            await asyncio.wait_for(
-                self._handle_message_flow(message), ack_timeout, loop=self._loop
-            )
-        else:
-            await self._handle_message_flow(message)
-
-        return message
-
-    async def _handle_message_flow(self, app_message):
-        """
-        Handle protocol flow for incoming and outgoing messages, depending on service level and according to MQTT
-        spec. paragraph 4.3-Quality of Service levels and protocol flows
-        :param app_message: PublishMessage to handle
-        :return: nothing.
-        """
-        if app_message.qos == QOS_0:
-            await self._handle_qos0_message_flow(app_message)
-        elif app_message.qos == QOS_1:
-            await self._handle_qos1_message_flow(app_message)
-        elif app_message.qos == QOS_2:
-            await self._handle_qos2_message_flow(app_message)
-        else:
-            raise HBMQTTException("Unexcepted QOS value '%d" % str(app_message.qos))
-
-    async def _handle_qos0_message_flow(self, app_message):
-        """
-        Handle QOS_0 application message acknowledgment
-        For incoming messages, this method stores the message
-        For outgoing messages, this methods sends PUBLISH
-        :param app_message:
-        :return:
-        """
-        assert app_message.qos == QOS_0
-        if app_message.direction == OUTGOING:
-            packet = app_message.build_publish_packet()
-            # Send PUBLISH packet
-            await self._send_packet(packet)
-            app_message.publish_packet = packet
-        elif app_message.direction == INCOMING:
-            if app_message.publish_packet.dup_flag:
-                self.logger.warning(
-                    "[MQTT-3.3.1-2] DUP flag must set to 0 for QOS 0 message. Message ignored: %s"
-                    % repr(app_message.publish_packet)
-                )
-            else:
-                try:
-                    self.session.delivered_message_queue.put_nowait(app_message)
-                except:
-                    self.logger.warning(
-                        "delivered messages queue full. QOS_0 message discarded"
-                    )
-
-    async def _handle_qos1_message_flow(self, app_message):
-        """
-        Handle QOS_1 application message acknowledgment
-        For incoming messages, this method stores the message and reply with PUBACK
-        For outgoing messages, this methods sends PUBLISH and waits for the corresponding PUBACK
-        :param app_message:
-        :return:
-        """
-        assert app_message.qos == QOS_1
-        if app_message.puback_packet:
-            raise HBMQTTException(
-                "Message '%d' has already been acknowledged" % app_message.packet_id
-            )
-        if app_message.direction == OUTGOING:
-            if app_message.packet_id not in self.session.inflight_out:
-                # Store message in session
-                self.session.inflight_out[app_message.packet_id] = app_message
-            if app_message.publish_packet is not None:
-                # A Publish packet has already been sent, this is a retry
-                publish_packet = app_message.build_publish_packet(dup=True)
-            else:
-                publish_packet = app_message.build_publish_packet()
-            # Send PUBLISH packet
-            await self._send_packet(publish_packet)
-            app_message.publish_packet = publish_packet
-
-            # Wait for puback
-            waiter = asyncio.Future(loop=self._loop)
-            self._puback_waiters[app_message.packet_id] = waiter
-            await waiter
-            del self._puback_waiters[app_message.packet_id]
-            app_message.puback_packet = waiter.result()
-
-            # Discard inflight message
-            del self.session.inflight_out[app_message.packet_id]
-        elif app_message.direction == INCOMING:
-            # Initiate delivery
-            self.logger.debug("Add message to delivery")
-            await self.session.delivered_message_queue.put(app_message)
-            # Send PUBACK
-            puback = PubackPacket.build(app_message.packet_id)
-            await self._send_packet(puback)
-            app_message.puback_packet = puback
-
-    async def _handle_qos2_message_flow(self, app_message):
-        """
-        Handle QOS_2 application message acknowledgment
-        For incoming messages, this method stores the message, sends PUBREC, waits for PUBREL, initiate delivery
-        and send PUBCOMP
-        For outgoing messages, this methods sends PUBLISH, waits for PUBREC, discards messages and wait for PUBCOMP
-        :param app_message:
-        :return:
-        """
-        assert app_message.qos == QOS_2
-        if app_message.direction == OUTGOING:
-            if app_message.pubrel_packet and app_message.pubcomp_packet:
-                raise HBMQTTException(
-                    "Message '%d' has already been acknowledged" % app_message.packet_id
-                )
-            if not app_message.pubrel_packet:
-                # Store message
-                if app_message.publish_packet is not None:
-                    # This is a retry flow, no need to store just check the message exists in session
-                    if app_message.packet_id not in self.session.inflight_out:
-                        raise HBMQTTException(
-                            "Unknown inflight message '%d' in session"
-                            % app_message.packet_id
-                        )
-                    publish_packet = app_message.build_publish_packet(dup=True)
-                else:
-                    # Store message in session
-                    self.session.inflight_out[app_message.packet_id] = app_message
-                    publish_packet = app_message.build_publish_packet()
-                # Send PUBLISH packet
-                await self._send_packet(publish_packet)
-                app_message.publish_packet = publish_packet
-                # Wait PUBREC
-                if app_message.packet_id in self._pubrec_waiters:
-                    # PUBREC waiter already exists for this packet ID
-                    message = (
-                        "Can't add PUBREC waiter, a waiter already exists for message Id '%s'"
-                        % app_message.packet_id
-                    )
-                    self.logger.warning(message)
-                    raise HBMQTTException(message)
-                waiter = asyncio.Future(loop=self._loop)
-                self._pubrec_waiters[app_message.packet_id] = waiter
-                await waiter
-                del self._pubrec_waiters[app_message.packet_id]
-                app_message.pubrec_packet = waiter.result()
-            if not app_message.pubcomp_packet:
-                # Send pubrel
-                app_message.pubrel_packet = PubrelPacket.build(app_message.packet_id)
-                await self._send_packet(app_message.pubrel_packet)
-                # Wait for PUBCOMP
-                waiter = asyncio.Future(loop=self._loop)
-                self._pubcomp_waiters[app_message.packet_id] = waiter
-                await waiter
-                del self._pubcomp_waiters[app_message.packet_id]
-                app_message.pubcomp_packet = waiter.result()
-            # Discard inflight message
-            del self.session.inflight_out[app_message.packet_id]
-        elif app_message.direction == INCOMING:
-            self.session.inflight_in[app_message.packet_id] = app_message
-            # Send pubrec
-            pubrec_packet = PubrecPacket.build(app_message.packet_id)
-            await self._send_packet(pubrec_packet)
-            app_message.pubrec_packet = pubrec_packet
-            # Wait PUBREL
-            if (
-                app_message.packet_id in self._pubrel_waiters
-                and not self._pubrel_waiters[app_message.packet_id].done()
-            ):
-                # PUBREL waiter already exists for this packet ID
-                message = (
-                    "A waiter already exists for message Id '%s', canceling it"
-                    % app_message.packet_id
-                )
-                self.logger.warning(message)
-                self._pubrel_waiters[app_message.packet_id].cancel()
-            try:
-                waiter = asyncio.Future(loop=self._loop)
-                self._pubrel_waiters[app_message.packet_id] = waiter
-                await waiter
-                del self._pubrel_waiters[app_message.packet_id]
-                app_message.pubrel_packet = waiter.result()
-                # Initiate delivery and discard message
-                await self.session.delivered_message_queue.put(app_message)
-                del self.session.inflight_in[app_message.packet_id]
-                # Send pubcomp
-                pubcomp_packet = PubcompPacket.build(app_message.packet_id)
-                await self._send_packet(pubcomp_packet)
-                app_message.pubcomp_packet = pubcomp_packet
-            except asyncio.CancelledError:
-                self.logger.debug("Message flow cancelled")
-
-    async def _reader_loop(self):
-        self.logger.debug("%s Starting reader coro" % self.session.client_id)
-        running_tasks = collections.deque()
-        keepalive_timeout = self.session.keep_alive
-        if keepalive_timeout <= 0:
-            keepalive_timeout = None
-        while True:
-            try:
-                self._reader_ready.set()
-                while running_tasks and running_tasks[0].done():
-                    running_tasks.popleft()
-                if len(running_tasks) > 1:
-                    self.logger.debug("handler running tasks: %d" % len(running_tasks))
-
-                fixed_header = await asyncio.wait_for(
-                    MQTTFixedHeader.from_stream(self.reader),
-                    keepalive_timeout,
-                    loop=self._loop,
-                )
-                if fixed_header:
-                    if (
-                        fixed_header.packet_type == RESERVED_0
-                        or fixed_header.packet_type == RESERVED_15
-                    ):
-                        self.logger.warning(
-                            "%s Received reserved packet, which is forbidden: closing connection"
-                            % (self.session.client_id)
-                        )
-                        await self.handle_connection_closed()
-                    else:
-                        cls = packet_class(fixed_header)
-                        packet = await cls.from_stream(
-                            self.reader, fixed_header=fixed_header
-                        )
-                        await self.plugins_manager.fire_event(
-                            EVENT_MQTT_PACKET_RECEIVED,
-                            packet=packet,
-                            session=self.session,
-                        )
-                        task = None
-                        if packet.fixed_header.packet_type == CONNACK:
-                            task = asyncio.ensure_future(
-                                self.handle_connack(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == SUBSCRIBE:
-                            task = asyncio.ensure_future(
-                                self.handle_subscribe(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == UNSUBSCRIBE:
-                            task = asyncio.ensure_future(
-                                self.handle_unsubscribe(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == SUBACK:
-                            task = asyncio.ensure_future(
-                                self.handle_suback(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == UNSUBACK:
-                            task = asyncio.ensure_future(
-                                self.handle_unsuback(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == PUBACK:
-                            task = asyncio.ensure_future(
-                                self.handle_puback(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == PUBREC:
-                            task = asyncio.ensure_future(
-                                self.handle_pubrec(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == PUBREL:
-                            task = asyncio.ensure_future(
-                                self.handle_pubrel(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == PUBCOMP:
-                            task = asyncio.ensure_future(
-                                self.handle_pubcomp(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == PINGREQ:
-                            task = asyncio.ensure_future(
-                                self.handle_pingreq(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == PINGRESP:
-                            task = asyncio.ensure_future(
-                                self.handle_pingresp(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == PUBLISH:
-                            task = asyncio.ensure_future(
-                                self.handle_publish(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == DISCONNECT:
-                            task = asyncio.ensure_future(
-                                self.handle_disconnect(packet), loop=self._loop
-                            )
-                        elif packet.fixed_header.packet_type == CONNECT:
-                            self.handle_connect(packet)
-                        else:
-                            self.logger.warning(
-                                "%s Unhandled packet type: %s"
-                                % (
-                                    self.session.client_id,
-                                    packet.fixed_header.packet_type,
-                                )
-                            )
-                        if task:
-                            running_tasks.append(task)
-                else:
-                    self.logger.debug(
-                        "%s No more data (EOF received), stopping reader coro"
-                        % self.session.client_id
-                    )
-                    break
-            except MQTTException:
-                self.logger.debug("Message discarded")
-            except asyncio.CancelledError:
-                self.logger.debug("Task cancelled, reader loop ending")
-                break
-            except asyncio.TimeoutError:
-                self.logger.debug(
-                    "%s Input stream read timeout" % self.session.client_id
-                )
-                self.handle_read_timeout()
-            except NoDataException:
-                self.logger.debug("%s No data available" % self.session.client_id)
-            except BaseException as e:
-                self.logger.warning(
-                    "%s Unhandled exception in reader coro: %r"
-                    % (type(self).__name__, e)
-                )
-                break
-        while running_tasks:
-            running_tasks.popleft().cancel()
-        await self.handle_connection_closed()
-        self._reader_stopped.set()
-        self.logger.debug("Reader coro stopped")
-        await self.stop()
-
-    async def _send_packet(self, packet):
-        try:
-            async with self._write_lock:
-                await packet.to_stream(self.writer)
-            if self._keepalive_task:
-                self._keepalive_task.cancel()
-                self._keepalive_task = self._loop.call_later(
-                    self.keepalive_timeout, self.handle_write_timeout
-                )
-
-            await self.plugins_manager.fire_event(
-                EVENT_MQTT_PACKET_SENT, packet=packet, session=self.session
-            )
-        except (ConnectionResetError, BrokenPipeError):
-            await self.handle_connection_closed()
-        except asyncio.CancelledError:
-            raise
-        except BaseException as e:
-            self.logger.warning("Unhandled exception: %s" % e)
-            raise
-
-    async def mqtt_deliver_next_message(self):
-        if not self._is_attached():
-            return None
-        if self.logger.isEnabledFor(logging.DEBUG):
-            self.logger.debug(
-                "%d message(s) available for delivery"
-                % self.session.delivered_message_queue.qsize()
-            )
-        try:
-            message = await self.session.delivered_message_queue.get()
-        except asyncio.CancelledError:
-            message = None
-        if self.logger.isEnabledFor(logging.DEBUG):
-            self.logger.debug("Delivering message %s" % message)
-        return message
-
-    def handle_write_timeout(self):
-        self.logger.debug("%s write timeout unhandled" % self.session.client_id)
-
-    def handle_read_timeout(self):
-        self.logger.debug("%s read timeout unhandled" % self.session.client_id)
-
-    async def handle_connack(self, connack: ConnackPacket):
-        self.logger.debug("%s CONNACK unhandled" % self.session.client_id)
-
-    async def handle_connect(self, connect: ConnectPacket):
-        self.logger.debug("%s CONNECT unhandled" % self.session.client_id)
-
-    async def handle_subscribe(self, subscribe: SubscribePacket):
-        self.logger.debug("%s SUBSCRIBE unhandled" % self.session.client_id)
-
-    async def handle_unsubscribe(self, subscribe: UnsubscribePacket):
-        self.logger.debug("%s UNSUBSCRIBE unhandled" % self.session.client_id)
-
-    async def handle_suback(self, suback: SubackPacket):
-        self.logger.debug("%s SUBACK unhandled" % self.session.client_id)
-
-    async def handle_unsuback(self, unsuback: UnsubackPacket):
-        self.logger.debug("%s UNSUBACK unhandled" % self.session.client_id)
-
-    async def handle_pingresp(self, pingresp: PingRespPacket):
-        self.logger.debug("%s PINGRESP unhandled" % self.session.client_id)
-
-    async def handle_pingreq(self, pingreq: PingReqPacket):
-        self.logger.debug("%s PINGREQ unhandled" % self.session.client_id)
-
-    async def handle_disconnect(self, disconnect: DisconnectPacket):
-        self.logger.debug("%s DISCONNECT unhandled" % self.session.client_id)
-
-    async def handle_connection_closed(self):
-        self.logger.debug("%s Connection closed unhandled" % self.session.client_id)
-
-    async def handle_puback(self, puback: PubackPacket):
-        packet_id = puback.variable_header.packet_id
-        try:
-            waiter = self._puback_waiters[packet_id]
-            waiter.set_result(puback)
-        except KeyError:
-            self.logger.warning(
-                "Received PUBACK for unknown pending message Id: '%d'" % packet_id
-            )
-        except InvalidStateError:
-            self.logger.warning("PUBACK waiter with Id '%d' already done" % packet_id)
-
-    async def handle_pubrec(self, pubrec: PubrecPacket):
-        packet_id = pubrec.packet_id
-        try:
-            waiter = self._pubrec_waiters[packet_id]
-            waiter.set_result(pubrec)
-        except KeyError:
-            self.logger.warning(
-                "Received PUBREC for unknown pending message with Id: %d" % packet_id
-            )
-        except InvalidStateError:
-            self.logger.warning("PUBREC waiter with Id '%d' already done" % packet_id)
-
-    async def handle_pubcomp(self, pubcomp: PubcompPacket):
-        packet_id = pubcomp.packet_id
-        try:
-            waiter = self._pubcomp_waiters[packet_id]
-            waiter.set_result(pubcomp)
-        except KeyError:
-            self.logger.warning(
-                "Received PUBCOMP for unknown pending message with Id: %d" % packet_id
-            )
-        except InvalidStateError:
-            self.logger.warning("PUBCOMP waiter with Id '%d' already done" % packet_id)
-
-    async def handle_pubrel(self, pubrel: PubrelPacket):
-        packet_id = pubrel.packet_id
-        try:
-            waiter = self._pubrel_waiters[packet_id]
-            waiter.set_result(pubrel)
-        except KeyError:
-            self.logger.warning(
-                "Received PUBREL for unknown pending message with Id: %d" % packet_id
-            )
-        except InvalidStateError:
-            self.logger.warning("PUBREL waiter with Id '%d' already done" % packet_id)
-
-    async def handle_publish(self, publish_packet: PublishPacket):
-        packet_id = publish_packet.variable_header.packet_id
-        qos = publish_packet.qos
-
-        incoming_message = IncomingApplicationMessage(
-            packet_id,
-            publish_packet.topic_name,
-            qos,
-            publish_packet.data,
-            publish_packet.retain_flag,
-        )
-        incoming_message.publish_packet = publish_packet
-        await self._handle_message_flow(incoming_message)
-        self.logger.debug(
-            "Message queue size: %d" % self.session.delivered_message_queue.qsize()
-        )
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/puback.py b/hbmqtt/mqtt/puback.py
index 78ddfae0..0636c0e1 100644
--- a/hbmqtt/mqtt/puback.py
+++ b/hbmqtt/mqtt/puback.py
@@ -1,47 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    PUBACK,
-    PacketIdVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.puback import *
 
-
-class PubackPacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = None
-
-    @property
-    def packet_id(self):
-        return self.variable_header.packet_id
-
-    @packet_id.setter
-    def packet_id(self, val: int):
-        self.variable_header.packet_id = val
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(PUBACK, 0x00)
-        else:
-            if fixed.packet_type is not PUBACK:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for PubackPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = None
-
-    @classmethod
-    def build(cls, packet_id: int):
-        v_header = PacketIdVariableHeader(packet_id)
-        packet = PubackPacket(variable_header=v_header)
-        return packet
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/pubcomp.py b/hbmqtt/mqtt/pubcomp.py
index ace5174c..0b60138b 100644
--- a/hbmqtt/mqtt/pubcomp.py
+++ b/hbmqtt/mqtt/pubcomp.py
@@ -1,47 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    PUBCOMP,
-    PacketIdVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.pubcomp import *
 
-
-class PubcompPacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = None
-
-    @property
-    def packet_id(self):
-        return self.variable_header.packet_id
-
-    @packet_id.setter
-    def packet_id(self, val: int):
-        self.variable_header.packet_id = val
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(PUBCOMP, 0x00)
-        else:
-            if fixed.packet_type is not PUBCOMP:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for PubcompPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = None
-
-    @classmethod
-    def build(cls, packet_id: int):
-        v_header = PacketIdVariableHeader(packet_id)
-        packet = PubcompPacket(variable_header=v_header)
-        return packet
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/publish.py b/hbmqtt/mqtt/publish.py
index e016ce37..92abd096 100644
--- a/hbmqtt/mqtt/publish.py
+++ b/hbmqtt/mqtt/publish.py
@@ -1,192 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import asyncio
+import warnings
+from amqtt.mqtt.publish import *
 
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    PUBLISH,
-    MQTTVariableHeader,
-    MQTTPayload,
-)
-from hbmqtt.errors import HBMQTTException, MQTTException
-from hbmqtt.codecs import decode_packet_id, decode_string, encode_string, int_to_bytes
-
-
-class PublishVariableHeader(MQTTVariableHeader):
-
-    __slots__ = ("topic_name", "packet_id")
-
-    def __init__(self, topic_name: str, packet_id: int = None):
-        super().__init__()
-        if "*" in topic_name:
-            raise MQTTException(
-                "[MQTT-3.3.2-2] Topic name in the PUBLISH Packet MUST NOT contain wildcard characters."
-            )
-        self.topic_name = topic_name
-        self.packet_id = packet_id
-
-    def __repr__(self):
-        return type(self).__name__ + "(topic={0}, packet_id={1})".format(
-            self.topic_name, self.packet_id
-        )
-
-    def to_bytes(self):
-        out = bytearray()
-        out.extend(encode_string(self.topic_name))
-        if self.packet_id is not None:
-            out.extend(int_to_bytes(self.packet_id, 2))
-        return out
-
-    @classmethod
-    async def from_stream(
-        cls, reader: asyncio.StreamReader, fixed_header: MQTTFixedHeader
-    ):
-        topic_name = await decode_string(reader)
-        has_qos = (fixed_header.flags >> 1) & 0x03
-        if has_qos:
-            packet_id = await decode_packet_id(reader)
-        else:
-            packet_id = None
-        return cls(topic_name, packet_id)
-
-
-class PublishPayload(MQTTPayload):
-
-    __slots__ = ("data",)
-
-    def __init__(self, data: bytes = None):
-        super().__init__()
-        self.data = data
-
-    def to_bytes(
-        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
-    ):
-        return self.data
-
-    @classmethod
-    async def from_stream(
-        cls,
-        reader: asyncio.StreamReader,
-        fixed_header: MQTTFixedHeader,
-        variable_header: MQTTVariableHeader,
-    ):
-        data = bytearray()
-        data_length = fixed_header.remaining_length - variable_header.bytes_length
-        length_read = 0
-        while length_read < data_length:
-            buffer = await reader.read(data_length - length_read)
-            data.extend(buffer)
-            length_read = len(data)
-        return cls(data)
-
-    def __repr__(self):
-        return type(self).__name__ + "(data={0!r})".format(repr(self.data))
-
-
-class PublishPacket(MQTTPacket):
-    VARIABLE_HEADER = PublishVariableHeader
-    PAYLOAD = PublishPayload
-
-    DUP_FLAG = 0x08
-    RETAIN_FLAG = 0x01
-    QOS_FLAG = 0x06
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PublishVariableHeader = None,
-        payload=None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(PUBLISH, 0x00)
-        else:
-            if fixed.packet_type is not PUBLISH:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for PublishPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = payload
-
-    def set_flags(self, dup_flag=False, qos=0, retain_flag=False):
-        self.dup_flag = dup_flag
-        self.retain_flag = retain_flag
-        self.qos = qos
-
-    def _set_header_flag(self, val, mask):
-        if val:
-            self.fixed_header.flags |= mask
-        else:
-            self.fixed_header.flags &= ~mask
-
-    def _get_header_flag(self, mask):
-        if self.fixed_header.flags & mask:
-            return True
-        else:
-            return False
-
-    @property
-    def dup_flag(self) -> bool:
-        return self._get_header_flag(self.DUP_FLAG)
-
-    @dup_flag.setter
-    def dup_flag(self, val: bool):
-        self._set_header_flag(val, self.DUP_FLAG)
-
-    @property
-    def retain_flag(self) -> bool:
-        return self._get_header_flag(self.RETAIN_FLAG)
-
-    @retain_flag.setter
-    def retain_flag(self, val: bool):
-        self._set_header_flag(val, self.RETAIN_FLAG)
-
-    @property
-    def qos(self):
-        return (self.fixed_header.flags & self.QOS_FLAG) >> 1
-
-    @qos.setter
-    def qos(self, val: int):
-        self.fixed_header.flags &= 0xF9
-        self.fixed_header.flags |= val << 1
-
-    @property
-    def packet_id(self):
-        return self.variable_header.packet_id
-
-    @packet_id.setter
-    def packet_id(self, val: int):
-        self.variable_header.packet_id = val
-
-    @property
-    def data(self):
-        return self.payload.data
-
-    @data.setter
-    def data(self, data: bytes):
-        self.payload.data = data
-
-    @property
-    def topic_name(self):
-        return self.variable_header.topic_name
-
-    @topic_name.setter
-    def topic_name(self, name: str):
-        self.variable_header.topic_name = name
-
-    @classmethod
-    def build(
-        cls, topic_name: str, message: bytes, packet_id: int, dup_flag, qos, retain
-    ):
-        v_header = PublishVariableHeader(topic_name, packet_id)
-        payload = PublishPayload(message)
-        packet = PublishPacket(variable_header=v_header, payload=payload)
-        packet.dup_flag = dup_flag
-        packet.retain_flag = retain
-        packet.qos = qos
-        return packet
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/pubrec.py b/hbmqtt/mqtt/pubrec.py
index eb3e7649..5aef64e7 100644
--- a/hbmqtt/mqtt/pubrec.py
+++ b/hbmqtt/mqtt/pubrec.py
@@ -1,47 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    PUBREC,
-    PacketIdVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.pubrec import *
 
-
-class PubrecPacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = None
-
-    @property
-    def packet_id(self):
-        return self.variable_header.packet_id
-
-    @packet_id.setter
-    def packet_id(self, val: int):
-        self.variable_header.packet_id = val
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(PUBREC, 0x00)
-        else:
-            if fixed.packet_type is not PUBREC:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for PubrecPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = None
-
-    @classmethod
-    def build(cls, packet_id: int):
-        v_header = PacketIdVariableHeader(packet_id)
-        packet = PubrecPacket(variable_header=v_header)
-        return packet
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/pubrel.py b/hbmqtt/mqtt/pubrel.py
index f69472ce..420cd09c 100644
--- a/hbmqtt/mqtt/pubrel.py
+++ b/hbmqtt/mqtt/pubrel.py
@@ -1,46 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    PUBREL,
-    PacketIdVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.pubrel import *
 
-
-class PubrelPacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = None
-
-    @property
-    def packet_id(self):
-        return self.variable_header.packet_id
-
-    @packet_id.setter
-    def packet_id(self, val: int):
-        self.variable_header.packet_id = val
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(PUBREL, 0x02)  # [MQTT-3.6.1-1]
-        else:
-            if fixed.packet_type is not PUBREL:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for PubrelPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = None
-
-    @classmethod
-    def build(cls, packet_id):
-        variable_header = PacketIdVariableHeader(packet_id)
-        return PubrelPacket(variable_header=variable_header)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/suback.py b/hbmqtt/mqtt/suback.py
index 29ab4ca9..865ed84d 100644
--- a/hbmqtt/mqtt/suback.py
+++ b/hbmqtt/mqtt/suback.py
@@ -1,90 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    SUBACK,
-    PacketIdVariableHeader,
-    MQTTPayload,
-    MQTTVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException, NoDataException
-from hbmqtt.adapters import ReaderAdapter
-from hbmqtt.codecs import bytes_to_int, int_to_bytes, read_or_raise
+import warnings
+from amqtt.mqtt.suback import *
 
-
-class SubackPayload(MQTTPayload):
-
-    __slots__ = ("return_codes",)
-
-    RETURN_CODE_00 = 0x00
-    RETURN_CODE_01 = 0x01
-    RETURN_CODE_02 = 0x02
-    RETURN_CODE_80 = 0x80
-
-    def __init__(self, return_codes=None):
-        super().__init__()
-        self.return_codes = return_codes or []
-
-    def __repr__(self):
-        return type(self).__name__ + "(return_codes={0})".format(
-            repr(self.return_codes)
-        )
-
-    def to_bytes(
-        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
-    ):
-        out = b""
-        for return_code in self.return_codes:
-            out += int_to_bytes(return_code, 1)
-        return out
-
-    @classmethod
-    async def from_stream(
-        cls,
-        reader: ReaderAdapter,
-        fixed_header: MQTTFixedHeader,
-        variable_header: MQTTVariableHeader,
-    ):
-        return_codes = []
-        bytes_to_read = fixed_header.remaining_length - variable_header.bytes_length
-        for i in range(0, bytes_to_read):
-            try:
-                return_code_byte = await read_or_raise(reader, 1)
-                return_code = bytes_to_int(return_code_byte)
-                return_codes.append(return_code)
-            except NoDataException:
-                break
-        return cls(return_codes)
-
-
-class SubackPacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = SubackPayload
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-        payload=None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(SUBACK, 0x00)
-        else:
-            if fixed.packet_type is not SUBACK:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for SubackPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = payload
-
-    @classmethod
-    def build(cls, packet_id, return_codes):
-        variable_header = cls.VARIABLE_HEADER(packet_id)
-        payload = cls.PAYLOAD(return_codes)
-        return cls(variable_header=variable_header, payload=payload)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/subscribe.py b/hbmqtt/mqtt/subscribe.py
index 864447c1..0959e393 100644
--- a/hbmqtt/mqtt/subscribe.py
+++ b/hbmqtt/mqtt/subscribe.py
@@ -1,94 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import asyncio
+import warnings
+from amqtt.mqtt.subscribe import *
 
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    SUBSCRIBE,
-    PacketIdVariableHeader,
-    MQTTPayload,
-    MQTTVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException, NoDataException
-from hbmqtt.codecs import (
-    bytes_to_int,
-    decode_string,
-    encode_string,
-    int_to_bytes,
-    read_or_raise,
-)
-
-
-class SubscribePayload(MQTTPayload):
-
-    __slots__ = ("topics",)
-
-    def __init__(self, topics=None):
-        super().__init__()
-        self.topics = topics or []
-
-    def to_bytes(
-        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
-    ):
-        out = b""
-        for topic in self.topics:
-            out += encode_string(topic[0])
-            out += int_to_bytes(topic[1], 1)
-        return out
-
-    @classmethod
-    async def from_stream(
-        cls,
-        reader: asyncio.StreamReader,
-        fixed_header: MQTTFixedHeader,
-        variable_header: MQTTVariableHeader,
-    ):
-        topics = []
-        payload_length = fixed_header.remaining_length - variable_header.bytes_length
-        read_bytes = 0
-        while read_bytes < payload_length:
-            try:
-                topic = await decode_string(reader)
-                qos_byte = await read_or_raise(reader, 1)
-                qos = bytes_to_int(qos_byte)
-                topics.append((topic, qos))
-                read_bytes += 2 + len(topic.encode("utf-8")) + 1
-            except NoDataException:
-                break
-        return cls(topics)
-
-    def __repr__(self):
-        return type(self).__name__ + "(topics={0!r})".format(self.topics)
-
-
-class SubscribePacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = SubscribePayload
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-        payload=None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(SUBSCRIBE, 0x02)  # [MQTT-3.8.1-1]
-        else:
-            if fixed.packet_type is not SUBSCRIBE:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for SubscribePacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = payload
-
-    @classmethod
-    def build(cls, topics, packet_id):
-        v_header = PacketIdVariableHeader(packet_id)
-        payload = SubscribePayload(topics)
-        return SubscribePacket(variable_header=v_header, payload=payload)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/unsuback.py b/hbmqtt/mqtt/unsuback.py
index b7ed88da..304b4ba1 100644
--- a/hbmqtt/mqtt/unsuback.py
+++ b/hbmqtt/mqtt/unsuback.py
@@ -1,40 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    UNSUBACK,
-    PacketIdVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.mqtt.unsuback import *
 
-
-class UnsubackPacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = None
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-        payload=None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(UNSUBACK, 0x00)
-        else:
-            if fixed.packet_type is not UNSUBACK:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for UnsubackPacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = payload
-
-    @classmethod
-    def build(cls, packet_id):
-        variable_header = PacketIdVariableHeader(packet_id)
-        return cls(variable_header=variable_header)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/mqtt/unsubscribe.py b/hbmqtt/mqtt/unsubscribe.py
index a289e246..379aa170 100644
--- a/hbmqtt/mqtt/unsubscribe.py
+++ b/hbmqtt/mqtt/unsubscribe.py
@@ -1,82 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import asyncio
+import warnings
+from amqtt.mqtt.unsubscribe import *
 
-from hbmqtt.mqtt.packet import (
-    MQTTPacket,
-    MQTTFixedHeader,
-    UNSUBSCRIBE,
-    PacketIdVariableHeader,
-    MQTTPayload,
-    MQTTVariableHeader,
-)
-from hbmqtt.errors import HBMQTTException, NoDataException
-from hbmqtt.codecs import decode_string, encode_string
-
-
-class UnubscribePayload(MQTTPayload):
-
-    __slots__ = ("topics",)
-
-    def __init__(self, topics=None):
-        super().__init__()
-        self.topics = topics or []
-
-    def to_bytes(
-        self, fixed_header: MQTTFixedHeader, variable_header: MQTTVariableHeader
-    ):
-        out = b""
-        for topic in self.topics:
-            out += encode_string(topic)
-        return out
-
-    @classmethod
-    async def from_stream(
-        cls,
-        reader: asyncio.StreamReader,
-        fixed_header: MQTTFixedHeader,
-        variable_header: MQTTVariableHeader,
-    ):
-        topics = []
-        payload_length = fixed_header.remaining_length - variable_header.bytes_length
-        read_bytes = 0
-        while read_bytes < payload_length:
-            try:
-                topic = await decode_string(reader)
-                topics.append(topic)
-                read_bytes += 2 + len(topic.encode("utf-8"))
-            except NoDataException:
-                break
-        return cls(topics)
-
-
-class UnsubscribePacket(MQTTPacket):
-    VARIABLE_HEADER = PacketIdVariableHeader
-    PAYLOAD = UnubscribePayload
-
-    def __init__(
-        self,
-        fixed: MQTTFixedHeader = None,
-        variable_header: PacketIdVariableHeader = None,
-        payload=None,
-    ):
-        if fixed is None:
-            header = MQTTFixedHeader(UNSUBSCRIBE, 0x02)  # [MQTT-3.10.1-1]
-        else:
-            if fixed.packet_type is not UNSUBSCRIBE:
-                raise HBMQTTException(
-                    "Invalid fixed packet type %s for UnsubscribePacket init"
-                    % fixed.packet_type
-                )
-            header = fixed
-
-        super().__init__(header)
-        self.variable_header = variable_header
-        self.payload = payload
-
-    @classmethod
-    def build(cls, topics, packet_id):
-        v_header = PacketIdVariableHeader(packet_id)
-        payload = UnubscribePayload(topics)
-        return UnsubscribePacket(variable_header=v_header, payload=payload)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/__init__.py b/hbmqtt/plugins/__init__.py
index 86831baf..52c82d7f 100644
--- a/hbmqtt/plugins/__init__.py
+++ b/hbmqtt/plugins/__init__.py
@@ -1,3 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.plugins import *
+
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/authentication.py b/hbmqtt/plugins/authentication.py
index a4f92dde..20b733a6 100644
--- a/hbmqtt/plugins/authentication.py
+++ b/hbmqtt/plugins/authentication.py
@@ -1,112 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import logging
-from passlib.apps import custom_app_context as pwd_context
+import warnings
+from amqtt.plugins.authentication import *
 
-
-class BaseAuthPlugin:
-    def __init__(self, context):
-        self.context = context
-        try:
-            self.auth_config = self.context.config["auth"]
-        except KeyError:
-            self.context.logger.warning(
-                "'auth' section not found in context configuration"
-            )
-
-    def authenticate(self, *args, **kwargs):
-        if not self.auth_config:
-            # auth config section not found
-            self.context.logger.warning(
-                "'auth' section not found in context configuration"
-            )
-            return False
-        return True
-
-
-class AnonymousAuthPlugin(BaseAuthPlugin):
-    def __init__(self, context):
-        super().__init__(context)
-
-    async def authenticate(self, *args, **kwargs):
-        authenticated = super().authenticate(*args, **kwargs)
-        if authenticated:
-            allow_anonymous = self.auth_config.get(
-                "allow-anonymous", True
-            )  # allow anonymous by default
-            if allow_anonymous:
-                authenticated = True
-                self.context.logger.debug(
-                    "Authentication success: config allows anonymous"
-                )
-            else:
-                try:
-                    session = kwargs.get("session", None)
-                    authenticated = True if session.username else False
-                    if self.context.logger.isEnabledFor(logging.DEBUG):
-                        if authenticated:
-                            self.context.logger.debug(
-                                "Authentication success: session has a non empty username"
-                            )
-                        else:
-                            self.context.logger.debug(
-                                "Authentication failure: session has an empty username"
-                            )
-                except KeyError:
-                    self.context.logger.warning("Session information not available")
-                    authenticated = False
-        return authenticated
-
-
-class FileAuthPlugin(BaseAuthPlugin):
-    def __init__(self, context):
-        super().__init__(context)
-        self._users = dict()
-        self._read_password_file()
-
-    def _read_password_file(self):
-        password_file = self.auth_config.get("password-file", None)
-        if password_file:
-            try:
-                with open(password_file) as f:
-                    self.context.logger.debug(
-                        "Reading user database from %s" % password_file
-                    )
-                    for line in f:
-                        line = line.strip()
-                        if not line.startswith("#"):  # Allow comments in files
-                            (username, pwd_hash) = line.split(sep=":", maxsplit=3)
-                            if username:
-                                self._users[username] = pwd_hash
-                                self.context.logger.debug(
-                                    "user %s , hash=%s" % (username, pwd_hash)
-                                )
-                self.context.logger.debug(
-                    "%d user(s) read from file %s" % (len(self._users), password_file)
-                )
-            except FileNotFoundError:
-                self.context.logger.warning(
-                    "Password file %s not found" % password_file
-                )
-        else:
-            self.context.logger.debug(
-                "Configuration parameter 'password_file' not found"
-            )
-
-    async def authenticate(self, *args, **kwargs):
-        authenticated = super().authenticate(*args, **kwargs)
-        if authenticated:
-            session = kwargs.get("session", None)
-            if session.username:
-                hash = self._users.get(session.username, None)
-                if not hash:
-                    authenticated = False
-                    self.context.logger.debug(
-                        "No hash found for user '%s'" % session.username
-                    )
-                else:
-                    authenticated = pwd_context.verify(session.password, hash)
-            else:
-                return None
-        return authenticated
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/logging.py b/hbmqtt/plugins/logging.py
index 4b76d1b6..a3f9557c 100644
--- a/hbmqtt/plugins/logging.py
+++ b/hbmqtt/plugins/logging.py
@@ -1,48 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.plugins.logging import *
 
-
-import logging
-from functools import partial
-
-
-class EventLoggerPlugin:
-    def __init__(self, context):
-        self.context = context
-
-    async def log_event(self, *args, **kwargs):
-        self.context.logger.info(
-            "### '%s' EVENT FIRED ###" % kwargs["event_name"].replace("old", "")
-        )
-
-    def __getattr__(self, name):
-        if name.startswith("on_"):
-            return partial(self.log_event, event_name=name)
-
-
-class PacketLoggerPlugin:
-    def __init__(self, context):
-        self.context = context
-
-    async def on_mqtt_packet_received(self, *args, **kwargs):
-        packet = kwargs.get("packet")
-        session = kwargs.get("session", None)
-        if self.context.logger.isEnabledFor(logging.DEBUG):
-            if session:
-                self.context.logger.debug(
-                    "%s <-in-- %s" % (session.client_id, repr(packet))
-                )
-            else:
-                self.context.logger.debug("<-in-- %s" % repr(packet))
-
-    async def on_mqtt_packet_sent(self, *args, **kwargs):
-        packet = kwargs.get("packet")
-        session = kwargs.get("session", None)
-        if self.context.logger.isEnabledFor(logging.DEBUG):
-            if session:
-                self.context.logger.debug(
-                    "%s -out-> %s" % (session.client_id, repr(packet))
-                )
-            else:
-                self.context.logger.debug("-out-> %s" % repr(packet))
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/manager.py b/hbmqtt/plugins/manager.py
index 4d7d0434..8883f94d 100644
--- a/hbmqtt/plugins/manager.py
+++ b/hbmqtt/plugins/manager.py
@@ -1,211 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
+import warnings
+from amqtt.plugins.manager import *
 
-__all__ = ["get_plugin_manager", "BaseContext", "PluginManager"]
-
-import pkg_resources
-import logging
-import asyncio
-import copy
-
-from collections import namedtuple
-
-
-Plugin = namedtuple("Plugin", ["name", "ep", "object"])
-
-plugins_manager = dict()
-
-
-def get_plugin_manager(namespace):
-    global plugins_manager
-    return plugins_manager.get(namespace, None)
-
-
-class BaseContext:
-    def __init__(self):
-        self.loop = None
-        self.logger = None
-
-
-class PluginManager:
-    """
-    Wraps setuptools Entry point mechanism to provide a basic plugin system. Plugins
-    are loaded for a given namespace (group). This plugin manager uses coroutines to
-    run plugin call asynchronously in an event queue
-    """
-
-    def __init__(self, namespace, context, loop=None):
-        global plugins_manager
-        if loop is not None:
-            self._loop = loop
-        else:
-            self._loop = asyncio.get_event_loop()
-
-        self.logger = logging.getLogger(namespace)
-        if context is None:
-            self.context = BaseContext()
-        else:
-            self.context = context
-        self.context.loop = self._loop
-        self._plugins = []
-        self._load_plugins(namespace)
-        self._fired_events = []
-        plugins_manager[namespace] = self
-
-    @property
-    def app_context(self):
-        return self.context
-
-    def _load_plugins(self, namespace):
-        self.logger.debug("Loading plugins for namespace %s" % namespace)
-        for ep in pkg_resources.iter_entry_points(group=namespace):
-            plugin = self._load_plugin(ep)
-            self._plugins.append(plugin)
-            self.logger.debug(" Plugin %s ready" % ep.name)
-
-    def _load_plugin(self, ep: pkg_resources.EntryPoint):
-        try:
-            self.logger.debug(" Loading plugin %s" % ep)
-            plugin = ep.load(require=True)
-            self.logger.debug(" Initializing plugin %s" % ep)
-            plugin_context = copy.copy(self.app_context)
-            plugin_context.logger = self.logger.getChild(ep.name)
-            obj = plugin(plugin_context)
-            return Plugin(ep.name, ep, obj)
-        except ImportError as ie:
-            self.logger.warning("Plugin %r import failed: %s" % (ep, ie))
-        except pkg_resources.UnknownExtra as ue:
-            self.logger.warning(
-                "Plugin %r dependencies resolution failed: %s" % (ep, ue)
-            )
-
-    def get_plugin(self, name):
-        """
-        Get a plugin by its name from the plugins loaded for the current namespace
-        :param name:
-        :return:
-        """
-        for p in self._plugins:
-            if p.name == name:
-                return p
-        return None
-
-    async def close(self):
-        """
-        Free PluginManager resources and cancel pending event methods
-        This method call a close() coroutine for each plugin, allowing plugins to close
-        and free resources
-        :return:
-        """
-        await self.map_plugin_coro("close")
-        for task in self._fired_events:
-            task.cancel()
-
-    @property
-    def plugins(self):
-        """
-        Get the loaded plugins list
-        :return:
-        """
-        return self._plugins
-
-    def _schedule_coro(self, coro):
-        return asyncio.ensure_future(coro, loop=self._loop)
-
-    async def fire_event(self, event_name, wait=False, *args, **kwargs):
-        """
-        Fire an event to plugins.
-        PluginManager schedule async calls for each plugin on method called "on_" + event_name
-        For example, on_connect will be called on event 'connect'
-        Method calls are schedule in the async loop. wait parameter must be set to true
-        to wait until all methods are completed.
-        :param event_name:
-        :param args:
-        :param kwargs:
-        :param wait: indicates if fire_event should wait for plugin calls completion (True), or not
-        :return:
-        """
-        tasks = []
-        event_method_name = "on_" + event_name
-        for plugin in self._plugins:
-            event_method = getattr(plugin.object, event_method_name, None)
-            if event_method:
-                try:
-                    task = self._schedule_coro(event_method(*args, **kwargs))
-                    tasks.append(task)
-
-                    def clean_fired_events(future):
-                        try:
-                            self._fired_events.remove(future)
-                        except (KeyError, ValueError):
-                            pass
-
-                    task.add_done_callback(clean_fired_events)
-                except AssertionError:
-                    self.logger.error(
-                        "Method '%s' on plugin '%s' is not a coroutine"
-                        % (event_method_name, plugin.name)
-                    )
-
-        self._fired_events.extend(tasks)
-        if wait:
-            if tasks:
-                await asyncio.wait(tasks, loop=self._loop)
-        self.logger.debug("Plugins len(_fired_events)=%d" % (len(self._fired_events)))
-
-    async def map(self, coro, *args, **kwargs):
-        """
-        Schedule a given coroutine call for each plugin.
-        The coro called get the Plugin instance as first argument of its method call
-        :param coro: coro to call on each plugin
-        :param filter_plugins: list of plugin names to filter (only plugin whose name is
-            in filter are called).
-        None will call all plugins. [] will call None.
-        :param args: arguments to pass to coro
-        :param kwargs: arguments to pass to coro
-        :return: dict containing return from coro call for each plugin
-        """
-        p_list = kwargs.pop("filter_plugins", None)
-        if p_list is None:
-            p_list = [p.name for p in self.plugins]
-        tasks = []
-        plugins_list = []
-        for plugin in self._plugins:
-            if plugin.name in p_list:
-                coro_instance = coro(plugin, *args, **kwargs)
-                if coro_instance:
-                    try:
-                        tasks.append(self._schedule_coro(coro_instance))
-                        plugins_list.append(plugin)
-                    except AssertionError:
-                        self.logger.error(
-                            "Method '%r' on plugin '%s' is not a coroutine"
-                            % (coro, plugin.name)
-                        )
-        if tasks:
-            ret_list = await asyncio.gather(*tasks, loop=self._loop)
-            # Create result map plugin=>ret
-            ret_dict = {k: v for k, v in zip(plugins_list, ret_list)}
-        else:
-            ret_dict = {}
-        return ret_dict
-
-    @staticmethod
-    async def _call_coro(plugin, coro_name, *args, **kwargs):
-        try:
-            coro = getattr(plugin.object, coro_name, None)(*args, **kwargs)
-            return await coro
-        except TypeError:
-            # Plugin doesn't implement coro_name
-            return None
-
-    async def map_plugin_coro(self, coro_name, *args, **kwargs):
-        """
-        Call a plugin declared by plugin by its name
-        :param coro_name:
-        :param args:
-        :param kwargs:
-        :return:
-        """
-        return await self.map(self._call_coro, coro_name, *args, **kwargs)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/persistence.py b/hbmqtt/plugins/persistence.py
index e61e70ac..6ac0be7d 100644
--- a/hbmqtt/plugins/persistence.py
+++ b/hbmqtt/plugins/persistence.py
@@ -1,72 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import sqlite3
-import pickle
+import warnings
+from amqtt.plugins.persistence import *
 
-
-class SQLitePlugin:
-    def __init__(self, context):
-        self.context = context
-        self.conn = None
-        self.cursor = None
-        self.db_file = None
-        try:
-            self.persistence_config = self.context.config["persistence"]
-            self.init_db()
-        except KeyError:
-            self.context.logger.warning(
-                "'persistence' section not found in context configuration"
-            )
-
-    def init_db(self):
-        self.db_file = self.persistence_config.get("file", None)
-        if not self.db_file:
-            self.context.logger.warning("'file' persistence parameter not found")
-        else:
-            try:
-                self.conn = sqlite3.connect(self.db_file)
-                self.cursor = self.conn.cursor()
-                self.context.logger.info("Database file '%s' opened" % self.db_file)
-            except Exception as e:
-                self.context.logger.error(
-                    "Error while initializing database '%s' : %s" % (self.db_file, e)
-                )
-        if self.cursor:
-            self.cursor.execute(
-                "CREATE TABLE IF NOT EXISTS session(client_id TEXT PRIMARY KEY, data BLOB)"
-            )
-
-    async def save_session(self, session):
-        if self.cursor:
-            dump = pickle.dumps(session)
-            try:
-                self.cursor.execute(
-                    "INSERT OR REPLACE INTO session (client_id, data) VALUES (?,?)",
-                    (session.client_id, dump),
-                )
-                self.conn.commit()
-            except Exception as e:
-                self.context.logger.error(
-                    "Failed saving session '%s': %s" % (session, e)
-                )
-
-    async def find_session(self, client_id):
-        if self.cursor:
-            row = self.cursor.execute(
-                "SELECT data FROM session where client_id=?", (client_id,)
-            ).fetchone()
-            if row:
-                return pickle.loads(row[0])
-            else:
-                return None
-
-    async def del_session(self, client_id):
-        if self.cursor:
-            self.cursor.execute("DELETE FROM session where client_id=?", (client_id,))
-            self.conn.commit()
-
-    async def on_broker_post_shutdown(self):
-        if self.conn:
-            self.conn.close()
-            self.context.logger.info("Database file '%s' closed" % self.db_file)
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/sys/__init__.py b/hbmqtt/plugins/sys/__init__.py
index e69de29b..99fc68ca 100644
--- a/hbmqtt/plugins/sys/__init__.py
+++ b/hbmqtt/plugins/sys/__init__.py
@@ -0,0 +1,4 @@
+import warnings
+from amqtt.plugins.sys import *
+
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/sys/broker.py b/hbmqtt/plugins/sys/broker.py
index 77e1ddad..0257bdb8 100644
--- a/hbmqtt/plugins/sys/broker.py
+++ b/hbmqtt/plugins/sys/broker.py
@@ -1,249 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-from datetime import datetime
-from collections import deque
-import asyncio
+import warnings
+from amqtt.plugins.sys.broker import *
 
-import hbmqtt
-from hbmqtt.mqtt.packet import PUBLISH
-from hbmqtt.codecs import int_to_bytes_str
-
-
-DOLLAR_SYS_ROOT = "$SYS/broker/"
-STAT_BYTES_SENT = "bytes_sent"
-STAT_BYTES_RECEIVED = "bytes_received"
-STAT_MSG_SENT = "messages_sent"
-STAT_MSG_RECEIVED = "messages_received"
-STAT_PUBLISH_SENT = "publish_sent"
-STAT_PUBLISH_RECEIVED = "publish_received"
-STAT_START_TIME = "start_time"
-STAT_CLIENTS_MAXIMUM = "clients_maximum"
-STAT_CLIENTS_CONNECTED = "clients_connected"
-STAT_CLIENTS_DISCONNECTED = "clients_disconnected"
-
-
-class BrokerSysPlugin:
-    def __init__(self, context):
-        self.context = context
-        # Broker statistics initialization
-        self._stats = dict()
-        self._sys_handle = None
-
-    def _clear_stats(self):
-        """
-        Initializes broker statistics data structures
-        """
-        for stat in (
-            STAT_BYTES_RECEIVED,
-            STAT_BYTES_SENT,
-            STAT_MSG_RECEIVED,
-            STAT_MSG_SENT,
-            STAT_CLIENTS_MAXIMUM,
-            STAT_CLIENTS_CONNECTED,
-            STAT_CLIENTS_DISCONNECTED,
-            STAT_PUBLISH_RECEIVED,
-            STAT_PUBLISH_SENT,
-        ):
-            self._stats[stat] = 0
-
-    async def _broadcast_sys_topic(self, topic_basename, data):
-        return await self.context.broadcast_message(topic_basename, data)
-
-    def schedule_broadcast_sys_topic(self, topic_basename, data):
-        return asyncio.ensure_future(
-            self._broadcast_sys_topic(DOLLAR_SYS_ROOT + topic_basename, data),
-            loop=self.context.loop,
-        )
-
-    async def on_broker_pre_start(self, *args, **kwargs):
-        self._clear_stats()
-
-    async def on_broker_post_start(self, *args, **kwargs):
-        self._stats[STAT_START_TIME] = datetime.now()
-        version = f"HBMQTT version {hbmqtt.__version__}"
-        self.context.retain_message(DOLLAR_SYS_ROOT + "version", version.encode())
-
-        # Start $SYS topics management
-        try:
-            sys_interval = int(self.context.config.get("sys_interval", 0))
-            if sys_interval > 0:
-                self.context.logger.debug(
-                    "Setup $SYS broadcasting every %d seconds" % sys_interval
-                )
-                self.sys_handle = self.context.loop.call_later(
-                    sys_interval, self.broadcast_dollar_sys_topics
-                )
-            else:
-                self.context.logger.debug("$SYS disabled")
-        except KeyError:
-            pass
-            # 'sys_internal' config parameter not found
-
-    async def on_broker_pre_stop(self, *args, **kwargs):
-        # Stop $SYS topics broadcasting
-        if self.sys_handle:
-            self.sys_handle.cancel()
-
-    def broadcast_dollar_sys_topics(self):
-        """
-        Broadcast dynamic $SYS topics updates and reschedule next execution depending on 'sys_interval' config
-        parameter.
-        """
-
-        # Update stats
-        uptime = datetime.now() - self._stats[STAT_START_TIME]
-        client_connected = self._stats[STAT_CLIENTS_CONNECTED]
-        client_disconnected = self._stats[STAT_CLIENTS_DISCONNECTED]
-        inflight_in = 0
-        inflight_out = 0
-        messages_stored = 0
-        for session in self.context.sessions:
-            inflight_in += session.inflight_in_count
-            inflight_out += session.inflight_out_count
-            messages_stored += session.retained_messages_count
-        messages_stored += len(self.context.retained_messages)
-        subscriptions_count = 0
-        for topic in self.context.subscriptions:
-            subscriptions_count += len(self.context.subscriptions[topic])
-
-        # Broadcast updates
-        tasks = deque()
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "load/bytes/received",
-                int_to_bytes_str(self._stats[STAT_BYTES_RECEIVED]),
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "load/bytes/sent", int_to_bytes_str(self._stats[STAT_BYTES_SENT])
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/received", int_to_bytes_str(self._stats[STAT_MSG_RECEIVED])
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/sent", int_to_bytes_str(self._stats[STAT_MSG_SENT])
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "time", str(datetime.now()).encode("utf-8")
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "uptime", int_to_bytes_str(int(uptime.total_seconds()))
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "uptime/formated", str(uptime).encode("utf-8")
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "clients/connected", int_to_bytes_str(client_connected)
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "clients/disconnected", int_to_bytes_str(client_disconnected)
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "clients/maximum", int_to_bytes_str(self._stats[STAT_CLIENTS_MAXIMUM])
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "clients/total",
-                int_to_bytes_str(client_connected + client_disconnected),
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/inflight", int_to_bytes_str(inflight_in + inflight_out)
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/inflight/in", int_to_bytes_str(inflight_in)
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/inflight/out", int_to_bytes_str(inflight_out)
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/inflight/stored", int_to_bytes_str(messages_stored)
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/publish/received",
-                int_to_bytes_str(self._stats[STAT_PUBLISH_RECEIVED]),
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/publish/sent",
-                int_to_bytes_str(self._stats[STAT_PUBLISH_SENT]),
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/retained/count",
-                int_to_bytes_str(len(self.context.retained_messages)),
-            )
-        )
-        tasks.append(
-            self.schedule_broadcast_sys_topic(
-                "messages/subscriptions/count", int_to_bytes_str(subscriptions_count)
-            )
-        )
-
-        # Wait until broadcasting tasks end
-        while tasks and tasks[0].done():
-            tasks.popleft()
-        # Reschedule
-        sys_interval = int(self.context.config["sys_interval"])
-        self.context.logger.debug("Broadcasting $SYS topics")
-        self.sys_handle = self.context.loop.call_later(
-            sys_interval, self.broadcast_dollar_sys_topics
-        )
-
-    async def on_mqtt_packet_received(self, *args, **kwargs):
-        packet = kwargs.get("packet")
-        if packet:
-            packet_size = packet.bytes_length
-            self._stats[STAT_BYTES_RECEIVED] += packet_size
-            self._stats[STAT_MSG_RECEIVED] += 1
-            if packet.fixed_header.packet_type == PUBLISH:
-                self._stats[STAT_PUBLISH_RECEIVED] += 1
-
-    async def on_mqtt_packet_sent(self, *args, **kwargs):
-        packet = kwargs.get("packet")
-        if packet:
-            packet_size = packet.bytes_length
-            self._stats[STAT_BYTES_SENT] += packet_size
-            self._stats[STAT_MSG_SENT] += 1
-            if packet.fixed_header.packet_type == PUBLISH:
-                self._stats[STAT_PUBLISH_SENT] += 1
-
-    async def on_broker_client_connected(self, *args, **kwargs):
-        self._stats[STAT_CLIENTS_CONNECTED] += 1
-        self._stats[STAT_CLIENTS_MAXIMUM] = max(
-            self._stats[STAT_CLIENTS_MAXIMUM], self._stats[STAT_CLIENTS_CONNECTED]
-        )
-
-    async def on_broker_client_disconnected(self, *args, **kwargs):
-        self._stats[STAT_CLIENTS_CONNECTED] -= 1
-        self._stats[STAT_CLIENTS_DISCONNECTED] += 1
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/plugins/topic_checking.py b/hbmqtt/plugins/topic_checking.py
index b52a156f..e06ac7c6 100644
--- a/hbmqtt/plugins/topic_checking.py
+++ b/hbmqtt/plugins/topic_checking.py
@@ -1,82 +1,4 @@
-class BaseTopicPlugin:
-    def __init__(self, context):
-        self.context = context
-        try:
-            self.topic_config = self.context.config["topic-check"]
-        except KeyError:
-            self.context.logger.warning(
-                "'topic-check' section not found in context configuration"
-            )
+import warnings
+from amqtt.plugins.topic_checking import *
 
-    def topic_filtering(self, *args, **kwargs):
-        if not self.topic_config:
-            # auth config section not found
-            self.context.logger.warning(
-                "'auth' section not found in context configuration"
-            )
-            return False
-        return True
-
-
-class TopicTabooPlugin(BaseTopicPlugin):
-    def __init__(self, context):
-        super().__init__(context)
-        self._taboo = ["prohibited", "top-secret", "data/classified"]
-
-    async def topic_filtering(self, *args, **kwargs):
-        filter_result = super().topic_filtering(*args, **kwargs)
-        if filter_result:
-            session = kwargs.get("session", None)
-            topic = kwargs.get("topic", None)
-            if session.username and session.username == "admin":
-                return True
-            if topic and topic in self._taboo:
-                return False
-            return True
-        return filter_result
-
-
-class TopicAccessControlListPlugin(BaseTopicPlugin):
-    def __init__(self, context):
-        super().__init__(context)
-
-    @staticmethod
-    def topic_ac(topic_requested, topic_allowed):
-        req_split = topic_requested.split("/")
-        allowed_split = topic_allowed.split("/")
-        ret = True
-        for i in range(max(len(req_split), len(allowed_split))):
-            try:
-                a_aux = req_split[i]
-                b_aux = allowed_split[i]
-            except IndexError:
-                ret = False
-                break
-            if b_aux == "#":
-                break
-            elif (b_aux == "+") or (b_aux == a_aux):
-                continue
-            else:
-                ret = False
-                break
-        return ret
-
-    async def topic_filtering(self, *args, **kwargs):
-        filter_result = super().topic_filtering(*args, **kwargs)
-        if filter_result:
-            session = kwargs.get("session", None)
-            req_topic = kwargs.get("topic", None)
-            if req_topic:
-                username = session.username
-                if username is None:
-                    username = "anonymous"
-                allowed_topics = self.topic_config["acl"].get(username, None)
-                if allowed_topics:
-                    for allowed_topic in allowed_topics:
-                        if self.topic_ac(req_topic, allowed_topic):
-                            return True
-                    return False
-                else:
-                    return False
-            else:
-                return False
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/scripts/__init__.py b/hbmqtt/scripts/__init__.py
index e69de29b..70b6315d 100644
--- a/hbmqtt/scripts/__init__.py
+++ b/hbmqtt/scripts/__init__.py
@@ -0,0 +1,4 @@
+import warnings
+from amqtt.scripts import *
+
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/scripts/broker_script.py b/hbmqtt/scripts/broker_script.py
index b96e1b6a..dc218a0d 100644
--- a/hbmqtt/scripts/broker_script.py
+++ b/hbmqtt/scripts/broker_script.py
@@ -1,87 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-"""
-HBMQTT - MQTT 3.1.1 broker
+import warnings
+from amqtt.scripts.broker_script import *
 
-Usage:
-    hbmqtt --version
-    hbmqtt (-h | --help)
-    hbmqtt [-c <config_file> ] [-d]
-
-Options:
-    -h --help           Show this screen.
-    --version           Show version.
-    -c <config_file>    Broker configuration file (YAML format)
-    -d                  Enable debug messages
-"""
-
-import sys
-import logging
-import asyncio
-import os
-
-import hbmqtt
-from hbmqtt.broker import Broker
-from docopt import docopt
-from hbmqtt.utils import read_yaml_config
-
-
-default_config = {
-    "listeners": {
-        "default": {
-            "type": "tcp",
-            "bind": "0.0.0.0:1883",
-        },
-    },
-    "sys_interval": 10,
-    "auth": {
-        "allow-anonymous": True,
-        "password-file": os.path.join(
-            os.path.dirname(os.path.realpath(__file__)), "passwd"
-        ),
-        "plugins": ["auth_file", "auth_anonymous"],
-    },
-    "topic-check": {"enabled": False},
-}
-
-logger = logging.getLogger(__name__)
-
-
-def main(*args, **kwargs):
-    if sys.version_info[:2] < (3, 4):
-        logger.fatal("Error: Python 3.4+ is required")
-        sys.exit(-1)
-
-    arguments = docopt(__doc__, version=hbmqtt.__version__)
-    formatter = "[%(asctime)s] :: %(levelname)s - %(message)s"
-
-    if arguments["-d"]:
-        level = logging.DEBUG
-    else:
-        level = logging.INFO
-    logging.basicConfig(level=level, format=formatter)
-
-    config = None
-    if arguments["-c"]:
-        config = read_yaml_config(arguments["-c"])
-    else:
-        config = read_yaml_config(
-            os.path.join(
-                os.path.dirname(os.path.realpath(__file__)), "default_broker.yaml"
-            )
-        )
-        logger.debug("Using default configuration")
-    loop = asyncio.get_event_loop()
-    broker = Broker(config)
-    try:
-        loop.run_until_complete(broker.start())
-        loop.run_forever()
-    except KeyboardInterrupt:
-        loop.run_until_complete(broker.shutdown())
-    finally:
-        loop.close()
-
-
-if __name__ == "__main__":
-    main()
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/scripts/pub_script.py b/hbmqtt/scripts/pub_script.py
index 1a58d3a6..e6a47601 100644
--- a/hbmqtt/scripts/pub_script.py
+++ b/hbmqtt/scripts/pub_script.py
@@ -1,186 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-"""
-hbmqtt_pub - MQTT 3.1.1 publisher
+import warnings
+from amqtt.scripts.pub_script import *
 
-Usage:
-    hbmqtt_pub --version
-    hbmqtt_pub (-h | --help)
-    hbmqtt_pub --url BROKER_URL -t TOPIC (-f FILE | -l | -m MESSAGE | -n | -s) [-c CONFIG_FILE] [-i CLIENT_ID] [-q | --qos QOS] [-d] [-k KEEP_ALIVE] [--clean-session] [--ca-file CAFILE] [--ca-path CAPATH] [--ca-data CADATA] [ --will-topic WILL_TOPIC [--will-message WILL_MESSAGE] [--will-qos WILL_QOS] [--will-retain] ] [--extra-headers HEADER] [-r]
-
-Options:
-    -h --help           Show this screen.
-    --version           Show version.
-    --url BROKER_URL    Broker connection URL (musr conform to MQTT URI scheme (see https://github.com/mqtt/mqtt.github.io/wiki/URI-Scheme>)
-    -c CONFIG_FILE      Broker configuration file (YAML format)
-    -i CLIENT_ID        Id to use as client ID.
-    -q | --qos QOS      Quality of service to use for the message, from 0, 1 and 2. Defaults to 0.
-    -r                  Set retain flag on connect
-    -t TOPIC            Message topic
-    -m MESSAGE          Message data to send
-    -f FILE             Read file by line and publish message for each line
-    -s                  Read from stdin and publish message for each line
-    -k KEEP_ALIVE       Keep alive timeout in second
-    --clean-session     Clean session on connect (defaults to False)
-    --ca-file CAFILE]   CA file
-    --ca-path CAPATH]   CA Path
-    --ca-data CADATA    CA data
-    --will-topic WILL_TOPIC
-    --will-message WILL_MESSAGE
-    --will-qos WILL_QOS
-    --will-retain
-    --extra-headers EXTRA_HEADERS      JSON object with key-value pairs of additional headers for websocket connections
-    -d                  Enable debug messages
-"""
-
-import sys
-import logging
-import asyncio
-import os
-import json
-
-import hbmqtt
-from hbmqtt.client import MQTTClient, ConnectException
-from docopt import docopt
-from hbmqtt.utils import read_yaml_config
-
-
-logger = logging.getLogger(__name__)
-
-
-def _gen_client_id():
-    import os
-    import socket
-
-    pid = os.getpid()
-    hostname = socket.gethostname()
-    return "hbmqtt_pub/%d-%s" % (pid, hostname)
-
-
-def _get_qos(arguments):
-    try:
-        return int(arguments["--qos"][0])
-    except:
-        return None
-
-
-def _get_extra_headers(arguments):
-    try:
-        return json.loads(arguments["--extra-headers"])
-    except:
-        return {}
-
-
-def _get_message(arguments):
-    if arguments["-n"]:
-        yield b""
-    if arguments["-m"]:
-        yield arguments["-m"].encode(encoding="utf-8")
-    if arguments["-f"]:
-        try:
-            with open(arguments["-f"], "r") as f:
-                for line in f:
-                    yield line.encode(encoding="utf-8")
-        except:
-            logger.error("Failed to read file '%s'" % arguments["-f"])
-    if arguments["-l"]:
-        import sys
-
-        for line in sys.stdin:
-            if line:
-                yield line.encode(encoding="utf-8")
-    if arguments["-s"]:
-        import sys
-
-        message = bytearray()
-        for line in sys.stdin:
-            message.extend(line.encode(encoding="utf-8"))
-        yield message
-
-
-async def do_pub(client, arguments):
-    running_tasks = []
-
-    try:
-        logger.info("%s Connecting to broker" % client.client_id)
-
-        await client.connect(
-            uri=arguments["--url"],
-            cleansession=arguments["--clean-session"],
-            cafile=arguments["--ca-file"],
-            capath=arguments["--ca-path"],
-            cadata=arguments["--ca-data"],
-            extra_headers=_get_extra_headers(arguments),
-        )
-        qos = _get_qos(arguments)
-        topic = arguments["-t"]
-        retain = arguments["-r"]
-        for message in _get_message(arguments):
-            logger.info("%s Publishing to '%s'" % (client.client_id, topic))
-            task = asyncio.ensure_future(client.publish(topic, message, qos, retain))
-            running_tasks.append(task)
-        if running_tasks:
-            await asyncio.wait(running_tasks)
-        await client.disconnect()
-        logger.info("%s Disconnected from broker" % client.client_id)
-    except KeyboardInterrupt:
-        await client.disconnect()
-        logger.info("%s Disconnected from broker" % client.client_id)
-    except ConnectException as ce:
-        logger.fatal("connection to '%s' failed: %r" % (arguments["--url"], ce))
-    except asyncio.CancelledError:
-        logger.fatal("Publish canceled due to previous error")
-
-
-def main(*args, **kwargs):
-    if sys.version_info[:2] < (3, 6):
-        logger.fatal("Error: Python 3.6+ is required")
-        sys.exit(-1)
-
-    arguments = docopt(__doc__, version=hbmqtt.__version__)
-    # print(arguments)
-    formatter = "[%(asctime)s] :: %(levelname)s - %(message)s"
-
-    if arguments["-d"]:
-        level = logging.DEBUG
-    else:
-        level = logging.INFO
-    logging.basicConfig(level=level, format=formatter)
-
-    if arguments["-c"]:
-        config = read_yaml_config(arguments["-c"])
-    else:
-        config = read_yaml_config(
-            os.path.join(
-                os.path.dirname(os.path.realpath(__file__)), "default_client.yaml"
-            )
-        )
-        logger.debug("Using default configuration")
-    loop = asyncio.get_event_loop()
-
-    client_id = arguments.get("-i", None)
-    if not client_id:
-        client_id = _gen_client_id()
-
-    if arguments["-k"]:
-        config["keep_alive"] = int(arguments["-k"])
-
-    if (
-        arguments["--will-topic"]
-        and arguments["--will-message"]
-        and arguments["--will-qos"]
-    ):
-        config["will"] = dict()
-        config["will"]["topic"] = arguments["--will-topic"]
-        config["will"]["message"] = arguments["--will-message"].encode("utf-8")
-        config["will"]["qos"] = int(arguments["--will-qos"])
-        config["will"]["retain"] = arguments["--will-retain"]
-
-    client = MQTTClient(client_id=client_id, config=config, loop=loop)
-    loop.run_until_complete(do_pub(client, arguments))
-    loop.close()
-
-
-if __name__ == "__main__":
-    main()
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/scripts/sub_script.py b/hbmqtt/scripts/sub_script.py
index 7d3057d6..6a178229 100644
--- a/hbmqtt/scripts/sub_script.py
+++ b/hbmqtt/scripts/sub_script.py
@@ -1,168 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-"""
-hbmqtt_sub - MQTT 3.1.1 publisher
+import warnings
+from amqtt.scripts.sub_script import *
 
-Usage:
-    hbmqtt_sub --version
-    hbmqtt_sub (-h | --help)
-    hbmqtt_sub --url BROKER_URL -t TOPIC... [-n COUNT] [-c CONFIG_FILE] [-i CLIENT_ID] [-q | --qos QOS] [-d] [-k KEEP_ALIVE] [--clean-session] [--ca-file CAFILE] [--ca-path CAPATH] [--ca-data CADATA] [ --will-topic WILL_TOPIC [--will-message WILL_MESSAGE] [--will-qos WILL_QOS] [--will-retain] ] [--extra-headers HEADER]
-
-Options:
-    -h --help           Show this screen.
-    --version           Show version.
-    --url BROKER_URL    Broker connection URL (musr conform to MQTT URI scheme (see https://github.com/mqtt/mqtt.github.io/wiki/URI-Scheme>)
-    -c CONFIG_FILE      Broker configuration file (YAML format)
-    -i CLIENT_ID        Id to use as client ID.
-    -n COUNT            Number of messages to read before ending.
-    -q | --qos QOS      Quality of service desired to receive messages, from 0, 1 and 2. Defaults to 0.
-    -t TOPIC...         Topic filter to subcribe
-    -k KEEP_ALIVE       Keep alive timeout in second
-    --clean-session     Clean session on connect (defaults to False)
-    --ca-file CAFILE]   CA file
-    --ca-path CAPATH]   CA Path
-    --ca-data CADATA    CA data
-    --will-topic WILL_TOPIC
-    --will-message WILL_MESSAGE
-    --will-qos WILL_QOS
-    --will-retain
-    --extra-headers EXTRA_HEADERS      JSON object with key-value pairs of additional headers for websocket connections
-    -d                  Enable debug messages
-"""
-
-import sys
-import logging
-import asyncio
-import os
-import json
-
-import hbmqtt
-from hbmqtt.client import MQTTClient, ConnectException
-from hbmqtt.errors import MQTTException
-from docopt import docopt
-from hbmqtt.mqtt.constants import QOS_0
-from hbmqtt.utils import read_yaml_config
-
-logger = logging.getLogger(__name__)
-
-
-def _gen_client_id():
-    import os
-    import socket
-
-    pid = os.getpid()
-    hostname = socket.gethostname()
-    return "hbmqtt_sub/%d-%s" % (pid, hostname)
-
-
-def _get_qos(arguments):
-    try:
-        return int(arguments["--qos"][0])
-    except:
-        return QOS_0
-
-
-def _get_extra_headers(arguments):
-    try:
-        return json.loads(arguments["--extra-headers"])
-    except:
-        return {}
-
-
-async def do_sub(client, arguments):
-
-    try:
-        await client.connect(
-            uri=arguments["--url"],
-            cleansession=arguments["--clean-session"],
-            cafile=arguments["--ca-file"],
-            capath=arguments["--ca-path"],
-            cadata=arguments["--ca-data"],
-            extra_headers=_get_extra_headers(arguments),
-        )
-        qos = _get_qos(arguments)
-        filters = []
-        for topic in arguments["-t"]:
-            filters.append((topic, qos))
-        await client.subscribe(filters)
-        if arguments["-n"]:
-            max_count = int(arguments["-n"])
-        else:
-            max_count = None
-        count = 0
-        while True:
-            if max_count and count >= max_count:
-                break
-            try:
-                message = await client.deliver_message()
-                count += 1
-                sys.stdout.buffer.write(message.publish_packet.data)
-                sys.stdout.write("\n")
-            except MQTTException:
-                logger.debug("Error reading packet")
-        await client.disconnect()
-    except KeyboardInterrupt:
-        await client.disconnect()
-    except ConnectException as ce:
-        logger.fatal("connection to '%s' failed: %r" % (arguments["--url"], ce))
-    except asyncio.CancelledError:
-        logger.fatal("Publish canceled due to previous error")
-
-
-def main(*args, **kwargs):
-    if sys.version_info[:2] < (3, 6):
-        logger.fatal("Error: Python 3.6+ is required")
-        sys.exit(-1)
-
-    arguments = docopt(__doc__, version=hbmqtt.__version__)
-    formatter = "[%(asctime)s] :: %(levelname)s - %(message)s"
-
-    if arguments["-d"]:
-        level = logging.DEBUG
-    else:
-        level = logging.INFO
-    logging.basicConfig(level=level, format=formatter)
-
-    config = None
-    if arguments["-c"]:
-        config = read_yaml_config(arguments["-c"])
-    else:
-        config = read_yaml_config(
-            os.path.join(
-                os.path.dirname(os.path.realpath(__file__)), "default_client.yaml"
-            )
-        )
-        logger.debug(
-            os.path.join(
-                os.path.dirname(os.path.realpath(__file__)), "default_client.yaml"
-            )
-        )
-        logger.debug("Using default configuration")
-    loop = asyncio.get_event_loop()
-
-    client_id = arguments.get("-i", None)
-    if not client_id:
-        client_id = _gen_client_id()
-
-    if arguments["-k"]:
-        config["keep_alive"] = int(arguments["-k"])
-
-    if (
-        arguments["--will-topic"]
-        and arguments["--will-message"]
-        and arguments["--will-qos"]
-    ):
-        config["will"] = dict()
-        config["will"]["topic"] = arguments["--will-topic"]
-        config["will"]["message"] = arguments["--will-message"].encode("utf-8")
-        config["will"]["qos"] = int(arguments["--will-qos"])
-        config["will"]["retain"] = arguments["--will-retain"]
-
-    client = MQTTClient(client_id=client_id, config=config, loop=loop)
-    loop.run_until_complete(do_sub(client, arguments))
-    loop.close()
-
-
-if __name__ == "__main__":
-    main()
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/session.py b/hbmqtt/session.py
index a23a01d4..b1bfe4ea 100644
--- a/hbmqtt/session.py
+++ b/hbmqtt/session.py
@@ -1,212 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import asyncio
-from transitions import Machine
-from asyncio import Queue
-from collections import OrderedDict
-from hbmqtt.mqtt.publish import PublishPacket
-from hbmqtt.errors import HBMQTTException
+import warnings
+from amqtt.session import *
 
-OUTGOING = 0
-INCOMING = 1
-
-
-class ApplicationMessage:
-
-    """
-    ApplicationMessage and subclasses are used to store published message information flow. These objects can contain different information depending on the way they were created (incoming or outgoing) and the quality of service used between peers.
-    """
-
-    __slots__ = (
-        "packet_id",
-        "topic",
-        "qos",
-        "data",
-        "retain",
-        "publish_packet",
-        "puback_packet",
-        "pubrec_packet",
-        "pubrel_packet",
-        "pubcomp_packet",
-    )
-
-    def __init__(self, packet_id, topic, qos, data, retain):
-        self.packet_id = packet_id
-        """ Publish message `packet identifier <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718025>`_"""
-
-        self.topic = topic
-        """ Publish message topic"""
-
-        self.qos = qos
-        """ Publish message Quality of Service"""
-
-        self.data = data
-        """ Publish message payload data"""
-
-        self.retain = retain
-        """ Publish message retain flag"""
-
-        self.publish_packet = None
-        """ :class:`hbmqtt.mqtt.publish.PublishPacket` instance corresponding to the `PUBLISH <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037>`_ packet in the messages flow. ``None`` if the PUBLISH packet has not already been received or sent."""
-
-        self.puback_packet = None
-        """ :class:`hbmqtt.mqtt.puback.PubackPacket` instance corresponding to the `PUBACK <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718043>`_ packet in the messages flow. ``None`` if QoS != QOS_1 or if the PUBACK packet has not already been received or sent."""
-
-        self.pubrec_packet = None
-        """ :class:`hbmqtt.mqtt.puback.PubrecPacket` instance corresponding to the `PUBREC <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718048>`_ packet in the messages flow. ``None`` if QoS != QOS_2 or if the PUBREC packet has not already been received or sent."""
-
-        self.pubrel_packet = None
-        """ :class:`hbmqtt.mqtt.puback.PubrelPacket` instance corresponding to the `PUBREL <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718053>`_ packet in the messages flow. ``None`` if QoS != QOS_2 or if the PUBREL packet has not already been received or sent."""
-
-        self.pubcomp_packet = None
-        """ :class:`hbmqtt.mqtt.puback.PubrelPacket` instance corresponding to the `PUBCOMP <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718058>`_ packet in the messages flow. ``None`` if QoS != QOS_2 or if the PUBCOMP packet has not already been received or sent."""
-
-    def build_publish_packet(self, dup=False):
-        """
-            Build :class:`hbmqtt.mqtt.publish.PublishPacket` from attributes
-
-        :param dup: force dup flag
-        :return: :class:`hbmqtt.mqtt.publish.PublishPacket` built from ApplicationMessage instance attributes
-        """
-        return PublishPacket.build(
-            self.topic, self.data, self.packet_id, dup, self.qos, self.retain
-        )
-
-    def __eq__(self, other):
-        return self.packet_id == other.packet_id
-
-
-class IncomingApplicationMessage(ApplicationMessage):
-
-    """
-    Incoming :class:`~hbmqtt.session.ApplicationMessage`.
-    """
-
-    __slots__ = ("direction",)
-
-    def __init__(self, packet_id, topic, qos, data, retain):
-        super().__init__(packet_id, topic, qos, data, retain)
-        self.direction = INCOMING
-
-
-class OutgoingApplicationMessage(ApplicationMessage):
-
-    """
-    Outgoing :class:`~hbmqtt.session.ApplicationMessage`.
-    """
-
-    __slots__ = ("direction",)
-
-    def __init__(self, packet_id, topic, qos, data, retain):
-        super().__init__(packet_id, topic, qos, data, retain)
-        self.direction = OUTGOING
-
-
-class Session:
-    states = ["new", "connected", "disconnected"]
-
-    def __init__(self, loop=None):
-        self._init_states()
-        self.remote_address = None
-        self.remote_port = None
-        self.client_id = None
-        self.clean_session = None
-        self.will_flag = False
-        self.will_message = None
-        self.will_qos = None
-        self.will_retain = None
-        self.will_topic = None
-        self.keep_alive = 0
-        self.publish_retry_delay = 0
-        self.broker_uri = None
-        self.username = None
-        self.password = None
-        self.cafile = None
-        self.capath = None
-        self.cadata = None
-        self._packet_id = 0
-        self.parent = 0
-        if loop is not None:
-            self._loop = loop
-        else:
-            self._loop = asyncio.get_event_loop()
-
-        # Used to store outgoing ApplicationMessage while publish protocol flows
-        self.inflight_out = OrderedDict()
-
-        # Used to store incoming ApplicationMessage while publish protocol flows
-        self.inflight_in = OrderedDict()
-
-        # Stores messages retained for this session
-        self.retained_messages = Queue(loop=self._loop)
-
-        # Stores PUBLISH messages ID received in order and ready for application process
-        self.delivered_message_queue = Queue(loop=self._loop)
-
-    def _init_states(self):
-        self.transitions = Machine(states=Session.states, initial="new")
-        self.transitions.add_transition(
-            trigger="connect", source="new", dest="connected"
-        )
-        self.transitions.add_transition(
-            trigger="connect", source="disconnected", dest="connected"
-        )
-        self.transitions.add_transition(
-            trigger="disconnect", source="connected", dest="disconnected"
-        )
-        self.transitions.add_transition(
-            trigger="disconnect", source="new", dest="disconnected"
-        )
-        self.transitions.add_transition(
-            trigger="disconnect", source="disconnected", dest="disconnected"
-        )
-
-    @property
-    def next_packet_id(self):
-        self._packet_id += 1
-        if self._packet_id > 65535:
-            self._packet_id = 1
-        while (
-            self._packet_id in self.inflight_in or self._packet_id in self.inflight_out
-        ):
-            self._packet_id += 1
-            if self._packet_id > 65535:
-                raise HBMQTTException(
-                    "More than 65525 messages pending. No free packet ID"
-                )
-
-        return self._packet_id
-
-    @property
-    def inflight_in_count(self):
-        return len(self.inflight_in)
-
-    @property
-    def inflight_out_count(self):
-        return len(self.inflight_out)
-
-    @property
-    def retained_messages_count(self):
-        return self.retained_messages.qsize()
-
-    def __repr__(self):
-        return type(self).__name__ + "(clientId={0}, state={1})".format(
-            self.client_id, self.transitions.state
-        )
-
-    def __getstate__(self):
-        state = self.__dict__.copy()
-        # Remove the unpicklable entries.
-        # del state['transitions']
-        del state["retained_messages"]
-        del state["delivered_message_queue"]
-        return state
-
-    def __setstate(self, state):
-        self.__dict__.update(state)
-        self.retained_messages = Queue()
-        self.delivered_message_queue = Queue()
-
-    def __eq__(self, other):
-        return self.client_id == other.client_id
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/utils.py b/hbmqtt/utils.py
index 63fa3db6..fd0fc128 100644
--- a/hbmqtt/utils.py
+++ b/hbmqtt/utils.py
@@ -1,42 +1,4 @@
-# Copyright (c) 2015 Nicolas JOUANIN
-#
-# See the file license.txt for copying permission.
-import logging
-import random
+import warnings
+from amqtt.utils import *
 
-import yaml
-
-
-logger = logging.getLogger(__name__)
-
-
-def format_client_message(session=None, address=None, port=None):
-    if session:
-        return "(client id=%s)" % session.client_id
-    elif address is not None and port is not None:
-        return "(client @=%s:%d)" % (address, port)
-    else:
-        return "(unknown client)"
-
-
-def gen_client_id() -> str:
-    """Generates random client ID"""
-    gen_id = "hbmqtt/"
-
-    for i in range(7, 23):
-        gen_id += chr(random.randint(0, 74) + 48)
-    return gen_id
-
-
-def read_yaml_config(config_file):
-    config = None
-    try:
-        with open(config_file, "r") as stream:
-            config = (
-                yaml.full_load(stream)
-                if hasattr(yaml, "full_load")
-                else yaml.load(stream)
-            )
-    except yaml.YAMLError as exc:
-        logger.error("Invalid config_file %s: %s" % (config_file, exc))
-    return config
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/hbmqtt/version.py b/hbmqtt/version.py
index 8b568c61..d7b48982 100644
--- a/hbmqtt/version.py
+++ b/hbmqtt/version.py
@@ -1,37 +1,4 @@
-# See the file license.txt for copying permission.
-import datetime
-import os
-import subprocess
 import warnings
+from amqtt.version import *
 
-import hbmqtt
-
-
-def get_version():
-    warnings.warn(
-        "hbmqtt.version.get_version() is deprecated, use hbmqtt.__version__ instead"
-    )
-    return hbmqtt.__version__
-
-
-def get_git_changeset():
-    """Returns a numeric identifier of the latest git changeset.
-    The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format.
-    This value isn't guaranteed to be unique, but collisions are very unlikely,
-    so it's sufficient for generating the development version numbers.
-    """
-    repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-    git_log = subprocess.Popen(
-        "git log --pretty=format:%ct --quiet -1 HEAD",
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-        shell=True,
-        cwd=repo_dir,
-        universal_newlines=True,
-    )
-    timestamp = git_log.communicate()[0]
-    try:
-        timestamp = datetime.datetime.utcfromtimestamp(int(timestamp))
-    except ValueError:
-        return None
-    return timestamp.strftime("%Y%m%d%H%M%S")
+warnings.warn("importing hbmqtt is deprecated. Please import amqtt", DeprecationWarning)
diff --git a/pyproject.toml b/pyproject.toml
index 422827c5..8cd99a18 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,6 +15,7 @@ classifiers = [
         "Topic :: Internet",
 ]
 packages = [
+    { include = "amqtt" },
     { include = "hbmqtt" },
 ]
 
@@ -42,28 +43,29 @@ black = "^20.8b1"
 flake8 = "^3.9.0"
 
 [tool.poetry.scripts]
-hbmqtt = 'hbmqtt.scripts.broker_script:main'
-hbmqtt_pub = 'hbmqtt.scripts.pub_script:main'
-hbmqtt_sub = 'hbmqtt.scripts.sub_script:main'
+hbmqtt = 'amqtt.scripts.broker_script:main'
+hbmqtt_pub = 'amqtt.scripts.pub_script:main'
+hbmqtt_sub = 'amqtt.scripts.sub_script:main'
 
 [tool.poetry.plugins]
 
-[tool.poetry.plugins."hbmqtt.test.plugins"]
+[tool.poetry.plugins."amqtt.test.plugins"]
 "test_plugin" = "tests.plugins.test_manager:EmptyTestPlugin"
 "event_plugin" = "tests.plugins.test_manager:EventTestPlugin"
-"packet_logger_plugin" = "hbmqtt.plugins.logging:PacketLoggerPlugin"
+"packet_logger_plugin" = "amqtt.plugins.logging:PacketLoggerPlugin"
 
-[tool.poetry.plugins."hbmqtt.broker.plugins"]
-"event_logger_plugin" = "hbmqtt.plugins.logging:EventLoggerPlugin"
-"packet_logger_plugin" = "hbmqtt.plugins.logging:PacketLoggerPlugin"
-"auth_anonymous" = "hbmqtt.plugins.authentication:AnonymousAuthPlugin"
-"auth_file" = "hbmqtt.plugins.authentication:FileAuthPlugin"
-"topic_taboo" = "hbmqtt.plugins.topic_checking:TopicTabooPlugin"
-"topic_acl" = "hbmqtt.plugins.topic_checking:TopicAccessControlListPlugin"
-"broker_sys" = "hbmqtt.plugins.sys.broker:BrokerSysPlugin"
+[tool.poetry.plugins."amqtt.broker.plugins"]
+"event_logger_plugin" = "amqtt.plugins.logging:EventLoggerPlugin"
+"packet_logger_plugin" = "amqtt.plugins.logging:PacketLoggerPlugin"
+"auth_anonymous" = "amqtt.plugins.authentication:AnonymousAuthPlugin"
+"auth_file" = "amqtt.plugins.authentication:FileAuthPlugin"
+"topic_taboo" = "amqtt.plugins.topic_checking:TopicTabooPlugin"
+"topic_acl" = "amqtt.plugins.topic_checking:TopicAccessControlListPlugin"
+"broker_sys" = "amqtt.plugins.sys.broker:BrokerSysPlugin"
 
-[tool.poetry.plugins."hbmqtt.client.plugins"]
-"packet_logger_plugin" = "hbmqtt.plugins.logging:PacketLoggerPlugin"
+
+[tool.poetry.plugins."amqtt.client.plugins"]
+"packet_logger_plugin" = "amqtt.plugins.logging:PacketLoggerPlugin"
 
 
 [build-system]
diff --git a/samples/broker_acl.py b/samples/broker_acl.py
index f3d06d7c..b82b979e 100644
--- a/samples/broker_acl.py
+++ b/samples/broker_acl.py
@@ -1,7 +1,7 @@
 import logging
 import asyncio
 import os
-from hbmqtt.broker import Broker
+from amqtt.broker import Broker
 
 logger = logging.getLogger(__name__)
 
diff --git a/samples/broker_start.py b/samples/broker_start.py
index 8e0ade80..5eda57c0 100644
--- a/samples/broker_start.py
+++ b/samples/broker_start.py
@@ -1,7 +1,7 @@
 import logging
 import asyncio
 import os
-from hbmqtt.broker import Broker
+from amqtt.broker import Broker
 
 logger = logging.getLogger(__name__)
 
diff --git a/samples/broker_taboo.py b/samples/broker_taboo.py
index bea66113..bed3c710 100644
--- a/samples/broker_taboo.py
+++ b/samples/broker_taboo.py
@@ -1,7 +1,7 @@
 import logging
 import asyncio
 import os
-from hbmqtt.broker import Broker
+from amqtt.broker import Broker
 
 logger = logging.getLogger(__name__)
 
diff --git a/samples/client_keepalive.py b/samples/client_keepalive.py
index eb734f1d..0d7b8abc 100644
--- a/samples/client_keepalive.py
+++ b/samples/client_keepalive.py
@@ -1,7 +1,7 @@
 import logging
 import asyncio
 
-from hbmqtt.client import MQTTClient
+from amqtt.client import MQTTClient
 
 
 #
diff --git a/samples/client_publish.py b/samples/client_publish.py
index 36f354df..5616d640 100644
--- a/samples/client_publish.py
+++ b/samples/client_publish.py
@@ -1,8 +1,8 @@
 import logging
 import asyncio
 
-from hbmqtt.client import MQTTClient, ConnectException
-from hbmqtt.mqtt.constants import QOS_1, QOS_2
+from amqtt.client import MQTTClient, ConnectException
+from amqtt.mqtt.constants import QOS_1, QOS_2
 
 
 #
diff --git a/samples/client_publish_acl.py b/samples/client_publish_acl.py
index 865fd824..54476d24 100644
--- a/samples/client_publish_acl.py
+++ b/samples/client_publish_acl.py
@@ -1,7 +1,7 @@
 import logging
 import asyncio
 
-from hbmqtt.client import MQTTClient, ConnectException
+from amqtt.client import MQTTClient, ConnectException
 
 
 #
@@ -18,11 +18,11 @@ async def test_coro():
         await C.connect("mqtt://0.0.0.0:1883")
         await C.publish("data/classified", b"TOP SECRET", qos=0x01)
         await C.publish("data/memes", b"REAL FUN", qos=0x01)
-        await C.publish("repositories/hbmqtt/master", b"NEW STABLE RELEASE", qos=0x01)
+        await C.publish("repositories/amqtt/master", b"NEW STABLE RELEASE", qos=0x01)
         await C.publish(
-            "repositories/hbmqtt/devel", b"THIS NEEDS TO BE CHECKED", qos=0x01
+            "repositories/amqtt/devel", b"THIS NEEDS TO BE CHECKED", qos=0x01
         )
-        await C.publish("calendar/hbmqtt/releases", b"NEW RELEASE", qos=0x01)
+        await C.publish("calendar/amqtt/releases", b"NEW RELEASE", qos=0x01)
         logger.info("messages published")
         await C.disconnect()
     except ConnectException as ce:
diff --git a/samples/client_publish_ssl.py b/samples/client_publish_ssl.py
index 9f714838..d495e2c2 100644
--- a/samples/client_publish_ssl.py
+++ b/samples/client_publish_ssl.py
@@ -1,8 +1,8 @@
 import logging
 import asyncio
 
-from hbmqtt.client import MQTTClient
-from hbmqtt.mqtt.constants import QOS_1, QOS_2
+from amqtt.client import MQTTClient
+from amqtt.mqtt.constants import QOS_1, QOS_2
 
 
 #
diff --git a/samples/client_publish_ws.py b/samples/client_publish_ws.py
index f74c7948..e4cba93c 100644
--- a/samples/client_publish_ws.py
+++ b/samples/client_publish_ws.py
@@ -1,8 +1,8 @@
 import logging
 import asyncio
 
-from hbmqtt.client import MQTTClient
-from hbmqtt.mqtt.constants import QOS_1, QOS_2
+from amqtt.client import MQTTClient
+from amqtt.mqtt.constants import QOS_1, QOS_2
 
 
 #
diff --git a/samples/client_subscribe.py b/samples/client_subscribe.py
index 55a3f2bd..ba463402 100644
--- a/samples/client_subscribe.py
+++ b/samples/client_subscribe.py
@@ -1,8 +1,8 @@
 import logging
 import asyncio
 
-from hbmqtt.client import MQTTClient, ClientException
-from hbmqtt.mqtt.constants import QOS_1, QOS_2
+from amqtt.client import MQTTClient, ClientException
+from amqtt.mqtt.constants import QOS_1, QOS_2
 
 
 #
diff --git a/samples/client_subscribe_acl.py b/samples/client_subscribe_acl.py
index a120dc8d..7bc25e3e 100644
--- a/samples/client_subscribe_acl.py
+++ b/samples/client_subscribe_acl.py
@@ -1,8 +1,8 @@
 import logging
 import asyncio
 
-from hbmqtt.client import MQTTClient, ClientException
-from hbmqtt.mqtt.constants import QOS_1
+from amqtt.client import MQTTClient, ClientException
+from amqtt.mqtt.constants import QOS_1
 
 
 #
@@ -23,9 +23,9 @@ async def uptime_coro():
         [
             ("data/memes", QOS_1),  # Topic allowed
             ("data/classified", QOS_1),  # Topic forbidden
-            ("repositories/hbmqtt/master", QOS_1),  # Topic allowed
-            ("repositories/hbmqtt/devel", QOS_1),  # Topic forbidden
-            ("calendar/hbmqtt/releases", QOS_1),  # Topic allowed
+            ("repositories/amqtt/master", QOS_1),  # Topic allowed
+            ("repositories/amqtt/devel", QOS_1),  # Topic forbidden
+            ("calendar/amqtt/releases", QOS_1),  # Topic allowed
         ]
     )
     logger.info("Subscribed")


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 4d04bbf9..5b58d47e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-import hbmqtt.broker
+import amqtt.broker
 
 test_config = {
     "listeners": {
@@ -17,7 +17,7 @@
 
 @pytest.fixture(scope="function")
 def mock_plugin_manager():
-    with unittest.mock.patch("hbmqtt.broker.PluginManager") as plugin_manager:
+    with unittest.mock.patch("amqtt.broker.PluginManager") as plugin_manager:
         yield plugin_manager
 
 
@@ -26,7 +26,7 @@ async def broker(mock_plugin_manager):
     # just making sure the mock is in place before we start our broker
     assert mock_plugin_manager is not None
 
-    broker = hbmqtt.broker.Broker(test_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = amqtt.broker.Broker(test_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     assert broker.transitions.is_started()
     assert broker._sessions == {}
diff --git a/tests/mqtt/protocol/test_handler.py b/tests/mqtt/protocol/test_handler.py
index a91cbc58..6e3896be 100644
--- a/tests/mqtt/protocol/test_handler.py
+++ b/tests/mqtt/protocol/test_handler.py
@@ -5,20 +5,20 @@
 import asyncio
 import logging
 import random
-from hbmqtt.plugins.manager import PluginManager
-from hbmqtt.session import (
+from amqtt.plugins.manager import PluginManager
+from amqtt.session import (
     Session,
     OutgoingApplicationMessage,
     IncomingApplicationMessage,
 )
-from hbmqtt.mqtt.protocol.handler import ProtocolHandler
-from hbmqtt.adapters import StreamWriterAdapter, StreamReaderAdapter
-from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
-from hbmqtt.mqtt.publish import PublishPacket
-from hbmqtt.mqtt.puback import PubackPacket
-from hbmqtt.mqtt.pubrec import PubrecPacket
-from hbmqtt.mqtt.pubrel import PubrelPacket
-from hbmqtt.mqtt.pubcomp import PubcompPacket
+from amqtt.mqtt.protocol.handler import ProtocolHandler
+from amqtt.adapters import StreamWriterAdapter, StreamReaderAdapter
+from amqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
+from amqtt.mqtt.publish import PublishPacket
+from amqtt.mqtt.puback import PubackPacket
+from amqtt.mqtt.pubrec import PubrecPacket
+from amqtt.mqtt.pubrel import PubrelPacket
+from amqtt.mqtt.pubcomp import PubcompPacket
 
 formatter = (
     "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
@@ -40,7 +40,7 @@ def setUp(self):
         self.loop = asyncio.new_event_loop()
         asyncio.set_event_loop(self.loop)
         self.plugin_manager = PluginManager(
-            "hbmqtt.test.plugins", context=None, loop=self.loop
+            "amqtt.test.plugins", context=None, loop=self.loop
         )
 
     def tearDown(self):
diff --git a/tests/mqtt/test_connect.py b/tests/mqtt/test_connect.py
index 4f10e46d..794bf90a 100644
--- a/tests/mqtt/test_connect.py
+++ b/tests/mqtt/test_connect.py
@@ -4,9 +4,9 @@
 import unittest
 import asyncio
 
-from hbmqtt.mqtt.connect import ConnectPacket, ConnectVariableHeader, ConnectPayload
-from hbmqtt.mqtt.packet import MQTTFixedHeader, CONNECT
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.connect import ConnectPacket, ConnectVariableHeader, ConnectPayload
+from amqtt.mqtt.packet import MQTTFixedHeader, CONNECT
+from amqtt.adapters import BufferReader
 
 
 class ConnectPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_packet.py b/tests/mqtt/test_packet.py
index 4b60d9e3..62f57e62 100644
--- a/tests/mqtt/test_packet.py
+++ b/tests/mqtt/test_packet.py
@@ -4,9 +4,9 @@
 import unittest
 import asyncio
 
-from hbmqtt.mqtt.packet import CONNECT, MQTTFixedHeader
-from hbmqtt.errors import MQTTException
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.packet import CONNECT, MQTTFixedHeader
+from amqtt.errors import MQTTException
+from amqtt.adapters import BufferReader
 
 
 class TestMQTTFixedHeaderTest(unittest.TestCase):
diff --git a/tests/mqtt/test_puback.py b/tests/mqtt/test_puback.py
index c65765cf..8ef9abdb 100644
--- a/tests/mqtt/test_puback.py
+++ b/tests/mqtt/test_puback.py
@@ -4,8 +4,8 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.puback import PubackPacket, PacketIdVariableHeader
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.puback import PubackPacket, PacketIdVariableHeader
+from amqtt.adapters import BufferReader
 
 
 class PubackPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_pubcomp.py b/tests/mqtt/test_pubcomp.py
index f905e594..0688a6b9 100644
--- a/tests/mqtt/test_pubcomp.py
+++ b/tests/mqtt/test_pubcomp.py
@@ -4,8 +4,8 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.pubcomp import PubcompPacket, PacketIdVariableHeader
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.pubcomp import PubcompPacket, PacketIdVariableHeader
+from amqtt.adapters import BufferReader
 
 
 class PubcompPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_publish.py b/tests/mqtt/test_publish.py
index 638e7d83..f4112959 100644
--- a/tests/mqtt/test_publish.py
+++ b/tests/mqtt/test_publish.py
@@ -4,9 +4,9 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.publish import PublishPacket, PublishVariableHeader, PublishPayload
-from hbmqtt.adapters import BufferReader
-from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
+from amqtt.mqtt.publish import PublishPacket, PublishVariableHeader, PublishPayload
+from amqtt.adapters import BufferReader
+from amqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
 
 
 class PublishPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_pubrec.py b/tests/mqtt/test_pubrec.py
index ba6ccdfa..95791c82 100644
--- a/tests/mqtt/test_pubrec.py
+++ b/tests/mqtt/test_pubrec.py
@@ -4,8 +4,8 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.pubrec import PubrecPacket, PacketIdVariableHeader
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.pubrec import PubrecPacket, PacketIdVariableHeader
+from amqtt.adapters import BufferReader
 
 
 class PubrecPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_pubrel.py b/tests/mqtt/test_pubrel.py
index 6a9453be..342986f2 100644
--- a/tests/mqtt/test_pubrel.py
+++ b/tests/mqtt/test_pubrel.py
@@ -4,8 +4,8 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.pubrel import PubrelPacket, PacketIdVariableHeader
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.pubrel import PubrelPacket, PacketIdVariableHeader
+from amqtt.adapters import BufferReader
 
 
 class PubrelPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_suback.py b/tests/mqtt/test_suback.py
index d0a14baf..00978e6b 100644
--- a/tests/mqtt/test_suback.py
+++ b/tests/mqtt/test_suback.py
@@ -4,9 +4,9 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.suback import SubackPacket, SubackPayload
-from hbmqtt.mqtt.packet import PacketIdVariableHeader
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.suback import SubackPacket, SubackPayload
+from amqtt.mqtt.packet import PacketIdVariableHeader
+from amqtt.adapters import BufferReader
 
 
 class SubackPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_subscribe.py b/tests/mqtt/test_subscribe.py
index c8c2aeb2..f3a70aee 100644
--- a/tests/mqtt/test_subscribe.py
+++ b/tests/mqtt/test_subscribe.py
@@ -4,10 +4,10 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.subscribe import SubscribePacket, SubscribePayload
-from hbmqtt.mqtt.packet import PacketIdVariableHeader
-from hbmqtt.mqtt.constants import QOS_1, QOS_2
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.subscribe import SubscribePacket, SubscribePayload
+from amqtt.mqtt.packet import PacketIdVariableHeader
+from amqtt.mqtt.constants import QOS_1, QOS_2
+from amqtt.adapters import BufferReader
 
 
 class SubscribePacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_unsuback.py b/tests/mqtt/test_unsuback.py
index 60994cfc..7cfc3097 100644
--- a/tests/mqtt/test_unsuback.py
+++ b/tests/mqtt/test_unsuback.py
@@ -4,9 +4,9 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.unsuback import UnsubackPacket
-from hbmqtt.mqtt.packet import PacketIdVariableHeader
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.unsuback import UnsubackPacket
+from amqtt.mqtt.packet import PacketIdVariableHeader
+from amqtt.adapters import BufferReader
 
 
 class UnsubackPacketTest(unittest.TestCase):
diff --git a/tests/mqtt/test_unsubscribe.py b/tests/mqtt/test_unsubscribe.py
index fbef0178..44d1500a 100644
--- a/tests/mqtt/test_unsubscribe.py
+++ b/tests/mqtt/test_unsubscribe.py
@@ -4,9 +4,9 @@
 import asyncio
 import unittest
 
-from hbmqtt.mqtt.unsubscribe import UnsubscribePacket, UnubscribePayload
-from hbmqtt.mqtt.packet import PacketIdVariableHeader
-from hbmqtt.adapters import BufferReader
+from amqtt.mqtt.unsubscribe import UnsubscribePacket, UnubscribePayload
+from amqtt.mqtt.packet import PacketIdVariableHeader
+from amqtt.adapters import BufferReader
 
 
 class UnsubscribePacketTest(unittest.TestCase):
diff --git a/tests/plugins/test_authentication.py b/tests/plugins/test_authentication.py
index 16477dae..ba8158e7 100644
--- a/tests/plugins/test_authentication.py
+++ b/tests/plugins/test_authentication.py
@@ -6,9 +6,9 @@
 import logging
 import os
 import asyncio
-from hbmqtt.plugins.manager import BaseContext
-from hbmqtt.plugins.authentication import AnonymousAuthPlugin, FileAuthPlugin
-from hbmqtt.session import Session
+from amqtt.plugins.manager import BaseContext
+from amqtt.plugins.authentication import AnonymousAuthPlugin, FileAuthPlugin
+from amqtt.session import Session
 
 formatter = (
     "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
diff --git a/tests/plugins/test_manager.py b/tests/plugins/test_manager.py
index 42ef049c..3c899a37 100644
--- a/tests/plugins/test_manager.py
+++ b/tests/plugins/test_manager.py
@@ -5,7 +5,7 @@
 import logging
 import asyncio
 
-from hbmqtt.plugins.manager import PluginManager
+from amqtt.plugins.manager import PluginManager
 
 formatter = (
     "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
@@ -40,6 +40,13 @@ def setUp(self):
         self.loop = asyncio.new_event_loop()
 
     def test_load_plugin(self):
+        manager = PluginManager("amqtt.test.plugins", context=None)
+        assert len(manager._plugins) > 0
+
+    def test_load_plugin_transition(self):
+        # entry points have been renamed from hbmqtt. to amqtt.
+        # For now the plugin manager automatically rewrites
+        # hbmqtt.test.plugins -> amqtt.test.plugins
         manager = PluginManager("hbmqtt.test.plugins", context=None)
         assert len(manager._plugins) > 0
 
@@ -49,7 +56,7 @@ async def fire_event():
             await asyncio.sleep(1, loop=self.loop)
             await manager.close()
 
-        manager = PluginManager("hbmqtt.test.plugins", context=None, loop=self.loop)
+        manager = PluginManager("amqtt.test.plugins", context=None, loop=self.loop)
         self.loop.run_until_complete(fire_event())
         plugin = manager.get_plugin("event_plugin")
         assert plugin.object.test_flag
@@ -59,7 +66,7 @@ async def fire_event():
             await manager.fire_event("test", wait=True)
             await manager.close()
 
-        manager = PluginManager("hbmqtt.test.plugins", context=None, loop=self.loop)
+        manager = PluginManager("amqtt.test.plugins", context=None, loop=self.loop)
         self.loop.run_until_complete(fire_event())
         plugin = manager.get_plugin("event_plugin")
         assert plugin.object.test_flag
@@ -68,7 +75,7 @@ def test_map_coro(self):
         async def call_coro():
             await manager.map_plugin_coro("test_coro")
 
-        manager = PluginManager("hbmqtt.test.plugins", context=None, loop=self.loop)
+        manager = PluginManager("amqtt.test.plugins", context=None, loop=self.loop)
         self.loop.run_until_complete(call_coro())
         plugin = manager.get_plugin("event_plugin")
         assert plugin.object.test_coro
@@ -77,7 +84,7 @@ def test_map_coro_return(self):
         async def call_coro():
             return await manager.map_plugin_coro("ret_coro")
 
-        manager = PluginManager("hbmqtt.test.plugins", context=None, loop=self.loop)
+        manager = PluginManager("amqtt.test.plugins", context=None, loop=self.loop)
         ret = self.loop.run_until_complete(call_coro())
         plugin = manager.get_plugin("event_plugin")
         self.assertEqual(ret[plugin], "TEST")
@@ -91,6 +98,6 @@ def test_map_coro_filter(self):
         async def call_coro():
             return await manager.map_plugin_coro("ret_coro", filter_plugins=[])
 
-        manager = PluginManager("hbmqtt.test.plugins", context=None, loop=self.loop)
+        manager = PluginManager("amqtt.test.plugins", context=None, loop=self.loop)
         ret = self.loop.run_until_complete(call_coro())
         assert len(ret) == 0
diff --git a/tests/plugins/test_persistence.py b/tests/plugins/test_persistence.py
index b1eb7a0c..013e2839 100644
--- a/tests/plugins/test_persistence.py
+++ b/tests/plugins/test_persistence.py
@@ -7,8 +7,8 @@
 import os
 import asyncio
 import sqlite3
-from hbmqtt.plugins.manager import BaseContext
-from hbmqtt.plugins.persistence import SQLitePlugin
+from amqtt.plugins.manager import BaseContext
+from amqtt.plugins.persistence import SQLitePlugin
 
 formatter = (
     "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
diff --git a/tests/test_broker.py b/tests/test_broker.py
index 153f0ef1..f9689e5d 100644
--- a/tests/test_broker.py
+++ b/tests/test_broker.py
@@ -7,8 +7,8 @@
 
 import pytest
 
-from hbmqtt.adapters import StreamReaderAdapter, StreamWriterAdapter
-from hbmqtt.broker import (
+from amqtt.adapters import StreamReaderAdapter, StreamWriterAdapter
+from amqtt.broker import (
     EVENT_BROKER_PRE_START,
     EVENT_BROKER_POST_START,
     EVENT_BROKER_PRE_SHUTDOWN,
@@ -19,8 +19,8 @@
     EVENT_BROKER_CLIENT_UNSUBSCRIBED,
     EVENT_BROKER_MESSAGE_RECEIVED,
 )
-from hbmqtt.client import MQTTClient, ConnectException
-from hbmqtt.mqtt import (
+from amqtt.client import MQTTClient, ConnectException
+from amqtt.mqtt import (
     ConnectPacket,
     ConnackPacket,
     PublishPacket,
@@ -29,8 +29,8 @@
     PubcompPacket,
     DisconnectPacket,
 )
-from hbmqtt.mqtt.connect import ConnectVariableHeader, ConnectPayload
-from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
+from amqtt.mqtt.connect import ConnectVariableHeader, ConnectPayload
+from amqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
 
 
 formatter = (
@@ -467,7 +467,7 @@ async def test_client_subscribe_publish_dollar_topic_2(broker):
 
 @pytest.mark.asyncio
 @pytest.mark.xfail(
-    reason="see https://github.com/Yakifo/aio-hbmqtt/issues/16", strict=False
+    reason="see https://github.com/Yakifo/aio-amqtt/issues/16", strict=False
 )
 async def test_client_publish_retain_subscribe(broker):
     sub_client = MQTTClient()
diff --git a/tests/test_client.py b/tests/test_client.py
index 10c27089..eb958653 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -10,9 +10,9 @@
 
 import pytest
 
-from hbmqtt.client import MQTTClient, ConnectException
-from hbmqtt.broker import Broker
-from hbmqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
+from amqtt.client import MQTTClient, ConnectException
+from amqtt.broker import Broker
+from amqtt.mqtt.constants import QOS_0, QOS_1, QOS_2
 
 formatter = (
     "[%(asctime)s] %(name)s {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
@@ -39,7 +39,7 @@
 def setup_module():
     global ca_file, temp_dir
 
-    temp_dir = tempfile.mkdtemp(prefix="hbmqtt-test-")
+    temp_dir = tempfile.mkdtemp(prefix="amqtt-test-")
     url = "http://test.mosquitto.org/ssl/mosquitto.org.crt"
     ca_file = os.path.join(temp_dir, "mosquitto.org.crt")
     urllib.request.urlretrieve(url, ca_file)
@@ -76,7 +76,7 @@ async def test_connect_tcp_failure():
 
 @pytest.mark.asyncio
 async def test_connect_ws():
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("ws://127.0.0.1:8080/")
@@ -87,7 +87,7 @@ async def test_connect_ws():
 
 @pytest.mark.asyncio
 async def test_reconnect_ws_retain_username_password():
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("ws://fred:password@127.0.0.1:8080/")
@@ -102,7 +102,7 @@ async def test_reconnect_ws_retain_username_password():
 
 @pytest.mark.asyncio
 async def test_connect_ws_secure():
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("ws://127.0.0.1:8081/", cafile=ca_file)
@@ -113,7 +113,7 @@ async def test_connect_ws_secure():
 
 @pytest.mark.asyncio
 async def test_ping():
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("mqtt://127.0.0.1/")
@@ -125,7 +125,7 @@ async def test_ping():
 
 @pytest.mark.asyncio
 async def test_subscribe():
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("mqtt://127.0.0.1/")
@@ -146,7 +146,7 @@ async def test_subscribe():
 
 @pytest.mark.asyncio
 async def test_unsubscribe():
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("mqtt://127.0.0.1/")
@@ -165,7 +165,7 @@ async def test_unsubscribe():
 @pytest.mark.asyncio
 async def test_deliver():
     data = b"data"
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("mqtt://127.0.0.1/")
@@ -191,7 +191,7 @@ async def test_deliver():
 
 @pytest.mark.asyncio
 async def test_deliver_timeout():
-    broker = Broker(broker_config, plugin_namespace="hbmqtt.test.plugins")
+    broker = Broker(broker_config, plugin_namespace="amqtt.test.plugins")
     await broker.start()
     client = MQTTClient()
     await client.connect("mqtt://127.0.0.1/")
diff --git a/tests/test_codecs.py b/tests/test_codecs.py
index acf8cfd1..a4314b18 100644
--- a/tests/test_codecs.py
+++ b/tests/test_codecs.py
@@ -4,7 +4,7 @@
 import unittest
 import asyncio
 
-from hbmqtt.codecs import (
+from amqtt.codecs import (
     bytes_to_hex_str,
     bytes_to_int,
     decode_string,