Skip to content

Commit

Permalink
Add transfer functions for common sensors
Browse files Browse the repository at this point in the history
  • Loading branch information
mesca committed Oct 3, 2021
1 parent afa6413 commit 0d1f709
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 3 deletions.
12 changes: 10 additions & 2 deletions examples/bitalino.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ graphs:
class: Bitalino
params:
port: /dev/tty.BITalino-02-44-DevB
#port: /dev/tty.BITalino-03-87-DevB
rate: 1000
sensors:
A1: ECG
A2: EEG
A3: EDA
- id: pub_bitalino
module: timeflux.nodes.zmq
class: Pub
Expand All @@ -33,13 +36,18 @@ graphs:
class: Sub
params:
topics: [ bitalino, offsets ]
- id: ui
module: timeflux_ui.nodes.ui
class: UI
- id: debug
module: timeflux.nodes.debug
class: Display
edges:
- source: subscribe:bitalino
target: ui:bitalino
- source: subscribe:bitalino
target: debug
rate: 1
rate: 10

# - id: record
# nodes:
Expand Down
Empty file.
29 changes: 29 additions & 0 deletions timeflux_bitalino/helpers/transfer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Transfer functions"""

# Working voltage
VCC = 3.3


def ECG(signal, resolution=10):
"""ECG value in millivolt [-1.5𝑚𝑉, 1.5𝑚𝑉]"""
return (((signal / 2 ** resolution) - 0.5) * VCC) / 1100 * 1000


def EMG(signal, resolution=10):
"""EMG value in millivolt [-1.64𝑚𝑉, 1.64𝑚𝑉]"""
return (((signal / 2 ** resolution) - 0.5) * VCC) / 1009 * 1000


def EDA(signal, resolution=10):
"""EDA value in microsiemens [0𝜇𝑆, 25𝜇𝑆]"""
return ((signal / 2 ** resolution) * VCC) / 0.132


def EEG(signal, resolution=10):
"""EEG value in microvolts [-39.49𝜇𝑉, 39.49𝜇𝑉]"""
return (((signal / 2 ** resolution) - 0.5) * VCC) / 41782 * 1e6


def PZT(signal, resolution=10):
"""EEG value in percents [-50%, 50%]"""
return ((signal / 2 ** resolution) - 0.5) * 100
52 changes: 51 additions & 1 deletion timeflux_bitalino/nodes/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
from bitalino import BITalino, ExceptionCode
from timeflux.core.exceptions import WorkerInterrupt
from timeflux.core.node import Node
import timeflux_bitalino.helpers.transfer as transfer


# Available transfer functions
TRANSFER = [f for f in dir(transfer) if not f.startswith("_")]


class Bitalino(Node):
Expand Down Expand Up @@ -32,6 +37,9 @@ class Bitalino(Node):
Possible values: ``1``, ``10``, ``100``, ``1000``. Default: ``1000``.
channels (tupple): The analog channels to read from.
Default: ``('A1', 'A2', 'A3', 'A4', 'A5', 'A6')``.
sensors (dict): The map of attached sensors. If set, transfer functions will be applied.
e.g. ``{"A1": "ECG", "A3": "EMG"}``.
Default: ``None``.
Example:
.. literalinclude:: /../examples/bitalino.yaml
Expand All @@ -47,7 +55,13 @@ class Bitalino(Node):
"""

def __init__(self, port, rate=1000, channels=("A1", "A2", "A3", "A4", "A5", "A6")):
def __init__(
self,
port,
rate=1000,
channels=("A1", "A2", "A3", "A4", "A5", "A6"),
sensors=None,
):

# Check port
if not port.startswith("/dev/") and not port.startswith("COM"):
Expand All @@ -58,6 +72,8 @@ def __init__(self, port, rate=1000, channels=("A1", "A2", "A3", "A4", "A5", "A6"
raise ValueError(f"Invalid rate: {rate}")

# Check channels
if sensors:
channels += tuple(sensors.keys())
unique_channels = set(channels)
analog_channels = ["A1", "A2", "A3", "A4", "A5", "A6"]
channels = []
Expand All @@ -72,6 +88,24 @@ def __init__(self, port, rate=1000, channels=("A1", "A2", "A3", "A4", "A5", "A6"
for channel in channels:
self.columns.append(analog_channels[channel])

# Map channels to transfer functions
self.functions = {}
for channel, sensor in sensors.items():
sensor = sensor.upper()
if channel in self.columns:
if sensor in TRANSFER:
resolution = None
if channel in ("A1", "A2", "A3", "A4"):
resolution = 10
if channel in ("A5", "A6"):
resolution = 6
column = f"{channel}_{sensor}"
self.columns.append(column)
self.functions[self.columns.index(channel)] = {
"function": sensor,
"resolution": resolution,
}

# Compute the sample size in bytes
self.channel_count = len(channels)
if self.channel_count <= 4:
Expand Down Expand Up @@ -117,9 +151,14 @@ def __init__(self, port, rate=1000, channels=("A1", "A2", "A3", "A4", "A5", "A6"
self.meta = {"rate": rate}

def update(self):

# Send BITalino data
data, timestamps = self._read_all()
if self.functions:
converted = self._transfer(data[:, list(self.functions.keys())])
data = np.concatenate((data, converted), axis=1)
self.o.set(data, timestamps, self.columns, self.meta)

# Send time offsets
if len(timestamps) > 0:
offset = (self.time_local - self.time_device).astype(int)
Expand Down Expand Up @@ -228,6 +267,17 @@ def _read_all(self):

return data, timestamps

def _transfer(self, data):

"""Convert signal to meaningful units"""

for index, converter in enumerate(self.functions.values()):
data[:, index] = getattr(transfer, converter["function"])(
data[:, index], converter["resolution"]
)

return data

def terminate(self):
self.device.stop()
self.device.close()

0 comments on commit 0d1f709

Please sign in to comment.