From ec40400cd58dee9c86a17db115bae07fc25ed03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Tue, 11 Mar 2014 17:55:12 +0100 Subject: [PATCH 1/4] single sensor tool --- sensors2osc/sensors2osc/sensorTest.py | 190 ++++++++++++++++++++++++++ sensors2osc/setup.py | 2 +- 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 sensors2osc/sensors2osc/sensorTest.py diff --git a/sensors2osc/sensors2osc/sensorTest.py b/sensors2osc/sensors2osc/sensorTest.py new file mode 100644 index 0000000..2fe385d --- /dev/null +++ b/sensors2osc/sensors2osc/sensorTest.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of chaosc +# +# chaosc 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. +# +# chaosc 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 chaosc. If not, see . +# +# Copyright (C) 2014 Stefan Kögl + +import argparse +import os.path +import select +import serial +import socket +import sys +import datetime + +try: + from chaosc.c_osc_lib import OSCMessage +except ImportError as e: + print(e) + from chaosc.osc_lib import OSCMessage + + +class Forwarder(object): + def __init__(self, actor, platform, device): + self.actor = actor + self.platform = platform + self.device = device + self.serial = serial.Serial() + self.serial.port = device + self.serial.baudrate = 115200 + self.serial.timeout = 0 + self.buf_ser2osc = "" + + self.serial.open() + + def close(self): + """Close all resources and unpublish service""" + print "%s: closing..." % (self.device, ) + self.serial.close() + + +class EHealth2OSC(Forwarder): + def __init__(self, actor, platform, device): + super(EHealth2OSC, self).__init__(actor, platform, device) + + def handle_read(self, osc_sock): + data = self.serial.readline()[:-2] + print repr(data) + try: + airFlow, emg, temp = data.split(";") + except ValueError: + return + try: + airFlow = int(airFlow) + emg = int(emg) + temp = int(temp); + except ValueError: + return + osc_message = OSCMessage("/%s/airFlow" % self.actor) + osc_message.appendTypedArg(airFlow, "i") + osc_sock.sendall(osc_message.encode_osc()) + osc_message = OSCMessage("/%s/emg" % self.actor) + osc_message.appendTypedArg(emg, "i") + osc_sock.sendall(osc_message.encode_osc()) + osc_message = OSCMessage("/%s/temperatur" % self.actor) + osc_message.appendTypedArg(temp, "i") + osc_sock.sendall(osc_message.encode_osc()) + + +class EKG2OSC(Forwarder): + def __init__(self, actor, platform, device): + super(EKG2OSC, self).__init__(actor, platform, device) + + def handle_read(self, osc_sock): + t = ord(self.serial.read(1)) + osc_message = OSCMessage("/%s/ekg" % self.actor) + osc_message.appendTypedArg(t, "i") + osc_sock.sendall(osc_message.encode_osc()) + + +class RingBuffer(object): + def __init__(self, length): + self.length = length + self.ring_buf = [-1 for i in xrange(length)] + self.head = 0 + + def append(self, value): + self.ring_buf[self.head] = value + self.head = (self.head + 1) % self.length + + def getData(self): + print "getData", self.ring_buf, self.head + data = list() + for i in range(7, 1, -1): + value = self.ring_buf[(self.head - i) % self.length] + if value == -1: + raise ValueError("not complete") + data.append(value) + if data[0] != 0x0 or data[1] != 0xff: + raise ValueError("not synced") + return data[2:] + + + +class Pulse2OSC(Forwarder): + def __init__(self, actor, platform, device): + super(Pulse2OSC, self).__init__(actor, platform, device) + self.buf = RingBuffer(6) + self.heartbeat_on = False + + def handle_read(self, osc_sock): + t = ord(self.serial.read(1)) + self.buf.append(t) + + if t == 0: + try: + heart_signal, heart_rate, o2, pulse = self.buf.getData() + + if pulse == 245 and not self.heartbeat_on: + osc_message = OSCMessage("/%s/heartbeat" % self.actor) + osc_message.appendTypedArg(1, "i") + osc_message.appendTypedArg(heart_rate, "i") + osc_message.appendTypedArg(o2, "i") + osc_sock.sendall(osc_message.encode_osc()) + print "heartbeat", datetime.datetime.now(), heart_signal + self.heartbeat_on = True + elif pulse == 1 and self.heartbeat_on: + #print "off heartbeat", datetime.datetime.now(), heart_signal + self.heartbeat_on = False + osc_message = OSCMessage("/%s/heartbeat" % self.actor) + osc_message.appendTypedArg(0, "i") + osc_message.appendTypedArg(heart_rate, "i") + osc_message.appendTypedArg(o2, "i") + osc_sock.sendall(osc_message.encode_osc()) + except ValueError, e: + print e + + +def main(): + parser = argparse.ArgumentParser(prog='psychose_actor') + parser.add_argument("-H", '--chaosc_host', required=True, + type=str, help='host of chaosc instance to control') + parser.add_argument("-p", '--chaosc_port', required=True, + type=int, help='port of chaosc instance to control') + parser.add_argument("-t", '--type', required=True, + type=str, help='ekg, pulse, ehealth') + parser.add_argument("-d", '--device', required=True, + type=str, help='device node under /dev') + parser.add_argument("-a", '--actor', required=True, + type=str, help='actor name') + + + args = parser.parse_args(sys.argv[1:]) + + osc_sock = socket.socket(2, 2, 17) + osc_sock.connect((args.chaosc_host, args.chaosc_port)) + + used_devices = dict() + + actor = args.actor + if args.type == "ehealth": + used_devices[device] = EHealth2OSC(actor, "ehealth", args.device) + elif args.type == "ekg": + used_devices[device] = EKG2OSC(actor, "ekg", args.device) + elif args.type == "pulse": + used_devices[device] = Pulse2OSC(actor, "pulse", args.device) + else: + raise ValueError("unknown description %r for device %r" % (description, device)) + + while 1: + read_map = {} + for forwarder in used_devices.values(): + read_map[forwarder.serial] = forwarder.handle_read + + readers, writers, errors = select.select(read_map, [], [], 0.1) + for reader in readers: + read_map[reader](osc_sock) diff --git a/sensors2osc/setup.py b/sensors2osc/setup.py index 35806a6..b42ad6c 100644 --- a/sensors2osc/setup.py +++ b/sensors2osc/setup.py @@ -4,7 +4,6 @@ from distribute_setup import use_setuptools use_setuptools() -from scripts.version import get_git_version import sys from setuptools import find_packages, setup @@ -33,6 +32,7 @@ setup( entry_points = """ [console_scripts] sensors2osc = sensors2osc.main:main + sensorTest = sensors2osc.sensorTest:main """, # pypi metadata author = "Stefan Kögl", From cb48fa27098ebe509f20dbcc5f5e6480df0d2ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Tue, 11 Mar 2014 18:35:45 +0100 Subject: [PATCH 2/4] ekgplotter --- ekgplotter/ekgplotter/main.py | 147 ++++++++++++++++++++++++++++++++++ ekgplotter/setup.py | 55 +++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 ekgplotter/ekgplotter/main.py create mode 100644 ekgplotter/setup.py diff --git a/ekgplotter/ekgplotter/main.py b/ekgplotter/ekgplotter/main.py new file mode 100644 index 0000000..34043a8 --- /dev/null +++ b/ekgplotter/ekgplotter/main.py @@ -0,0 +1,147 @@ +import pyqtgraph as pg +import select +import socket +import cStringIO +import subprocess +import threading +import time +from PyQt4.QtCore import QBuffer, QByteArray, QIODevice + +from collections import deque + +# -*- coding: utf-8 -*- + +"""This module implements the standalone filtering tool in the chaosc framework. + +It uses the chaosc osc_lib but does not depend on chaosc features, so it can +be used with other osc compatible gear. + +We provide here osc message filtering based on python regex defined in a file +and a very flexible transcoding toolchain, but it's up to your python skills +to master them. The TranscoderBaseHandler subclasses should be defined in the +appropriate python module you place in the config directory. Please refer for +a howto/examples to our comprehensive docs or look into the provided example +transcoding.py file. +""" + +# This file is part of chaosc +# +# chaosc 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. +# +# chaosc 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 chaosc. If not, see . +# +# Copyright (C) 2012-2014 Stefan Kögl + +from __future__ import absolute_import + + +import atexit +import sys, argparse + +from datetime import datetime +from chaosc.simpleOSCServer import SimpleOSCServer +import chaosc._version + + +class ChaoscLogger(SimpleOSCServer): + """OSC filtering/transcoding middleware + """ + + def __init__(self, args): + """ctor for filter server + + starts the server, loads scene filters and transcoders and chooses + the request handler, which is one of + forward only, forward and dump, dump only. + + :param result: return value of argparse.parse_args + :type result: namespace object + """ + + d = datetime.now().strftime("%x %X") + print "%s: starting up chaosc_dump-%s..." % (d, chaosc._version.__version__) + SimpleOSCServer.__init__(self, (args.own_host, args.own_port)) + self.args = args + self.chaosc_address = (args.chaosc_host, args.chaosc_port) + + if args.subscribe: + self.subscribe_me(self.chaosc_address, (args.own_host, args.own_port), + args.token, args.subscriber_label) + + self.plot_data = deque([0] * 100) + self.plt = pg.plot() + + + def dispatchMessage(self, osc_address, typetags, args, packet, + client_address): + """Handles this filtering, transcoding steps and forwards the result + + :param osc_address: the OSC address string. + :type osc_address: str + + :param typetags: the typetags of args + :type typetags: list + + :param args: the osc message args + :type args: list + + :param packet: the binary representation of a osc message + :type packet: str + + :param client_address: (host, port) of the requesting client + :type client_address: tuple + """ + + + if osc_address == "/uwe/ekg": + self.plot_data.appendleft(args[0]) + self.plot_data.pop() + self.plt.plot(self.plot_data, clear=True) + exporter = pg.exporters.ImageExporter.ImageExporter(self.plt.plotItem) + exporter.parameters()['width'] = 1024 + name = '/tmp/plotImage.jpg' + exporter.export(name) + + + + def unsubscribe(self): + self.unsubscribe_me(self.chaosc_address, (self.args.own_host, self.args.own_port), + self.args.token) + + + +def main(): + parser = argparse.ArgumentParser(prog='ekgplotter') + main_args_group = parser.add_argument_group('main flags', 'flags for chaosc_transcoder') + chaosc_args_group = parser.add_argument_group('chaosc', 'flags relevant for interacting with chaosc') + + main_args_group.add_argument('-o', "--own_host", required=True, + type=str, help='my host') + main_args_group.add_argument('-p', "--own_port", required=True, + type=int, help='my port') + + chaosc_args_group.add_argument('-s', '--subscribe', action="store_true", + help='if True, this transcoder subscribes itself to chaosc. If you use this, you need to provide more flags in this group') + chaosc_args_group.add_argument('-S', '--subscriber_label', type=str, default="chaosc_transcoder", + help='the string to use for subscription label, default="chaosc_transcoder"') + chaosc_args_group.add_argument('-t', '--token', type=str, default="sekret", + help='token to authorize subscription command, default="sekret"') + chaosc_args_group.add_argument("-H", '--chaosc_host', + type=str, help='host of chaosc instance') + chaosc_args_group.add_argument("-P", '--chaosc_port', + type=int, help='port of chaosc instance') + + server = ChaoscLogger(parser.parse_args(sys.argv[1:])) + + atexit.register(server.unsubscribe) + server.serve_forever() + diff --git a/ekgplotter/setup.py b/ekgplotter/setup.py new file mode 100644 index 0000000..2a6892a --- /dev/null +++ b/ekgplotter/setup.py @@ -0,0 +1,55 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from distribute_setup import use_setuptools +use_setuptools() + +import sys +from setuptools import find_packages, setup + +if sys.version_info >= (3,): + extras['use_2to3'] = True + +setup( + name='ekgplotter', + version="0.1", + packages=find_packages(exclude=["scripts",]), + + include_package_data = True, + + package_data = { + "chaosc" : ["config/*",]}, + + exclude_package_data = {'': ['.gitignore']}, + + install_requires=[], + + # installing unzipped + zip_safe = False, + + # predefined extension points, e.g. for plugins + entry_points = """ + [console_scripts] + ekgplot = ekgplotter.main:main + """, + # pypi metadata + author = "Stefan Kögl", + + # FIXME: add author email + author_email = "", + description = "osc filtering application level gateway", + + # FIXME: add long_description + long_description = """ + """, + + # FIXME: add license + license = "LGPL", + + # FIXME: add keywords + keywords = "", + + # FIXME: add download url + url = "", + test_suite='tests' +) From 952f1967a795ba07398b441ebf9a9f63bddbebd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Tue, 11 Mar 2014 18:39:17 +0100 Subject: [PATCH 3/4] ekgplotter 2 --- ekgplotter/ekgplotter/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ekgplotter/ekgplotter/__init__.py diff --git a/ekgplotter/ekgplotter/__init__.py b/ekgplotter/ekgplotter/__init__.py new file mode 100644 index 0000000..e69de29 From 292175d936f5117d442f245697c96248819021e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Tue, 11 Mar 2014 18:41:08 +0100 Subject: [PATCH 4/4] ekgplotter 2 --- ekgplotter/ekgplotter/main.py | 10 +++++++--- ekgplotter/setup.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ekgplotter/ekgplotter/main.py b/ekgplotter/ekgplotter/main.py index 34043a8..247e02a 100644 --- a/ekgplotter/ekgplotter/main.py +++ b/ekgplotter/ekgplotter/main.py @@ -1,3 +1,10 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + + +from __future__ import absolute_import + + import pyqtgraph as pg import select import socket @@ -9,7 +16,6 @@ from PyQt4.QtCore import QBuffer, QByteArray, QIODevice from collections import deque -# -*- coding: utf-8 -*- """This module implements the standalone filtering tool in the chaosc framework. @@ -41,8 +47,6 @@ transcoding.py file. # # Copyright (C) 2012-2014 Stefan Kögl -from __future__ import absolute_import - import atexit import sys, argparse diff --git a/ekgplotter/setup.py b/ekgplotter/setup.py index 2a6892a..2d45c49 100644 --- a/ekgplotter/setup.py +++ b/ekgplotter/setup.py @@ -30,7 +30,7 @@ setup( # predefined extension points, e.g. for plugins entry_points = """ [console_scripts] - ekgplot = ekgplotter.main:main + ekgplotter = ekgplotter.main:main """, # pypi metadata author = "Stefan Kögl",