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
4 changes: 2 additions & 2 deletions .github/workflows/build-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install pypa/build/pylint
- name: Install pypa/build/pylint/requirements.txt
run: >-
python3 -m
pip install
build
pyserial
pylint
-r src/science_mode_4/requirements.txt
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
python3 -m
pip install
build
-r src/science_mode_4/requirements.txt
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
python3 -m
pip install
build
-r src/science_mode_4/requirements.txt
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
Expand Down
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
// "program": "__main__.py",
"module": "examples.dyscom.example_dyscom_write_csv",
"program": "__main__.py",
// "module": "examples.dyscom.example_dyscom_write_csv",
"justMyCode": false,
// "args": ["COM3"],
"console": "integratedTerminal"
Expand Down
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,31 @@ Python 3.11 or higher
# Library

## Installation

- Install science_mode_4 library inclusive dependencies via pip
- `pip install science_mode_4`
- https://pypi.org/project/science-mode-4/

## Dependencies

- PySerial
- https://pypi.org/project/pyserial/
- `pip install pyserial`
- PyUSB - currently not used
- PyUSB
- https://pypi.org/project/pyusb/
- `pip install pyusb`
- `pip install pyusb`
- On Windows
- Download libusb from https://libusb.info/
- Copy libusb-XX.dll into environment root folder (besides python.exe)
- Under Windows there are driver issues
- Code is currently commented out and not usable
- Install libusb-package to get _libusb-XX.dll_
- https://pypi.org/project/libusb-package/
- `pip install libusb-package`
- Under Windows there may be driver issues
- See https://github.com/libusb/libusb/wiki/Windows#How_to_use_libusb_on_Windows
- Use Zadig to change driver for _STM32 Virtual ComPort_ to _libusb-XX.dll_ and reinstall driver

## Build library
- Only necessary, if you made changes to the library or install a version from a branch
- Install dependencies
- Install build dependencies
- `python -m pip install --upgrade build`
- Install other library dependencies
- `pip install -r src/science_mode_4/requirements.txt`
- Optional run linter
- `pip install pylint`
- `pylint .\src\science_mode_4\`
Expand Down Expand Up @@ -87,6 +89,13 @@ Python 3.11 or higher
- https://pypi.org/project/matplotlib/
- `pip install matplotlib`
- Fastplotlib with glfw backend
- https://pypi.org/project/fastplotlib/
- `pip install -U fastplotlib`
- `pip install -U glfw`

# Changes

## 0.0.11
- Implemented UsbConnection class
- Alternative for SerialPortConnection, both share the same base class Connection
- Added _PyUSB_ and _libusb-package_ as dependencies
92 changes: 83 additions & 9 deletions __main__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,104 @@
"""Test program how to use library without installing the library,
DO NOT USE THIS FILE, USE EXAMPLES INSTEAD"""

from timeit import default_timer as timer

import logging
import sys
import asyncio

from science_mode_4.device_i24 import DeviceI24
from science_mode_4.device_p24 import DeviceP24
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 DyscomFilterType, DyscomGetType, DyscomInitParams, DyscomPowerModulePowerType, DyscomPowerModuleType, DyscomSignalType
from science_mode_4.protocol.commands import Commands
from science_mode_4.protocol.types import ResultAndError
from science_mode_4.utils import logger
from science_mode_4.utils.serial_port_connection import SerialPortConnection
from science_mode_4.utils.usb_connection import UsbConnection



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

devices = SerialPortConnection.list_science_mode_device_ports()
connection = SerialPortConnection(devices[0].device)
# devices = UsbConnection.list_science_mode_devices()
# connection = UsbConnection(devices[0])
logger().disabled = True

logger().setLevel(logging.DEBUG)
# 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 = DeviceP24(connection)
device = DeviceI24(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}")
# get dyscom layer to call dyscom level commands
dyscom = device.get_layer_dyscom()

# call enable measurement power module for measurement
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_2
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
init_params.register_map_ads129x.config_register_1.power_mode = Ads129xPowerMode.HIGH_RESOLUTION
await dyscom.init(init_params)

# start dyscom measurement
await dyscom.start()

start_time = timer()
total_count = 0

# loop for some time
for x in range(1000):
# check operation mode from time to time, this function is not waiting for response
# so we have to handle it by ourself later
if x % 100 == 0:
dyscom.send_get_operation_mode()

live_data_counter = 0
while True:
# process all available packages
ack = dyscom.packet_buffer.get_packet_from_buffer(live_data_counter == 0)
if ack:
# because there are multiple get commands, we need to additionally check kind,
# which is always associated DyscomGetType
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.name}")
# check if measurement is still active
if om_ack.result_error != ResultAndError.NO_ERROR:
break
elif ack.command == Commands.DL_SEND_LIVE_DATA:
live_data_counter += 1
total_count += 1

sld: PacketDyscomSendLiveData = ack
if sld.status_error:
print(f"SendLiveData status error {sld.samples}")
break

else:
# print(f"Live data acknowledges per iteration {live_data_counter}")
break

# await asyncio.sleep(0.01)

# print stats
end_time = timer()
print(f"Samples: {total_count}, duration: {end_time - start_time}, sample rate: {total_count / (end_time - start_time)}")

# stop measurement
await dyscom.stop()
# turn power module off
await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_OFF)

connection.close()

Expand Down
6 changes: 4 additions & 2 deletions 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.10"
version = "0.0.11"
authors = [
{ name="Marc Hofmann", email="marc-hofmann@gmx.de" },
]
Expand All @@ -20,7 +20,9 @@ license-files = [
"LICENSE"
]
dependencies = [
"pyserial >= 3.5",
"pyserial",
"pyusb",
"libusb-package"
]

[project.urls]
Expand Down
1 change: 0 additions & 1 deletion src/science_mode_4/protocol/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ def packet_to_bytes(packet: Packet) -> bytes:
bb.append_byte(Protocol.STOP_BYTE)

logger().debug("Build package, %s", packet)
logger().debug("Outgoing data, %s", bb)
result = bb.get_bytes()
return bytes(result)

Expand Down
3 changes: 3 additions & 0 deletions src/science_mode_4/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pyserial
pyusb
libusb-package
13 changes: 11 additions & 2 deletions src/science_mode_4/utils/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from abc import ABC, abstractmethod

from .logger import logger


class Connection(ABC):
"""Abstract base class for connection"""
Expand All @@ -22,16 +24,23 @@ def is_open(self) -> bool:
"""Checks if connection is open"""


@abstractmethod
def write(self, data: bytes):
"""Write data to connection"""
logger().debug("Outgoing data, length: %d, bytes: %s", len(data), data.hex(" ").upper())


@abstractmethod
def read(self) -> bytes:
"""Read all data from connection"""
result = self._read_intern()
logger().debug("Incoming data, length: %d, bytes: %s", len(result), result.hex(" ").upper())
return result


@abstractmethod
def clear_buffer(self):
"""Clear buffer from connection"""


@abstractmethod
def _read_intern(self):
"""Read all data from connection"""
19 changes: 11 additions & 8 deletions src/science_mode_4/utils/serial_port_connection.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Provides a class for a serial connection"""

import os
import serial
import serial.tools.list_ports
import serial.tools.list_ports_common

from .connection import Connection
from .logger import logger


class SerialPortConnection(Connection):
Expand Down Expand Up @@ -35,7 +35,9 @@ def __init__(self, port: str):

def open(self):
self._ser.open()
self._ser.set_buffer_size(4096*128)

if os.name == "nt":
self._ser.set_buffer_size(4096*128)


def close(self):
Expand All @@ -47,16 +49,17 @@ def is_open(self) -> bool:


def write(self, data: bytes):
super().write(data)
self._ser.write(data)


def read(self) -> bytes:
def clear_buffer(self):
self._ser.reset_input_buffer()


def _read_intern(self) -> bytes:
result = []
if self._ser.in_waiting > 0:
result = self._ser.read_all()
logger().debug("Incoming data, length: %d, bytes: %s", len(result), result.hex(" ").upper())
return bytes(result)


def clear_buffer(self):
self._ser.reset_input_buffer()
return bytes(result)
Loading