From 5c9b91de6a3b4312bcc4a1328e0dfbe29c17051a Mon Sep 17 00:00:00 2001
From: Philipp Klaus <klaus@physik.uni-frankfurt.de>
Date: Wed, 31 May 2017 17:36:11 +0200
Subject: [PATCH] pt100 board: python package for users added

---
 .../pt100_board/__init__.py                   | 31 ++++++
 .../pt100_board/pt100_csv_reader.py           | 73 ++++++++++++++
 .../pt100_board/pt100_logfile_reader.py       | 97 +++++++++++++++++++
 .../pt100_board/pt100_reader.py               | 81 ++++++++++++++++
 sensors/pt100-board-python-package/setup.py   | 21 ++++
 5 files changed, 303 insertions(+)
 create mode 100755 sensors/pt100-board-python-package/pt100_board/__init__.py
 create mode 100755 sensors/pt100-board-python-package/pt100_board/pt100_csv_reader.py
 create mode 100755 sensors/pt100-board-python-package/pt100_board/pt100_logfile_reader.py
 create mode 100755 sensors/pt100-board-python-package/pt100_board/pt100_reader.py
 create mode 100644 sensors/pt100-board-python-package/setup.py

diff --git a/sensors/pt100-board-python-package/pt100_board/__init__.py b/sensors/pt100-board-python-package/pt100_board/__init__.py
new file mode 100755
index 0000000..5c2d473
--- /dev/null
+++ b/sensors/pt100-board-python-package/pt100_board/__init__.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+class PT100BoardDatagram(object):
+
+    SENDERS = {b'A': 'Answer', b'W': 'Write'}
+    KINDS = {b'T': 'temperature', b'E': 'EEPROM', b'R': 'reload configuration', b'I': 'board information'}
+    TERMINATOR = b'\n'
+
+    def __init__(self, data):
+        """
+        data: bytes
+        """
+        assert len(data) == 11
+        assert data[-1:] == self.TERMINATOR
+        self.data = data
+
+        self.sender = self.SENDERS[data[0:1]]
+        self.kind = self.KINDS[data[1:2]]
+
+        self.board = int(data[2:3], 16)
+        self.sensor = int(data[3:4], 16)
+
+        if self.kind == 'temperature':
+            self.temperature = None
+            self.connected = int(data[4:5], 16) == 0
+            if self.connected:
+                value = int(data[5:10], 16)
+                if value & 0x80000: value -= 0x100000
+                value /= 1000.
+                self.temperature = value
+
diff --git a/sensors/pt100-board-python-package/pt100_board/pt100_csv_reader.py b/sensors/pt100-board-python-package/pt100_board/pt100_csv_reader.py
new file mode 100755
index 0000000..9c0f3bc
--- /dev/null
+++ b/sensors/pt100-board-python-package/pt100_board/pt100_csv_reader.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+import argparse
+import sys
+import time
+from datetime import datetime as dt
+
+from pt100_board import PT100BoardDatagram
+
+try:
+    import serial
+except ImportError:
+    sys.stderr.write("Could not import the PySerial module. Please install it first.\n")
+    sys.exit(1)
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('device', help='The serial port to open')
+    parser.add_argument('--timestamp', choices=('none', 'relative', 'absolute'), default='relative', help='Type of timestamps')
+    parser.add_argument('--headline', action='store_true', help='Output column headers in first line of output')
+    args = parser.parse_args()
+
+    ser = serial.Serial(args.device, baudrate=38400, timeout=0.1)
+
+    if args.headline:
+        if args.timestamp == 'relative':
+            headers = ['reltime']
+        elif args.timestamp == 'absolute':
+            headers = ['abstime']
+        else:
+            headers = []
+        headers += ['boardid']
+        headers += ['sensor%d' % i for i in range(8)]
+        print(' '.join(headers))
+
+    start = time.time()
+    sensor_values = {}
+    try:
+        while True:
+            line = ser.readline()
+            if not line: continue
+            line = line.strip(b'\x00')
+            try:
+                dg = PT100BoardDatagram(line)
+            except:
+                sys.stderr.write("Could not interpret this line: {}\n".format(repr(line)))
+                continue
+            if dg.kind == 'temperature':
+                if dg.connected:
+                    sensor_values['{}.{}'.format(dg.board, dg.sensor)] = dg.temperature
+                else:
+                    sensor_values['{}.{}'.format(dg.board, dg.sensor)] = float('nan')
+                if dg.sensor == 0x7:
+                    try:
+                        data = []
+                        if args.timestamp == 'relative':
+                            data += ['%.3f' % (time.time() - start)]
+                        if args.timestamp == 'absolute':
+                            data += [dt.now().isoformat()]
+                        data += [str(dg.board)]
+                        data += ['%.3f' % sensor_values['{}.{}'.format(dg.board, sensor)] for sensor in range(8)]
+                        sys.stdout.write(' '.join(data) + '\n')
+                        sys.stdout.flush()
+                    except KeyError:
+                        sys.stderr.write('not enough values yet for board %d.\n' % dg.board)
+            else:
+                continue
+
+    except KeyboardInterrupt:
+        sys.stderr.write("Ctrl-C pressed, exiting...\n")
+        sys.exit(1)
+
+if __name__ == "__main__": main()
diff --git a/sensors/pt100-board-python-package/pt100_board/pt100_logfile_reader.py b/sensors/pt100-board-python-package/pt100_board/pt100_logfile_reader.py
new file mode 100755
index 0000000..fc90161
--- /dev/null
+++ b/sensors/pt100-board-python-package/pt100_board/pt100_logfile_reader.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+
+import argparse
+import sys
+import pandas as pd
+from IPython import embed
+from datetime import datetime as dt, timedelta
+from matplotlib import pyplot as plt
+
+from pt100_board import PT100BoardDatagram
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--config', help='A config file (with names for the sensors)')
+parser.add_argument('logfile', help='The serial port to open')
+args = parser.parse_args()
+
+config = {}
+if args.config:
+    with open(args.config, 'r') as configfile:
+        for line in configfile:
+            if line.startswith('#'): continue
+            line = line.rstrip('\n')
+            if not line: continue
+            parts = line.split()
+            if len(parts) == 4: parts = parts + ['']
+            if len(parts) != 5: continue
+            board, sensor, y, x, name = parts
+            board, sensor, y, x = (int(val) for val in (board, sensor, y, x))
+            sensor_id = '{}.{}'.format(board, sensor)
+            if not name: name = sensor_id
+            config[sensor_id] = dict(name=name, x=x, y=y)
+
+timestamp = None
+#last_time = 0
+#log_n_plot_interval = 1.0
+last_time = dt(1900,1,1)
+log_n_plot_interval = timedelta(seconds=1)
+data = {'timestamps': [], 'sensor_values': {}}
+sensor_values = {}
+with open(args.logfile, 'rb') as logfile:
+    for line in logfile:
+        line = line.strip(b'\x00')
+        if not line: continue
+        if line.startswith(b'#'):
+            try:
+                #timestamp = int(line[1:])
+                timestamp = dt.utcfromtimestamp(int(line[1:]))
+            except:
+                 sys.stderr.write('could not read this timestamp: {}\n'.format(line))
+            continue
+        try:
+            dg = PT100BoardDatagram(line)
+        except:
+            sys.stderr.write("Could not interpret this line: {}\n".format(repr(line)))
+            continue
+        if dg.kind == 'temperature':
+            if dg.connected:
+                sensor_id = '{}.{}'.format(dg.board, dg.sensor)
+                if config: sensor_name = config[sensor_id]['name']
+                sensor_values[sensor_name] = dg.temperature
+            else:
+                continue
+        else:
+            continue
+
+        if timestamp and timestamp >= last_time + log_n_plot_interval:
+            data['timestamps'].append(timestamp)
+            for sensor in sensor_values:
+                if sensor not in data['sensor_values']:
+                    data['sensor_values'][sensor] = [float('nan')] * (len(data['timestamps']) - 1)
+                data['sensor_values'][sensor].append(sensor_values[sensor])
+            for sensor in data['sensor_values']:
+                if sensor not in sensor_values:
+                    data['sensor_values'][sensor].append(float('nan'))
+            sensor_values = {}
+            last_time = timestamp
+
+df = pd.DataFrame(data['sensor_values'])
+df.index = data['timestamps'] 
+
+print("""
+df.plot()
+plt.show()
+
+# removing spikes with 1st derivative
+threshold = 0.3
+for col in df.columns:
+    df[col][df[col].diff().abs()>threshold] = float('nan')
+
+# removing spikes with 2nd derivative
+threshold = .7
+for col in df.columns:
+    df[col][df[col].diff().diff().abs()>threshold] = float('nan')
+""")
+
+embed()
+
diff --git a/sensors/pt100-board-python-package/pt100_board/pt100_reader.py b/sensors/pt100-board-python-package/pt100_board/pt100_reader.py
new file mode 100755
index 0000000..c6533f2
--- /dev/null
+++ b/sensors/pt100-board-python-package/pt100_board/pt100_reader.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+import argparse
+import sys
+import time
+from datetime import datetime as dt
+
+from pt100_board import PT100BoardDatagram
+
+try:
+    import serial
+except ImportError:
+    sys.stderr.write("Could not import the PySerial module. Please install it first.\n")
+    sys.exit(1)
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--plot', action='store_true', help='Real time plot of the temperatures')
+    parser.add_argument('device', help='The serial port to open')
+    args = parser.parse_args()
+
+    if args.plot:
+        import matplotlib
+        matplotlib.use('TKAgg')
+        #matplotlib.use('GTKAgg')
+        from matplotlib import pyplot as plt
+        import random
+
+    ser = serial.Serial(args.device, baudrate=38400, timeout=0.1)
+
+    log_n_plot_interval = 2.
+    last_time = time.time()
+    data = {'timestamps': [], 'sensor_values': {}}
+    sensor_values = {}
+    try:
+        while True:
+            line = ser.readline()
+            if not line: continue
+            line = line.strip(b'\x00')
+            try:
+                dg = PT100BoardDatagram(line)
+            except:
+                sys.stderr.write("Could not interpret this line: {}\n".format(repr(line)))
+                continue
+            if dg.kind == 'temperature':
+                if dg.connected:
+                    line = '{}.{} {}\n'.format(dg.board, dg.sensor, dg.temperature)
+                    sensor_values['{}.{}'.format(dg.board, dg.sensor)] = dg.temperature
+                else:
+                    continue
+            else:
+                continue
+            sys.stdout.write(line)
+            sys.stdout.flush()
+
+            now = time.time()
+            if now >= last_time + log_n_plot_interval:
+                last_time += log_n_plot_interval
+                data['timestamps'].append(dt.now().replace(microsecond=0))
+                for sensor in sensor_values:
+                    if sensor not in data['sensor_values']:
+                        data['sensor_values'][sensor] = [float('nan')] * (len(data['timestamps']) - 1)
+                    data['sensor_values'][sensor].append(sensor_values[sensor])
+                for sensor in data['sensor_values']:
+                    if sensor not in sensor_values:
+                        data['sensor_values'][sensor].append(float('nan'))
+                sensor_values = {}
+
+                if args.plot:
+                    plt.clf()
+                    for sensor in data['sensor_values']:
+                        plt.plot(data['timestamps'], data['sensor_values'][sensor], label=sensor)
+                    plt.legend()
+                    plt.draw()
+                    plt.pause(0.0001)
+
+    except KeyboardInterrupt:
+        sys.stderr.write("Ctrl-C pressed, exiting...\n")
+        sys.exit(1)
+
+if __name__ == "__main__": main()
diff --git a/sensors/pt100-board-python-package/setup.py b/sensors/pt100-board-python-package/setup.py
new file mode 100644
index 0000000..200604c
--- /dev/null
+++ b/sensors/pt100-board-python-package/setup.py
@@ -0,0 +1,21 @@
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
+setup(name='pt100_board',
+      version='0.2',
+      description='Utilities to work with the Pt100 Reader Board',
+      url='',
+      author='Philipp Klaus',
+      author_email='klaus@physik.uni-frankfurt.de',
+      packages=['pt100_board'],
+      install_requires=['PySerial'],
+      entry_points = {
+        'console_scripts': [
+          'pt100_reader         = pt100_board.pt100_reader:main',
+          'pt100_csv_reader     = pt100_board.pt100_csv_reader:main',
+          'pt100_logfile_reader = pt100_board.pt100_logfile_reader:main',
+        ],
+      },
+      zip_safe=True)
-- 
2.43.0