diff --git a/test/.config/moberg.d/00-comedi_ttyUSB0_serial.conf b/examples/00-comedi_ttyUSB0_serial.conf
similarity index 100%
rename from test/.config/moberg.d/00-comedi_ttyUSB0_serial.conf
rename to examples/00-comedi_ttyUSB0_serial.conf
diff --git a/test/.config/moberg.d/30-comedi_ttyUSB0_serial.conf b/examples/30-comedi_ttyUSB0_serial.conf
similarity index 100%
rename from test/.config/moberg.d/30-comedi_ttyUSB0_serial.conf
rename to examples/30-comedi_ttyUSB0_serial.conf
diff --git a/plugins/libtest/Makefile b/plugins/libtest/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..189d37b28aef1196d6256f58fe9206ebad5a0cf4
--- /dev/null
+++ b/plugins/libtest/Makefile
@@ -0,0 +1,18 @@
+LIBRARIES=libmoberg_libtest.so
+CCFLAGS+=-Wall -Werror -I../.. -I. -O3 -g -fPIC
+LDFLAGS+=-Lbuild/ -lmoberg
+LDFLAGS_libtest=-shared -fPIC -L../../build -lmoberg  
+
+all:	$(LIBRARIES:%=build/%)
+
+build/libmoberg_%.so: build/%.o Makefile | build
+	$(CC) $(LDFLAGS) $(LDFLAGS_$(*)) -o $@ $(filter %.o,$^)
+
+.PRECIOUS: build/%.o
+build/%.o:      %.c Makefile | build
+	$(CC) $(CCFLAGS) -c -o $@ $<
+
+build:
+	mkdir -p $@
+
+build/libtest.o: ../../moberg_module.h
diff --git a/plugins/libtest/libtest.c b/plugins/libtest/libtest.c
new file mode 100644
index 0000000000000000000000000000000000000000..eb202de83f1cd31305ee6f050474d10ad7adc820
--- /dev/null
+++ b/plugins/libtest/libtest.c
@@ -0,0 +1,393 @@
+/*
+    libtest.c -- libtest plugin for moberg
+
+    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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <asm/termbits.h>
+#include <linux/serial.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <moberg.h>
+#include <moberg_config.h>
+#include <moberg_device.h>
+#include <moberg_inline.h>
+#include <moberg_module.h>
+#include <moberg_parser.h>
+
+struct moberg_device_context {
+  struct moberg *moberg;
+  int (*dlclose)(void *dlhandle);
+  void *dlhandle;
+  int use_count;
+  double analog;
+  int digital;
+  long encoder;
+};
+
+struct moberg_channel_context {
+  void *to_free; /* To be free'd when use_count goes to zero */
+  struct moberg_device_context *device;
+  int use_count;
+  int index;
+};
+
+struct moberg_channel_analog_in {
+  struct moberg_channel channel;
+  struct moberg_channel_context channel_context;
+};
+
+struct moberg_channel_analog_out {
+  struct moberg_channel channel;
+  struct moberg_channel_context channel_context;
+};
+
+struct moberg_channel_digital_in {
+  struct moberg_channel channel;
+  struct moberg_channel_context channel_context;
+};
+
+struct moberg_channel_digital_out {
+  struct moberg_channel channel;
+  struct moberg_channel_context channel_context;
+};
+
+struct moberg_channel_encoder_in {
+  struct moberg_channel channel;
+  struct moberg_channel_context channel_context;
+};
+
+static struct moberg_status analog_in_read(
+  struct moberg_channel_analog_in *analog_in,
+  double *value)
+{
+  if (! value) { goto err_einval; }
+  
+  struct moberg_channel_context *channel = &analog_in->channel_context;
+  struct moberg_device_context *device = channel->device;
+  *value = device->analog / (channel->index + 1);
+  return MOBERG_OK;
+err_einval:
+  return MOBERG_ERRNO(EINVAL);
+
+}
+
+static struct moberg_status analog_out_write(
+  struct moberg_channel_analog_out *analog_out,
+  double value)
+{
+  struct moberg_channel_context *channel = &analog_out->channel_context;
+  struct moberg_device_context *device = channel->device;
+  device->analog = value * (channel->index + 1);
+  return MOBERG_OK;
+}
+
+static struct moberg_status digital_in_read(
+  struct moberg_channel_digital_in *digital_in,
+  int *value)
+{
+  if (! value) { goto err_einval; }
+
+  struct moberg_channel_context *channel = &digital_in->channel_context;
+  struct moberg_device_context *device = channel->device;
+  *value = (device->digital & (1<<channel->index)) != 0;
+  return MOBERG_OK;
+err_einval:
+  return MOBERG_ERRNO(EINVAL);
+}
+
+static struct moberg_status digital_out_write(
+  struct moberg_channel_digital_out *digital_out,
+  int value)
+{
+  struct moberg_channel_context *channel = &digital_out->channel_context;
+  struct moberg_device_context *device = channel->device;
+  int mask =  (1<<channel->index);
+  if (value) {
+    device->digital |= mask;
+  } else {
+    device->digital &= ~mask;
+  }
+  return MOBERG_OK;
+}
+
+static struct moberg_status encoder_in_read(
+  struct moberg_channel_encoder_in *encoder_in,
+  long *value)
+{
+  if (! value) { goto err_einval; }
+  
+  struct moberg_channel_context *channel = &encoder_in->channel_context;
+  struct moberg_device_context *device = channel->device;
+  *value = device->digital;
+  return MOBERG_OK;
+err_einval:
+  return MOBERG_ERRNO(EINVAL);
+}
+
+static struct moberg_device_context *new_context(struct moberg *moberg,
+                                                 int (*dlclose)(void *dlhandle),
+                                                 void *dlhandle)
+{
+  struct moberg_device_context *result = malloc(sizeof(*result));
+  if (result) {
+    memset(result, 0, sizeof(*result));
+    result->moberg = moberg;
+    result->dlclose = dlclose;
+    result->dlhandle = dlhandle;
+  }
+  return result;
+}
+
+static int device_up(struct moberg_device_context *context)
+{
+  context->use_count++;
+  return context->use_count;
+}
+
+static int device_down(struct moberg_device_context *context)
+{
+  context->use_count--;
+  if (context->use_count <= 0) {
+    moberg_deferred_action(context->moberg,
+                           context->dlclose, context->dlhandle);
+    free(context);
+    return 0;
+  }
+  return context->use_count;
+}
+
+static struct moberg_status device_open(struct moberg_device_context *device)
+{
+  return MOBERG_OK;
+}
+
+static struct moberg_status device_close(struct moberg_device_context *device)
+{
+  return MOBERG_OK;
+}
+
+static int channel_up(struct moberg_channel *channel)
+{
+  device_up(channel->context->device);
+  channel->context->use_count++;
+  return channel->context->use_count;
+}
+
+static int channel_down(struct moberg_channel *channel)
+{
+  device_down(channel->context->device);
+  channel->context->use_count--;
+  if (channel->context->use_count <= 0) {
+    free(channel->context->to_free);
+    return 0;
+  }
+  return channel->context->use_count;
+}
+
+static struct moberg_status channel_open(struct moberg_channel *channel)
+{
+  device_open(channel->context->device);
+  return MOBERG_OK;
+}
+
+static struct moberg_status channel_close(struct moberg_channel *channel)
+{
+  device_close(channel->context->device);
+  return MOBERG_OK;
+}
+
+static void init_channel(
+  struct moberg_channel *channel,
+  void *to_free,
+  struct moberg_channel_context *context,
+  struct moberg_device_context *device,
+  int index,
+  enum moberg_channel_kind kind,
+  union moberg_channel_action action)
+{
+  context->to_free = to_free;
+  context->device = device;
+  context->use_count = 0;
+  context->index = index;
+  
+  channel->context = context;
+  channel->up = channel_up;
+  channel->down = channel_down;
+  channel->open = channel_open;
+  channel->close = channel_close;
+  channel->kind = kind;
+  channel->action = action;
+};
+
+static struct moberg_status parse_config(
+  struct moberg_device_context *device,
+  struct moberg_parser_context *c)
+{
+  if (! acceptsym(c, tok_LBRACE, NULL)) { goto syntax_err; }
+  for (;;) {
+    if (acceptsym(c, tok_RBRACE, NULL)) {
+	break;
+    } else {
+      goto syntax_err;
+    }
+  }
+  return MOBERG_OK;
+syntax_err:
+  return moberg_parser_failed(c, stderr);
+}
+
+static struct moberg_status parse_map(
+  struct moberg_device_context *device,
+  struct moberg_parser_context *c,
+  enum moberg_channel_kind ignore,
+  struct moberg_channel_map *map)
+{
+  enum moberg_channel_kind kind;
+  token_t min, max;
+ 
+  if (acceptkeyword(c, "analog_in")) { kind = chan_ANALOGIN; }
+  else if (acceptkeyword(c, "analog_out")) { kind = chan_ANALOGOUT; }
+  else if (acceptkeyword(c, "digital_in")) { kind = chan_DIGITALIN; }
+  else if (acceptkeyword(c, "digital_out")) { kind = chan_DIGITALOUT; }
+  else if (acceptkeyword(c, "encoder_in")) { kind = chan_ENCODERIN; }
+  else { goto syntax_err; }
+  if (! acceptsym(c, tok_LBRACKET, NULL)) { goto syntax_err; }
+  if (! acceptsym(c, tok_INTEGER, &min)) { goto syntax_err; }
+  if (acceptsym(c, tok_COLON, NULL)) { 
+    if (! acceptsym(c, tok_INTEGER, &max)) { goto syntax_err; }
+  } else {
+    max = min;
+  }
+  if (! acceptsym(c, tok_RBRACKET, NULL)) { goto syntax_err; }
+  for (int i = min.u.integer.value ; i <= max.u.integer.value ; i++) {
+    switch (kind) {
+      case chan_ANALOGIN: {
+        struct moberg_channel_analog_in *channel = malloc(sizeof(*channel));
+
+        if (! channel) { goto err_enomem; }
+        init_channel(&channel->channel,
+                     channel,
+                     &channel->channel_context,
+                     device,
+                     i,
+                     kind,
+                     (union moberg_channel_action) {
+                       .analog_in.context=channel,
+                       .analog_in.read=analog_in_read });
+        map->map(map->device, &channel->channel);
+      } break;
+      case chan_ANALOGOUT: {
+        struct moberg_channel_analog_out *channel = malloc(sizeof(*channel));
+        
+        if (! channel) { goto err_enomem; }
+        init_channel(&channel->channel,
+                     channel,
+                     &channel->channel_context,
+                     device,
+                     i,
+                     kind,
+                     (union moberg_channel_action) {
+                       .analog_out.context=channel,
+                       .analog_out.write=analog_out_write });
+        map->map(map->device, &channel->channel);
+      } break;
+      case chan_DIGITALIN: {
+        struct moberg_channel_digital_in *channel = malloc(sizeof(*channel));
+
+        if (! channel) { goto err_enomem; }
+        init_channel(&channel->channel,
+                     channel,
+                     &channel->channel_context,
+                     device,
+                     i,
+                     kind,
+                     (union moberg_channel_action) {
+                       .digital_in.context=channel,
+                       .digital_in.read=digital_in_read });
+        map->map(map->device, &channel->channel);
+      } break;
+      case chan_DIGITALOUT: {
+        struct moberg_channel_digital_out *channel = malloc(sizeof(*channel));
+
+        if (! channel) { goto err_enomem; }        
+        init_channel(&channel->channel,
+                     channel,
+                     &channel->channel_context,
+                     device,
+                     i,
+                     kind,
+                     (union moberg_channel_action) {
+                       .digital_out.context=channel,
+                       .digital_out.write=digital_out_write });
+        map->map(map->device, &channel->channel);
+      } break;
+      case chan_ENCODERIN: {
+        struct moberg_channel_encoder_in *channel = malloc(sizeof(*channel));
+        
+        if (! channel) { goto err_enomem; }
+        init_channel(&channel->channel,
+                     channel,
+                     &channel->channel_context,
+                     device,
+                     i,
+                     kind,
+                     (union moberg_channel_action) {
+                       .encoder_in.context=channel,
+                       .encoder_in.read=encoder_in_read });
+        map->map(map->device, &channel->channel);
+      } break;
+    }
+  }
+  return MOBERG_OK;
+err_enomem:
+  return MOBERG_ERRNO(ENOMEM);
+syntax_err:
+  return moberg_parser_failed(c, stderr);
+}
+
+static struct moberg_status start(struct moberg_device_context *device,
+                                  FILE *f)
+{
+  fprintf(f, "# %s %s\n", __FILE__, __FUNCTION__);
+  return MOBERG_OK;
+}
+
+static struct moberg_status stop(struct moberg_device_context *device,
+                                 FILE *f)
+{
+  fprintf(f, "# %s %s\n", __FILE__, __FUNCTION__);
+  return MOBERG_OK;
+}
+
+struct moberg_device_driver moberg_device_driver = {
+  .new=new_context,
+  .up=device_up,
+  .down=device_down,
+  .parse_config=parse_config,
+  .parse_map=parse_map,
+  .start=start,
+  .stop=stop
+};
diff --git a/test/.config/moberg.d/moberg.conf b/test/.config/moberg.d/moberg.conf
index 9332da8a4fbf4b54099df6f2895d1b2215d71598..4dc16c423696b76e592f937a65ed35f531a78b67 100644
--- a/test/.config/moberg.d/moberg.conf
+++ b/test/.config/moberg.d/moberg.conf
@@ -1,50 +1,8 @@
-driver(comedi) {
-    config {
-        /* Parsed by parse_config in libmoberg_comedi.so */
-        device = "/dev/comedi0" ;
-        modprobe = [ comedi "8255" comedi_fc mite ni_tio ni_tiocmd ni_pcimio ] ;
-        config = [ ni_pcimio ] ;
-    }
-    /* Moberg mapping[indices] = {driver specific}[indices]
-      {driver specific} is parsed by parse_map in libmoberg_comedi.so */
-    map analog_in[0:15] = { subdevice[0][0:15] };
-    map analog_out[0:1] = { subdevice[1][0:1] };
-    map digital_in[0:7] = { subdevice[7][15],
-                            subdevice[7][2],
-                            subdevice[7][11],
-                            subdevice[7][4],
-                            subdevice[7][3],
-                            subdevice[7][10],
-                            subdevice[7][8],
-                            subdevice[7][9] };
-    map digital_out[0:7] = { subdevice[7] route 16 [0:1],
-                             subdevice[7] route 16 [5:7],
-                             subdevice[7] route 16 [14],
-                             subdevice[7] route 16 [13],
-                             subdevice[7] route 16 [12] };
-}
-driver(comedi) {
-    config {
-        /* Parsed by parse_config in libmoberg_comedi.so */
-        device = "/dev/comedi0" ;
-        modprobe = [ comedi serial2002 ] ;
-        config = [ serial2002 "100" "115200" ] ;
-    }
-    /* Moberg mapping[indices] = {driver specific}[indices]
-      {driver specific} is parsed by parse_map in libmoberg_comedi.so */
-    map analog_in[0:1] = { subdevice[2][0:1] };
-}
-driver(serial2002) {
-    config {
-        /* Parsed by parse_config in libmoberg_serial2002.so */
-        device = "/dev/ttyS0" ;
-        baud = 115200 ;
-    }
-    /* Moberg mapping[indices] = {driver specific}[indices]
-      {driver specific} is parsed by parse_map in libmoberg_serial2002.so */
-    map analog_in[30:37] = analog_in[0:7] ;
-    map analog_out[30:37] = analog_out[0:7] ;
-    map digital_in[30:37] = digital_in[0:7] ;
-    map digital_out[30:37] = digital_out[0:7] ;
-    map encoder_in[30:37] = encoder_in[0:7] ;
+driver(libtest) {
+  config { }
+  map analog_in[0:7] = analog_in[0:7] ;
+  map analog_out[0:7] = analog_out[0:7] ;
+  map digital_in[0:7] = digital_in[0:7] ;
+  map digital_out[0:7] = digital_out[0:7] ;
+  map encoder_in[0:7] = encoder_in[0:7] ;
 }
diff --git a/test/Makefile b/test/Makefile
index 10abd013ad0daf67a0c4c7c4edfc685bbae59f33..ca3141846f7a7f964a245fd2a7ebb06a3462246b 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -1,7 +1,7 @@
 TEST = test_start_stop test_io test_moberg4simulink
 CCFLAGS += -Wall -Werror -I$(shell pwd) -g
 LDFLAGS += -L$(shell pwd)/build/ -lmoberg
-ENV_TEST = LD_LIBRARY_PATH=../build HOME=.
+ENV_TEST = LD_LIBRARY_PATH=../build XDG_CONFIG_HOME=.config XDG_CONFIG_DIRS=.
 LDFLAGS_test_moberg4simulink = -lmoberg4simulink
 CCFLAGS_test_moberg4simulink = -I../adaptors/matlab -Wall -Werror -I$(shell pwd) -g