From d29b2b1b54efbab11dd50dad20ee47f6d6068d53 Mon Sep 17 00:00:00 2001
From: Anders Blomdell <anders.blomdell@control.lth.se>
Date: Thu, 22 Apr 2021 20:43:52 +0200
Subject: [PATCH] Added batch polling for serial_2002

---
 plugins/serial2002/serial2002.c     | 205 +++++++++++++++++++++++-----
 plugins/serial2002/serial2002_lib.c | 128 ++++++++++++-----
 plugins/serial2002/serial2002_lib.h |  28 +++-
 3 files changed, 287 insertions(+), 74 deletions(-)

diff --git a/plugins/serial2002/serial2002.c b/plugins/serial2002/serial2002.c
index 564e689..dbc902c 100644
--- a/plugins/serial2002/serial2002.c
+++ b/plugins/serial2002/serial2002.c
@@ -29,6 +29,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 #include <errno.h>
 #include <moberg.h>
 #include <moberg_config.h>
@@ -48,7 +49,7 @@ struct moberg_device_context {
     char *name;
     int baud;
     long timeout;
-    int fd;
+    struct serial2002_io io;
   } port;
   struct remap_analog {
     int count;
@@ -65,8 +66,13 @@ struct moberg_device_context {
     struct digital_map {
       unsigned char index;
     } map[32];
-  } digital_in, digital_out,
-    encoder_in;
+  } digital_in, digital_out, encoder_in;
+  struct batch {
+    int active;
+    struct timespec expires;
+    struct serial2002_data digital[32];
+    struct serial2002_data channel[32];
+  } batch;
 };
 
 struct moberg_channel_context {
@@ -101,6 +107,125 @@ struct moberg_channel_encoder_in {
   struct moberg_channel_context channel_context;
 };
 
+static struct timespec timespec_sub(struct timespec t1, struct timespec t2)
+{
+  struct timespec result;
+  result.tv_sec = t1.tv_sec - t2.tv_sec;
+  result.tv_nsec = t1.tv_nsec - t2.tv_nsec;
+  if (result.tv_nsec < 0) {
+    result.tv_sec--;
+    result.tv_nsec += 1000000000L;
+  }
+  return result;
+}
+
+static struct timespec timespec_add(struct timespec t1, struct timespec t2)
+{
+  struct timespec result;
+  result.tv_sec = t1.tv_sec + t2.tv_sec;
+  result.tv_nsec = t1.tv_nsec + t2.tv_nsec;
+  if (result.tv_nsec > 1000000000L) {
+    result.tv_sec++;
+    result.tv_nsec -= 1000000000L;
+  }
+  return result;
+}
+
+static struct moberg_status batch_sampling(
+  struct moberg_device_context *device,
+  struct analog_map *analog,
+  struct digital_map *digital,
+  struct digital_map *encoder,
+  struct serial2002_data *data)
+{
+  struct moberg_status result = MOBERG_OK;
+  if (! data) {
+    result = MOBERG_ERRNO(EINVAL);
+    goto return_result;
+  }
+  struct timespec start, diff;
+  if (clock_gettime(CLOCK_REALTIME, &start) < 0) {
+    result = MOBERG_ERRNO(errno);
+    goto return_result;
+  }
+  diff = timespec_sub(device->batch.expires, start);
+  if (diff.tv_sec < 0 ||
+      (digital && device->batch.digital[digital->index].kind != is_digital) ||
+      (analog && device->batch.channel[analog->index].kind != is_channel) ||
+      (encoder && device->batch.channel[encoder->index].kind != is_channel)) {
+    /* Batch has expired */
+    int expected = 0;
+    for (int i = 0 ; i < device->digital_in.count ; i++) {
+      struct digital_map *map = &device->digital_in.map[i];
+      device->batch.digital[map->index].kind = is_invalid;
+      expected++;
+      result = serial2002_poll_digital(&device->port.io, map->index, 0);
+      if (! OK(result)) {
+        goto return_result;
+      }
+    }
+    for (int i = 0 ; i < device->analog_in.count ; i++) {
+      struct analog_map *map = &device->analog_in.map[i];
+      device->batch.channel[map->index].kind = is_invalid;
+      expected++;
+      result = serial2002_poll_channel(&device->port.io, map->index, 0);
+      if (! OK(result)) {
+        goto return_result;
+      }
+    }
+    for (int i = 0 ; i < device->encoder_in.count ; i++) {
+      struct digital_map *map = &device->encoder_in.map[i];
+      device->batch.channel[map->index].kind = is_invalid;
+      expected++;
+      result = serial2002_poll_channel(&device->port.io, map->index, 0);
+      if (! OK(result)) {
+        goto return_result;
+      }
+    }
+    serial2002_flush(&device->port.io);
+    while (expected > 0) {
+      struct serial2002_data data;
+      result = serial2002_read(&device->port.io, device->port.timeout, &data);
+      if (! OK(result)) {
+        goto return_result;
+      }
+      if (data.kind == is_digital) {
+        device->batch.digital[data.index] = data;
+        expected--;
+      } else if (data.kind == is_channel) {
+        device->batch.channel[data.index] = data;
+        expected--;
+      } else {
+        result = MOBERG_ERRNO(EINVAL);
+        goto return_result;
+      }
+    }
+    struct timespec now;
+    if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+      result = MOBERG_ERRNO(errno);
+      goto return_result;
+    }
+    diff = timespec_sub(now, start);
+    device->batch.expires = timespec_add(now, diff);
+  }
+  if (digital &&
+      device->batch.digital[digital->index].kind == is_digital) {
+    *data = device->batch.digital[digital->index];
+    device->batch.digital[digital->index].kind = is_invalid;
+  } else if (analog &&
+             device->batch.channel[analog->index].kind == is_channel) {
+    *data = device->batch.channel[analog->index];
+    device->batch.channel[analog->index].kind = is_invalid;
+  } else if (encoder &&
+             device->batch.channel[encoder->index].kind == is_channel) {
+    *data = device->batch.channel[encoder->index];
+    device->batch.channel[encoder->index].kind = is_invalid;
+  }
+
+return_result:
+  return result;
+}
+
 static struct moberg_status analog_in_read(
   struct moberg_channel_analog_in *analog_in,
   double *value)
@@ -111,19 +236,22 @@ static struct moberg_status analog_in_read(
   struct moberg_device_context *device = channel->device;
   struct serial2002_data data;
   struct analog_map map = device->analog_in.map[channel->index];
-  struct moberg_status result = serial2002_poll_channel(
-    device->port.fd, map.index);
-  if (! OK(result)) {
-    goto return_result;
-  }
-  result = serial2002_read(device->port.fd, device->port.timeout, &data);
-  if (OK(result)) {
+  struct moberg_status result;
+
+  if (device->batch.active) {
+    result = batch_sampling(device, &map,  NULL, NULL, &data);
+    if (! OK(result)) { goto return_result; }
+  } else {
+    result = serial2002_poll_channel(&device->port.io, map.index, 0);
+    if (! OK(result)) { goto return_result; }
+    result = serial2002_read(&device->port.io, device->port.timeout, &data);
+    if (! OK(result)) { goto return_result; }
     if ((data.kind != is_channel) || (data.index != map.index)) {
       result = MOBERG_ERRNO(ECHRNG);
       goto return_result;
     }
-    *value = (data.value * map.delta + map.min);
   }
+  *value = (data.value * map.delta + map.min);
 return_result:
   return result;
 err_einval:
@@ -153,7 +281,7 @@ static struct moberg_status analog_out_write(
   }
 
   struct serial2002_data data = { is_channel, map.index, as_long };
-  struct moberg_status result = serial2002_write(device->port.fd,  data);
+  struct moberg_status result = serial2002_write(&device->port.io,  data, 1);
   if (OK(result) && actual_value) {
     *actual_value = data.value * map.delta + map.min;    
   }
@@ -170,25 +298,26 @@ static struct moberg_status digital_in_read(
   struct moberg_device_context *device = channel->device;
   struct serial2002_data data = { 0, 0 };
   struct digital_map map = device->digital_in.map[channel->index];
-  struct moberg_status result = serial2002_poll_digital(
-    device->port.fd, map.index);
-  if (! OK(result)) {
-    goto return_result;
-  }
-  result = serial2002_read(device->port.fd, device->port.timeout, &data);
-  if (OK(result)) {
+  struct moberg_status result;
+
+  if (device->batch.active) {
+    result = batch_sampling(device, NULL, &map, NULL, &data);
+    if (! OK(result)) { goto return_result; }
+  } else {
+    result = serial2002_poll_digital(&device->port.io, map.index, 1);
+    if (! OK(result)) { goto return_result; }
+    result = serial2002_read(&device->port.io, device->port.timeout, &data);
+    if (! OK(result)) { goto return_result; }
     if ((data.kind != is_digital) || (data.index != map.index)) {
       result = MOBERG_ERRNO(ECHRNG);
       goto return_result;
     }
-    *value = data.value != 0;
   }
+  *value = data.value != 0;
 return_result:
   return result;
 err_einval:
   return MOBERG_ERRNO(EINVAL);
-  *value = 1;
-  return MOBERG_OK;
 }
 
 static struct moberg_status digital_out_write(
@@ -200,7 +329,7 @@ static struct moberg_status digital_out_write(
   struct moberg_device_context *device = channel->device;
   struct digital_map map = device->digital_out.map[channel->index];
   struct serial2002_data data = { is_digital, map.index, desired_value != 0 };
-  struct moberg_status result = serial2002_write(device->port.fd,  data);
+  struct moberg_status result = serial2002_write(&device->port.io,  data, 0);
   if (OK(result) && actual_value) {
     *actual_value = data.value;
   }
@@ -217,19 +346,21 @@ static struct moberg_status encoder_in_read(
   struct moberg_device_context *device = channel->device;
   struct serial2002_data data;
   struct digital_map map = device->encoder_in.map[channel->index];
-  struct moberg_status result = serial2002_poll_channel(
-    device->port.fd, map.index);
-  if (! OK(result)) {
-    goto return_result;
-  }
-  result = serial2002_read(device->port.fd, device->port.timeout, &data);
-  if (OK(result)) {
+  struct moberg_status result;
+  if (device->batch.active) {
+    result = batch_sampling(device, NULL, NULL, &map, &data);
+    if (! OK(result)) { goto return_result; }
+  } else {
+    result = serial2002_poll_channel(&device->port.io, map.index, 0);
+    if (! OK(result)) { goto return_result; }
+    result = serial2002_read(&device->port.io, device->port.timeout, &data);
+    if (! OK(result)) { goto return_result; }
     if ((data.kind != is_channel) || (data.index != map.index)) {
       result = MOBERG_ERRNO(ECHRNG);
       goto return_result;
     }
-    *value = (data.value);
   }
+  *value = (data.value);
 return_result:
   return result;
 err_einval:
@@ -337,8 +468,12 @@ static struct moberg_status device_open(struct moberg_device_context *device)
       /* It's expected for this to fail for at least some USB serial adapters */
       ioctl(fd, TIOCSSERIAL, &settings);
     }
+    device->port.io.fd = fd;
+    device->port.io.read.pos = 0;
+    device->port.io.write.pos = 0;
     struct serial2002_config config;
-    result = serial2002_read_config(fd, device->port.timeout, &config);
+    result = serial2002_read_config(&device->port.io,
+                                    device->port.timeout, &config);
     if (! OK(result)) { goto err_result; }
     remap_analog(&device->analog_in, SERIAL2002_ANALOG_IN,
                   config.channel_in, 31);
@@ -350,7 +485,6 @@ static struct moberg_status device_open(struct moberg_device_context *device)
                   config.digital_out, 32);
     remap_digital(&device->encoder_in, SERIAL2002_COUNTER_IN,
                   config.channel_in, 31);
-    device->port.fd = fd;
   }
   device->port.count++;
   return MOBERG_OK;
@@ -368,7 +502,7 @@ static struct moberg_status device_close(struct moberg_device_context *device)
   if (device->port.count < 0) { errno = ENODEV; goto err_errno; }
   device->port.count--;
   if (device->port.count == 0) {
-    if (close(device->port.fd) < 0) { goto err_errno; }
+    if (close(device->port.io.fd) < 0) { goto err_errno; }
   }
   return MOBERG_OK;
 err_errno:
@@ -486,6 +620,9 @@ static struct moberg_status parse_config(
       } 
       if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto syntax_err; }
       device->port.timeout = timeout.u.integer.value * multiplier;
+    } else if (acceptkeyword(c, "batch_sampling")) {
+      device->batch.active = 1;
+      if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto syntax_err; }
     } else {
       goto syntax_err;
     }
diff --git a/plugins/serial2002/serial2002_lib.c b/plugins/serial2002/serial2002_lib.c
index 41359e8..ea212cc 100644
--- a/plugins/serial2002/serial2002_lib.c
+++ b/plugins/serial2002/serial2002_lib.c
@@ -24,14 +24,15 @@
 #include <poll.h>
 #include <errno.h>
 #include <string.h>
+#include <sys/ioctl.h>
 #include <moberg_inline.h>
 #include <serial2002_lib.h>
 
-static struct moberg_status tty_write(int fd, unsigned char *buf, int count)
+struct moberg_status serial2002_flush(struct serial2002_io *io)
 {
   int n = 0;
-  while (n < count) {
-    int written = write(fd, &buf[n], count - n);
+  while (n < io->write.pos) {
+    int written = write(io->fd, &io->write.data[n], io->write.pos - n);
     if (written == 0) {
       return MOBERG_ERRNO(ENODATA);
     } else if (written < 0) {
@@ -39,50 +40,105 @@ static struct moberg_status tty_write(int fd, unsigned char *buf, int count)
     }
     n += written;
   }
+  io->write.pos = 0;
   return MOBERG_OK;
 }
 
-static struct moberg_status tty_read(int fd, long timeout, unsigned char *value)
+static struct moberg_status tty_write(struct serial2002_io *io,
+                                      unsigned char *buf,
+                                      int count,
+                                      int flush)
 {
-  struct pollfd pollfd;
+  struct moberg_status result = MOBERG_OK;
 
-  while (1) {
-    pollfd.fd = fd;
+  for (int i = 0 ; i < count ; i++) {
+    io->write.data[io->write.pos] = buf[i];
+    io->write.pos++;
+    if (io->write.pos >= sizeof(io->write.data)) {
+      result = serial2002_flush(io);
+      if (! OK(result)) {
+        goto return_result;
+      }
+    }
+  }
+  if (flush) {
+    result = serial2002_flush(io);
+    if (! OK(result)) {
+      goto return_result;
+    }
+  }
+return_result:
+  return result;
+}
+
+static struct moberg_status tty_read(struct serial2002_io *io,
+                                     long timeout,
+                                     unsigned char *value)
+{
+  struct moberg_status result = MOBERG_OK;
+
+  if (io->read.pos >= io->read.count) {
+    struct pollfd pollfd;
+    pollfd.fd = io->fd;
     pollfd.events = POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR;
     int err = poll(&pollfd, 1, timeout / 1000);
-    if (err >= 1) {
-      break;
+    if (err == 0) {
+      result = MOBERG_ERRNO(ETIMEDOUT);
+      goto return_result;
+
+    } else if (err < 0) {
+      result = MOBERG_ERRNO(errno);
+      goto return_result;
+    }
+    int available;
+    err = ioctl(io->fd, FIONREAD, &available);
+    if (err < 0) {
+      result = MOBERG_ERRNO(errno);
+      goto return_result;
+    }
+    if (available > sizeof(io->read.data)) {
+      available = sizeof(io->read.data);
+    }
+    err = read(io->fd, &io->read.data[0], available);
+    if (err > 0) {
+      io->read.pos = 0;
+      io->read.count = err;
     } else if (err == 0) {
-      return MOBERG_ERRNO(ETIMEDOUT);
+      result = MOBERG_ERRNO(ENODATA);
+      goto return_result;
     } else if (err < 0) {
-      return MOBERG_ERRNO(errno);
+      result = MOBERG_ERRNO(errno);
+      goto return_result;
     }
   }
-  int err = read(fd, value, 1);
-  if (err == 1) {
-    return MOBERG_OK;
-  } else {
-    return MOBERG_ERRNO(errno);
-  }
+return_result:
+  *value = io->read.data[io->read.pos];
+  io->read.pos++;
+  return result;
 }
 
-struct moberg_status serial2002_poll_digital(int fd, int channel)
+struct moberg_status serial2002_poll_digital(struct serial2002_io *io,
+                                             int channel,
+                                             int flush)
 {
   unsigned char cmd;
   
   cmd = 0x40 | (channel & 0x1f);
-  return tty_write(fd, &cmd, 1);
+  return tty_write(io, &cmd, 1, flush);
 }
 
-struct moberg_status serial2002_poll_channel(int fd, int channel)
+struct moberg_status serial2002_poll_channel(struct serial2002_io *io,
+                                             int channel,
+                                             int flush)
 {
   unsigned char cmd;
   
   cmd = 0x60 | (channel & 0x1f);
-  return tty_write(fd, &cmd, 1);
+  return tty_write(io, &cmd, 1, flush);
 }
 
-struct moberg_status serial2002_read(int f, long timeout,
+struct moberg_status serial2002_read(struct serial2002_io *io,
+                                     long timeout,
                                      struct serial2002_data *value)
 {
   int length;
@@ -93,7 +149,7 @@ struct moberg_status serial2002_read(int f, long timeout,
   length = 0;
   while (value->kind == is_invalid) {
     unsigned char data;
-    struct moberg_status result = tty_read(f, timeout, &data);
+    struct moberg_status result = tty_read(io, timeout, &data);
     if (! OK(result)) {
       return result;
     }
@@ -141,11 +197,13 @@ struct moberg_status serial2002_read(int f, long timeout,
   return MOBERG_OK;
 }
 
-struct moberg_status serial2002_write(int f, struct serial2002_data data)
+struct moberg_status serial2002_write(struct serial2002_io *io,
+                                      struct serial2002_data data,
+                                      int flush)
 {
   if (data.kind == is_digital) {
     unsigned char ch = ((data.value << 5) & 0x20) | (data.index & 0x1f);
-    return tty_write(f, &ch, 1);
+    return tty_write(io, &ch, 1, 1);
   } else {
     unsigned char ch[6];
     int i = 0;
@@ -169,7 +227,7 @@ struct moberg_status serial2002_write(int f, struct serial2002_data data)
     i++;
     ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f);
     i++;
-    return tty_write(f, ch, i);
+    return tty_write(io, ch, i, 1);
   }
 }
 
@@ -217,35 +275,35 @@ static struct moberg_status update_channel(struct serial2002_channel *channel,
   return MOBERG_OK;
 }
 
-static void discard_pending(int fd)
+static void discard_pending(struct serial2002_io *io)
 {
   struct pollfd pollfd;
 
   while (1) {
-    pollfd.fd = fd;
+    pollfd.fd = io->fd;
     pollfd.events = POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR;
     int err = poll(&pollfd, 1, 0);
     if (err <= 0) {
       break;
     } else {
       char discard;
-      read(fd, &discard, 1);
+      read(io->fd, &discard, 1);
     }
   }
 }
 
 static struct moberg_status do_read_config(
-  int fd,
+  struct serial2002_io *io,
   long timeout,
   struct serial2002_config *config)
 {
   struct serial2002_data data = { 0, 0 };
 
-  discard_pending(fd);
+  discard_pending(io);
   memset(config, 0, sizeof(*config));
-  serial2002_poll_channel(fd, 31);
+  serial2002_poll_channel(io, 31, 1);
   while (1) {
-    struct moberg_status result = serial2002_read(fd, timeout, &data);
+    struct moberg_status result = serial2002_read(io, timeout, &data);
     if (! OK(result)) { return result; }
     unsigned int channel = (data.value & 0x0001f);
     unsigned int kind = (data.value & 0x00e0) >> 5;
@@ -311,11 +369,11 @@ static struct moberg_status check_config(struct serial2002_config *c)
 }
 
 struct moberg_status serial2002_read_config(
-  int fd,
+  struct serial2002_io *io,
   long timeout,
   struct serial2002_config *config)
 { 
-  struct moberg_status result = do_read_config(fd, timeout, config);
+  struct moberg_status result = do_read_config(io, timeout, config);
   if (OK(result)) {
     result = check_config(config);
   }
diff --git a/plugins/serial2002/serial2002_lib.h b/plugins/serial2002/serial2002_lib.h
index dc56e3e..7a925a5 100644
--- a/plugins/serial2002/serial2002_lib.h
+++ b/plugins/serial2002/serial2002_lib.h
@@ -49,17 +49,35 @@ struct serial2002_config {
     digital_in[32], digital_out[32];
 };
 
-struct moberg_status serial2002_poll_digital(int fd, int channel);
+struct serial2002_io {
+  int fd;
+  struct buffer {
+    unsigned char data[128];
+    int pos;
+    int count;
+  } read, write;
+};
+
+struct moberg_status serial2002_poll_digital(struct serial2002_io *io,
+                                             int channel,
+                                             int flush);
 
-struct moberg_status serial2002_poll_channel(int fd, int channel);
+struct moberg_status serial2002_poll_channel(struct serial2002_io *io,
+                                             int channel,
+                                             int flush);
 
-struct moberg_status serial2002_read(int fd, long timeout,
+struct moberg_status serial2002_read(struct serial2002_io *io,
+                                     long timeout,
                                      struct serial2002_data *data);
 
-struct moberg_status serial2002_write(int fd, struct serial2002_data data);
+struct moberg_status serial2002_write(struct serial2002_io *io,
+                                      struct serial2002_data data,
+                                      int flush);
 
-struct moberg_status serial2002_read_config(int fd,
+struct moberg_status serial2002_read_config(struct serial2002_io *io,
                                             long timeout,
                                             struct serial2002_config *config);
 
+struct moberg_status serial2002_flush(struct serial2002_io *io);
+
 #endif
-- 
GitLab