Skip to content

Commit 7087237

Browse files
Merge pull request #16 from nextroundwinner/dyscom_example
Dyscom example
2 parents 95a3242 + 7bf6aaa commit 7087237

14 files changed

Lines changed: 254 additions & 104 deletions

.pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ max-attributes=15
44

55
[DESIGN]
66
max-statements=100
7+
max-locals = 20
78

89
[STRING]
910
check-quote-consistency=yes

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"type": "debugpy",
1010
"request": "launch",
1111
// "program": "src/__main__.py",
12-
"module": "examples.low_level.example_low_level",
12+
"module": "examples.dyscom.example_dyscom_send_file",
1313
"justMyCode": false,
1414
// "args": ["COM3"],
1515
"console": "integratedTerminal"

HINTS.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,24 @@ This page describes implementation details.
6565
## Dyscom layer (I24)
6666
- Contains functions for dyscom level
6767
- This mode is used by I24 to measure EMG or BI
68-
- Usage
68+
- Usage for live data
6969
- Call _power_module()_ to power on measurement module
70-
- Call _init()_ with parameter for measurement
70+
- Call _init()_ with parameter for measurement and DyscomInitFlag for live data
7171
- Call _start()_ to start measurement
7272
- Device sends now _DlSendLiveData_ packets with measurement data
7373
- Call _stop()_ to end measurement
7474
- Call _power_module()_ to power off measurement module
75-
- IMPORTANT: all storage related functions are untested
75+
- Usage for measurement data read from memory card
76+
- Call _power_module()_ to power on measurement module and memory card
77+
- Call _init()_ with parameter for measurement and DyscomInitFlag for storage mode
78+
- Result contains measurement id, that is the filename used later
79+
- Call _start()_ to start measurement
80+
- Device sends nothing but stores measurement data on memory card
81+
- Call _stop()_ to end measurement
82+
- Call _power_module()_ to power off measurement module
83+
- Call _get_meas_file_content()_ with filename from _init()_ to get measurement data
84+
- Call _power_module()_ to power off memory card
85+
- IMPORTANT: not all storage related functions are tested
7686

7787
# Platform hints
7888

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ Python 3.11 or higher
7878
- Demonstrate how to use dyscom layer to measure data and plotting values using PyPlot
7979
- `python -m examples.dyscom.example_dyscom_write_csv`
8080
- Demonstrate how to use dyscom layer to measure data and writing measurement data to a .csv-file
81+
- `python -m examples.dyscom.example_dyscom_send_file`
82+
- Demonstrate how to use dyscom layer to save measurement data on memory card and reading it afterwards
8183

8284
## Dependencies for examples
8385
- Install all dependencies
@@ -116,8 +118,11 @@ Python 3.11 or higher
116118
- Improved examples under Linux/MacOS
117119

118120
## 0.0.15
119-
- Clarified readme
121+
- Enhanced readme
120122
- Changed current for ChannelPoint from int to float
121123

122124
## 0.0.16
123-
- Fixed error with PacketLowLevelChannelConfigAck result
125+
- Fixed error with PacketLowLevelChannelConfigAck result
126+
127+
## 0.0.17
128+
- Added sample that demonstrates how to read measurement data files from I24 devices

examples/dyscom/example_dyscom_get.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,8 @@ async def main() -> int:
4848

4949
####
5050
calibration_filename = f"rehaingest_{device_id}.cal"
51-
# get calibration file info
52-
await dyscom.get_file_info(calibration_filename)
53-
# get calibration file -> does not work
54-
# there should be DL_Send_File commands afterwards
55-
await dyscom.get_file_by_name(calibration_filename)
51+
calibration_content = await dyscom.get_file_content(calibration_filename)
52+
print(f"Calibration content length: {len(calibration_content)}")
5653

5754
# close serial port connection
5855
connection.close()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Provides an example how to use dyscom level layer to read stored data from device"""
2+
3+
import asyncio
4+
5+
from science_mode_4 import DeviceI24
6+
from science_mode_4 import SerialPortConnection
7+
from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomInitFlag, DyscomInitParams, DyscomPowerModulePowerType,\
8+
DyscomPowerModuleType, DyscomSignalType
9+
from examples.utils.example_utils import ExampleUtils
10+
11+
12+
async def main() -> int:
13+
"""Main function"""
14+
15+
# get comport from command line argument
16+
com_port = ExampleUtils.get_comport_from_commandline_argument()
17+
# create serial port connection
18+
connection = SerialPortConnection(com_port)
19+
# open connection, now we can read and write data
20+
connection.open()
21+
22+
# create science mode device
23+
device = DeviceI24(connection)
24+
# call initialize to get basic information (serial, versions) and stop any active stimulation/measurement
25+
# to have a defined state
26+
await device.initialize()
27+
28+
# get dyscom layer to call low level commands
29+
dyscom = device.get_layer_dyscom()
30+
31+
# call enable measurement power module and memory card for measurement
32+
await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_ON)
33+
await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_ON)
34+
# call init with 1k sample rate
35+
init_params = DyscomInitParams()
36+
init_params.signal_type = [DyscomSignalType.BI, DyscomSignalType.EMG_1]
37+
init_params.filter = DyscomFilterType.PREDEFINED_FILTER_1
38+
# we want no live data and write all data to memory card
39+
init_params.flags = [DyscomInitFlag.ENABLE_SD_STORAGE_MODE]
40+
init_result = await dyscom.init(init_params)
41+
42+
# start dyscom measurement
43+
await dyscom.start()
44+
45+
# wait 10s to have some measurement data
46+
await asyncio.sleep(10)
47+
48+
# stop measurement
49+
await dyscom.stop()
50+
# turn power module off
51+
await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_OFF)
52+
53+
# get all meas data
54+
measurement = await dyscom.get_meas_file_content(init_result.measurement_file_id)
55+
print(f"Sample rate: {measurement[0].name}")
56+
for key, value in measurement[1].items():
57+
print(f"Signal type: {key.name}, sample count: {len(value)}")
58+
59+
# turn memory card off
60+
await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_OFF)
61+
# close serial port connection
62+
connection.close()
63+
64+
return 0
65+
66+
67+
if __name__ == "__main__":
68+
asyncio.run(main())

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "science_mode_4"
7-
version = "0.0.16"
7+
version = "0.0.17"
88
authors = [
99
{ name="Marc Hofmann", email="marc-hofmann@gmx.de" },
1010
]

src/__main__.py

Lines changed: 39 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,105 +4,62 @@
44
import sys
55
import asyncio
66

7-
from science_mode_4 import DeviceP24
8-
from science_mode_4.low_level.low_level_channel_config import PacketLowLevelChannelConfigAck
9-
from science_mode_4.protocol import ChannelPoint
10-
from science_mode_4.protocol.commands import Commands
11-
from science_mode_4.utils import SerialPortConnection
12-
from science_mode_4.low_level import LayerLowLevel
13-
from science_mode_4.protocol import Connector, Channel
14-
from science_mode_4.low_level import LowLevelHighVoltageSource, LowLevelMode
7+
from science_mode_4.device_i24 import DeviceI24
8+
from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomInitFlag, DyscomInitParams, DyscomPowerModulePowerType,\
9+
DyscomPowerModuleType, DyscomSignalType
10+
from science_mode_4.utils.serial_port_connection import SerialPortConnection
1511

1612

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

20-
# logger().setLevel(logging.DEBUG)
21-
current = 70
22-
23-
# # keyboard is our trigger to start specific stimulation
24-
# def input_callback(input_value: str) -> bool:
25-
# """Callback call from keyboard input thread"""
26-
# print(f"Input value {input_value}")
27-
28-
# nonlocal current
29-
# if input_value == "1":
30-
# send_channel_config(low_level_layer, Connector.GREEN)
31-
# elif input_value == "2":
32-
# send_channel_config(low_level_layer, Connector.YELLOW)
33-
# elif input_value == "+":
34-
# current += 0.5
35-
# print(f"current: {current}")
36-
# elif input_value == "-":
37-
# current -= 0.5
38-
# print(f"current: {current}")
39-
# elif input_value == "q":
40-
# # end keyboard input thread
41-
# return True
42-
# else:
43-
# print("Invalid command")
44-
45-
# return False
46-
47-
48-
def send_channel_config(low_level_layer: LayerLowLevel, connector: Connector):
49-
"""Sends channel update"""
50-
# device can store up to 10 channel config commands
51-
for channel in Channel:
52-
# send_channel_config does not wait for an acknowledge
53-
low_level_layer.send_channel_config(True, channel, connector,
54-
[ChannelPoint(1000, current), ChannelPoint(4000, 0),
55-
ChannelPoint(1000, -current)])
56-
57-
58-
print("Usage:")
59-
print("Press 1 or 2 to stimulate green or yellow connector")
60-
print("Press + or - to increase or decrease current")
61-
print("Press q to quit")
62-
# create keyboard input thread for non blocking console input
63-
# keyboard_input_thread = KeyboardInputThread(input_callback)
64-
65-
# get comport from command line argument
66-
# com_port = ExampleUtils.get_comport_from_commandline_argument()
6716
# create serial port connection
6817
connection = SerialPortConnection(SerialPortConnection.list_science_mode_device_ports()[0].device)
6918
# open connection, now we can read and write data
7019
connection.open()
7120

7221
# create science mode device
73-
device = DeviceP24(connection)
22+
device = DeviceI24(connection)
7423
# call initialize to get basic information (serial, versions) and stop any active stimulation/measurement
7524
# to have a defined state
7625
await device.initialize()
7726

78-
# get low level layer to call low level commands
79-
low_level_layer = device.get_layer_low_level()
80-
81-
# call init low level
82-
await low_level_layer.init(LowLevelMode.NO_MEASUREMENT, LowLevelHighVoltageSource.STANDARD)
83-
84-
# now we can start stimulation
85-
# while keyboard_input_thread.is_alive():
86-
for x in range(500):
87-
if x % 100 == 0:
88-
send_channel_config(low_level_layer, Connector.YELLOW)
89-
# get new packets from connection
90-
ack = low_level_layer.packet_buffer.get_packet_from_buffer()
91-
if ack and ack.command == Commands.LOW_LEVEL_CHANNEL_CONFIG_ACK:
92-
cca: PacketLowLevelChannelConfigAck = ack
93-
# do something with packet ack
94-
# here we print that an acknowledge arrived
95-
print(cca.result.name)
96-
97-
await asyncio.sleep(0.1)
98-
99-
# wait until all acknowledges are received
100-
await asyncio.sleep(0.5)
101-
# call stop low level
102-
await low_level_layer.stop()
103-
27+
# get dyscom layer to call low level commands
28+
dyscom = device.get_layer_dyscom()
29+
30+
# call enable measurement power module and memory card for measurement
31+
await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_ON)
32+
await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_ON)
33+
# call init with lowest sample rate (because of performance issues with plotting values)
34+
init_params = DyscomInitParams()
35+
init_params.signal_type = [DyscomSignalType.BI, DyscomSignalType.EMG_1]
36+
init_params.filter = DyscomFilterType.PREDEFINED_FILTER_1
37+
# we want no live data and write all data to memory card
38+
init_params.flags = [DyscomInitFlag.ENABLE_SD_STORAGE_MODE]
39+
init_result = await dyscom.init(init_params)
40+
41+
# start dyscom measurement
42+
await dyscom.start()
43+
44+
# wait 10s to have some measurement data
45+
await asyncio.sleep(10)
46+
47+
# stop measurement
48+
await dyscom.stop()
49+
# turn power module off
50+
await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_OFF)
51+
52+
# get all meas data
53+
measurement = await dyscom.get_meas_file_content(init_result.measurement_file_id)
54+
print(f"Sample rate: {measurement[0].name}")
55+
for key, value in measurement[1].items():
56+
print(f"Signal type: {key.name}, sample count: {len(value)}")
57+
58+
# turn memory card off
59+
await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_OFF)
10460
# close serial port connection
10561
connection.close()
62+
10663
return 0
10764

10865

src/science_mode_4/device.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ async def initialize(self):
8787
if operation_mode in [DyscomGetOperationModeType.LIVE_MEASURING_PRE,
8888
DyscomGetOperationModeType.LIVE_MEASURING,
8989
DyscomGetOperationModeType.RECORD_PRE,
90-
DyscomGetOperationModeType.RECORD]:
90+
DyscomGetOperationModeType.RECORD,
91+
DyscomGetOperationModeType.DATATRANSFER_PRE,
92+
DyscomGetOperationModeType.DATATRANSFER]:
9193
await self.get_layer_dyscom().stop()
9294

9395
await self.get_layer_general().initialize()

src/science_mode_4/dyscom/dyscom_get_file_by_name.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,38 @@ class PacketDyscomGetFileByName(PacketDyscomGet):
2121
"""Packet for dyscom get with type file by name"""
2222

2323

24-
def __init__(self, filename: str = ""):
24+
def __init__(self, filename: str = "", mode: DyscomFileByNameMode = DyscomFileByNameMode.MULTI_BLOCK):
2525
super().__init__()
2626
self._type = DyscomGetType.FILE_BY_NAME
2727
self._kind = int(self._type)
2828
self._filename = filename
29+
self._mode = mode
30+
31+
32+
@property
33+
def filename(self) -> str:
34+
"""Getter for filename"""
35+
return self._filename
36+
37+
38+
@property
39+
def mode(self) -> DyscomFileByNameMode:
40+
"""Getter for mode"""
41+
return self._mode
2942

3043

3144
def get_data(self) -> bytes:
3245
bb = ByteBuilder()
3346
bb.append_bytes(super().get_data())
3447
bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 128))
48+
# block offset
49+
bb.append_value(0, 4, True)
50+
# file size
51+
bb.append_value(0, 8, True)
52+
# number of blocks
53+
bb.append_value(0, 4, True)
54+
# mode
55+
bb.append_byte(self._mode)
3556
# maybe more parameters are necessary here
3657
# block_offset, file_size, n_blocks, mode
3758
return bb.get_bytes()

0 commit comments

Comments
 (0)