diff --git a/Makefile b/Makefile
index 9690024b5deac070debc5c16a60261f1d837a48b..f57f57a08430b9699992d658d71fb682359f1cb5 100644
--- a/Makefile
+++ b/Makefile
@@ -31,13 +31,15 @@ build/%.o:	%.c Makefile
 build/lib/%.o:	%.c Makefile | build/lib
 	$(CC) $(CCFLAGS) -c -fPIC -o $@ $<
 
-.PHONY: $(PLUGINS) $(ADAPTORS)
-$(ADAPTORS) $(PLUGINS): 
+.PHONY: $(ADAPTORS)
+$(ADAPTORS): Makefile
 	$(MAKE) -C $@
 
-.PHONY: $(PLUGINS) $(ADAPTORS)
-$(ADAPTORS) $(PLUGINS): 
+.PHONY: $(PLUGINS)
+$(PLUGINS): Makefile
 	$(MAKE) -C $@
+	cp $@/build/libmoberg_*.so build
+
 
 .PHONY: TAR
 TAR:
@@ -51,8 +53,7 @@ moberg-$(MOBERG_VERSION).spec: moberg.spec.template Makefile
 
 .PHONY: SRPM
 SRPM:	moberg-$(MOBERG_VERSION).spec TAR
-	rpmbuild --define "_sourcedir $$(pwd)" \
-		 -bs $<
+	rpmbuild --define "_sourcedir $$(pwd)" -bs $<
 
 
 
diff --git a/plugins/comedi/Makefile b/plugins/comedi/Makefile
index 7a574aeb4833e5806e01665c8fcc7a0e8935ec84..346e57658e1ce12e114a43c655630b64729ff87d 100644
--- a/plugins/comedi/Makefile
+++ b/plugins/comedi/Makefile
@@ -1,14 +1,23 @@
 LIBRARIES=libmoberg_comedi.so
-CCFLAGS+=-Wall -Werror -I../.. -g
+CCFLAGS+=-Wall -Werror -I../.. -I. -O3 -g -fPIC
+LDFLAGS+=-Lbuild/ -lmoberg
+LDFLAGS_comedi=-shared -fPIC -L../../build -lmoberg  
 
-all:	$(LIBRARIES:%=../../build/%) 
+all:	$(LIBRARIES:%=build/%)
 
-../../build/libmoberg_comedi.so: comedi.c Makefile
-	$(CC) -o $@ $(CCFLAGS) -shared -fPIC -lcomedi -L../../build -lmoberg $<
+build/libmoberg_%.so: build/%.o Makefile | build
+	$(CC) $(LDFLAGS) $(LDFLAGS_$(*)) -o $@ $(filter %.o,$^)
 
-../../build/libmoberg_comedi.so: ../../moberg.h
-../../build/libmoberg_comedi.so: ../../moberg_config.h
-../../build/libmoberg_comedi.so: ../../moberg_device.h
-../../build/libmoberg_comedi.so: ../../moberg_inline.h
-../../build/libmoberg_comedi.so: ../../moberg_module.h
-../../build/libmoberg_comedi.so: ../../moberg_parser.h
+.PRECIOUS: build/%.o
+build/%.o:      %.c Makefile | build
+	$(CC) $(CCFLAGS) -c -o $@ $<
+
+build:
+	mkdir -p $@
+
+build/comedi.c: ../../moberg.h
+build/comedi.o: ../../moberg_config.h
+build/comedi.o: ../../moberg_device.h
+build/comedi.o: ../../moberg_inline.h
+build/comedi.o: ../../moberg_module.h
+build/comedi.o: ../../moberg_parser.h
diff --git a/plugins/serial2002/Makefile b/plugins/serial2002/Makefile
index 9d13537c2362bf126e44833a792ceb233bf005ad..f5a9a0a068787f033301a726a51e9bd208aed013 100644
--- a/plugins/serial2002/Makefile
+++ b/plugins/serial2002/Makefile
@@ -1,11 +1,21 @@
 LIBRARIES=libmoberg_serial2002.so
-CCFLAGS+=-Wall -Werror -I../.. -g
+CCFLAGS+=-Wall -Werror -I../.. -I. -O3 -g -fPIC
 LDFLAGS+=-Lbuild/ -lmoberg
-MODULES=$(wildcard modules/*)
+LDFLAGS_serial2002=-shared -fPIC -L../../build -lmoberg  
 
-all:	$(LIBRARIES:%=../../build/%) 
+all:	$(LIBRARIES:%=build/%)
 
-../../build/libmoberg_serial2002.so: serial2002.c Makefile
-	$(CC) -o $@ $(CCFLAGS) -shared -fPIC -L../../build -lmoberg $<
+build/libmoberg_%.so: build/%.o Makefile | build
+	$(CC) $(LDFLAGS) $(LDFLAGS_$(*)) -o $@ $(filter %.o,$^)
 
-../../build/libmoberg_serial2002.so: ../../moberg_module.h
+.PRECIOUS: build/%.o
+build/%.o:      %.c Makefile | build
+	$(CC) $(CCFLAGS) -c -o $@ $<
+
+build:
+	mkdir -p $@
+
+build/serial2002.o: ../../moberg_module.h
+build/serial2002.o: serial2002_lib.h
+build/libmoberg_serial2002.so: build/serial2002.o
+build/libmoberg_serial2002.so: build/serial2002_lib.o
diff --git a/plugins/serial2002/serial2002.c b/plugins/serial2002/serial2002.c
index 06c8176cfba7a342a71c30c3df835cf61130062b..fc90d523260880646882f8a4be77869a2c1903f1 100644
--- a/plugins/serial2002/serial2002.c
+++ b/plugins/serial2002/serial2002.c
@@ -19,6 +19,13 @@
     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>
@@ -29,20 +36,43 @@
 #include <moberg_inline.h>
 #include <moberg_module.h>
 #include <moberg_parser.h>
+#include <serial2002_lib.h>
 
 struct moberg_device_context {
   struct moberg *moberg;
   int (*dlclose)(void *dlhandle);
   void *dlhandle;
   int use_count;
-  char *name;
-  int baud;
+  struct {
+    int count;
+    char *name;
+    int baud;
+    int fd;
+  } port;
+  struct remap_analog {
+    int count;
+    struct {
+      unsigned char index;
+      unsigned long maxdata;
+      double min;
+      double max;
+      double delta;
+    } map[31];
+  } analog_in, analog_out;
+  struct remap_digital {
+    int count;
+    struct {
+      unsigned char index;
+    } map[32];
+  } digital_in, digital_out,
+    encoder_in;
 };
 
 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 {
@@ -70,6 +100,59 @@ struct moberg_channel_encoder_in {
   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;
+  struct serial2002_data data = { 0, 0 };
+  serial2002_poll_channel(device->port.fd,
+                          device->analog_in.map[channel->index].index);
+  struct moberg_status result = serial2002_read(device->port.fd, 1000, &data);
+  if (OK(result)) {
+    *value = (data.value * device->analog_in.map[channel->index].delta +
+              device->analog_in.map[channel->index].min);
+  }
+  return result;
+err_einval:
+  return MOBERG_ERRNO(EINVAL);
+}
+
+static struct moberg_status analog_out_write(
+  struct moberg_channel_analog_out *analog_out,
+  double value)
+{
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  return MOBERG_OK;
+}
+
+static struct moberg_status digital_in_read(
+  struct moberg_channel_digital_in *digital_in,
+  int *value)
+{
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  *value = 1;
+  return MOBERG_OK;
+}
+
+static struct moberg_status digital_out_write(
+  struct moberg_channel_digital_out *digital_out,
+  int value)
+{
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  return MOBERG_OK;
+}
+
+static struct moberg_status encoder_in_read(struct moberg_channel_encoder_in *encoder_in,
+                           long *value)
+{
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  return MOBERG_OK;
+}
+
 static struct moberg_device_context *new_context(struct moberg *moberg,
                                                  int (*dlclose)(void *dlhandle),
                                                  void *dlhandle)
@@ -96,13 +179,123 @@ static int device_down(struct moberg_device_context *context)
   if (context->use_count <= 0) {
     moberg_deferred_action(context->moberg,
                            context->dlclose, context->dlhandle);
-    free(context->name);
+    free(context->port.name);
     free(context);
     return 0;
   }
   return context->use_count;
 }
 
+static void remap_analog(
+  struct remap_analog *remap,
+  enum serial2002_kind kind,
+  struct serial2002_channel *channel,
+  int count)
+{
+  remap->count = 0;
+  for (int i = 0 ; i < count ; i++) {
+    if (channel[i].kind == kind) {
+      remap->map[remap->count].index = i;
+      remap->map[remap->count].maxdata = (1 << channel[i].bits) - 1;
+      remap->map[remap->count].min = channel[i].min;
+      remap->map[remap->count].max = channel[i].max;
+      if (remap->map[remap->count].maxdata) {
+        remap->map[remap->count].delta =
+          (remap->map[remap->count].max - remap->map[remap->count].min) /
+          remap->map[remap->count].maxdata;
+      }
+
+      fprintf(stderr, "%d -> %d\n", remap->count, i);
+      remap->count++;
+    }
+  }
+}
+
+static void remap_digital(
+  struct remap_digital *remap,
+  enum serial2002_kind kind,
+  struct serial2002_channel *channel,
+  int count)
+{
+  remap->count = 0;
+  for (int i = 0 ; i < count ; i++) {
+    if (channel[i].kind == kind) {
+      remap->map[remap->count].index = i;
+      fprintf(stderr, "%d -> %d\n", remap->count, i);
+      remap->count++;
+    }
+  }
+}
+
+static struct moberg_status device_open(struct moberg_device_context *device)
+{
+  struct moberg_status result;
+  int fd = -1;
+
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  if (device->port.count == 0) {
+    fd = open(device->port.name, O_RDWR);
+    if (fd < 0) { goto err_errno; }
+    struct termios2 termios2;
+    if (ioctl(fd, TCGETS2, &termios2) < 0) { goto err_errno; }
+    termios2.c_iflag = 0;
+    termios2.c_oflag = 0;
+    termios2.c_lflag = 0;
+    termios2.c_cflag = CLOCAL | CS8 | CREAD | BOTHER;
+    termios2.c_cc[VMIN] = 0;
+    termios2.c_cc[VTIME] = 0;
+    termios2.c_ispeed = device->port.baud;
+    termios2.c_ospeed = device->port.baud;
+    if (ioctl(fd, TCSETS2, &termios2) < 0) { goto err_errno; }
+    struct serial_struct settings; 
+    if (ioctl(fd, TIOCGSERIAL, &settings) >= 0) {
+      /* It's expected for this to fail for at least some USB serial adapters */
+      ioctl(fd, TIOCSSERIAL, &settings);
+    }
+    struct serial2002_config config;
+    result = serial2002_read_config(fd, &config);
+    if (! OK(result)) { goto err_result; }
+    remap_analog(&device->analog_in, SERIAL2002_ANALOG_IN,
+                  config.channel_in, 31);
+    remap_analog(&device->analog_out, SERIAL2002_ANALOG_OUT,
+                  config.channel_out, 31);
+    remap_digital(&device->digital_in, SERIAL2002_DIGITAL_IN,
+                  config.digital_in, 32);
+    remap_digital(&device->digital_out, SERIAL2002_DIGITAL_OUT,
+                  config.digital_out, 32);
+    remap_digital(&device->encoder_in, SERIAL2002_COUNTER_IN,
+                  config.channel_in, 31);
+    device->port.fd = fd;
+  }
+  device->port.count++;
+  fprintf(stderr, "OPENED %d\n", device->port.count);
+  return MOBERG_OK;
+err_errno:
+  fprintf(stderr, "ERRNO %d\n", errno);
+  result = MOBERG_ERRNO(errno);
+err_result:
+  if (fd >= 0) {
+    close(fd);
+  }
+  return result;
+}
+
+static struct moberg_status device_close(struct moberg_device_context *device)
+{
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  if (device->port.count < 0) { errno = ENODEV; goto err_errno; }
+  device->port.count--;
+  if (device->port.count == 0) {
+    fprintf(stderr, "CLOSE\n");
+    if (close(device->port.fd) < 0) { goto err_errno; }
+  }
+  return MOBERG_OK;
+err_errno:
+  fprintf(stderr, "CLOSE %d\n", errno);
+  return MOBERG_ERRNO(errno);
+
+}
+
 static int channel_up(struct moberg_channel *channel)
 {
   device_up(channel->context->device);
@@ -121,23 +314,39 @@ static int channel_down(struct moberg_channel *channel)
   return channel->context->use_count;
 }
 
+static struct moberg_status channel_open(struct moberg_channel *channel)
+{
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  struct moberg_status result = device_open(channel->context->device);
+  return result;
+}
+
+static struct moberg_status channel_close(struct moberg_channel *channel)
+{
+  fprintf(stderr, "%s\n", __FUNCTION__);
+  struct moberg_status result = device_close(channel->context->device);
+  return result;
+}
+
 static void init_channel(
   struct moberg_channel *channel,
   void *to_free,
   struct moberg_channel_context *context,
-  struct moberg_device_context *device,  
+  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 = NULL;
-  channel->close = NULL;
+  channel->open = channel_open;
+  channel->close = channel_close;
   channel->kind = kind;
   channel->action = action;
 };
@@ -155,14 +364,14 @@ static struct moberg_status parse_config(
       if (! acceptsym(c, tok_EQUAL, NULL)) { goto syntax_err; }
       if (! acceptsym(c, tok_STRING, &name)) { goto syntax_err; }
       if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto syntax_err; }
-      device->name = strndup(name.u.idstr.value, name.u.idstr.length);
-      if (! device->name) { goto err_enomem; }
+      device->port.name = strndup(name.u.idstr.value, name.u.idstr.length);
+      if (! device->port.name) { goto err_enomem; }
     } else if (acceptkeyword(c, "baud")) {
       token_t baud;
       if (! acceptsym(c, tok_EQUAL, NULL)) { goto syntax_err; }
       if (! acceptsym(c, tok_INTEGER, &baud)) { goto syntax_err; }
       if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto syntax_err; }
-      device->baud = baud.u.integer.value;
+      device->port.baud = baud.u.integer.value;
     } else {
       goto syntax_err;
     }
@@ -207,10 +416,11 @@ static struct moberg_status parse_map(
                      channel,
                      &channel->channel_context,
                      device,
+                     i,
                      kind,
                      (union moberg_channel_action) {
                        .analog_in.context=channel,
-                       .analog_in.read=NULL });
+                       .analog_in.read=analog_in_read });
         map->map(map->device, &channel->channel);
       } break;
       case chan_ANALOGOUT: {
@@ -221,10 +431,11 @@ static struct moberg_status parse_map(
                      channel,
                      &channel->channel_context,
                      device,
+                     i,
                      kind,
                      (union moberg_channel_action) {
                        .analog_out.context=channel,
-                       .analog_out.write=NULL });
+                       .analog_out.write=analog_out_write });
         map->map(map->device, &channel->channel);
       } break;
       case chan_DIGITALIN: {
@@ -235,10 +446,11 @@ static struct moberg_status parse_map(
                      channel,
                      &channel->channel_context,
                      device,
+                     i,
                      kind,
                      (union moberg_channel_action) {
                        .digital_in.context=channel,
-                         .digital_in.read=NULL });
+                       .digital_in.read=digital_in_read });
         map->map(map->device, &channel->channel);
       } break;
       case chan_DIGITALOUT: {
@@ -249,10 +461,11 @@ static struct moberg_status parse_map(
                      channel,
                      &channel->channel_context,
                      device,
+                     i,
                      kind,
                      (union moberg_channel_action) {
                        .digital_out.context=channel,
-                       .digital_out.write=NULL });
+                       .digital_out.write=digital_out_write });
         map->map(map->device, &channel->channel);
       } break;
       case chan_ENCODERIN: {
@@ -263,10 +476,11 @@ static struct moberg_status parse_map(
                      channel,
                      &channel->channel_context,
                      device,
+                     i,
                      kind,
                      (union moberg_channel_action) {
                        .encoder_in.context=channel,
-                       .encoder_in.read=NULL });
+                       .encoder_in.read=encoder_in_read });
         map->map(map->device, &channel->channel);
       } break;
     }
diff --git a/plugins/serial2002/serial2002_lib.c b/plugins/serial2002/serial2002_lib.c
new file mode 100644
index 0000000000000000000000000000000000000000..295b43e3003cd8a46ee1b2db3026a24ff632ae34
--- /dev/null
+++ b/plugins/serial2002/serial2002_lib.c
@@ -0,0 +1,693 @@
+/*
+    serial2002_lib.c -- serial2002 protocol
+
+    Copyright (C) 2002,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 <unistd.h>
+#include <stdio.h>
+#include <poll.h>
+#include <errno.h>
+#include <string.h>
+#include <moberg_inline.h>
+#include <serial2002_lib.h>
+
+static struct moberg_status tty_write(int fd, unsigned char *buf, int count)
+{
+  int n = 0;
+  while (n < count) {
+    int written = write(fd, &buf[n], count - n);
+    if (written == 0) {
+      fprintf(stderr, "Failed to write\n");
+      return MOBERG_ERRNO(ENODATA);
+    } else if (written < 0) {
+      return MOBERG_ERRNO(errno);
+    }
+    n += written;
+  }
+  return MOBERG_OK;
+}
+
+static struct moberg_status tty_read(int fd, int timeout, unsigned char *value)
+{
+  struct pollfd pollfd;
+
+  while (1) {
+    pollfd.fd = fd;
+    pollfd.events = POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR;
+    int err = poll(&pollfd, 1, timeout);
+    if (err >= 1) {
+      break;
+    } else if (err == 0) {
+      return MOBERG_ERRNO(ETIMEDOUT);
+    } else if (err < 0) {
+      return MOBERG_ERRNO(errno);
+    }
+  }
+  int err = read(fd, value, 1);
+  if (err == 1) {
+    return MOBERG_OK;
+  } else {
+    return MOBERG_ERRNO(errno);
+  }
+}
+
+struct moberg_status serial2002_poll_digital(int fd, int channel)
+{
+  unsigned char cmd;
+  
+  cmd = 0x40 | (channel & 0x1f);
+  return tty_write(fd, &cmd, 1);
+}
+
+struct moberg_status serial2002_poll_channel(int fd, int channel)
+{
+  unsigned char cmd;
+  
+  cmd = 0x60 | (channel & 0x1f);
+  return tty_write(fd, &cmd, 1);
+}
+
+struct moberg_status serial2002_read(int f, int timeout,
+                                            struct serial2002_data *value)
+{
+  int length;
+  
+  value->kind = is_invalid;
+  value->index = 0;
+  value->value = 0;
+  length = 0;
+  while (1) {
+    unsigned char data;
+    struct moberg_status result = tty_read(f, timeout, &data);
+    if (! OK(result)) {
+      return result;
+    }
+    
+    length++;
+    if (data < 0) {
+      fprintf(stderr, "serial2002 error\n");
+      break;
+    } else if (length < 6 && data & 0x80) {
+      value->value = (value->value << 7) | (data & 0x7f);
+    } else if (length == 6 && data & 0x80) {
+      fprintf(stderr, "Too long\n");
+      value->kind = is_invalid;
+      break;
+    } else {
+      if (length == 1) {
+        switch ((data >> 5) & 0x03) {
+          case 0:{
+            value->value = 0;
+            value->kind = is_digital;
+          } break;
+          case 1:{
+            value->value = 1;
+            value->kind = is_digital;
+          } break;
+        }
+      } else {
+        value->value = (value->value << 2) | ((data & 0x60) >> 5);
+        value->kind = is_channel;
+      }
+      value->index = data & 0x1f;
+      break;
+    }
+  }
+  return MOBERG_OK;
+}
+
+struct moberg_status serial2002_write(int f, struct serial2002_data data)
+{
+  if (data.kind == is_digital) {
+    unsigned char ch = ((data.value << 5) & 0x20) | (data.index & 0x1f);
+    return tty_write(f, &ch, 1);
+  } else {
+    unsigned char ch[6];
+    int i = 0;
+    if (data.value >= (1L << 30)) {
+      ch[i] = 0x80 | ((data.value >> 30) & 0x03);
+      i++;
+    }
+    if (data.value >= (1L << 23)) {
+      ch[i] = 0x80 | ((data.value >> 23) & 0x7f);
+      i++;
+    }
+    if (data.value >= (1L << 16)) {
+      ch[i] = 0x80 | ((data.value >> 16) & 0x7f);
+      i++;
+    }
+    if (data.value >= (1L << 9)) {
+      ch[i] = 0x80 | ((data.value >> 9) & 0x7f);
+      i++;
+    }
+    ch[i] = 0x80 | ((data.value >> 2) & 0x7f);
+    i++;
+    ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f);
+    i++;
+    return tty_write(f, ch, i);
+  }
+}
+
+static double interpret_value(int value)
+{
+  unsigned int unit = (value & 0x7);
+  unsigned int sign = (value & 0x8) >> 3;
+  double result = (value & ~0xf) >> 4;
+  if (sign == 1) {
+    result = -result;
+  }
+  switch (unit) {
+    case 1:
+      result *= 1e-3;
+      break;
+    case 2:
+      result *= 1e-6;
+      break;
+  }
+  fprintf(stderr, "%f\n", result);
+  return result;
+}
+
+static struct moberg_status update_channel(struct serial2002_channel *channel,
+                                           unsigned int kind,
+                                           unsigned int cmd,
+                                           unsigned int value)
+{
+  if (channel->kind == 0) {
+    channel->kind = kind;
+  } else if (channel->kind != kind) {
+    return MOBERG_ERRNO(EINVAL);
+  }
+  channel->valid |= 1 << cmd;
+  switch (cmd) {
+    case 0:
+      channel->bits = value;
+      break;
+    case 1:
+      channel->min = interpret_value(value);
+      break;
+    case 2:
+      channel->max = interpret_value(value);
+      break;
+  }
+  return MOBERG_OK;
+}
+
+static struct moberg_status do_read_config(
+  int fd,
+  struct serial2002_config *config)
+{
+  struct serial2002_data data = { 0, 0 };
+
+  memset(config, 0, sizeof(*config));
+  serial2002_poll_channel(fd, 31);
+  while (1) {
+    struct moberg_status result = serial2002_read(fd, 1000, &data);
+    if (! OK(result)) { return result; }
+    unsigned int channel = (data.value & 0x0001f);
+    unsigned int kind = (data.value & 0x00e0) >> 5;
+    unsigned int cmd = (data.value & 0x0300) >> 8;
+    unsigned int value = (data.value & ~0x3ff) >> 10;
+    switch (kind) {
+      case 0: return MOBERG_OK;
+      case 1: /* digital in */
+        config->digital_in[channel].valid = 1;
+        config->digital_in[channel].kind = kind;
+        break;
+      case 2: /* digital out */
+        config->digital_out[channel].valid = 1;
+        config->digital_out[channel].kind = kind;
+        break;
+      case 3: /* analog in */
+        update_channel(&config->channel_in[channel], kind, cmd, value);
+        break;
+      case 4: /* analog out */
+        update_channel(&config->channel_out[channel], kind, cmd, value);
+        break;
+      case 5: /* counter in */
+        update_channel(&config->channel_in[channel], kind, cmd, value);
+        break;
+    }
+  }
+}
+
+static struct moberg_status check_config(struct serial2002_config *c)
+{
+  struct moberg_status result = MOBERG_OK;
+  
+  for (int i = 0 ; i < 31 ; i++) {
+    switch (c->channel_in[i].kind) {
+      case 3: /* analog in */
+        if (c->channel_in[i].valid && c->channel_in[i].valid != 0x7) {
+          fprintf(stderr, "Analog input channel[%d] is missing ", i);
+          if (! (c->channel_in[i].valid & 0x1)) { fprintf(stderr, "#bits "); }
+          if (! (c->channel_in[i].valid & 0x2)) { fprintf(stderr, "min "); }
+          if (! (c->channel_in[i].valid & 0x4)) { fprintf(stderr, "max "); }
+          fprintf(stderr, "\n");
+          result = MOBERG_ERRNO(EINVAL);
+        }
+        break;
+      case 4: /* analog out */
+        if (c->channel_out[i].valid && c->channel_out[i].valid != 0x7) {
+          fprintf(stderr, "Analog output channel[%d] is missing ", i);
+          if (! (c->channel_out[i].valid & 0x1)) { fprintf(stderr, "#bits "); }
+          if (! (c->channel_out[i].valid & 0x2)) { fprintf(stderr, "min "); }
+          if (! (c->channel_out[i].valid & 0x4)) { fprintf(stderr, "max "); }
+          fprintf(stderr, "\n");
+          result = MOBERG_ERRNO(EINVAL);
+        }
+      case 5: /* counter in */
+        if (c->channel_in[i].valid && c->channel_in[i].valid != 0x1) {
+          fprintf(stderr, "Counter input channel[%d] is missing #bits\n", i);
+          result = MOBERG_ERRNO(EINVAL);
+        }
+        break;
+    }
+  }
+  return result;
+}
+
+struct moberg_status serial2002_read_config(
+  int fd,
+  struct serial2002_config *config)
+{ 
+  struct moberg_status result = do_read_config(fd, config);
+  if (OK(result)) {
+    result = check_config(config);
+  }
+  return result;
+}
+
+
+#if 0
+
+static void x() {
+  typedef struct {
+    short int kind;
+    short int bits;
+    int min;
+    int max;
+  } config_t;
+  config_t *dig_in_config;
+  config_t *dig_out_config;
+  config_t *chan_in_config;
+  config_t *chan_out_config;
+  int i;
+  
+  result = 0;
+  dig_in_config = kcalloc(32, sizeof(config_t), GFP_KERNEL);
+  dig_out_config = kcalloc(32, sizeof(config_t), GFP_KERNEL);
+  chan_in_config = kcalloc(32, sizeof(config_t), GFP_KERNEL);
+  chan_out_config = kcalloc(32, sizeof(config_t), GFP_KERNEL);
+  if (!dig_in_config || !dig_out_config
+      || !chan_in_config || !chan_out_config) {
+    result = -ENOMEM;
+    goto err_alloc_configs;
+  }
+
+  tty_setspeed(devpriv->tty, devpriv->speed);
+  poll_channel(devpriv->tty, 31);	// Start reading configuration
+  while (1) {
+    struct serial2002_data data;
+    
+    data = serial_read(devpriv->tty, 1000);
+    if (data.kind != is_channel || data.index != 31
+        || !(data.value & 0xe0)) {
+      break;
+    } else {
+      int command, channel, kind;
+      config_t *cur_config = 0;
+      
+      channel = data.value & 0x1f;
+      kind = (data.value >> 5) & 0x7;
+      command = (data.value >> 8) & 0x3;
+      switch (kind) {
+        case 1:{
+          cur_config = dig_in_config;
+        }
+          break;
+        case 2:{
+          cur_config = dig_out_config;
+        }
+          break;
+        case 3:{
+          cur_config = chan_in_config;
+        }
+          break;
+        case 4:{
+          cur_config = chan_out_config;
+        }
+          break;
+        case 5:{
+          cur_config = chan_in_config;
+        }
+          break;
+      }
+      
+      if (cur_config) {
+        cur_config[channel].kind = kind;
+        switch (command) {
+          case 0:{
+            cur_config[channel].
+              bits =
+              (data.
+               value >> 10) &
+              0x3f;
+          }
+            break;
+          case 1:{
+            int unit, sign, min;
+            unit = (data.
+                    value >> 10) &
+              0x7;
+            sign = (data.
+                    value >> 13) &
+              0x1;
+            min = (data.
+                   value >> 14) &
+              0xfffff;
+            
+            switch (unit) {
+              case 0:{
+                min = min * 1000000;
+              }
+                break;
+              case 1:{
+                min = min * 1000;
+              }
+                break;
+              case 2:{
+                min = min * 1;
+              }
+                break;
+            }
+            if (sign) {
+              min = -min;
+            }
+            cur_config[channel].
+              min = min;
+          }
+            break;
+          case 2:{
+            int unit, sign, max;
+            unit = (data.
+                    value >> 10) &
+              0x7;
+            sign = (data.
+                    value >> 13) &
+              0x1;
+            max = (data.
+                   value >> 14) &
+              0xfffff;
+            
+            switch (unit) {
+              case 0:{
+                max = max * 1000000;
+              }
+                break;
+              case 1:{
+                max = max * 1000;
+              }
+                break;
+              case 2:{
+                max = max * 1;
+              }
+                break;
+            }
+            if (sign) {
+              max = -max;
+            }
+            cur_config[channel].
+              max = max;
+          }
+            break;
+        }
+      }
+    }
+  }
+  for (i = 0; i <= 4; i++) {
+    // Fill in subdev data
+    config_t *c;
+    unsigned char *mapping = 0;
+    serial2002_range_table_t *range = 0;
+    int kind = 0;
+    
+    switch (i) {
+      case 0:{
+        c = dig_in_config;
+        mapping = devpriv->digital_in_mapping;
+        kind = 1;
+      }
+        break;
+      case 1:{
+        c = dig_out_config;
+        mapping = devpriv->digital_out_mapping;
+        kind = 2;
+      }
+        break;
+      case 2:{
+        c = chan_in_config;
+        mapping = devpriv->analog_in_mapping;
+        range = devpriv->in_range;
+        kind = 3;
+      }
+        break;
+      case 3:{
+        c = chan_out_config;
+        mapping = devpriv->analog_out_mapping;
+        range = devpriv->out_range;
+        kind = 4;
+      }
+        break;
+      case 4:{
+        c = chan_in_config;
+        mapping = devpriv->encoder_in_mapping;
+        range = devpriv->in_range;
+        kind = 5;
+      }
+        break;
+      default:{
+        c = 0;
+      }
+        break;
+    }
+    if (c) {
+      comedi_subdevice *s;
+      const comedi_lrange **range_table_list = NULL;
+      unsigned int *maxdata_list;
+      int j, chan;
+      
+      for (chan = 0, j = 0; j < 32; j++) {
+        if (c[j].kind == kind) {
+          chan++;
+        }
+      }
+      s = &dev->subdevices[i];
+      s->n_chan = chan;
+      s->maxdata = 0;
+      kfree(s->maxdata_list);
+      s->maxdata_list = maxdata_list =
+        kmalloc(sizeof(lsampl_t) * s->n_chan,
+                GFP_KERNEL);
+      if (!s->maxdata_list) {
+        break;	/* error handled below */
+      }
+      kfree(s->range_table_list);
+      s->range_table = 0;
+      s->range_table_list = 0;
+      if (kind == 1 || kind == 2) {
+        s->range_table = &range_digital;
+      } else if (range) {
+        s->range_table_list = range_table_list =
+          kmalloc(sizeof
+                  (serial2002_range_table_t) *
+                  s->n_chan, GFP_KERNEL);
+        if (!s->range_table_list) {
+          break;	/* error handled below */
+        }
+      }
+      for (chan = 0, j = 0; j < 32; j++) {
+        if (c[j].kind == kind) {
+          if (mapping) {
+            mapping[chan] = j;
+          }
+          if (range) {
+            range[j].length = 1;
+            range[j].range.min =
+              c[j].min;
+            range[j].range.max =
+              c[j].max;
+            range_table_list[chan] =
+              (const
+               comedi_lrange *)
+              &range[j];
+          }
+          maxdata_list[chan] =
+            ((long long)1 << c[j].
+             bits) - 1;
+          chan++;
+        }
+      }
+    }
+  }
+  if (i <= 4) {
+    /* Failed to allocate maxdata_list or range_table_list
+     * for a subdevice that needed it. */
+    result = -ENOMEM;
+    for (i = 0; i <= 4; i++) {
+      comedi_subdevice *s = &dev->subdevices[i];
+      
+      kfree(s->maxdata_list);
+      s->maxdata_list = NULL;
+      kfree(s->range_table_list);
+      s->range_table_list = NULL;
+    }
+  }
+err_alloc_configs:
+  kfree(dig_in_config);
+  kfree(dig_out_config);
+  kfree(chan_in_config);
+  kfree(chan_out_config);
+  if (result) {
+    if (devpriv->tty) {
+      filp_close(devpriv->tty, 0);
+      devpriv->tty = NULL;
+    }
+  }
+}
+return result;
+}
+
+static int serial2002_di_rinsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int n;
+	int chan;
+
+	chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)];
+	for (n = 0; n < insn->n; n++) {
+		struct serial2002_data read;
+
+		poll_digital(devpriv->tty, chan);
+		while (1) {
+			read = serial_read(devpriv->tty, 1000);
+			if (read.kind != is_digital || read.index == chan) {
+				break;
+			}
+		}
+		data[n] = read.value;
+	}
+	return n;
+}
+
+static int serial2002_do_winsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int n;
+	int chan;
+
+	chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)];
+	for (n = 0; n < insn->n; n++) {
+		struct serial2002_data write;
+
+		write.kind = is_digital;
+		write.index = chan;
+		write.value = data[n];
+		serial_write(devpriv->tty, write);
+	}
+	return n;
+}
+
+static int serial2002_ai_rinsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int n;
+	int chan;
+
+	chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)];
+	for (n = 0; n < insn->n; n++) {
+		struct serial2002_data read;
+
+		poll_channel(devpriv->tty, chan);
+		while (1) {
+			read = serial_read(devpriv->tty, 1000);
+			if (read.kind != is_channel || read.index == chan) {
+				break;
+			}
+		}
+		data[n] = read.value;
+	}
+	return n;
+}
+
+static int serial2002_ao_winsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int n;
+	int chan;
+
+	chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)];
+	for (n = 0; n < insn->n; n++) {
+		struct serial2002_data write;
+
+		write.kind = is_channel;
+		write.index = chan;
+		write.value = data[n];
+		serial_write(devpriv->tty, write);
+		devpriv->ao_readback[chan] = data[n];
+	}
+	return n;
+}
+
+static int serial2002_ao_rinsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int n;
+	int chan = CR_CHAN(insn->chanspec);
+
+	for (n = 0; n < insn->n; n++) {
+		data[n] = devpriv->ao_readback[chan];
+	}
+
+	return n;
+}
+
+static int serial2002_ei_rinsn(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int n;
+	int chan;
+
+	chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)];
+	for (n = 0; n < insn->n; n++) {
+		struct serial2002_data read;
+
+		poll_channel(devpriv->tty, chan);
+		while (1) {
+			read = serial_read(devpriv->tty, 1000);
+			if (read.kind != is_channel || read.index == chan) {
+				break;
+			}
+		}
+		data[n] = read.value;
+	}
+	return n;
+}
+
+#endif
diff --git a/plugins/serial2002/serial2002_lib.h b/plugins/serial2002/serial2002_lib.h
new file mode 100644
index 0000000000000000000000000000000000000000..f53133a0a942f16325806bc4a87e55d8dd2758a8
--- /dev/null
+++ b/plugins/serial2002/serial2002_lib.h
@@ -0,0 +1,64 @@
+/*
+    serial2002_lib.h -- serial2002 protocol
+
+    Copyright (C) 2002,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/>.
+*/
+
+#ifndef __SERIAL2002_LIB_H__
+#define __SERIAL2002_LIB_H__
+
+#include <moberg.h>
+
+struct serial2002_data {
+  enum { is_invalid, is_digital, is_channel } kind;
+  int index;
+  unsigned long value;
+};
+
+enum serial2002_kind {
+  SERIAL2002_DIGITAL_IN = 1,
+  SERIAL2002_DIGITAL_OUT = 2,
+  SERIAL2002_ANALOG_IN = 3,
+  SERIAL2002_ANALOG_OUT = 4,
+  SERIAL2002_COUNTER_IN = 5
+};
+
+struct serial2002_config {
+  struct serial2002_channel {
+    int valid;
+    int kind;
+    int bits;
+    double min;
+    double max;
+  } channel_in[31], channel_out[31],
+    digital_in[32], digital_out[32];
+};
+
+struct moberg_status serial2002_poll_digital(int fd, int channel);
+
+struct moberg_status serial2002_poll_channel(int fd, int channel);
+
+struct moberg_status serial2002_read(int fd, int timeout,
+                                     struct serial2002_data *data);
+
+struct moberg_status serial2002_write(int fd, struct serial2002_data data);
+
+struct moberg_status serial2002_read_config(int fd,
+                                            struct serial2002_config *config);
+
+#endif