diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bc49ac9d1b4c8fa601d09fd605ae07ab7b8b31cf
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,21 @@
+language: python
+python: 2.7
+env:
+- TOX_ENV=py27
+- TOX_ENV=py33
+- TOX_ENV=py34
+- TOX_ENV=flake8
+- TOX_ENV=pylint
+install:
+- pip install tox
+script:
+- tox -e $TOX_ENV
+deploy:
+  provider: pypi
+  user: flyte
+  password:
+    secure: doZN0aLEdMiNq9lw4n2AWYTMa+HiW0SIirMX3ffSLT9L4ynFkZA1R0RCpXkVRgIC2ug/bl7OjYpLp6QRbvoRkqaMpbtT+vFB9j0ywkZte8V00uEqgrEa2D2bOAXmcSRq965LJ46b9WoHy3+5/1YjTKv+VXzDxy7wVK1i97pc0N51YxNVKO8c77C+D3CXo/Oey3webXllMwiUFuSTEQgErE8qMMNsE1XI9M67Vy55ZK9qs+UTqUdU+fXDIaJmK3jnDp1b/yhtZTeHyWiPRM2DNUGj+dsAulLBINAmWqC7tswUJ53WdXtKgETffslwEYCLw+6hoLPAYv2vMumlihm7PjrK65dN7PoPBL5qYSVLG2DpxBYtYkmLiu1yvOcaswgk0TmwMAQa76sF0fJ1AIwPCUEOn7OvVAqG76dggvl00hZU/gBic+PaFqmZT7t72cAP9i+9xdfeUdwdOsuLuKH2lvvQKskS2tX0U21kdzhj7G5S8g5qNVJ9hnerG6oQx2aHvP9lCzMr6/VFu6Zbr6XrHmNyQjOuimO1ytL7X5j4k9UdME2FyVnxXqc/8NydwWrTFINFtsKN8jXl1J+mCdeAud+X4Q/8uFq5KqYtE3UgZYwY71vt06SN3Nk+O9hMNkfkcgIwf4jLap/QgHpK3jXVz1Zqd/atV8motNZ3ZzBgKSU=
+  on:
+    tags: true
+    repo: flyte/pi-mqtt-gpio
+  distributions: sdist
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000000000000000000000000000000000000..46ef27da87930416841cc71231b1babc7bdbe55e
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+# http://packages.python.org/distribute/setuptools.html#including-data-files
+include *requirements.txt
diff --git a/pi_mqtt_gpio/modules/__init__.py b/pi_mqtt_gpio/modules/__init__.py
index 89d66978e59ec8091ca2be283f83f714496a0d87..0c8265eb18d06ec0fed5ecc006841b95f9261e00 100644
--- a/pi_mqtt_gpio/modules/__init__.py
+++ b/pi_mqtt_gpio/modules/__init__.py
@@ -15,25 +15,11 @@ class PinPullup(Enum):
 
 class GenericGPIO(object):
     """
-    Abstracts a generic GPIO interface to be implemented by the modules in this directory.
+    Abstracts a generic GPIO interface to be implemented by the modules in this
+    directory.
     """
     __metaclass__ = abc.ABCMeta
 
-    # def __init__(self, config):
-    #     self.setup_gpio(config)
-
-    #     for pin_config in config.get("digital_inputs"):
-    #         pullup = None
-    #         if pin_config.get("pullup"):
-    #             pullup = PinPullup.UP
-    #         elif pin_config.get("pulldown"):
-    #             pullup = PinPullup.DOWN
-
-    #         self.setup_pin(pin_config["pin"], PinDirection.INPUT, pullup, pin_config)
-
-    #     for pin_config in config.get("digital_outputs"):
-    #         self.setup_pin(pin_config["pin"], PinDirection.OUTPUT, None, pin_config)
-
     @abc.abstractmethod
     def setup_pin(self, pin, direction, pullup, pin_config):
         pass
diff --git a/pi_mqtt_gpio/server.py b/pi_mqtt_gpio/server.py
index dc95fc0dd284b8a8f182f0cf02dc5b3fd7b00ee1..a1adb97786abff2e33b75789b5762b78397b998c 100644
--- a/pi_mqtt_gpio/server.py
+++ b/pi_mqtt_gpio/server.py
@@ -82,14 +82,17 @@ if __name__ == "__main__":
         gpio = GPIOS[output_config["module"]]
         gpio.set_pin(output_config["pin"], value)
         _LOG.info("Set output %r to %r", output_config["name"], value)
-        client.publish("%s/output/%s" % (topic_prefix, output_name), payload=msg.payload)
+        client.publish(
+            "%s/output/%s" % (topic_prefix, output_name),
+            payload=msg.payload)
 
     client.on_disconnect = on_disconnect
     client.on_connect = on_conn
     client.on_message = on_msg
 
     for gpio_config in config["gpio_modules"]:
-        gpio_module = import_module("pi_mqtt_gpio.modules.%s" % gpio_config["module"])
+        gpio_module = import_module(
+            "pi_mqtt_gpio.modules.%s" % gpio_config["module"])
         install_missing_requirements(gpio_module)
         GPIOS[gpio_config["name"]] = gpio_module.GPIO(gpio_config)
 
@@ -101,12 +104,14 @@ if __name__ == "__main__":
             pud = PinPullup.DOWN
 
         gpio = GPIOS[input_config["module"]]
-        gpio.setup_pin(input_config["pin"], PinDirection.INPUT, pud, input_config)
+        gpio.setup_pin(
+            input_config["pin"], PinDirection.INPUT, pud, input_config)
         LAST_STATES[input_config["name"]] = None
 
     for output_config in config["digital_outputs"]:
         gpio = GPIOS[output_config["module"]]
-        gpio.setup_pin(output_config["pin"], PinDirection.OUTPUT, None, output_config)
+        gpio.setup_pin(
+            output_config["pin"], PinDirection.OUTPUT, None, output_config)
 
     client.connect(config["mqtt"]["host"], config["mqtt"]["port"], 60)
     client.loop_start()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..6fa95e2c3752035a66daf9c5daa1177ee9f2f922
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,14 @@
+[bumpversion]
+current_version = 0.0.1
+commit = True
+tag = True
+tag_name = {new_version}
+message = Bump version to {new_version}
+
+[bumpversion:file:setup.py]
+
+[aliases]
+test = pytest
+
+[flake8]
+exclude = docs
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..e347ba4cd6fdd2044d03df62fc0a0c56f65b56df
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,77 @@
+#  -*- coding: utf-8 -*-
+"""
+Setuptools script for the pi-mqtt-gpio project.
+"""
+
+import os
+from textwrap import fill, dedent
+
+try:
+    from setuptools import setup, find_packages
+except ImportError:
+    from ez_setup import use_setuptools
+    use_setuptools()
+    from setuptools import setup, find_packages
+
+
+def required(fname):
+    return open(
+        os.path.join(
+            os.path.dirname(__file__), fname
+        )
+    ).read().split('\n')
+
+
+setup(
+    name="pi_mqtt_gpio",
+    version="0.0.1",
+    packages=find_packages(
+        exclude=[
+            "*.tests",
+            "*.tests.*",
+            "tests.*",
+            "tests",
+            "*.ez_setup",
+            "*.ez_setup.*",
+            "ez_setup.*",
+            "ez_setup",
+            "*.examples",
+            "*.examples.*",
+            "examples.*",
+            "examples"
+        ]
+    ),
+    scripts=[],
+    entry_points={
+        "console_scripts": [
+            "pi_mqtt_gpio = pi_mqtt_gpio.__main__:main"
+        ]
+    },
+    include_package_data=True,
+    setup_requires='pytest-runner',
+    tests_require='pytest',
+    install_requires=required('requirements.txt'),
+    test_suite='pytest',
+    zip_safe=False,
+    # Metadata for upload to PyPI
+    author='Ellis Percival',
+    author_email="pi_mqtt_gpio@failcode.co.uk",
+    description=fill(dedent("""\
+        Expose the Raspberry Pi GPIO pins (and/or external IO modules such as the PCF8574) to an
+        MQTT server. This allows pins to be read and switched by reading or writing messages to
+        MQTT topics.
+    """)),
+    classifiers=[
+        "Programming Language :: Python",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: MIT License",
+        "Natural Language :: English",
+        "Operating System :: OS Independent",
+        "Topic :: Communications",
+        "Topic :: Home Automation",
+        "Topic :: System :: Networking"
+    ],
+    license="MIT",
+    keywords="",
+    url="https://github.com/flyte/pi-mqtt-gpio"
+)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000000000000000000000000000000000000..3b2abaf82bdc80913985cf295a024ca00f4056a5
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = py27,py3{3,4},flake8
+
+[testenv]
+deps =
+    -rrequirements.txt
+    flake8
+    pytest
+setenv =
+    PYTHONPATH = {toxinidir}
+commands =
+    py.test
+
+[testenv:flake8]
+commands = flake8 pi_mqtt_gpio