diff --git a/Makefile b/Makefile
index 05c50f93b783291c165da02a95a06222e63bd126..bccdd8652ac439914386111547bb6203b30fe6ca 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
 LIBRARIES=libmoberg.so
-MOBERG_VERSION=$(shell git describe --tags | sed -e 's/^v//;s/-/./g' )
+MOBERG_VERSION=$(shell git describe --tags \
+		| sed -re 's/^v//;s/-(.*)-(.*)$$/.dev\1+\2/')
 CCFLAGS+=-Wall -Werror -I$(shell pwd) -O3 -g
 LDFLAGS+=-L$(shell pwd)/build/ -lmoberg
 PLUGINS:=$(sort $(wildcard plugins/*))
diff --git a/adaptors/python2/.gitignore b/adaptors/python2/.gitignore
deleted file mode 100644
index f65eb2942dc78eb04f2d5d1e680fd693a7f484aa..0000000000000000000000000000000000000000
--- a/adaptors/python2/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/__pycache__
-/install
\ No newline at end of file
diff --git a/adaptors/python2/Makefile b/adaptors/python2/Makefile
deleted file mode 100644
index eeb6d4596350123987bb6e5fe0f537587c08584c..0000000000000000000000000000000000000000
--- a/adaptors/python2/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-MOBERG_VERSION=$(shell git describe --tags)
-
-all: BUILD INSTALL
-
-.PHONY: BUILD 
-BUILD:
-	python2 ./setup.py build
-
-.PHONY: INSTALL
-INSTALL:
-	MOBERG_VERSION=$(MOBERG_VERSION) python2 \
-		./setup.py install -O1 --prefix=/usr/ --root=./install
-
-clean:
-	rm -rf build install
diff --git a/adaptors/python2/python-moberg.c b/adaptors/python2/python-moberg.c
deleted file mode 100644
index 3ebadf94f8aa1504397e0349e698f8a97e35f947..0000000000000000000000000000000000000000
--- a/adaptors/python2/python-moberg.c
+++ /dev/null
@@ -1,771 +0,0 @@
-/*
-    python-moberg.c -- python interface to moberg I/O system
-
-    Copyright (C) 2019 Anders Blomdell <anders.blomdell@gmail.com>
-
-    This file is part of Moberg.
-
-    Moberg is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-*/
-
-#include <Python.h>
-#include <structmember.h>
-#include <moberg.h>
-
-#ifndef Py_UNUSED	/* This is already defined for Python 3.4 onwards */
-#ifdef __GNUC__
-#define Py_UNUSED(name) _unused_ ## name __attribute__((unused))
-#else
-#define Py_UNUSED(name) _unused_ ## name
-#endif
-#endif
-
-static PyTypeObject MobergType;
-static PyTypeObject MobergAnalogInType;
-static PyTypeObject MobergAnalogOutType;
-static PyTypeObject MobergDigitalInType;
-static PyTypeObject MobergDigitalOutType;
-static PyTypeObject MobergEncoderInType;
-
-/*
- * moberg.Moberg class
- */
-
-typedef struct {
-  PyObject_HEAD
-  struct moberg *moberg;
-} MobergObject;
-
-static void
-Moberg_dealloc(MobergObject *self)
-{
-  if (self->moberg) {
-    moberg_free(self->moberg);
-  }
-  Py_TYPE(self)->tp_free((PyObject *) self);
-}
-
-static PyObject *
-Moberg_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-  static char *kwlist[] = { NULL };
-  if (! PyArg_ParseTupleAndKeywords(args, kwds, "", kwlist)) {
-    return NULL;
-  }
-  MobergObject *self = (MobergObject *) type->tp_alloc(type, 0);
-  return (PyObject *) self;
-}
-
-static int
-Moberg_init(MobergObject *self, PyObject *args, PyObject *kwds)
-{
-  static char *kwlist[] = { NULL };
-  if (! PyArg_ParseTupleAndKeywords(args, kwds, "", kwlist)) {
-    return -1;
-  }
-  self->moberg = moberg_new();
-  if (! self->moberg) {
-    PyErr_SetString(PyExc_OSError, "moberg.Moberg.__init__() failed");
-    return -1;
-  }
-  return 0;
-}
-
-static PyObject *
-Moberg_analog_in(MobergObject *self, PyObject *args)
-{
-  int index;
-  if (! PyArg_ParseTuple(args, "i", &index)) {
-    PyErr_SetString(PyExc_AttributeError, "index");
-    return NULL;
-  }
-  PyObject *ain_args = Py_BuildValue("Oi", self, index);
-  PyObject *ain = MobergAnalogInType.tp_new(&MobergAnalogInType,
-                                            ain_args, NULL);
-  /* NB: _AnalogIn.__init__ should never be called */
-  Py_DECREF(ain_args);
-  return ain;
-}
-
-static PyObject *
-Moberg_analog_out(MobergObject *self, PyObject *args)
-{
-  int index;
-  if (! PyArg_ParseTuple(args, "i", &index)) {
-    PyErr_SetString(PyExc_AttributeError, "index");
-    return NULL;
-  }
-  PyObject *aout_args = Py_BuildValue("Oi", self, index);
-  PyObject *aout = MobergAnalogOutType.tp_new(&MobergAnalogOutType,
-                                              aout_args, NULL);
-  /* NB: _AnalogOut.__init__ should never be called */
-  Py_DECREF(aout_args);
-  return aout;
-}
-
-static PyObject *
-Moberg_digital_in(MobergObject *self, PyObject *args)
-{
-  int index;
-  if (! PyArg_ParseTuple(args, "i", &index)) {
-    PyErr_SetString(PyExc_AttributeError, "index");
-    return NULL;
-  }
-  PyObject *din_args = Py_BuildValue("Oi", self, index);
-  PyObject *din = MobergDigitalInType.tp_new(&MobergDigitalInType,
-                                            din_args, NULL);
-  /* NB: _DigitalIn.__init__ should never be called */
-  Py_DECREF(din_args);
-  return din;
-}
-
-static PyObject *
-Moberg_digital_out(MobergObject *self, PyObject *args)
-{
-  int index;
-  if (! PyArg_ParseTuple(args, "i", &index)) {
-    PyErr_SetString(PyExc_AttributeError, "index");
-    return NULL;
-  }
-  PyObject *dout_args = Py_BuildValue("Oi", self, index);
-  PyObject *dout = MobergDigitalOutType.tp_new(&MobergDigitalOutType,
-                                              dout_args, NULL);
-  /* NB: _DigitalOut.__init__ should never be called */
-  Py_DECREF(dout_args);
-  return dout;
-}
-
-static PyObject *
-Moberg_encoder_in(MobergObject *self, PyObject *args)
-{
-  int index;
-  if (! PyArg_ParseTuple(args, "i", &index)) {
-    PyErr_SetString(PyExc_AttributeError, "index");
-    return NULL;
-  }
-  PyObject *ein_args = Py_BuildValue("Oi", self, index);
-  PyObject *ein = MobergEncoderInType.tp_new(&MobergEncoderInType,
-                                             ein_args, NULL);
-  /* NB: _EncoderIn.__init__ should never be called */
-  Py_DECREF(ein_args);
-  return ein;
-}
-
-static PyMethodDef Moberg_methods[] = {
-    {"analog_in", (PyCFunction) Moberg_analog_in, METH_VARARGS,
-     "Return AnalogIn object for channel"
-    },
-    {"analog_out", (PyCFunction) Moberg_analog_out, METH_VARARGS,
-     "Return AnalogOut object for channel"
-    },
-    {"digital_in", (PyCFunction) Moberg_digital_in, METH_VARARGS,
-     "Return DigitalIn object for channel"
-    },
-    {"digital_out", (PyCFunction) Moberg_digital_out, METH_VARARGS,
-     "Return DigitalOut object for channel"
-    },
-    {"encoder_in", (PyCFunction) Moberg_encoder_in, METH_VARARGS,
-     "Return EncoderIn object for channel"
-    },
-
-    {NULL}  /* Sentinel */
-};
-
-static PyTypeObject MobergType = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_name = "moberg.Moberg",
-    .tp_doc = "Moberg objects",
-    .tp_basicsize = sizeof(MobergObject),
-    .tp_itemsize = 0,
-    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-    .tp_new = Moberg_new,
-    .tp_init = (initproc) Moberg_init,
-    .tp_dealloc = (destructor) Moberg_dealloc,
-    .tp_methods = Moberg_methods,
-};
-
-/*
- * moberg._AnalogIn class (should never be directly instatiated) 
- */
-
-typedef struct {
-  PyObject_HEAD
-  PyObject *moberg_object;
-  int index;
-  struct moberg_analog_in channel;
-} MobergAnalogInObject;
-
-static void
-MobergAnalogIn_dealloc(MobergAnalogInObject *self)
-{
-  if (self->moberg_object) {
-    struct moberg *moberg = ((MobergObject*)self->moberg_object)->moberg;
-    struct moberg_status status =
-      moberg_analog_in_close(moberg, self->index, self->channel);
-    if (! moberg_OK(status)) {
-      fprintf(stderr, "Failed to close moberg._AnalogIn(%d) [errno=%d]\n",
-              self->index, status.result);
-    }
-    Py_DECREF(self->moberg_object);
-  }
-  Py_TYPE(self)->tp_free((PyObject *) self);
-}
-
-static PyObject *
-MobergAnalogIn_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-  PyObject *moberg_object;
-  int index;
-  struct moberg_analog_in channel = {0};
-  
-  static char *kwlist[] = { "moberg", "index", NULL };
-  if (! PyArg_ParseTupleAndKeywords(args, kwds, "Oi", kwlist,
-                                    &moberg_object, &index)) {
-    goto err;
-  }
-  if (Py_TYPE(moberg_object) != &MobergType) {
-    PyErr_SetString(PyExc_AttributeError, "moberg argument is not Moberg");
-    goto err;
-  }
-  struct moberg *moberg = ((MobergObject*)moberg_object)->moberg;
-  struct moberg_status status = moberg_analog_in_open(moberg, index, &channel);
-  if (! moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._AnalogIn(%d) failed with %d",
-                 index, status.result);
-    goto err;
-  }
-  MobergAnalogInObject *self;
-  self = (MobergAnalogInObject *) type->tp_alloc(type, 0);
-  if (self == NULL) { goto close; }
-  Py_INCREF(moberg_object);
-  self->moberg_object = moberg_object;
-  self->index = index;
-  self->channel = channel;
-  return (PyObject *) self;
-close:
-  status = moberg_analog_in_close(moberg, index, channel);
-err:
-  return NULL;
-}
-
-static int
-MobergAnalogIn_init(MobergAnalogInObject *self, PyObject *args, PyObject *kwds)
-{
-  PyErr_SetString(PyExc_AttributeError, "Not intended to be called");
-  return -1;
-}
-
-static PyObject *
-MobergAnalogIn_read(MobergAnalogInObject *self, PyObject *Py_UNUSED(ignored))
-{
-  double value;
-  struct moberg_status status = self->channel.read(self->channel.context,
-                                                   &value);
-  if (!moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._AnalogIn(%d).read() failed with %d",
-                 self->index, status.result);
-  }
-  return Py_BuildValue("d", value);
-}
-
-static PyMethodDef MobergAnalogIn_methods[] = {
-    {"read", (PyCFunction) MobergAnalogIn_read, METH_NOARGS,
-     "Sample and return the AnalogIn value"
-    },
-    {NULL}  /* Sentinel */
-};
-
-static PyTypeObject MobergAnalogInType = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_name = "moberg.AnalogIn",
-    .tp_doc = "AnalogIn objects",
-    .tp_basicsize = sizeof(MobergAnalogInObject),
-    .tp_itemsize = 0,
-    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-    .tp_new = MobergAnalogIn_new,
-    .tp_init = (initproc) MobergAnalogIn_init,
-    .tp_dealloc = (destructor) MobergAnalogIn_dealloc,
-    .tp_methods = MobergAnalogIn_methods,
-};
-
-/*
- * moberg._AnalogOut class (should never be directly instatiated) 
- */
-
-typedef struct {
-  PyObject_HEAD
-  PyObject *moberg_object;
-  int index;
-  struct moberg_analog_out channel;
-} MobergAnalogOutObject;
-
-static void
-MobergAnalogOut_dealloc(MobergAnalogOutObject *self)
-{
-  if (self->moberg_object) {
-    struct moberg *moberg = ((MobergObject*)self->moberg_object)->moberg;
-    struct moberg_status status =
-      moberg_analog_out_close(moberg, self->index, self->channel);
-    if (! moberg_OK(status)) {
-      fprintf(stderr, "Failed to close moberg._AnalogOut(%d) [errno=%d]\n",
-              self->index, status.result);
-    }
-    Py_DECREF(self->moberg_object);
-  }
-  Py_TYPE(self)->tp_free((PyObject *) self);
-}
-
-static PyObject *
-MobergAnalogOut_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-  PyObject *moberg_object;
-  int index;
-  struct moberg_analog_out channel = {0};
-  
-  static char *kwlist[] = { "moberg", "index", NULL };
-  if (! PyArg_ParseTupleAndKeywords(args, kwds, "Oi", kwlist,
-                                    &moberg_object, &index)) {
-    goto err;
-  }
-  if (Py_TYPE(moberg_object) != &MobergType) {
-    PyErr_SetString(PyExc_AttributeError, "moberg argument is not Moberg");
-    goto err;
-  }
-  struct moberg *moberg = ((MobergObject*)moberg_object)->moberg;
-  struct moberg_status status = moberg_analog_out_open(moberg, index, &channel);
-  if (! moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._AnalogOut(%d) failed with %d",
-                 index, status.result);
-    goto err;
-  }
-  MobergAnalogOutObject *self;
-  self = (MobergAnalogOutObject *) type->tp_alloc(type, 0);
-  if (self == NULL) { goto close; }
-  Py_INCREF(moberg_object);
-  self->moberg_object = moberg_object;
-  self->index = index;
-  self->channel = channel;
-  return (PyObject *) self;
-close:
-  status = moberg_analog_out_close(moberg, index, channel);
-err:
-  return NULL;
-}
-
-static int
-MobergAnalogOut_init(MobergAnalogOutObject *self, PyObject *args, PyObject *kwds)
-{
-  PyErr_SetString(PyExc_AttributeError, "Not intended to be called");
-  return -1;
-}
-
-static PyObject *
-MobergAnalogOut_write(MobergAnalogOutObject *self, PyObject *args)
-{
-  double desired_value, actual_value;
-  if (! PyArg_ParseTuple(args, "d", &desired_value)) {
-    goto err;
-  }
-  struct moberg_status status = self->channel.write(self->channel.context,
-                                                    desired_value,
-                                                    &actual_value);
-  if (!moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._AnalogOut(%d).write() failed with %d",
-                 self->index, status.result);
-    goto err;
-  }
-  
-  return Py_BuildValue("d", actual_value);
-err:
-  return NULL;
-}
-
-static PyMethodDef MobergAnalogOut_methods[] = {
-    {"write", (PyCFunction) MobergAnalogOut_write, METH_VARARGS,
-     "Set AnalogOut value"
-    },
-    {NULL}  /* Sentinel */
-};
-
-static PyTypeObject MobergAnalogOutType = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_name = "moberg.AnalogOut",
-    .tp_doc = "AnalogOut objects",
-    .tp_basicsize = sizeof(MobergAnalogOutObject),
-    .tp_itemsize = 0,
-    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-    .tp_new = MobergAnalogOut_new,
-    .tp_init = (initproc) MobergAnalogOut_init,
-    .tp_dealloc = (destructor) MobergAnalogOut_dealloc,
-    .tp_methods = MobergAnalogOut_methods,
-};
-
-/*
- * moberg._DigitalIn class (should never be directly instatiated) 
- */
-
-typedef struct {
-  PyObject_HEAD
-  PyObject *moberg_object;
-  int index;
-  struct moberg_digital_in channel;
-} MobergDigitalInObject;
-
-static void
-MobergDigitalIn_dealloc(MobergDigitalInObject *self)
-{
-  if (self->moberg_object) {
-    struct moberg *moberg = ((MobergObject*)self->moberg_object)->moberg;
-    struct moberg_status status =
-      moberg_digital_in_close(moberg, self->index, self->channel);
-    if (! moberg_OK(status)) {
-      fprintf(stderr, "Failed to close moberg._DigitalIn(%d) [errno=%d]\n",
-              self->index, status.result);
-    }
-    Py_DECREF(self->moberg_object);
-  }
-  Py_TYPE(self)->tp_free((PyObject *) self);
-}
-
-static PyObject *
-MobergDigitalIn_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-  PyObject *moberg_object;
-  int index;
-  struct moberg_digital_in channel = {0};
-  
-  static char *kwlist[] = { "moberg", "index", NULL };
-  if (! PyArg_ParseTupleAndKeywords(args, kwds, "Oi", kwlist,
-                                    &moberg_object, &index)) {
-    goto err;
-  }
-  if (Py_TYPE(moberg_object) != &MobergType) {
-    PyErr_SetString(PyExc_AttributeError, "moberg argument is not Moberg");
-    goto err;
-  }
-  struct moberg *moberg = ((MobergObject*)moberg_object)->moberg;
-  struct moberg_status status = moberg_digital_in_open(moberg, index, &channel);
-  if (! moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._DigitalIn(%d) failed with %d",
-                 index, status.result);
-    goto err;
-  }
-  MobergDigitalInObject *self;
-  self = (MobergDigitalInObject *) type->tp_alloc(type, 0);
-  if (self == NULL) { goto close; }
-  Py_INCREF(moberg_object);
-  self->moberg_object = moberg_object;
-  self->index = index;
-  self->channel = channel;
-  return (PyObject *) self;
-close:
-  status = moberg_digital_in_close(moberg, index, channel);
-err:
-  return NULL;
-}
-
-static int
-MobergDigitalIn_init(MobergDigitalInObject *self, PyObject *args, PyObject *kwds)
-{
-  PyErr_SetString(PyExc_AttributeError, "Not intended to be called");
-  return -1;
-}
-
-static PyObject *
-MobergDigitalIn_read(MobergDigitalInObject *self, PyObject *Py_UNUSED(ignored))
-{
-  int value;
-  struct moberg_status status = self->channel.read(self->channel.context,
-                                                   &value);
-  if (!moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._DigitalIn(%d).read() failed with %d",
-                 self->index, status.result);
-  }
-  return Py_BuildValue("O", value ? Py_True: Py_False);
-}
-
-static PyMethodDef MobergDigitalIn_methods[] = {
-    {"read", (PyCFunction) MobergDigitalIn_read, METH_NOARGS,
-     "Sample and return the DigitalIn value"
-    },
-    {NULL}  /* Sentinel */
-};
-
-static PyTypeObject MobergDigitalInType = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_name = "moberg.DigitalIn",
-    .tp_doc = "DigitalIn objects",
-    .tp_basicsize = sizeof(MobergDigitalInObject),
-    .tp_itemsize = 0,
-    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-    .tp_new = MobergDigitalIn_new,
-    .tp_init = (initproc) MobergDigitalIn_init,
-    .tp_dealloc = (destructor) MobergDigitalIn_dealloc,
-    .tp_methods = MobergDigitalIn_methods,
-};
-
-/*
- * moberg._DigitalOut class (should never be directly instatiated) 
- */
-
-typedef struct {
-  PyObject_HEAD
-  PyObject *moberg_object;
-  int index;
-  struct moberg_digital_out channel;
-} MobergDigitalOutObject;
-
-static void
-MobergDigitalOut_dealloc(MobergDigitalOutObject *self)
-{
-  if (self->moberg_object) {
-    struct moberg *moberg = ((MobergObject*)self->moberg_object)->moberg;
-    struct moberg_status status =
-      moberg_digital_out_close(moberg, self->index, self->channel);
-    if (! moberg_OK(status)) {
-      fprintf(stderr, "Failed to close moberg._DigitalOut(%d) [errno=%d]\n",
-              self->index, status.result);
-    }
-    Py_DECREF(self->moberg_object);
-  }
-  Py_TYPE(self)->tp_free((PyObject *) self);
-}
-
-static PyObject *
-MobergDigitalOut_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-  PyObject *moberg_object;
-  int index;
-  struct moberg_digital_out channel = {0};
-  
-  static char *kwlist[] = { "moberg", "index", NULL };
-  if (! PyArg_ParseTupleAndKeywords(args, kwds, "Oi", kwlist,
-                                    &moberg_object, &index)) {
-    goto err;
-  }
-  if (Py_TYPE(moberg_object) != &MobergType) {
-    PyErr_SetString(PyExc_AttributeError, "moberg argument is not Moberg");
-    goto err;
-  }
-  struct moberg *moberg = ((MobergObject*)moberg_object)->moberg;
-  struct moberg_status status = moberg_digital_out_open(moberg, index, &channel);
-  if (! moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._DigitalOut(%d) failed with %d",
-                 index, status.result);
-    goto err;
-  }
-  MobergDigitalOutObject *self;
-  self = (MobergDigitalOutObject *) type->tp_alloc(type, 0);
-  if (self == NULL) { goto close; }
-  Py_INCREF(moberg_object);
-  self->moberg_object = moberg_object;
-  self->index = index;
-  self->channel = channel;
-  return (PyObject *) self;
-close:
-  status = moberg_digital_out_close(moberg, index, channel);
-err:
-  return NULL;
-}
-
-static int
-MobergDigitalOut_init(MobergDigitalOutObject *self, PyObject *args, PyObject *kwds)
-{
-  PyErr_SetString(PyExc_AttributeError, "Not intended to be called");
-  return -1;
-}
-
-static PyObject *
-MobergDigitalOut_write(MobergDigitalOutObject *self, PyObject *args)
-{
-  PyObject *desired;
-  int actual;
-  if (! PyArg_ParseTuple(args, "O", &desired)) {
-    goto err;
-  }
-  struct moberg_status status = self->channel.write(self->channel.context,
-                                                    PyObject_IsTrue(desired),
-                                                    &actual);
-  if (!moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._DigitalOut(%d).write() failed with %d",
-                 self->index, status.result);
-    goto err;
-  }
-  return Py_BuildValue("O", actual ? Py_True: Py_False);
-err:
-  return NULL;
-}
-
-static PyMethodDef MobergDigitalOut_methods[] = {
-    {"write", (PyCFunction) MobergDigitalOut_write, METH_VARARGS,
-     "Set DigitalOut value"
-    },
-    {NULL}  /* Sentinel */
-};
-
-static PyTypeObject MobergDigitalOutType = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_name = "moberg.DigitalOut",
-    .tp_doc = "DigitalOut objects",
-    .tp_basicsize = sizeof(MobergDigitalOutObject),
-    .tp_itemsize = 0,
-    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-    .tp_new = MobergDigitalOut_new,
-    .tp_init = (initproc) MobergDigitalOut_init,
-    .tp_dealloc = (destructor) MobergDigitalOut_dealloc,
-    .tp_methods = MobergDigitalOut_methods,
-};
-
-/*
- * moberg._EncoderIn class (should never be directly instatiated) 
- */
-
-typedef struct {
-  PyObject_HEAD
-  PyObject *moberg_object;
-  int index;
-  struct moberg_encoder_in channel;
-} MobergEncoderInObject;
-
-static void
-MobergEncoderIn_dealloc(MobergEncoderInObject *self)
-{
-  if (self->moberg_object) {
-    struct moberg *moberg = ((MobergObject*)self->moberg_object)->moberg;
-    struct moberg_status status =
-      moberg_encoder_in_close(moberg, self->index, self->channel);
-    if (! moberg_OK(status)) {
-      fprintf(stderr, "Failed to close moberg._EncoderIn(%d) [errno=%d]\n",
-              self->index, status.result);
-    }
-    Py_DECREF(self->moberg_object);
-  }
-  Py_TYPE(self)->tp_free((PyObject *) self);
-}
-
-static PyObject *
-MobergEncoderIn_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-  PyObject *moberg_object;
-  int index;
-  struct moberg_encoder_in channel = {0};
-  
-  static char *kwlist[] = { "moberg", "index", NULL };
-  if (! PyArg_ParseTupleAndKeywords(args, kwds, "Oi", kwlist,
-                                    &moberg_object, &index)) {
-    goto err;
-  }
-  if (Py_TYPE(moberg_object) != &MobergType) {
-    PyErr_SetString(PyExc_AttributeError, "moberg argument is not Moberg");
-    goto err;
-  }
-  struct moberg *moberg = ((MobergObject*)moberg_object)->moberg;
-  struct moberg_status status = moberg_encoder_in_open(moberg, index, &channel);
-  if (! moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._EncoderIn(%d) failed with %d",
-                 index, status.result);
-    goto err;
-  }
-  MobergEncoderInObject *self;
-  self = (MobergEncoderInObject *) type->tp_alloc(type, 0);
-  if (self == NULL) { goto close; }
-  Py_INCREF(moberg_object);
-  self->moberg_object = moberg_object;
-  self->index = index;
-  self->channel = channel;
-  return (PyObject *) self;
-close:
-  status = moberg_encoder_in_close(moberg, index, channel);
-err:
-  return NULL;
-}
-
-static int
-MobergEncoderIn_init(MobergEncoderInObject *self, PyObject *args, PyObject *kwds)
-{
-  PyErr_SetString(PyExc_AttributeError, "Not intended to be called");
-  return -1;
-}
-
-static PyObject *
-MobergEncoderIn_read(MobergEncoderInObject *self, PyObject *Py_UNUSED(ignored))
-{
-  long value;
-  struct moberg_status status = self->channel.read(self->channel.context,
-                                                   &value);
-  if (!moberg_OK(status)) {
-    PyErr_Format(PyExc_OSError, "moberg._EncoderIn(%d).read() failed with %d",
-                 self->index, status.result);
-  }
-  return Py_BuildValue("l", value);
-}
-
-static PyMethodDef MobergEncoderIn_methods[] = {
-    {"read", (PyCFunction) MobergEncoderIn_read, METH_NOARGS,
-     "Sample and return the EncoderIn value"
-    },
-    {NULL}  /* Sentinel */
-};
-
-static PyTypeObject MobergEncoderInType = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    .tp_name = "moberg.EncoderIn",
-    .tp_doc = "EncoderIn objects",
-    .tp_basicsize = sizeof(MobergEncoderInObject),
-    .tp_itemsize = 0,
-    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
-    .tp_new = MobergEncoderIn_new,
-    .tp_init = (initproc) MobergEncoderIn_init,
-    .tp_dealloc = (destructor) MobergEncoderIn_dealloc,
-    .tp_methods = MobergEncoderIn_methods,
-};
-
-/*
- * Module initialization
- */
-
-#define INITERROR return
-
-PyMODINIT_FUNC initmoberg(void)
-{
-  PyObject *m;
-  if (PyType_Ready(&MobergType) < 0 ||
-      PyType_Ready(&MobergAnalogInType) < 0 ||
-      PyType_Ready(&MobergAnalogOutType) < 0 ||
-      PyType_Ready(&MobergDigitalInType) < 0 ||
-      PyType_Ready(&MobergDigitalOutType) < 0 ||
-      PyType_Ready(&MobergEncoderInType) < 0) {
-    INITERROR;
-  }
-
-  static PyMethodDef methods[] = { {NULL, NULL, 0, NULL} };
-  m = Py_InitModule("moberg", methods);
-  if (m == NULL) {
-    INITERROR;
-  }
-
-  Py_INCREF(&MobergType);
-  PyModule_AddObject(m, "Moberg", (PyObject *) &MobergType);
-  Py_INCREF(&MobergAnalogInType);
-  PyModule_AddObject(m, "_AnalogIn", (PyObject *) &MobergAnalogInType);
-  Py_INCREF(&MobergAnalogOutType);
-  PyModule_AddObject(m, "_AnalogOut", (PyObject *) &MobergAnalogOutType);
-  Py_INCREF(&MobergDigitalInType);
-  PyModule_AddObject(m, "_DigitalIn", (PyObject *) &MobergDigitalInType);
-  Py_INCREF(&MobergDigitalOutType);
-  PyModule_AddObject(m, "_DigitalOut", (PyObject *) &MobergDigitalOutType);
-  Py_INCREF(&MobergEncoderInType);
-  PyModule_AddObject(m, "_EncoderIn", (PyObject *) &MobergEncoderInType);
-
-}
diff --git a/adaptors/python2/setup.py b/adaptors/python2/setup.py
deleted file mode 100755
index 2e5fa2fdfa166fde009731479e2986c639f6607a..0000000000000000000000000000000000000000
--- a/adaptors/python2/setup.py
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env python
-from distutils.core import setup, Extension
-
-module = Extension('moberg',
-                   sources = ['python-moberg.c'],
-                   include_dirs=['../..'],
-                   libraries=['moberg'])
-longdesc = '''This extension provides bindings to the Moberg I/O library
-'''
-
-try:
-    import os
-    VERSION=os.environ['MOBERG_VERSION']
-except KeyError:
-    VERSION='_UNKNOWN_'
-    pass
-
-setup(
-    name        = 'python-moberg',
-    version     = VERSION,
-    description = 'Python bindings to the Moberg I/O library',
-    long_description = longdesc,
-    author      = 'Anders Blomdell',
-    author_email = 'anders.blomdell@control.lth.se',
-    url         = 'http://gitlab.control.lth.se/anders_blomdell/moberg.git',
-    license     = 'GPLv2',
-    platforms   = 'Linux',
-    ext_modules = [module])
diff --git a/adaptors/python3/.gitignore b/adaptors/python3/.gitignore
index f65eb2942dc78eb04f2d5d1e680fd693a7f484aa..f0cab8eb71c04a7bea29990994b7350287dc36a0 100644
--- a/adaptors/python3/.gitignore
+++ b/adaptors/python3/.gitignore
@@ -1,2 +1,4 @@
 /__pycache__
-/install
\ No newline at end of file
+/install
+/dist
+/python_moberg.egg-info
diff --git a/adaptors/python3/Makefile b/adaptors/python3/Makefile
index 486da900f2eb2bb5af6bd79b991ea53fc8ac77ac..3e4c3940657d8f1cc6ed032248456acf59febcfd 100644
--- a/adaptors/python3/Makefile
+++ b/adaptors/python3/Makefile
@@ -1,15 +1,18 @@
-MOBERG_VERSION=$(shell git describe --tags)
+MOBERG_VERSION=$(shell git describe --tags \
+		| sed -re 's/^v//;s/-(.*)-(.*)$$/.dev\1+\2/')
 
 all: BUILD INSTALL
 
 .PHONY: BUILD 
 BUILD:
-	python3 ./setup.py build
+	MOBERG_SRC_PATH=$(shell realpath ../..) \
+        MOBERG_VERSION=$(MOBERG_VERSION) \
+		python3 -m build
 
 .PHONY: INSTALL
 INSTALL:
-	MOBERG_VERSION=$(MOBERG_VERSION) python3 \
-		./setup.py install -O1 --prefix=/usr/ --root=./install
+	pip install --prefix=/usr --root=install \
+		./dist/python_moberg-${MOBERG_VERSION}-*.whl
 
 clean:
-	rm -rf build install
+	rm -rf build install dist python_moberg.egg-info *~
diff --git a/adaptors/python3/README b/adaptors/python3/README
new file mode 100644
index 0000000000000000000000000000000000000000..38fd43756184ea70d7075cb6a65a07cde0f5f4ff
--- /dev/null
+++ b/adaptors/python3/README
@@ -0,0 +1 @@
+Moberg support for python3
\ No newline at end of file
diff --git a/adaptors/python3/setup.py b/adaptors/python3/setup.py
index 2e5fa2fdfa166fde009731479e2986c639f6607a..c8cf0125bc094b8ce6c2ae8acac39f254c60ddce 100755
--- a/adaptors/python3/setup.py
+++ b/adaptors/python3/setup.py
@@ -1,13 +1,6 @@
 #!/usr/bin/env python
 from distutils.core import setup, Extension
 
-module = Extension('moberg',
-                   sources = ['python-moberg.c'],
-                   include_dirs=['../..'],
-                   libraries=['moberg'])
-longdesc = '''This extension provides bindings to the Moberg I/O library
-'''
-
 try:
     import os
     VERSION=os.environ['MOBERG_VERSION']
@@ -15,6 +8,25 @@ except KeyError:
     VERSION='_UNKNOWN_'
     pass
 
+try:
+    import os
+    src=os.environ['MOBERG_SRC_PATH']
+    INCLUDE_DIRS=[ src ]
+    LIBRARY_DIRS=[ os.path.join(src, 'build') ]
+except KeyError:
+    INCLUDE_DIRS=None
+    LIBRARY_DIRS=None
+    pass
+
+module = Extension('moberg',
+                   sources = ['python-moberg.c'],
+                   include_dirs=INCLUDE_DIRS,
+                   library_dirs=LIBRARY_DIRS,
+                   libraries=['moberg'])
+longdesc = '''This extension provides bindings to the Moberg I/O library
+'''
+
+
 setup(
     name        = 'python-moberg',
     version     = VERSION,
diff --git a/moberg.spec.template b/moberg.spec.template
index 63352be9cde04350cfb7ad705d6df7dd81d8dab2..62197cb9df74a0f5ed22dcfd219c4e583b6b6f7a 100644
--- a/moberg.spec.template
+++ b/moberg.spec.template
@@ -15,8 +15,9 @@ BuildRequires:  comedilib-devel
 BuildRequires:  valgrind
 BuildRequires:  libxdg-basedir-devel
 BuildRequires:  java-1.8.0-devel
-BuildRequires:  python2-devel
+BuildRequires:  pip
 BuildRequires:  python3-devel
+BuildRequires:  python3-build
 BuildRequires:  python3-setuptools
 BuildRequires:  julia
 BuildRequires:  git
@@ -56,13 +57,6 @@ Requires: %{name}-devel = %{version}-%{release}
 %description matlab
 Matlab support files for %{name}
 
-%package python2
-Summary: Python2 support files for %{name}
-Requires: %{name} = %{version}-%{release}
-
-%description python2
-Python2 support files for %{name}
-
 %package python%{python3_pkgversion}
 Summary: Python3 support files for %{name}
 Requires: %{name} = %{version}-%{release}
@@ -87,7 +81,7 @@ Julia support files for %{name}
 make MOBERG_VERSION=%{version}
 
 %check
-make test || true
+make test MOBERG_VERSION=%{version}
 
 %install
 rm -rf ${RPM_BUILD_ROOT}
@@ -128,20 +122,13 @@ cp adaptors/matlab/*out.c ${RPM_BUILD_ROOT}/opt/matlab/src/moberg
 cp adaptors/matlab/Makefile.mex ${RPM_BUILD_ROOT}/opt/matlab/src/moberg/Makefile
 
 # Python
-(
-    cd adaptors/python2
-    export MOBERG_VERSION=%{version}
-    %{__python2} setup.py install -O1 \
-        --root=$RPM_BUILD_ROOT --prefix /usr \
-        --record=INSTALLED_python2
-)
-(
-    cd adaptors/python3
-    export MOBERG_VERSION=%{version}
-    %{__python3} setup.py install -O1 \
-        --root=$RPM_BUILD_ROOT --prefix /usr \
-        --record=INSTALLED_python3
-)
+find adaptors/python3/install/ -type f \
+     | sed -e 's|adaptors/python3/install/|/|' \
+     > adaptors/python3/INSTALLED_python3
+cat adaptors/python3/INSTALLED_python3 | while read f ; do
+  mkdir -p ${RPM_BUILD_ROOT}/$(dirname $f)
+  cp adaptors/python3/install/$f ${RPM_BUILD_ROOT}/$f
+done
 
 # Julia
 mkdir -p ${RPM_BUILD_ROOT}/opt/julia/local/packages/MobergIO/src
@@ -207,9 +194,6 @@ EOF
 /opt/matlab/src/moberg/*
 %{_includedir}/moberg4simulink.h
 
-%files python2 -f adaptors/python2/INSTALLED_python2
-%defattr(-,root,root,-)
-
 %files python%{python3_pkgversion} -f adaptors/python3/INSTALLED_python3
 %defattr(-,root,root,-)
 
diff --git a/test/Makefile b/test/Makefile
index 7ae172c1e5272ecb7c39afd0be65d708946ae12a..d1799e19068f2ebca67ab3b146934285cde23048 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -9,7 +9,6 @@ ENV_TEST = LD_LIBRARY_PATH=../build \
 	   JULIA_LOAD_PATH=../adaptors/julia
 LDFLAGS_test_moberg4simulink = -lmoberg4simulink
 CCFLAGS_test_moberg4simulink = -I../adaptors/matlab -Wall -Werror -I$(shell pwd) -g
-PYTHON2PATH=$(shell realpath ../adaptors/python2/install/usr/lib*/python2*/site-packages)
 PYTHON3PATH=$(shell realpath ../adaptors/python3/install/usr/lib*/python3*/site-packages)
 all:
 
@@ -23,7 +22,6 @@ run_c_%:build/%
 
 .PHONY: run_py_%
 run_py_%: %.py
-	$(ENV_TEST) PYTHONPATH=$(PYTHON2PATH) python2 $*.py
 	$(ENV_TEST) PYTHONPATH=$(PYTHON3PATH) python3 $*.py
 
 .PHONY: run_jl_%
@@ -33,7 +31,7 @@ run_jl_%: %.jl
 .PRECIOUS: build/%
 
 build/%: %.c | build
-	$(CC) $(CCFLAGS) $(CCFLAGS_$*) -o $@ $< $(LDFLAGS) $(LDFLAGS_$*)
+	$(CC) $(CCFLAGS) $(CCFLAGS_$*) -fPIE -o $@ $< $(LDFLAGS) $(LDFLAGS_$*)
 
 build:
 	mkdir build