diff --git a/.gitignore b/.gitignore
index 72364f99fe4bf8d5262df3b19b33102aeaa791e5..3106894ec01b2314691f2c683effeda978596682 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.idea
+
 # Byte-compiled / optimized / DLL files
 __pycache__/
 *.py[cod]
diff --git a/pi_mqtt_gpio/server.py b/pi_mqtt_gpio/server.py
index b82623e476d75f90aa6c54635a3ebd107b4ddb04..a1fa80af901ab38368943af21c0901e4a2767b82 100644
--- a/pi_mqtt_gpio/server.py
+++ b/pi_mqtt_gpio/server.py
@@ -12,12 +12,19 @@ from pi_mqtt_gpio.modules import PinPullup, PinDirection
 RECONNECT_DELAY_SECS = 5
 GPIOS = {}
 LAST_STATES = {}
+SET_TOPIC = "set"
+OUTPUT_TOPIC = "output"
+
 
 _LOG = logging.getLogger(__name__)
 _LOG.addHandler(logging.StreamHandler())
 _LOG.setLevel(logging.DEBUG)
 
 
+class CannotInstallModuleRequirements(Exception):
+    pass
+
+
 def on_disconnect(client, userdata, rc):
     _LOG.warning("Disconnected from MQTT server with code: %s" % rc)
     while rc != 0:
@@ -26,20 +33,70 @@ def on_disconnect(client, userdata, rc):
 
 
 def install_missing_requirements(module):
+    """
+    Some of the modules require external packages to be installed. This gets
+    the list from the `REQUIREMENTS` module attribute and attempts to
+    install the requirements using pip.
+    """
+    reqs = []
     try:
         reqs = getattr(module, "REQUIREMENTS")
     except AttributeError:
+        pass
+    if not reqs:
         _LOG.info("Module %r has no extra requirements to install." % module)
         return
     import pkg_resources
-    installed = pkg_resources.WorkingSet()
-    not_installed = []
+    pkgs_installed = pkg_resources.WorkingSet()
+    pkgs_required = []
     for req in reqs:
-        if installed.find(pkg_resources.Requirement.parse(req)) is None:
-            not_installed.append(req)
-    if not_installed:
-        import pip
-        pip.main(["install"] + not_installed)
+        if pkgs_installed.find(pkg_resources.Requirement.parse(req)) is None:
+            pkgs_required.append(req)
+    if pkgs_required:
+        from pip.commands.install import InstallCommand
+        from pip.status_codes import SUCCESS
+        cmd = InstallCommand()
+        result = cmd.main(pkgs_required)
+        if result != SUCCESS:
+            raise CannotInstallModuleRequirements(
+                "Unable to install packages for module %r (%s)..." % (
+                    module, pkgs_required))
+
+
+def output_name_from_topic_set(topic, topic_prefix):
+    """
+    Return the name of the output which the topic is setting.
+    :param topic: str such as mytopicprefix/output/tv_lamp/set
+    :param topic_prefix: str prefix of our topics
+    :return: str name of the output this topic is setting
+    """
+    if not topic.endswith("/%s" % SET_TOPIC):
+        raise ValueError("This topic does not end with '/%s'" % SET_TOPIC)
+    return topic[len("%s/%s/" % (topic_prefix, OUTPUT_TOPIC)):-len(SET_TOPIC)-1]
+
+
+def init_mqtt(config):
+    """
+    Configure MQTT client.
+    """
+    client = mqtt.Client()
+    user = config["mqtt"].get("user")
+    password = config["mqtt"].get("password")
+    topic_prefix = config["mqtt"]["topic_prefix"].rstrip("/")
+
+    if user and password:
+        client.username_pw_set(user, password)
+
+    def on_conn(client, userdata, flags, rc):
+        for output_config in config.get("digital_outputs", []):
+            topic = "%s/%s/%s/%s" % (topic_prefix, OUTPUT_TOPIC, output_config["name"], SET_TOPIC)
+            client.subscribe(topic, qos=1)
+            _LOG.info("Subscribed to topic: %r", topic)
+
+    def on_msg(client, userdata, msg):
+        _LOG.info("Received message on topic %r: %r", msg.topic, msg.payload)
+
+
 
 
 if __name__ == "__main__":
@@ -60,7 +117,7 @@ if __name__ == "__main__":
 
     def on_conn(client, userdata, flags, rc):
         for output_config in config.get("digital_outputs", []):
-            topic = "%s/output/%s/set" % (topic_prefix, output_config["name"])
+            topic = "%s/output/%s/%s" % (topic_prefix, output_config["name"], SET_TOPIC)
             client.subscribe(topic, qos=1)
             _LOG.info("Subscribed to topic: %r", topic)
 
@@ -138,7 +195,7 @@ if __name__ == "__main__":
                     LAST_STATES[input_config["name"]] = state
             sleep(0.05)
     except KeyboardInterrupt:
-        print ""
+        print("")
     finally:
         client.disconnect()
         client.loop_stop()
diff --git a/tests/test_pi_mqtt_gpio.py b/tests/test_pi_mqtt_gpio.py
deleted file mode 100644
index 61de3a2c2bfb9b98b919ee2a0cbeb9fb31e0e92e..0000000000000000000000000000000000000000
--- a/tests/test_pi_mqtt_gpio.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def test_noop():
-    pass
diff --git a/tests/test_server.py b/tests/test_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8c96c2f9ed89a5d66bd4730c47d261fbe310444
--- /dev/null
+++ b/tests/test_server.py
@@ -0,0 +1,72 @@
+import uuid
+
+import mock
+import pytest
+from pip.status_codes import SUCCESS
+
+from pi_mqtt_gpio import server
+
+
+@mock.patch("pkg_resources.WorkingSet")
+def test_imr_no_attribute(mock_ws):
+    """
+    Should not bother looking up what's installed when there's no requirements.
+    """
+    module = object()
+    server.install_missing_requirements(module)
+    mock_ws.assert_not_called()
+
+
+@mock.patch("pkg_resources.WorkingSet")
+def test_imr_blank_list(mock_ws):
+    """
+    Should not bother looking up what's installed when there's no requirements.
+    """
+    module = mock.Mock()
+    module.REQUIREMENTS = []
+    server.install_missing_requirements(module)
+    mock_ws.assert_not_called()
+
+
+@mock.patch("pkg_resources.WorkingSet.find", return_value=None)
+@mock.patch("pip.commands.install.InstallCommand.main", return_value=SUCCESS)
+def test_imr_has_requirements(mock_pip, mock_find):
+    """
+    Should install all missing args.
+    """
+    module = mock.Mock()
+    module.REQUIREMENTS = ["testreq1", "testreq2==1.2.3"]
+    server.install_missing_requirements(module)
+    args, _ = mock_pip.call_args
+    assert args == (["testreq1", "testreq2==1.2.3"],)
+
+
+@mock.patch("pkg_resources.WorkingSet.find", return_value=None)
+def test_imr_bad_pkg_fails(mock_find):
+    """
+    Should raise exception when pkg installation fails.
+    """
+    module = mock.Mock()
+    module.REQUIREMENTS = [str(uuid.uuid4())]
+    with pytest.raises(server.CannotInstallModuleRequirements):
+        server.install_missing_requirements(module)
+
+
+def test_onfts_no_set():
+    """
+    Should raise a ValueError when there's no /set at the end of the topic.
+    """
+    with pytest.raises(ValueError):
+        server.output_name_from_topic_set("myprefix/output/myoutputname", "myprefix")
+
+
+def test_onfts_returns_output_name():
+    """
+    Should return the proper output name.
+    """
+    output_name = "myoutputname"
+    ret = server.output_name_from_topic_set(
+        "myprefix/output/%s/%s" % (output_name, server.SET_TOPIC),
+        "myprefix"
+    )
+    assert ret == output_name
diff --git a/tox.ini b/tox.ini
index 26b89add27c23176e0e442cde627bfa377ef5ee1..fdd947892747669c7fee2528c36368738f273a9b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,6 +6,7 @@ deps =
     -rrequirements.txt
     flake8
     pytest
+    mock
 setenv =
     PYTHONPATH = {toxinidir}
 commands =