From b6dfc58810f5262536b84f172f2b1a2ca594671b Mon Sep 17 00:00:00 2001 From: Marc Hofmann Date: Mon, 9 Jun 2025 21:27:46 +0200 Subject: [PATCH 1/3] dyscom fixes --- .vscode/launch.json | 4 +- src/__main__.py | 157 +++++++++--------- src/science_mode_4/device.py | 4 +- .../dyscom/dyscom_get_file_by_name.py | 23 ++- src/science_mode_4/dyscom/dyscom_layer.py | 52 +++++- src/science_mode_4/dyscom/dyscom_send_file.py | 4 +- .../dyscom_send_measurement_meta_info.py | 2 +- src/science_mode_4/utils/packet_buffer.py | 2 +- 8 files changed, 156 insertions(+), 92 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e29e90b..9395a71 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,8 @@ "name": "Python Debugger: Current File", "type": "debugpy", "request": "launch", - // "program": "src/__main__.py", - "module": "examples.low_level.example_low_level", + "program": "src/__main__.py", + // "module": "examples.dyscom.example_dyscom_get", "justMyCode": false, // "args": ["COM3"], "console": "integratedTerminal" diff --git a/src/__main__.py b/src/__main__.py index 7f360d5..ec318ac 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -4,103 +4,98 @@ import sys import asyncio -from science_mode_4 import DeviceP24 -from science_mode_4.low_level.low_level_channel_config import PacketLowLevelChannelConfigAck -from science_mode_4.protocol import ChannelPoint +from science_mode_4.device_i24 import DeviceI24 +from science_mode_4.dyscom.dyscom_get_operation_mode import PacketDyscomGetAckOperationMode +from science_mode_4.dyscom.dyscom_send_file import PacketDyscomSendFile +from science_mode_4.dyscom.dyscom_types import DyscomGetType, DyscomPowerModulePowerType, DyscomPowerModuleType from science_mode_4.protocol.commands import Commands -from science_mode_4.utils import SerialPortConnection -from science_mode_4.low_level import LayerLowLevel -from science_mode_4.protocol import Connector, Channel -from science_mode_4.low_level import LowLevelHighVoltageSource, LowLevelMode +from science_mode_4.utils.serial_port_connection import SerialPortConnection async def main() -> int: """Main function""" - # logger().setLevel(logging.DEBUG) - current = 70 - - # # keyboard is our trigger to start specific stimulation - # def input_callback(input_value: str) -> bool: - # """Callback call from keyboard input thread""" - # print(f"Input value {input_value}") - - # nonlocal current - # if input_value == "1": - # send_channel_config(low_level_layer, Connector.GREEN) - # elif input_value == "2": - # send_channel_config(low_level_layer, Connector.YELLOW) - # elif input_value == "+": - # current += 0.5 - # print(f"current: {current}") - # elif input_value == "-": - # current -= 0.5 - # print(f"current: {current}") - # elif input_value == "q": - # # end keyboard input thread - # return True - # else: - # print("Invalid command") - - # return False - - - 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(1000, current), ChannelPoint(4000, 0), - ChannelPoint(1000, -current)]) - - - print("Usage:") - print("Press 1 or 2 to stimulate green or yellow connector") - print("Press + or - to increase or decrease current") - print("Press q to quit") - # create keyboard input thread for non blocking console input - # keyboard_input_thread = KeyboardInputThread(input_callback) - - # get comport from command line argument - # com_port = ExampleUtils.get_comport_from_commandline_argument() # create serial port connection connection = SerialPortConnection(SerialPortConnection.list_science_mode_device_ports()[0].device) # open connection, now we can read and write data connection.open() # create science mode device - device = DeviceP24(connection) + device = DeviceI24(connection) # call initialize to get basic information (serial, versions) and stop any active stimulation/measurement # to have a defined state await device.initialize() - # get low level layer to call low level commands - low_level_layer = device.get_layer_low_level() - - # call init low level - await low_level_layer.init(LowLevelMode.NO_MEASUREMENT, LowLevelHighVoltageSource.STANDARD) - - # now we can start stimulation - # while keyboard_input_thread.is_alive(): - for x in range(500): - if x % 100 == 0: - send_channel_config(low_level_layer, Connector.YELLOW) - # get new packets from connection - ack = low_level_layer.packet_buffer.get_packet_from_buffer() - if ack and ack.command == Commands.LOW_LEVEL_CHANNEL_CONFIG_ACK: - cca: PacketLowLevelChannelConfigAck = ack - # do something with packet ack - # here we print that an acknowledge arrived - print(cca.result.name) - - await asyncio.sleep(0.1) - - # wait until all acknowledges are received - await asyncio.sleep(0.5) - # call stop low level - await low_level_layer.stop() - + # get general layer to access general commands + general = device.get_layer_general() + # get dyscom layer to access dyscom commands + dyscom = device.get_layer_dyscom() + + pm = await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, + DyscomPowerModulePowerType.SWITCH_ON) + print(pm) + + ##### + # dyscom layer provides a get command for device id, it is redundant to general layer device id + # both values should be identical + device_id = await dyscom.get_device_id() + print(f"Device id from general layer: {general.device_id}, from dyscom layer: {device_id}") + + ##### + # dyscom layer provides a get command for firmware version, it is redundant to general layer firmware verion + # both values should be identical + firmware_version = await dyscom.get_firmware_version() + print(f"Firmware version from general layer: {general.firmware_version}, from dyscom layer: {firmware_version}") + + ##### + # get command for file system status + file_system_status = await dyscom.get_file_system_status() + print(file_system_status) + + om = await dyscom.get_operation_mode() + print(om) + + #### + calibration_filename = f"rehaingest_{device_id}.cal" + content = await dyscom.get_file_content(calibration_filename) + print(len(content)) + # get calibration file info + # file_info = await dyscom.get_file_info(calibration_filename) + # print(file_info) + + # om = await dyscom.get_operation_mode() + # print(om) + + # # get calibration file -> does not work + # # there should be DL_Send_File commands afterwards + # file_by_name = await dyscom.get_file_by_name(calibration_filename) + # print(file_by_name) + + # dyscom.send_get_operation_mode() + # dyscom.send_start() + # dyscom.send_get_operation_mode() + # while True: + # # process all available packages + # ack = dyscom.packet_buffer.get_packet_from_buffer() + # if ack: + # 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}") + # elif ack.command == Commands.DL_SEND_FILE: + # sf: PacketDyscomSendFile = ack + # print(f"Block number: {sf.block_number}, block size: {sf.block_size}") + # print(sf.data) + + # dyscom.send_send_file_ack(sf.block_number) + + # if sf.block_number == file_by_name.number_of_blocks: + # break + # else: + # print(f"Not process command: {ack.command}") + + # await asyncio.sleep(0.01) + + # await dyscom.stop() # close serial port connection connection.close() return 0 diff --git a/src/science_mode_4/device.py b/src/science_mode_4/device.py index 8f73cb3..4d81142 100644 --- a/src/science_mode_4/device.py +++ b/src/science_mode_4/device.py @@ -87,7 +87,9 @@ async def initialize(self): if operation_mode in [DyscomGetOperationModeType.LIVE_MEASURING_PRE, DyscomGetOperationModeType.LIVE_MEASURING, DyscomGetOperationModeType.RECORD_PRE, - DyscomGetOperationModeType.RECORD]: + DyscomGetOperationModeType.RECORD, + DyscomGetOperationModeType.DATATRANSFER_PRE, + DyscomGetOperationModeType.DATATRANSFER]: await self.get_layer_dyscom().stop() await self.get_layer_general().initialize() diff --git a/src/science_mode_4/dyscom/dyscom_get_file_by_name.py b/src/science_mode_4/dyscom/dyscom_get_file_by_name.py index 3d1610a..9bda834 100644 --- a/src/science_mode_4/dyscom/dyscom_get_file_by_name.py +++ b/src/science_mode_4/dyscom/dyscom_get_file_by_name.py @@ -21,17 +21,38 @@ class PacketDyscomGetFileByName(PacketDyscomGet): """Packet for dyscom get with type file by name""" - def __init__(self, filename: str = ""): + def __init__(self, filename: str = "", mode: DyscomFileByNameMode = DyscomFileByNameMode.MULTI_BLOCK): super().__init__() self._type = DyscomGetType.FILE_BY_NAME self._kind = int(self._type) self._filename = filename + self._mode = mode + + + @property + def filename(self) -> str: + """Getter for filename""" + return self._filename + + + @property + def mode(self) -> DyscomFileByNameMode: + """Getter for mode""" + return self._mode def get_data(self) -> bytes: bb = ByteBuilder() bb.append_bytes(super().get_data()) bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 128)) + # block offset + bb.append_value(0, 4, True) + # file size + bb.append_value(0, 8, True) + # number of blocks + bb.append_value(0, 4, True) + # mode + bb.append_byte(self._mode) # maybe more parameters are necessary here # block_offset, file_size, n_blocks, mode return bb.get_bytes() diff --git a/src/science_mode_4/dyscom/dyscom_layer.py b/src/science_mode_4/dyscom/dyscom_layer.py index 48c56f3..47cf7a3 100644 --- a/src/science_mode_4/dyscom/dyscom_layer.py +++ b/src/science_mode_4/dyscom/dyscom_layer.py @@ -1,6 +1,8 @@ """Provides low level layer""" +import asyncio from science_mode_4.layer import Layer +from science_mode_4.protocol.commands import Commands from science_mode_4.utils.logger import logger from .dyscom_types import DyscomGetOperationModeType, DyscomPowerModuleType, DyscomPowerModulePowerType, DyscomSysType from .dyscom_init import DyscomInitResult, PacketDyscomInit, PacketDyscomInitAck, DyscomInitParams @@ -17,7 +19,7 @@ from .dyscom_get_file_info import DyscomGetFileInfoResult, PacketDyscomGetAckFileInfo, PacketDyscomGetFileInfo from .dyscom_get_battery_status import DyscomGetBatteryResult, PacketDyscomGetAckBatteryStatus, PacketDyscomGetBatteryStatus from .dyscom_sys import DyscomSysResult, PacketDyscomSys, PacketDyscomSysAck -from .dyscom_send_file import PacketDyscomSendFile +from .dyscom_send_file import PacketDyscomSendFile, PacketDyscomSendFileAck class LayerDyscom(Layer): @@ -138,10 +140,10 @@ async def sys(self, sys_type: DyscomSysType, filename: str = "") -> DyscomSysRes return DyscomSysResult(ack.sys_type, ack.state, ack.filename) - def send_send_file(self, block_number: int): + def send_send_file_ack(self, block_number: int): """Sends dyscom send file ack and returns immediately without waiting for response""" logger().info("Dyscom send file ack, block_number: %d", block_number) - p = PacketDyscomSendFile(block_number) + p = PacketDyscomSendFileAck(block_number) self.send_packet(p) @@ -173,3 +175,47 @@ def send_stop(self): logger().info("Dyscom stop") p = PacketDyscomStop() self.send_packet(p) + + + async def get_file_content(self, filename: str) -> bytes: + """Gets content of a file. Device must be in Idle operating mode""" + om = await self.get_operation_mode() + if om != DyscomGetOperationModeType.IDLE: + raise ValueError(f"Error wrong operation mode {om.name}") + + # get meta information and sets device in mode DATATRANSFER_PRE + # we need number of blocks to know how many SendFile commands we expect + # and filesize to know exact filesize + file_by_name = await self.get_file_by_name(filename) + + # start measurement, so device send automatically SendFile packets + await self.start() + + result = bytes() + while True: + # process all available packages + ack = self.packet_buffer.get_packet_from_buffer() + if ack: + if ack.command == Commands.DL_SEND_FILE: + # process SendFile data + sf: PacketDyscomSendFile = ack + result += sf.data + + # send acknowledge for this packet, so device can send + # next block automatically + self.send_send_file_ack(sf.block_number) + + # check if we have all blocks + if sf.block_number >= file_by_name.number_of_blocks: + break + else: + print(f"Unexpected command: {ack.command}") + + await asyncio.sleep(0.01) + + # stop measurement, we have all blocks + await self.stop() + + # trim to filesize (because SendFile sends always data with blocksize) + result = result[0:file_by_name.filesize] + return result diff --git a/src/science_mode_4/dyscom/dyscom_send_file.py b/src/science_mode_4/dyscom/dyscom_send_file.py index a6cab24..a861e8c 100644 --- a/src/science_mode_4/dyscom/dyscom_send_file.py +++ b/src/science_mode_4/dyscom/dyscom_send_file.py @@ -23,8 +23,8 @@ def __init__(self, data: bytes): self._data: bytes = bytes() if not data is None: - self._block_number, self._block_size = PacketDyscomSendFile._unpack_func(">IH", data) - self._data = data[6:self._block_size] + self._block_number, self._block_size = PacketDyscomSendFile._unpack_func(data[0:6]) + self._data = data[6:6+self._block_size] @property diff --git a/src/science_mode_4/dyscom/dyscom_send_measurement_meta_info.py b/src/science_mode_4/dyscom/dyscom_send_measurement_meta_info.py index 7b290b0..f81ea60 100644 --- a/src/science_mode_4/dyscom/dyscom_send_measurement_meta_info.py +++ b/src/science_mode_4/dyscom/dyscom_send_measurement_meta_info.py @@ -14,7 +14,7 @@ class PacketDyscomSendMeasurementMetaInfo(PacketAck): def __init__(self, data: bytes): super().__init__(data) - self._command = Commands.DL_SEND_FILE + self._command = Commands.DL_MMI self._init_params: DyscomInitParams = DyscomInitParams() self._file_name = "" self._file_size = 0 diff --git a/src/science_mode_4/utils/packet_buffer.py b/src/science_mode_4/utils/packet_buffer.py index 96c455b..74b5cf0 100644 --- a/src/science_mode_4/utils/packet_buffer.py +++ b/src/science_mode_4/utils/packet_buffer.py @@ -79,7 +79,7 @@ def get_packet_from_buffer(self, do_update_buffer = True) -> Packet | None: key = ack_data[0], ack_data[1] wait_ack = self._open_acknowledges.get(key) if wait_ack is None: - if ack_data[0] not in [Commands.DL_SEND_LIVE_DATA]: + if ack_data[0] not in [Commands.DL_SEND_LIVE_DATA, Commands.DL_SEND_FILE]: logger().warning("Unexpected acknowledge command: %s, number: %d", Commands(ack_data[0]).name, ack_data[1]) else: self._open_acknowledges[ack_data[0], ack_data[1]] -= 1 From 6967caf17d7da9ea156b75f2aede437f62ae283e Mon Sep 17 00:00:00 2001 From: Marc Hofmann Date: Tue, 10 Jun 2025 21:56:25 +0200 Subject: [PATCH 2/3] added dyscom example that shows usage of SendFile --- .vscode/launch.json | 4 +- HINTS.md | 16 +- README.md | 9 +- examples/dyscom/example_dyscom_get.py | 7 +- examples/dyscom/example_dyscom_send_file.py | 68 ++++++++ pyproject.toml | 2 +- src/__main__.py | 173 ++++++++++++-------- src/science_mode_4/dyscom/dyscom_layer.py | 46 +++++- 8 files changed, 241 insertions(+), 84 deletions(-) create mode 100644 examples/dyscom/example_dyscom_send_file.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 9395a71..8a51cd3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,8 @@ "name": "Python Debugger: Current File", "type": "debugpy", "request": "launch", - "program": "src/__main__.py", - // "module": "examples.dyscom.example_dyscom_get", + // "program": "src/__main__.py", + "module": "examples.dyscom.example_dyscom_send_file", "justMyCode": false, // "args": ["COM3"], "console": "integratedTerminal" diff --git a/HINTS.md b/HINTS.md index 5f3175d..ee0c4c3 100644 --- a/HINTS.md +++ b/HINTS.md @@ -65,14 +65,24 @@ This page describes implementation details. ## Dyscom layer (I24) - Contains functions for dyscom level - This mode is used by I24 to measure EMG or BI -- Usage +- Usage for live data - Call _power_module()_ to power on measurement module - - Call _init()_ with parameter for measurement + - Call _init()_ with parameter for measurement and DyscomInitFlag for live data - Call _start()_ to start measurement - 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 +- Usage for measurement data read from memory card + - Call _power_module()_ to power on measurement module and memory card + - Call _init()_ with parameter for measurement and DyscomInitFlag for storage mode + - Result contains measurement id, that is the filename used later + - Call _start()_ to start measurement + - Device sends nothing but stores measurement data on memory card + - Call _stop()_ to end measurement + - Call _power_module()_ to power off measurement module + - Call _get_meas_file_content()_ with filename from _init()_ to get measurement data + - Call _power_module()_ to power off memory card +- IMPORTANT: not all storage related functions are tested # Platform hints diff --git a/README.md b/README.md index 4f5dcf6..cf189a4 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ Python 3.11 or higher - Demonstrate how to use dyscom layer to measure data and plotting values using PyPlot - `python -m examples.dyscom.example_dyscom_write_csv` - Demonstrate how to use dyscom layer to measure data and writing measurement data to a .csv-file + - `python -m examples.dyscom.example_dyscom_send_file` + - Demonstrate how to use dyscom layer to save measurement data on memory card and reading it afterwards ## Dependencies for examples - Install all dependencies @@ -116,8 +118,11 @@ Python 3.11 or higher - Improved examples under Linux/MacOS ## 0.0.15 -- Clarified readme +- Enhanced readme - Changed current for ChannelPoint from int to float ## 0.0.16 -- Fixed error with PacketLowLevelChannelConfigAck result \ No newline at end of file +- Fixed error with PacketLowLevelChannelConfigAck result + +## 0.0.17 +- Added sample that demonstrates how to read measurement data files from I24 devices \ No newline at end of file diff --git a/examples/dyscom/example_dyscom_get.py b/examples/dyscom/example_dyscom_get.py index be45e50..ee66057 100644 --- a/examples/dyscom/example_dyscom_get.py +++ b/examples/dyscom/example_dyscom_get.py @@ -48,11 +48,8 @@ async def main() -> int: #### calibration_filename = f"rehaingest_{device_id}.cal" - # get calibration file info - await dyscom.get_file_info(calibration_filename) - # get calibration file -> does not work - # there should be DL_Send_File commands afterwards - await dyscom.get_file_by_name(calibration_filename) + calibration_content = await dyscom.get_file_content(calibration_filename) + print(f"Calibration content length: {len(calibration_content)}") # close serial port connection connection.close() diff --git a/examples/dyscom/example_dyscom_send_file.py b/examples/dyscom/example_dyscom_send_file.py new file mode 100644 index 0000000..64e4459 --- /dev/null +++ b/examples/dyscom/example_dyscom_send_file.py @@ -0,0 +1,68 @@ +"""Provides an example how to use dyscom level layer to read stored data from device""" + +import asyncio + +from science_mode_4 import DeviceI24 +from science_mode_4 import SerialPortConnection +from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomInitFlag, DyscomInitParams, DyscomPowerModulePowerType,\ + DyscomPowerModuleType, DyscomSignalType +from examples.utils.example_utils import ExampleUtils + + +async def main() -> int: + """Main function""" + + # get comport from command line argument + com_port = ExampleUtils.get_comport_from_commandline_argument() + # create serial port connection + connection = SerialPortConnection(com_port) + # open connection, now we can read and write data + connection.open() + + # create science mode device + device = DeviceI24(connection) + # call initialize to get basic information (serial, versions) and stop any active stimulation/measurement + # to have a defined state + await device.initialize() + + # get dyscom layer to call low level commands + dyscom = device.get_layer_dyscom() + + # call enable measurement power module and memory card for measurement + await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_ON) + await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_ON) + # call init with 1k sample rate + init_params = DyscomInitParams() + init_params.signal_type = [DyscomSignalType.BI, DyscomSignalType.EMG_1] + init_params.filter = DyscomFilterType.PREDEFINED_FILTER_1 + # we want no live data and write all data to memory card + init_params.flags = [DyscomInitFlag.ENABLE_SD_STORAGE_MODE] + init_result = await dyscom.init(init_params) + + # start dyscom measurement + await dyscom.start() + + # wait 10s to have some measurement data + await asyncio.sleep(10) + + # stop measurement + await dyscom.stop() + # turn power module off + await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_OFF) + + # get all meas data + measurement = await dyscom.get_meas_file_content(init_result.measurement_file_id) + print(f"Sample rate: {measurement[0].name}") + for key, value in measurement[1].items(): + print(f"Signal type: {key.name}, sample count: {len(value)}") + + # turn memory card off + await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_OFF) + # close serial port connection + connection.close() + + return 0 + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml index 089ae16..3c58ea4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "science_mode_4" -version = "0.0.16" +version = "0.0.17" authors = [ { name="Marc Hofmann", email="marc-hofmann@gmx.de" }, ] diff --git a/src/__main__.py b/src/__main__.py index ec318ac..3b1498f 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,13 +1,14 @@ """Test program how to use library without installing the library, DO NOT USE THIS FILE, USE EXAMPLES INSTEAD""" +import struct import sys import asyncio from science_mode_4.device_i24 import DeviceI24 from science_mode_4.dyscom.dyscom_get_operation_mode import PacketDyscomGetAckOperationMode from science_mode_4.dyscom.dyscom_send_file import PacketDyscomSendFile -from science_mode_4.dyscom.dyscom_types import DyscomGetType, DyscomPowerModulePowerType, DyscomPowerModuleType +from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomGetType, DyscomInitFlag, DyscomInitParams, DyscomPowerModulePowerType, DyscomPowerModuleType, DyscomSignalType from science_mode_4.protocol.commands import Commands from science_mode_4.utils.serial_port_connection import SerialPortConnection @@ -26,81 +27,115 @@ async def main() -> int: # to have a defined state await device.initialize() - # get general layer to access general commands - general = device.get_layer_general() - # get dyscom layer to access dyscom commands + # get dyscom layer to call low level commands dyscom = device.get_layer_dyscom() - pm = await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, - DyscomPowerModulePowerType.SWITCH_ON) - print(pm) - - ##### - # dyscom layer provides a get command for device id, it is redundant to general layer device id - # both values should be identical - device_id = await dyscom.get_device_id() - print(f"Device id from general layer: {general.device_id}, from dyscom layer: {device_id}") - - ##### - # dyscom layer provides a get command for firmware version, it is redundant to general layer firmware verion - # both values should be identical - firmware_version = await dyscom.get_firmware_version() - print(f"Firmware version from general layer: {general.firmware_version}, from dyscom layer: {firmware_version}") - - ##### - # get command for file system status - file_system_status = await dyscom.get_file_system_status() - print(file_system_status) - - om = await dyscom.get_operation_mode() - print(om) - - #### - calibration_filename = f"rehaingest_{device_id}.cal" - content = await dyscom.get_file_content(calibration_filename) - print(len(content)) - # get calibration file info - # file_info = await dyscom.get_file_info(calibration_filename) - # print(file_info) - - # om = await dyscom.get_operation_mode() - # print(om) - - # # get calibration file -> does not work - # # there should be DL_Send_File commands afterwards - # file_by_name = await dyscom.get_file_by_name(calibration_filename) - # print(file_by_name) - - # dyscom.send_get_operation_mode() - # dyscom.send_start() - # dyscom.send_get_operation_mode() - # while True: - # # process all available packages - # ack = dyscom.packet_buffer.get_packet_from_buffer() - # if ack: - # 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}") - # elif ack.command == Commands.DL_SEND_FILE: - # sf: PacketDyscomSendFile = ack - # print(f"Block number: {sf.block_number}, block size: {sf.block_size}") - # print(sf.data) - - # dyscom.send_send_file_ack(sf.block_number) - - # if sf.block_number == file_by_name.number_of_blocks: - # break - # else: - # print(f"Not process command: {ack.command}") - - # await asyncio.sleep(0.01) - - # await dyscom.stop() + # call enable measurement power module and memory card for measurement + await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_ON) + await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_ON) + # call init with lowest sample rate (because of performance issues with plotting values) + init_params = DyscomInitParams() + init_params.signal_type = [DyscomSignalType.BI, DyscomSignalType.EMG_1] + init_params.filter = DyscomFilterType.PREDEFINED_FILTER_1 + # we want no live data and write all data to memory card + init_params.flags = [DyscomInitFlag.ENABLE_SD_STORAGE_MODE] + init_result = await dyscom.init(init_params) + + # start dyscom measurement + await dyscom.start() + + # wait 10s to have some measurement data + await asyncio.sleep(10) + + # stop measurement + await dyscom.stop() + # turn power module off + await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_OFF) + + # get all meas data + measurement = await dyscom.get_meas_file_content(init_result.measurement_file_id) + print(f"Sample rate: {measurement[0].name}") + for key, value in measurement[1].items(): + print(f"Signal type: {key.name}, sample count: {len(value)}") + + # turn memory card off + await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_OFF) # close serial port connection connection.close() + return 0 if __name__ == "__main__": res = asyncio.run(main()) sys.exit(res) + + +# init +# 00 00 00 00 00 00 00 00 83 00 FC 00 00 00 00 00 00 00 00 00 +# 00 EA EA 02 00 00 12 00 0A 0E 06 13 02 00 A0 00 7D 12 00 0A +# 0E 06 13 02 00 A0 00 7D 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 02 00 00 00 00 02 03 00 00 00 00 00 00 00 00 00 +# 02 + + +# 01 03 00 04 00 00 00 00 00 00 02 03 04 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 83 00 fc 00 00 00 00 00 00 00 00 00 +# 02 ea 00 00 00 00 00 00 00 ea 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# 00 00 00 00 00 00 00 00 00 00 00 00 + +# 00 00 00 00 50 72 08 41 c0 39 ff bd +# fa 00 00 00 75 a0 09 41 e0 4e f3 bd +# fa 00 00 00 c9 b7 0c 41 e0 45 e8 bd +# fb 00 00 00 56 c7 0e 41 a0 06 de bd +# fa 00 00 00 32 45 11 41 e0 78 d4 bd +# fa 00 00 00 fa 37 13 41 15 90 cb bd +# fa 00 00 00 24 4e 14 41 a0 33 c3 bd +# fb 00 00 00 87 1d 17 41 60 5a bb bd +# f9 00 00 00 d1 8e 18 41 d5 f9 b3 bd +# fa 00 00 00 4a 28 19 41 60 04 ad bd +# fa 00 00 00 2a a3 1a 41 eb 74 a6 bd +# fb 00 00 00 cd 52 1c 41 c0 40 a0 bd +# f9 00 00 00 dc f5 1c 41 a0 60 9a bd +# fa 00 00 00 0c f4 1d 41 75 d1 94 bd +# fa 00 00 00 5b 4d 1f 41 00 8d 8f bd +# fa 00 00 00 e8 71 20 41 ab 8b 8a bd +# fa 00 00 00 4c 6b 21 41 f5 cc 85 bd +# fa 00 00 00 90 09 22 41 2b 4a 81 bd +# fa 00 00 00 2c e6 22 41 eb fa 79 bd \ No newline at end of file diff --git a/src/science_mode_4/dyscom/dyscom_layer.py b/src/science_mode_4/dyscom/dyscom_layer.py index 47cf7a3..8018c79 100644 --- a/src/science_mode_4/dyscom/dyscom_layer.py +++ b/src/science_mode_4/dyscom/dyscom_layer.py @@ -1,10 +1,12 @@ """Provides low level layer""" import asyncio +import struct + from science_mode_4.layer import Layer from science_mode_4.protocol.commands import Commands from science_mode_4.utils.logger import logger -from .dyscom_types import DyscomGetOperationModeType, DyscomPowerModuleType, DyscomPowerModulePowerType, DyscomSysType +from .dyscom_types import DyscomFrequencyOut, DyscomGetOperationModeType, DyscomPowerModuleType, DyscomPowerModulePowerType, DyscomSignalType, DyscomSysType from .dyscom_init import DyscomInitResult, PacketDyscomInit, PacketDyscomInitAck, DyscomInitParams from .dyscom_get_file_system_status import PacketDyscomGetFileSystemStatus, PacketDyscomGetAckFileSystemStatus,\ DyscomGetFileSystemStatusResult @@ -187,6 +189,8 @@ async def get_file_content(self, filename: str) -> bytes: # we need number of blocks to know how many SendFile commands we expect # and filesize to know exact filesize file_by_name = await self.get_file_by_name(filename) + logger().info("Dyscom get file content, filesize: %d, number of blocks: %d", + file_by_name.filesize, file_by_name.number_of_blocks) # start measurement, so device send automatically SendFile packets await self.start() @@ -209,7 +213,7 @@ async def get_file_content(self, filename: str) -> bytes: if sf.block_number >= file_by_name.number_of_blocks: break else: - print(f"Unexpected command: {ack.command}") + logger().warning("Unexpected command: %d", ack.command) await asyncio.sleep(0.01) @@ -219,3 +223,41 @@ async def get_file_content(self, filename: str) -> bytes: # trim to filesize (because SendFile sends always data with blocksize) result = result[0:file_by_name.filesize] return result + + + async def get_meas_file_content(self, filename: str) -> tuple[DyscomFrequencyOut, dict[DyscomSignalType, list[float]]]: + """Gets measurement data of a file. Device must be in Idle operating mode""" + meas_data = await self.get_file_content(filename) + + result: dict[DyscomSignalType, list[float]] = {} + # signal types differ from DyscomSignalType enum + signal_type_map = { 1: 1, 2: 10, 3: 2, 4: 3, 5: 11, 6: 9, 7: 12} + signal_types: list[DyscomSignalType] = [] + for x in range(meas_data[10]): + signal_type = DyscomSignalType(signal_type_map[meas_data[11+x]]) + signal_types.append(signal_type) + + result[signal_type] = [] + + # sample rate + sample_rate = DyscomFrequencyOut(meas_data[3]) + + # build string to unpack samples + # each sample consist of a time difference and n time signal type values + value_structure = " Date: Tue, 10 Jun 2025 22:11:37 +0200 Subject: [PATCH 3/3] removed warnings --- .pylintrc | 1 + src/__main__.py | 77 +---------------------- src/science_mode_4/dyscom/dyscom_layer.py | 5 +- 3 files changed, 6 insertions(+), 77 deletions(-) diff --git a/.pylintrc b/.pylintrc index 5d1be9c..3d1c8ad 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,6 +4,7 @@ max-attributes=15 [DESIGN] max-statements=100 +max-locals = 20 [STRING] check-quote-consistency=yes diff --git a/src/__main__.py b/src/__main__.py index 3b1498f..437c1f2 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,15 +1,12 @@ """Test program how to use library without installing the library, DO NOT USE THIS FILE, USE EXAMPLES INSTEAD""" -import struct import sys import asyncio from science_mode_4.device_i24 import DeviceI24 -from science_mode_4.dyscom.dyscom_get_operation_mode import PacketDyscomGetAckOperationMode -from science_mode_4.dyscom.dyscom_send_file import PacketDyscomSendFile -from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomGetType, DyscomInitFlag, DyscomInitParams, DyscomPowerModulePowerType, DyscomPowerModuleType, DyscomSignalType -from science_mode_4.protocol.commands import Commands +from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomInitFlag, DyscomInitParams, DyscomPowerModulePowerType,\ + DyscomPowerModuleType, DyscomSignalType from science_mode_4.utils.serial_port_connection import SerialPortConnection @@ -69,73 +66,3 @@ async def main() -> int: if __name__ == "__main__": res = asyncio.run(main()) sys.exit(res) - - -# init -# 00 00 00 00 00 00 00 00 83 00 FC 00 00 00 00 00 00 00 00 00 -# 00 EA EA 02 00 00 12 00 0A 0E 06 13 02 00 A0 00 7D 12 00 0A -# 0E 06 13 02 00 A0 00 7D 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 02 00 00 00 00 02 03 00 00 00 00 00 00 00 00 00 -# 02 - - -# 01 03 00 04 00 00 00 00 00 00 02 03 04 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 83 00 fc 00 00 00 00 00 00 00 00 00 -# 02 ea 00 00 00 00 00 00 00 ea 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 -# 00 00 00 00 00 00 00 00 00 00 00 00 - -# 00 00 00 00 50 72 08 41 c0 39 ff bd -# fa 00 00 00 75 a0 09 41 e0 4e f3 bd -# fa 00 00 00 c9 b7 0c 41 e0 45 e8 bd -# fb 00 00 00 56 c7 0e 41 a0 06 de bd -# fa 00 00 00 32 45 11 41 e0 78 d4 bd -# fa 00 00 00 fa 37 13 41 15 90 cb bd -# fa 00 00 00 24 4e 14 41 a0 33 c3 bd -# fb 00 00 00 87 1d 17 41 60 5a bb bd -# f9 00 00 00 d1 8e 18 41 d5 f9 b3 bd -# fa 00 00 00 4a 28 19 41 60 04 ad bd -# fa 00 00 00 2a a3 1a 41 eb 74 a6 bd -# fb 00 00 00 cd 52 1c 41 c0 40 a0 bd -# f9 00 00 00 dc f5 1c 41 a0 60 9a bd -# fa 00 00 00 0c f4 1d 41 75 d1 94 bd -# fa 00 00 00 5b 4d 1f 41 00 8d 8f bd -# fa 00 00 00 e8 71 20 41 ab 8b 8a bd -# fa 00 00 00 4c 6b 21 41 f5 cc 85 bd -# fa 00 00 00 90 09 22 41 2b 4a 81 bd -# fa 00 00 00 2c e6 22 41 eb fa 79 bd \ No newline at end of file diff --git a/src/science_mode_4/dyscom/dyscom_layer.py b/src/science_mode_4/dyscom/dyscom_layer.py index 8018c79..2171908 100644 --- a/src/science_mode_4/dyscom/dyscom_layer.py +++ b/src/science_mode_4/dyscom/dyscom_layer.py @@ -6,7 +6,8 @@ from science_mode_4.layer import Layer from science_mode_4.protocol.commands import Commands from science_mode_4.utils.logger import logger -from .dyscom_types import DyscomFrequencyOut, DyscomGetOperationModeType, DyscomPowerModuleType, DyscomPowerModulePowerType, DyscomSignalType, DyscomSysType +from .dyscom_types import DyscomFrequencyOut, DyscomGetOperationModeType, DyscomPowerModuleType,\ + DyscomPowerModulePowerType, DyscomSignalType, DyscomSysType from .dyscom_init import DyscomInitResult, PacketDyscomInit, PacketDyscomInitAck, DyscomInitParams from .dyscom_get_file_system_status import PacketDyscomGetFileSystemStatus, PacketDyscomGetAckFileSystemStatus,\ DyscomGetFileSystemStatusResult @@ -189,7 +190,7 @@ async def get_file_content(self, filename: str) -> bytes: # we need number of blocks to know how many SendFile commands we expect # and filesize to know exact filesize file_by_name = await self.get_file_by_name(filename) - logger().info("Dyscom get file content, filesize: %d, number of blocks: %d", + logger().info("Dyscom get file content, filesize: %d, number of blocks: %d", file_by_name.filesize, file_by_name.number_of_blocks) # start measurement, so device send automatically SendFile packets