Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "debugpy",
"request": "launch",
// "program": "__main__.py",
"module": "examples.mid_level.example_mid_level",
"module": "examples.dyscom.example_dyscom_write_csv",
"justMyCode": false,
// "args": ["COM3"],
"console": "integratedTerminal"
Expand Down
13 changes: 12 additions & 1 deletion HINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ This page describes implementation details.
- The acknowledge needs to handled manually by using _PacketBuffer_ object from device
- _PacketBuffer_ reads data from connection and separates packets from data stream

## Logging
- Library creates a custom logger, see class _Logger_
- By default some information is logged to console
- Set log level to DEBUG to get more detailed information
- For more performance, disable logger

## General layer
- Contains functions to get common information like device serial or firmware version

Expand Down Expand Up @@ -57,6 +63,7 @@ This page describes implementation details.
- Device sends now _DlSendLiveData_ packets with measurement data
- Call _stop()_ to end measurement
- Call _power_module()_ to power off measurement module
- Important: all storage related functions are untested

# Deviation from Instruction for Use

Expand All @@ -66,8 +73,12 @@ This page describes implementation details.
- Strings are 1 byte less long (null termination is not an extra byte) in acknowledge packets
- Datetime parameters have a different order

### DL_init
- Init state seems always be UNUSED
- Output data rate depends on init params filter property

### DL_get_ack for type file by name
- Addition parameter mode (1 byte)
- Additional parameter mode (1 byte)
- Undefined = 0
- Multiblock = 1
- Singleblock = 2
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Python 3.11 or higher
- Located in folder `examples`
- Run examples with `python -m examples.<layer>.<example>`
- Example: `python -m examples.dyscom.example_dyscom_fastplotlib`
- All examples try to find the serial port that a science mode device is connected to automatically
- If that fails, provide serial port name as parameter, e.g. `python -m examples.<layer>.<example> COM3`
- Examples have own dependencies, see [Dependencies for examples](#dependencies-for-examples)
- General layer
- `example_general.py`
Expand Down
130 changes: 9 additions & 121 deletions __main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,139 +4,27 @@
import sys
import asyncio

import matplotlib.pyplot as plt
import numpy as np
from science_mode_4.device_p24 import DeviceP24
from science_mode_4.utils.serial_port_connection import SerialPortConnection

from src.science_mode_4 import LayerDyscom, LayerLowLevel,\
Commands, Connector, Channel, ChannelPoint,\
SerialPortConnection,\
DeviceI24,\
Ads129xOutputDataRate, Ads129xPowerMode,\
PacketDyscomGetAckOperationMode, PacketDyscomSendLiveData,\
DyscomInitParams, DyscomPowerModulePowerType, DyscomPowerModuleType, DyscomSignalType



# print(science_mode_4.__version__)

def send_channel_config(low_level_layer: LayerLowLevel, connector: Connector):
"""Sends channel update"""
# device can store up to 10 channel config commands
for channel in Channel:
# send_channel_config does not wait for an acknowledge
low_level_layer.send_channel_config(True, channel, connector,
[ChannelPoint(4000, 20), ChannelPoint(4000, -20),
ChannelPoint(4000, 0)])

async def main() -> int:
"""Main function"""

connection = SerialPortConnection("COM6")
devices = SerialPortConnection.list_science_mode_device_ports()
connection = SerialPortConnection(devices[0].device)
# devices = UsbConnection.list_science_mode_devices()
# connection = UsbConnection(devices[0])
# connection = NullConnection()
connection.open()

device = DeviceI24(connection)
device = DeviceP24(connection)
await device.initialize()
# general = device.get_layer_general()
# print(f"device id: {general.device_id}")
# print(f"firmware version: {general.firmware_version}")
# print(f"science mode version: {general.science_mode_version}")

dyscom: LayerDyscom = device.get_layer_dyscom()
# fss: DyscomGetFileSystemStatusResult = await dyscom.get_file_system_status()
# print(f"Ready {fss.file_system_ready}, used size {fss.used_size}, free size {fss.free_size}")
# fbn: DyscomGetFileByNameResult = await dyscom.get_file_by_name()
# print(f"Filename {fbn.filename}, block offset {fbn.block_offset}, filesize {fbn.filesize}, nr of blocks {fbn.number_of_blocks}")
# fv: str = await dyscom.get_firmware_version()
# print(f"Firmware version {fv}")
# nrof = await dyscom.get_list_of_measurement_meta_info()
# print(f"Number of measurement meta info {nrof}")
# did = await dyscom.get_device_id()
# print(f"Device ID {did}")
# fi = await dyscom.get_file_info()
# print(f"File info {fi.filename} {fi.filesize} {fi.checksum}")
# b = await dyscom.get_battery()
# print(f"Battery {b.voltage} {b.current} {b.percentage} {b.temperature} {b.energy_state}")

# sys_ack: DyscomSysResult = await dyscom.sys(DyscomSysType.DEVICE_STORAGE)
# print(f"Sys {sys_ack.sys_type} {sys_ack.state} {sys_ack.filename}")

await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_ON)
init_params = DyscomInitParams()
init_params.register_map_ads129x.config_register_1.output_data_rate = Ads129xOutputDataRate.HR_MODE_500_SPS__LP_MODE_250_SPS
init_params.register_map_ads129x.config_register_1.power_mode = Ads129xPowerMode.LOW_POWER
await dyscom.init(init_params)

fig, ax = plt.subplots()
ax.set(xlabel="Sample Time (µs)", ylabel="Current (mA)",
title="Current measurement")
ax.grid()
plt.ion()
plt.show()

def update_ylim(data: list[float]):
if len(data) == 0:
return

new_min = data[0]
new_max = data[0]
for x in data:
new_min = min(new_min, x)
new_max = max(new_max, x)

offset = (new_max - new_min) * 0.1
plt.ylim(new_min - offset, new_max + offset)

plot_buffer: list[float] = []
plot_data, = ax.plot(np.linspace(0, 100, len(plot_buffer)), plot_buffer)
update_ylim(plot_buffer)
used_signals: set[DyscomSignalType] = set()
await dyscom.start()

for x in range(1000):
if x % 100 == 0:
dyscom.send_get_operation_mode()

while True:
ack = dyscom.packet_buffer.get_packet_from_buffer()
if ack:
if ack.command == Commands.DlGetAck:
om_ack: PacketDyscomGetAckOperationMode = ack
print(f"Operation mode {om_ack.operation_mode}")
elif ack.command == Commands.DlSendLiveData:
sld: PacketDyscomSendLiveData = ack
if sld.status_error:
print(f"SendLiveData status error {sld.samples}")
break
if sld.number % 50 == 0:
# print(f"Append {sld.value} {sld.signal_type}")
for s in sld.samples:
used_signals.add(s.signal_type)
if len(plot_buffer) > 250:
plot_buffer.pop(0)
plot_buffer.append(sld.time_offset) # samples[1].value
plot_data.remove()
plot_data, = ax.plot(np.linspace(0, len(plot_buffer), len(plot_buffer)), plot_buffer, color = "b")
# plot_data.set_xdata(np.linspace(0, 100, len(plot_buffer)))
# plot_data.set_ydata(plot_buffer)
# update_ylim(plot_buffer)
fig.canvas.draw()
fig.canvas.flush_events()

else:
break

await asyncio.sleep(0.01)

# wait until all acknowledges are received
await asyncio.sleep(0.5)

await dyscom.stop()
await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_OFF)

print(used_signals)
general = device.get_layer_general()
print(f"Device id: {general.device_id}")
print(f"Firmware version: {general.firmware_version}")
print(f"Science mode version: {general.science_mode_version}")

connection.close()

Expand Down
9 changes: 3 additions & 6 deletions examples/dyscom/example_dyscom_fastplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ async def device_communication() -> int:
if ack:
# because there are multiple get commands, we need to additionally check kind,
# which is always associated DyscomGetType
if ack.command == Commands.DlGetAck and ack.kind == DyscomGetType.OPERATION_MODE:
if ack.command == Commands.DL_GET_ACK and ack.kind == DyscomGetType.OPERATION_MODE:
om_ack: PacketDyscomGetAckOperationMode = ack
print(f"Operation mode {om_ack.operation_mode}")
print(f"Operation mode {om_ack.operation_mode.name}")
# check if measurement is still active
if om_ack.result_error != ResultAndError.NO_ERROR:
break
elif ack.command == Commands.DlSendLiveData:
elif ack.command == Commands.DL_SEND_LIVE_DATA:
live_data_counter += 1

sld: PacketDyscomSendLiveData = ack
Expand All @@ -111,9 +111,6 @@ async def device_communication() -> int:

await asyncio.sleep(0.01)

# wait until all acknowledges are received
await asyncio.sleep(0.5)

# stop measurement
await dyscom.stop()
# turn power module off
Expand Down
13 changes: 7 additions & 6 deletions examples/dyscom/example_dyscom_pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from science_mode_4.dyscom.dyscom_get_operation_mode import PacketDyscomGetAckOperationMode
from science_mode_4.dyscom.dyscom_send_live_data import PacketDyscomSendLiveData
from science_mode_4.dyscom.dyscom_types import DyscomInitParams, DyscomPowerModulePowerType, DyscomPowerModuleType, DyscomSignalType
from science_mode_4.utils.logger import logger
from examples.utils.example_utils import ExampleUtils
from examples.utils.pyplot_utils import PyPlotHelper

Expand All @@ -18,6 +19,9 @@ async def main() -> int:

plot_helper = PyPlotHelper({0: ["BI", "blue"]}, 250)

# disable logger to increase performance
logger().disabled = True

# get comport from command line argument
com_port = ExampleUtils.get_comport_from_commandline_argument()
# create serial port connection
Expand Down Expand Up @@ -55,10 +59,10 @@ async def main() -> int:
while True:
ack = dyscom.packet_buffer.get_packet_from_buffer(live_data_counter == 0)
if ack:
if ack.command == Commands.DlGetAck:
if ack.command == Commands.DL_GET_ACK:
om_ack: PacketDyscomGetAckOperationMode = ack
print(f"Operation mode {om_ack.operation_mode}")
elif ack.command == Commands.DlSendLiveData:
print(f"Operation mode {om_ack.operation_mode.name}")
elif ack.command == Commands.DL_SEND_LIVE_DATA:
live_data_counter += 1

sld: PacketDyscomSendLiveData = ack
Expand All @@ -77,9 +81,6 @@ async def main() -> int:

await asyncio.sleep(0.01)

# wait until all acknowledges are received
await asyncio.sleep(0.5)

# stop measurement
await dyscom.stop()
# turn power module off
Expand Down
17 changes: 10 additions & 7 deletions examples/dyscom/example_dyscom_write_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
from science_mode_4.dyscom.ads129x.ads129x_config_register_1 import Ads129xOutputDataRate, Ads129xPowerMode
from science_mode_4.dyscom.dyscom_get_operation_mode import PacketDyscomGetAckOperationMode
from science_mode_4.dyscom.dyscom_send_live_data import PacketDyscomSendLiveData
from science_mode_4.dyscom.dyscom_types import DyscomGetType, DyscomInitParams, DyscomPowerModulePowerType,\
from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomGetType, DyscomInitParams, DyscomPowerModulePowerType,\
DyscomPowerModuleType, DyscomSignalType
from science_mode_4.protocol.types import ResultAndError
from science_mode_4.utils.logger import logger
from examples.utils.example_utils import ExampleUtils
from examples.utils.csv_utils import CsvHelper

Expand All @@ -27,6 +28,9 @@ def main():
async def device_communication() -> int:
"""Communication with science mode device"""

# disable logger to increase performance
logger().disabled = True

# get comport from command line argument
com_port = ExampleUtils.get_comport_from_commandline_argument()
# create serial port connection
Expand All @@ -47,6 +51,7 @@ async def device_communication() -> int:
await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_ON)
# call init with 4k sample rate and enable signal types
init_params = DyscomInitParams()
init_params.filter = DyscomFilterType.PREDEFINED_FILTER_3
init_params.signal_type = [DyscomSignalType.BI, DyscomSignalType.EMG_1,\
DyscomSignalType.EMG_2, DyscomSignalType.BREATHING, DyscomSignalType.TEMPERATURE]
init_params.register_map_ads129x.config_register_1.output_data_rate = Ads129xOutputDataRate.HR_MODE_4_KSPS__LP_MODE_2_KSPS
Expand All @@ -73,13 +78,13 @@ async def device_communication() -> int:
if ack:
# because there are multiple get commands, we need to additionally check kind,
# which is always associated DyscomGetType
if ack.command == Commands.DlGetAck and ack.kind == DyscomGetType.OPERATION_MODE:
if ack.command == Commands.DL_GET_ACK and ack.kind == DyscomGetType.OPERATION_MODE:
om_ack: PacketDyscomGetAckOperationMode = ack
print(f"Operation mode {om_ack.operation_mode}")
print(f"Operation mode {om_ack.operation_mode.name}")
# check if measurement is still active
if om_ack.result_error != ResultAndError.NO_ERROR:
break
elif ack.command == Commands.DlSendLiveData:
elif ack.command == Commands.DL_SEND_LIVE_DATA:
live_data_counter += 1
total_count += 1

Expand All @@ -90,7 +95,7 @@ async def device_communication() -> int:

csv_helper.append_values(ack.number, [sld.samples[0].value, sld.samples[1].value,\
sld.samples[2].value, sld.samples[3].value,\
sld.samples[4].value], sld.time_offset)
sld.samples[4].value], sld.time_offset)

else:
# print(f"Live data acknowledges per iteration {live_data_counter}")
Expand All @@ -101,8 +106,6 @@ async def device_communication() -> int:
# print stats
end_time = timer()
print(f"Samples: {total_count}, duration: {end_time - start_time}, sample rate: {total_count / (end_time - start_time)}")
# wait until all acknowledges are received
await asyncio.sleep(0.5)

# stop measurement
await dyscom.stop()
Expand Down
2 changes: 1 addition & 1 deletion examples/low_level/example_low_level_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async def main() -> int:
# get new data from connection
ack = low_level_layer.packet_buffer.get_packet_from_buffer()
if ack:
if ack.command == Commands.LowLevelChannelConfigAck:
if ack.command == Commands.LOW_LEVEL_CHANNEL_CONFIG_ACK:
ll_config_ack: PacketLowLevelChannelConfigAck = ack
measurement_sample_time = ll_config_ack.sampling_time_in_microseconds
measurement_samples.extend(ll_config_ack.measurement_samples)
Expand Down
4 changes: 2 additions & 2 deletions examples/utils/fastplotlib_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, sub_plot, max_value_count: int, color: str):
self._sub_plot = sub_plot

# we use an array with zero as start data
y_data = np.array([0] * max_value_count)
y_data = np.array([0.0] * max_value_count, dtype=np.float32)
self._line = sub_plot.add_line(y_data, name="values", colors=color)

# this queue is used to synchronize data between background and main thread
Expand Down Expand Up @@ -88,7 +88,7 @@ def __init__(self, channels: dict[int, tuple[str, str]], max_value_count: int):

sub_plot_counter += 1

# set animation function that is called regulary to update plots
# set animation function that is called regularly to update plots
self._figure.add_animations(self._animation)
# show figure
self._figure.show(maintain_aspect=False)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "science_mode_4"
version = "0.0.9"
version = "0.0.10"
authors = [
{ name="Marc Hofmann", email="marc-hofmann@gmx.de" },
]
Expand Down
1 change: 1 addition & 0 deletions src/science_mode_4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
from .device_i24 import *

__version__ = version("science_mode_4")
logger().info("Library version %s", __version__)
2 changes: 1 addition & 1 deletion src/science_mode_4/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def capabilities(self) -> set[DeviceCapability]:

async def initialize(self):
"""Initialize device to get basic information (serial, versions) and stop any active stimulation/measurement"""
if [DeviceCapability.LOW_LEVEL, DeviceCapability.MID_LEVEL] in self._capabilities:
if {DeviceCapability.LOW_LEVEL, DeviceCapability.MID_LEVEL}.issubset(self._capabilities):
# get stim status to see if low/mid level is initialized or running
stim_status = await self.get_layer_general().get_stim_status()
if stim_status.stim_status == StimStatus.LOW_LEVEL_INITIALIZED:
Expand Down
Loading