From 7d32bffef506e7b0f3937d539e9342614830b457 Mon Sep 17 00:00:00 2001 From: Marc Hofmann Date: Mon, 28 Apr 2025 17:41:46 +0200 Subject: [PATCH 1/5] refactor and bugfixes --- .vscode/launch.json | 4 +- HINTS.md | 28 +++- README.md | 6 +- __main__.py | 110 ---------------- examples/dyscom/example_dyscom_fastplotlib.py | 14 +- examples/dyscom/example_dyscom_get.py | 8 ++ examples/dyscom/example_dyscom_pyplot.py | 5 +- examples/dyscom/example_dyscom_write_csv.py | 5 +- examples/utils/fastplotlib_utils.py | 6 +- examples/utils/plot_base.py | 9 +- examples/utils/pyplot_utils.py | 6 +- pyproject.toml | 2 +- src/__main__.py | 123 ++++++++++++++++++ src/science_mode_4/__init__.py | 9 +- src/science_mode_4/dyscom/ads129x/__init__.py | 1 + src/science_mode_4/dyscom/ads129x/ads129x.py | 67 ++++++++-- .../ads129x_channel_settings_register.py | 55 ++++++++ .../dyscom/dyscom_get_file_by_name.py | 17 ++- .../dyscom/dyscom_get_file_info.py | 15 ++- .../dyscom/dyscom_get_file_system_status.py | 4 +- ...yscom_get_list_of_measurement_meta_info.py | 2 +- src/science_mode_4/dyscom/dyscom_helper.py | 9 +- src/science_mode_4/dyscom/dyscom_init.py | 10 +- src/science_mode_4/dyscom/dyscom_layer.py | 94 +++++++------ src/science_mode_4/dyscom/dyscom_send_file.py | 10 +- src/science_mode_4/dyscom/dyscom_sys.py | 2 +- src/science_mode_4/dyscom/dyscom_types.py | 13 +- src/science_mode_4/general/__init__.py | 1 + src/science_mode_4/general/general_layer.py | 14 +- src/science_mode_4/general/general_types.py | 11 ++ src/science_mode_4/general/general_version.py | 15 ++- src/science_mode_4/layer.py | 4 +- .../low_level/low_level_layer.py | 10 +- .../mid_level/mid_level_layer.py | 8 +- src/science_mode_4/protocol/protocol.py | 2 +- src/science_mode_4/utils/connection.py | 3 +- 36 files changed, 455 insertions(+), 247 deletions(-) delete mode 100644 __main__.py create mode 100644 src/__main__.py create mode 100644 src/science_mode_4/dyscom/ads129x/ads129x_channel_settings_register.py create mode 100644 src/science_mode_4/general/general_types.py diff --git a/.vscode/launch.json b/.vscode/launch.json index c269084..f709071 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,8 @@ "name": "Python Debugger: Current File", "type": "debugpy", "request": "launch", - "program": "__main__.py", - // "module": "examples.dyscom.example_dyscom_write_csv", + "program": "src/__main__.py", + // "module": "examples.dyscom.example_dyscom_fastplotlib", "justMyCode": false, // "args": ["COM3"], "console": "integratedTerminal" diff --git a/HINTS.md b/HINTS.md index 1ba8509..613784f 100644 --- a/HINTS.md +++ b/HINTS.md @@ -7,7 +7,7 @@ This page describes implementation details. - Starting point is using a _Device_ object matching attached hardware: _DeviceP24_ or _DeviceI24_ - If you have multiple devices, create multiple _Device_ instances - With _capabilities_ it is possible to query for available layer -- To create a _Device_ object a _Connection_ object is required, use _SerialConnection_ to connect to a serial port +- To create a _Device_ object, a _Connection_ object is required, use _SerialConnection_ to connect to a serial port - _Connection_ must be opened and closed - Call _device.initialize()_ to get a defined state of the device (it stops any active stimulation/measurement) - _Device_ object has layers to access commands @@ -18,8 +18,16 @@ This page describes implementation details. - Do not mix usage of layers because device has a single internal state, e.g. calling low level _init()_ and afterwards mid level _update()_ will not work ## Device communication +- Each device has an instance of a _PacketBuffer_ + - Should be used to read packets from connection + - Handles extraction of packets from byte stream - Most functions communicating with the device are async functions, because they wait for a matching acknowledge and return values from acknowledge - If no matching acknowledge or no acknowledge arrives in time, an exception is raised + - The async functions connection buffer handling is always identical: + - Clear buffer + - Send command + - Process incoming data until the expected acknowledge arrives + - More data remains in connection buffer - Additionally functions with naming schema _send_xxx_ are normal functions not waiting for acknowledge - The acknowledge needs to handled manually by using _PacketBuffer_ object from device - _PacketBuffer_ reads data from connection and separates packets from data stream @@ -28,7 +36,9 @@ This page describes implementation details. - 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 + - `logger().setLevel(logging.DEBUG)` +- For better performance, disable logger + - `logger().disabled = True` ## General layer - Contains functions to get common information like device serial or firmware version @@ -63,7 +73,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 +- IMPORTANT: all storage related functions are untested # Deviation from Instruction for Use @@ -76,6 +86,13 @@ This page describes implementation details. ### DL_init - Init state seems always be UNUSED - Output data rate depends on init params filter property +- Setting a filter overwrite other settings + - ADS129x register channel 1-4 settings + - ADS129x config register output data rate + - Maybe more register values are changed + +### DL_get for type file system status and list of measurement info +- Return never meaningful values, probably not implemented on I24 side ### DL_get_ack for type file by name - Additional parameter mode (1 byte) @@ -98,5 +115,6 @@ This page describes implementation details. - Block number, 4 byte, block number of DL_send_file ### DL_send_live_data -- Parameters are big endian? -- SignalType for each sample is always 0 \ No newline at end of file +- SignalType for each sample is always 0 +- Contains always 5 samples, regardless of selected signal types in DL_init + - Fifth sample value seems always be zero \ No newline at end of file diff --git a/README.md b/README.md index 12f4f96..c652ece 100644 --- a/README.md +++ b/README.md @@ -98,4 +98,8 @@ Python 3.11 or higher ## 0.0.11 - Implemented UsbConnection class - Alternative for SerialPortConnection, both share the same base class Connection - - Added _PyUSB_ and _libusb-package_ as dependencies \ No newline at end of file + - Added _PyUSB_ and _libusb-package_ as dependencies + +## 0.0.12 +- Dyscom init + - Added channel settings register \ No newline at end of file diff --git a/__main__.py b/__main__.py deleted file mode 100644 index 6791a42..0000000 --- a/__main__.py +++ /dev/null @@ -1,110 +0,0 @@ -"""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""" - - 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 = DeviceI24(connection) - await device.initialize() - # 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() - - return 0 - - -if __name__ == "__main__": - res = asyncio.run(main()) - sys.exit(res) diff --git a/examples/dyscom/example_dyscom_fastplotlib.py b/examples/dyscom/example_dyscom_fastplotlib.py index 43df477..1e108a2 100644 --- a/examples/dyscom/example_dyscom_fastplotlib.py +++ b/examples/dyscom/example_dyscom_fastplotlib.py @@ -93,23 +93,15 @@ async def device_communication() -> int: print(f"SendLiveData status error {sld.samples}") break - # these samples match signal types from dl_init command - plot_helper.append_value(0, sld.samples[0].value) - plot_helper.append_value(1, sld.samples[1].value) - plot_helper.append_value(2, sld.samples[2].value) - plot_helper.append_value(3, sld.samples[3].value) - plot_helper.append_value(4, sld.samples[4].value) - - # signal type seems to be always 0, so we can't use it - # for s in sld.samples: - # ph.append_value(int(s.signal_type), s.value) + for x, sample in enumerate(sld.samples): + plot_helper.append_value(x, sample.value) plot_helper.update() else: # print(f"Live data acknowledges per iteration {live_data_counter}") break - await asyncio.sleep(0.01) + await asyncio.sleep(0.001) # stop measurement await dyscom.stop() diff --git a/examples/dyscom/example_dyscom_get.py b/examples/dyscom/example_dyscom_get.py index 9485138..be45e50 100644 --- a/examples/dyscom/example_dyscom_get.py +++ b/examples/dyscom/example_dyscom_get.py @@ -46,6 +46,14 @@ async def main() -> int: file_system_status = await dyscom.get_file_system_status() print(file_system_status) + #### + 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) + # close serial port connection connection.close() return 0 diff --git a/examples/dyscom/example_dyscom_pyplot.py b/examples/dyscom/example_dyscom_pyplot.py index 96fe81c..9a03abb 100644 --- a/examples/dyscom/example_dyscom_pyplot.py +++ b/examples/dyscom/example_dyscom_pyplot.py @@ -18,7 +18,6 @@ async def main() -> int: """Main function""" plot_helper = PyPlotHelper({0: ["BI", "blue"]}, 250) - # disable logger to increase performance logger().disabled = True @@ -42,7 +41,7 @@ async def main() -> int: await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, 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.signal_type = [DyscomSignalType.BI] 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) @@ -79,7 +78,7 @@ async def main() -> int: # print(f"Live data acknowledges per iteration {live_data_counter}") break - await asyncio.sleep(0.01) + await asyncio.sleep(0.001) # stop measurement await dyscom.stop() diff --git a/examples/dyscom/example_dyscom_write_csv.py b/examples/dyscom/example_dyscom_write_csv.py index 09706c2..134b1be 100644 --- a/examples/dyscom/example_dyscom_write_csv.py +++ b/examples/dyscom/example_dyscom_write_csv.py @@ -11,7 +11,7 @@ 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,\ +from science_mode_4.dyscom.dyscom_types import DyscomGetType, DyscomInitParams, DyscomPowerModulePowerType,\ DyscomPowerModuleType, DyscomSignalType from science_mode_4.protocol.types import ResultAndError from science_mode_4.utils.logger import logger @@ -51,7 +51,6 @@ 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 @@ -101,7 +100,7 @@ async def device_communication() -> int: # print(f"Live data acknowledges per iteration {live_data_counter}") break - # await asyncio.sleep(0.01) + await asyncio.sleep(0.001) # print stats end_time = timer() diff --git a/examples/utils/fastplotlib_utils.py b/examples/utils/fastplotlib_utils.py index ce2715c..a34019e 100644 --- a/examples/utils/fastplotlib_utils.py +++ b/examples/utils/fastplotlib_utils.py @@ -76,12 +76,12 @@ def __init__(self, channels: dict[int, tuple[str, str]], max_value_count: int): names.extend([""] * ((x_dimension * y_dimension) - len(channels))) # create figure - self._figure = fpl.Figure(size=(1024, 768), shape=(x_dimension, y_dimension), names=names, ) + self._figure = fpl.Figure(size=(1024, 768), shape=(y_dimension, x_dimension), names=names, ) sub_plot_counter = 0 for key, value in channels.items(): - x_pos, y_pos = self._calc_layout_pos(sub_plot_counter) - sub_plot = self._figure[x_pos, y_pos] + x_pos, y_pos = self._calc_layout_pos(sub_plot_counter, len(channels)) + sub_plot = self._figure[y_pos, x_pos] # setting name here does not work # sub_plot.name = value[0] self._data[key] = FastPlotLibValueChannel(sub_plot, max_value_count, value[1]) diff --git a/examples/utils/plot_base.py b/examples/utils/plot_base.py index 4c60c6a..abcdbb0 100644 --- a/examples/utils/plot_base.py +++ b/examples/utils/plot_base.py @@ -62,12 +62,13 @@ def update(self): def _calc_layout_dimension(self, channel_count: int) -> tuple[int, int]: """Calculates layout for a specific number of channels, tries to grow equal in both directions""" - layouts = {1: [1, 1], 2: [2, 1], 3: [3, 1], 4: [2, 2], 5: [3, 2]} return layouts[channel_count] - def _calc_layout_pos(self, index: int) -> tuple[int, int]: + def _calc_layout_pos(self, index: int, channel_count: int) -> tuple[int, int]: """Calculates from a 1 dimensional index a 2 dimensional position""" - x, y = self._calc_layout_dimension(index + 1) - return [x-1, y-1] + cols, _ = self._calc_layout_dimension(channel_count) + x = index % cols + y = index // cols + return [x, y] diff --git a/examples/utils/pyplot_utils.py b/examples/utils/pyplot_utils.py index dd3d0f4..8b1a0f5 100644 --- a/examples/utils/pyplot_utils.py +++ b/examples/utils/pyplot_utils.py @@ -72,13 +72,13 @@ def __init__(self, channels: dict[int, tuple[str, str]], max_value_count: int): super().__init__() x_dimension, y_dimension = self._calc_layout_dimension(len(channels)) - self._figure, self._axes = plt.subplots(x_dimension, y_dimension, constrained_layout=True, squeeze=False) + self._figure, self._axes = plt.subplots(y_dimension, x_dimension, constrained_layout=True, squeeze=False) sub_plot_counter = 0 for key, value in channels.items(): - x_pos, y_pos = self._calc_layout_pos(sub_plot_counter) - ax = self._axes[x_pos, y_pos] + x_pos, y_pos = self._calc_layout_pos(sub_plot_counter, len(channels)) + ax = self._axes[y_pos, x_pos] sub_plot_counter += 1 ax.set(xlabel="Samples", ylabel=value[0], title=value[0]) diff --git a/pyproject.toml b/pyproject.toml index 494fa83..7447226 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "science_mode_4" -version = "0.0.11" +version = "0.0.12" authors = [ { name="Marc Hofmann", email="marc-hofmann@gmx.de" }, ] diff --git a/src/__main__.py b/src/__main__.py new file mode 100644 index 0000000..30b295c --- /dev/null +++ b/src/__main__.py @@ -0,0 +1,123 @@ +"""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_channel_settings_register import Ads129xChannelPowerMode +from science_mode_4.dyscom.ads129x.ads129x_config_register_1 import Ads129xOutputDataRate, Ads129xPowerMode +from science_mode_4.dyscom.dyscom_get_file_by_name import PacketDyscomGetAckFileByName, PacketDyscomGetFileByName +from science_mode_4.dyscom.dyscom_get_operation_mode import PacketDyscomGetAckOperationMode +from science_mode_4.dyscom.dyscom_layer import LayerDyscom +from science_mode_4.dyscom.dyscom_send_file import PacketDyscomSendFile +from science_mode_4.dyscom.dyscom_send_live_data import PacketDyscomSendLiveData +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.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""" + + # 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 = DeviceI24(connection) + await device.initialize() + + general = device.get_layer_general() + await general.get_version() + # get dyscom layer to call dyscom level commands + dyscom = device.get_layer_dyscom() + + device_id = await dyscom.get_device_id() + + # await dyscom.power_module(DyscomPowerModuleType.MEMORY_CARD, DyscomPowerModulePowerType.SWITCH_ON) + # await dyscom.power_module(DyscomPowerModuleType.MEASUREMENT, DyscomPowerModulePowerType.SWITCH_ON) + # params = DyscomInitParams() + # params.flags = [DyscomInitFlag.ENABLE_SD_STORAGE_MODE] + # init_ack = await dyscom.init(params) + + # dyscom.send_start() + # await asyncio.sleep(1) + # dyscom.send_get_operation_mode() + # await asyncio.sleep(4) + # dyscom.send_stop() + # await asyncio.sleep(5) + # process_ack(dyscom) + + # mmi = await dyscom.get_list_of_measurement_meta_info() + + # # get calibration file + calibration_filename = f"rehaingest_{device_id}.cal" + await dyscom.get_file_info(calibration_filename) + + # p = PacketDyscomGetFileByName(calibration_filename) + # dyscom.send_packet(p) + get_file_by_name_ack = await dyscom.get_file_by_name(calibration_filename) + + # dyscom.send_send_file(get_file_by_name_ack.block_offset) + # for x in range(get_file_by_name_ack.number_of_blocks): + # dyscom.send_send_file(get_file_by_name_ack.block_offset + x) + + meas_info = await dyscom.get_file_info(init_ack.measurement_file_id) + await dyscom.get_operation_mode() + + p = PacketDyscomGetFileByName(init_ack.measurement_file_id) + dyscom.send_packet(p) + dyscom.send_get_operation_mode() + # get_file_by_name_ack = await dyscom.get_file_by_name(init_ack.measurement_file_id) + # await dyscom.get_operation_mode() + + await asyncio.sleep(5) + offset = process_ack(dyscom) + dyscom.send_send_file(offset) + await asyncio.sleep(5) + process_ack(dyscom) + + connection.close() + + return 0 + +def process_ack(dyscom: LayerDyscom) -> int: + offset = 0 + while True: + # process all available packages + ack = dyscom.packet_buffer.get_packet_from_buffer() + print(ack) + if ack: + if ack.command == Commands.DL_SEND_FILE: + send_file: PacketDyscomSendFile = ack + data = send_file.data + elif ack.command == Commands.DL_GET_ACK and ack.kind == DyscomGetType.OPERATION_MODE: + op_mode: PacketDyscomGetAckOperationMode = ack + print(op_mode.operation_mode.name) + elif ack.command == Commands.DL_GET_ACK and ack.kind == DyscomGetType.FILE_BY_NAME: + fbn: PacketDyscomGetAckFileByName = ack + print(fbn.block_offset) + offset = fbn.block_offset + else: + break + + return offset + + +if __name__ == "__main__": + res = asyncio.run(main()) + sys.exit(res) diff --git a/src/science_mode_4/__init__.py b/src/science_mode_4/__init__.py index b8a003f..f0caf5c 100644 --- a/src/science_mode_4/__init__.py +++ b/src/science_mode_4/__init__.py @@ -1,6 +1,6 @@ """Init file""" -from importlib.metadata import version +from importlib.metadata import PackageNotFoundError, version from .protocol import * from .utils import * @@ -14,5 +14,8 @@ from .device_p24 import * from .device_i24 import * -__version__ = version("science_mode_4") -logger().info("Library version %s", __version__) +try: + __version__ = version("science_mode_4") + logger().info("Library version %s", __version__) +except PackageNotFoundError: + pass diff --git a/src/science_mode_4/dyscom/ads129x/__init__.py b/src/science_mode_4/dyscom/ads129x/__init__.py index 29325c1..8b20c30 100644 --- a/src/science_mode_4/dyscom/ads129x/__init__.py +++ b/src/science_mode_4/dyscom/ads129x/__init__.py @@ -1,6 +1,7 @@ """Init file for dyscom ADS129x""" +from .ads129x_channel_settings_register import * from .ads129x_config_register_1 import * from .ads129x_config_register_2 import * from .ads129x_config_register_3 import * diff --git a/src/science_mode_4/dyscom/ads129x/ads129x.py b/src/science_mode_4/dyscom/ads129x/ads129x.py index ded62f8..a206aa4 100644 --- a/src/science_mode_4/dyscom/ads129x/ads129x.py +++ b/src/science_mode_4/dyscom/ads129x/ads129x.py @@ -8,6 +8,7 @@ from .ads129x_config_register_3 import Ads129xConfigRegister3 from .ads129x_config_register_4 import Ads129xConfigRegister4 from .ads129x_respiration_control_register import Ads129xRespirationControlRegister +from .ads129x_channel_settings_register import Ads129xChannelSettingsRegister @dataclass @@ -17,49 +18,97 @@ class Ads129x: # control register device_id = 0 - # config register config_register_1 = Ads129xConfigRegister1() # CONFIG1 config_register_2 = Ads129xConfigRegister2() # CONFIG2 config_register_3 = Ads129xConfigRegister3() # CONFIG3 config_register_4 = Ads129xConfigRegister4() # CONFIG4 - # signal derivation register + lead_off_control_register = 0 # LOFF + + channel_1_setting_register = Ads129xChannelSettingsRegister() # CH1SET + channel_2_setting_register = Ads129xChannelSettingsRegister() # CH2SET + channel_3_setting_register = Ads129xChannelSettingsRegister() # CH3SET + channel_4_setting_register = Ads129xChannelSettingsRegister() # CH4SET + positive_signal_derivation_register = 0x02 # RLD_SENSP negative_signal_derivation_register = 0xEA # RLD_SENSN + positive_signal_lead_off_detection_register = 0 # LOFF_SENSP + negative_signal_lead_off_detection_register = 0 # LOFF_SENSN + + lead_off_flip_register = 0 # LOFF_FLIP + lead_off_positive_signal_status_register = 0 # LOFF_STATP + lead_off_negative_signal_status_register = 0 # LOFF_STATN + gpio_register = 0 # GPIO + pace_detect_register = 0 # PACE - # respiration register respiration_control_register = Ads129xRespirationControlRegister() # RESP + wilson_central_terminal_and_augmented_lead_control_register = 0 # WCT1 + wilson_central_terminal_control_register = 0 # WCT2 + + def set_data(self, data: bytes): """Convert data to information""" - control_register = data[0] - self.device_id = control_register + self.channel_1_setting_register.set_data([data[0]]) + self.channel_2_setting_register.set_data([data[1]]) + self.channel_3_setting_register.set_data([data[2]]) + self.channel_4_setting_register.set_data([data[3]]) self.config_register_1.set_data([data[8]]) self.config_register_2.set_data([data[9]]) self.config_register_3.set_data([data[10]]) self.config_register_4.set_data([data[11]]) - self.positive_signal_derivation_register = data[23] - self.negative_signal_derivation_register = data[22] + self.gpio_register = data[12] + self.device_id = data[13] + self.lead_off_control_register = data[14] + self.lead_off_flip_register = data[15] + self.negative_signal_lead_off_detection_register = data[16] + self.positive_signal_lead_off_detection_register = data[17] + self.lead_off_negative_signal_status_register = data[18] + self.lead_off_positive_signal_status_register = data[19] + self.pace_detect_register = data[20] self.respiration_control_register.set_data([data[21]]) + self.negative_signal_derivation_register = data[22] + self.positive_signal_derivation_register = data[23] + + self.wilson_central_terminal_and_augmented_lead_control_register = data[24] + self.wilson_central_terminal_control_register = data[25] + def get_data(self) -> bytes: """Convert information to bytes""" bb = ByteBuilder(0, 26) + bb.set_bytes_to_position(self.channel_1_setting_register.get_data(), 0, 1) + bb.set_bytes_to_position(self.channel_2_setting_register.get_data(), 1, 1) + bb.set_bytes_to_position(self.channel_3_setting_register.get_data(), 2, 1) + bb.set_bytes_to_position(self.channel_4_setting_register.get_data(), 3, 1) + bb.set_bytes_to_position(self.config_register_1.get_data(), 8, 1) bb.set_bytes_to_position(self.config_register_2.get_data(), 9, 1) bb.set_bytes_to_position(self.config_register_3.get_data(), 10, 1) bb.set_bytes_to_position(self.config_register_4.get_data(), 11, 1) - bb.set_bytes_to_position([self.positive_signal_derivation_register], 23, 1) - bb.set_bytes_to_position([self.negative_signal_derivation_register], 22, 1) + bb.set_bytes_to_position([self.gpio_register], 12, 1) + bb.set_bytes_to_position([self.device_id], 13, 1) + bb.set_bytes_to_position([self.lead_off_control_register], 14, 1) + bb.set_bytes_to_position([self.lead_off_flip_register], 15, 1) + bb.set_bytes_to_position([self.negative_signal_lead_off_detection_register], 16, 1) + bb.set_bytes_to_position([self.positive_signal_lead_off_detection_register], 17, 1) + bb.set_bytes_to_position([self.lead_off_negative_signal_status_register], 18, 1) + bb.set_bytes_to_position([self.lead_off_positive_signal_status_register], 19, 1) + bb.set_bytes_to_position([self.pace_detect_register], 20, 1) bb.set_bytes_to_position(self.respiration_control_register.get_data(), 21, 1) + bb.set_bytes_to_position([self.negative_signal_derivation_register], 22, 1) + bb.set_bytes_to_position([self.positive_signal_derivation_register], 23, 1) + + bb.set_bytes_to_position([self.wilson_central_terminal_and_augmented_lead_control_register], 24, 1) + bb.set_bytes_to_position([self.wilson_central_terminal_control_register], 25, 1) return bb.get_bytes() diff --git a/src/science_mode_4/dyscom/ads129x/ads129x_channel_settings_register.py b/src/science_mode_4/dyscom/ads129x/ads129x_channel_settings_register.py new file mode 100644 index 0000000..405bbaf --- /dev/null +++ b/src/science_mode_4/dyscom/ads129x/ads129x_channel_settings_register.py @@ -0,0 +1,55 @@ +"""Provides class for ADS129x chip channel settings register""" + +from dataclasses import dataclass +from enum import IntEnum + + +class Ads129xChannelPowerMode(IntEnum): + """Represent ADS129xtype for power mode (channel settings register, 0x05 to 0x0C)""" + NORMAL_OPERATION = 0 + CHANNEL_POWER_DOWN = 1 + + +class Ads129xChannelGain(IntEnum): + """Represent ADS129xtype for channel gain (channel settings register, 0x05 to 0x0C)""" + GAIN_1 = 1 + GAIN_2 = 2 + GAIN_3 = 3 + GAIN_4 = 4 + GAIN_6 = 0 + GAIN_8 = 5 + GAIN_12 = 6 + + +class Ads129xChannelInput(IntEnum): + """Represent ADS129xtype for channel input (channel settings register, 0x05 to 0x0C)""" + NORMAL_ELECTRODE_INPUT = 0 + INPUT_SHORTED = 1 + CONJUNCTION_WITH_RLD_MEAS = 2 # Used in conjunction with RLD_MEAS bit for RLD measurements + MVDD_FOR_SUPPLY_MEASUREMENT = 3 + TEMPERATURE_SENSOR = 4 + TEST_SIGNAL = 5 + RLD_DRP = 6 # RLD_DRP (positive electrode is the driver) + RLD_DRN = 7 # RLD_DRN (negative electrode is the driver) + + +@dataclass +class Ads129xChannelSettingsRegister: + """Describes a channel settings register of ADS129x chip""" + + power_mode = Ads129xChannelPowerMode.NORMAL_OPERATION + gain = Ads129xChannelGain.GAIN_6 + input = Ads129xChannelInput.NORMAL_ELECTRODE_INPUT + + def set_data(self, data: bytes): + """Convert data to information""" + tmp = data[0] + self.power_mode = Ads129xChannelPowerMode((tmp >> 7) & 0x01) + self.gain = Ads129xChannelGain((tmp >> 4) & 0x07) + self.input = Ads129xChannelInput((tmp >> 0) & 0x07) + + + def get_data(self) -> bytes: + """Convert information to bytes""" + tmp = ((self.power_mode << 7) | (self.gain << 4) | (self.input << 0)) + return [tmp] 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 d5253a1..f34df5b 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 @@ -2,6 +2,7 @@ from typing import NamedTuple +from science_mode_4.utils.byte_builder import ByteBuilder from .dyscom_types import DyscomFileByNameMode, DyscomGetType from .dyscom_helper import DyscomHelper from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -20,10 +21,18 @@ class PacketDyscomGetFileByName(PacketDyscomGet): """Packet for dyscom get with type file by name""" - def __init__(self): + def __init__(self, filename: str = ""): super().__init__() self._type = DyscomGetType.FILE_BY_NAME self._kind = int(self._type) + self._filename = filename + + + def get_data(self) -> bytes: + bb = ByteBuilder() + bb.append_bytes(super().get_data()) + bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 128)) + return bb.get_bytes() class PacketDyscomGetAckFileByName(PacketDyscomGetAck): @@ -41,9 +50,9 @@ def __init__(self, data: bytes): if not data is None: self._filename = DyscomHelper.bytes_to_str(data[2:130], 128) - self._block_offset = int.from_bytes(data[130:134], "little") - self._filesize = int.from_bytes(data[134:142], "little") - self._number_of_blocks = int.from_bytes(data[142:146], "little") + self._block_offset = int.from_bytes(data[130:134], "big") + self._filesize = int.from_bytes(data[134:142], "big") + self._number_of_blocks = int.from_bytes(data[142:146], "big") self._mode = DyscomFileByNameMode(data[146]) diff --git a/src/science_mode_4/dyscom/dyscom_get_file_info.py b/src/science_mode_4/dyscom/dyscom_get_file_info.py index 78ddfd4..5fb27a2 100644 --- a/src/science_mode_4/dyscom/dyscom_get_file_info.py +++ b/src/science_mode_4/dyscom/dyscom_get_file_info.py @@ -3,6 +3,7 @@ from typing import NamedTuple from science_mode_4.protocol.commands import Commands +from science_mode_4.utils.byte_builder import ByteBuilder from .dyscom_types import DyscomGetType from .dyscom_helper import DyscomHelper from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -19,11 +20,19 @@ class PacketDyscomGetFileInfo(PacketDyscomGet): """Packet for dyscom get with type file info""" - def __init__(self): + def __init__(self, filename: str = ""): super().__init__() self._command = Commands.DL_GET self._type = DyscomGetType.FILE_INFO self._kind = int(self._type) + self._filename = filename + + + def get_data(self) -> bytes: + bb = ByteBuilder() + bb.append_bytes(super().get_data()) + bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 128)) + return bb.get_bytes() class PacketDyscomGetAckFileInfo(PacketDyscomGetAck): @@ -39,8 +48,8 @@ def __init__(self, data: bytes): if not data is None: self._filename = DyscomHelper.bytes_to_str(data[2:130], 128) - self._filesize = int.from_bytes(data[130:134], "little") - self._checksum = int.from_bytes(data[134:136], "little") + self._filesize = int.from_bytes(data[130:134], "big") + self._checksum = int.from_bytes(data[134:136], "big") @property diff --git a/src/science_mode_4/dyscom/dyscom_get_file_system_status.py b/src/science_mode_4/dyscom/dyscom_get_file_system_status.py index 6908ff9..82e0a6d 100644 --- a/src/science_mode_4/dyscom/dyscom_get_file_system_status.py +++ b/src/science_mode_4/dyscom/dyscom_get_file_system_status.py @@ -39,8 +39,8 @@ def __init__(self, data: bytes): if not data is None: self._file_system_ready = bool(data[2]) - self._used_size = int.from_bytes(data[3:11], "little") - self._free_size = int.from_bytes(data[11:19], "little") + self._used_size = int.from_bytes(data[3:11], "big") + self._free_size = int.from_bytes(data[11:19], "big") @property diff --git a/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py b/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py index 9d76e9c..7838891 100644 --- a/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py +++ b/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py @@ -26,7 +26,7 @@ def __init__(self, data: bytes): self._number_of_measurements = 0 if not data is None: - self._number_of_measurements = int.from_bytes(data[2:4], "little") + self._number_of_measurements = int.from_bytes(data[2:4], "big") @property diff --git a/src/science_mode_4/dyscom/dyscom_helper.py b/src/science_mode_4/dyscom/dyscom_helper.py index 0c201ba..bd9fb6d 100644 --- a/src/science_mode_4/dyscom/dyscom_helper.py +++ b/src/science_mode_4/dyscom/dyscom_helper.py @@ -43,9 +43,12 @@ def bytes_to_datetime(data: bytes) -> datetime.datetime: @staticmethod def str_to_bytes(value: str, byte_count: int) -> bytes: """Converts value to bytes with byte_count bytes, last byte will always be 0""" - temp = bytearray(value.zfill(byte_count), "ascii") - for x in range(len(value), byte_count): - temp[x] = 0 + temp = bytearray(byte_count) + for x in range(byte_count): + if x < len(value): + temp[x] = ord(value[x]) + else: + temp[x] = 0 temp[byte_count-1] = 0 return bytes(temp) diff --git a/src/science_mode_4/dyscom/dyscom_init.py b/src/science_mode_4/dyscom/dyscom_init.py index 0693586..55f0afe 100644 --- a/src/science_mode_4/dyscom/dyscom_init.py +++ b/src/science_mode_4/dyscom/dyscom_init.py @@ -1,6 +1,7 @@ """Provides packet classes for dyscom init""" from typing import NamedTuple +from science_mode_4.dyscom.dyscom_helper import DyscomHelper from science_mode_4.protocol.commands import Commands from science_mode_4.protocol.types import ResultAndError from science_mode_4.protocol.packet import Packet, PacketAck @@ -11,6 +12,7 @@ class DyscomInitResult(NamedTuple): """Helper class for dyscom get with type file system status""" register_map_ads129x: Ads129x + measurement_file_id: str init_state: DyscomInitState frequency_out: DyscomFrequencyOut @@ -58,7 +60,7 @@ def __init__(self, data: bytes): self._result_error = ResultAndError(data[0]) self._register_map_ads129x = Ads129x() self._register_map_ads129x.set_data(data[1:27]) - self._measurement_file_id = data[27:87] + self._measurement_file_id = DyscomHelper.bytes_to_str(data[27:87], 60) self._init_state = DyscomInitState(data[87]) self._frequency_out = DyscomFrequencyOut(data[88]) @@ -75,6 +77,12 @@ def register_map_ads129x(self) -> Ads129x: return self._register_map_ads129x + @property + def measurement_file_id(self) -> str: + """Getter for measurement file id""" + return self._measurement_file_id + + @property def init_state(self) -> DyscomInitState: """Getter for init state""" diff --git a/src/science_mode_4/dyscom/dyscom_layer.py b/src/science_mode_4/dyscom/dyscom_layer.py index f0c8614..d097028 100644 --- a/src/science_mode_4/dyscom/dyscom_layer.py +++ b/src/science_mode_4/dyscom/dyscom_layer.py @@ -17,7 +17,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 PacketDyscomSendFileAck +from .dyscom_send_file import PacketDyscomSendFile class LayerDyscom(Layer): @@ -26,19 +26,20 @@ class LayerDyscom(Layer): """ - async def init(self, params = DyscomInitParams()) -> DyscomInitResult: + async def init(self, params) -> DyscomInitResult: """Send dyscom init command and waits for response""" p = PacketDyscomInit(params) - ack: PacketDyscomInitAck = await self._send_packet_and_wait(p) + ack: PacketDyscomInitAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomInit") - logger().info("Dyscom init, state: %s, frequency: %s", ack.init_state.name, ack.frequency_out.name) - return DyscomInitResult(ack.register_map_ads129x, ack.init_state, ack.frequency_out) + logger().info("Dyscom init, measurement_file_id: %s, state: %s, frequency: %s",\ + ack.measurement_file_id, ack.init_state.name, ack.frequency_out.name) + return DyscomInitResult(ack.register_map_ads129x, ack.measurement_file_id, ack.init_state, ack.frequency_out) async def get_file_system_status(self) -> DyscomGetFileSystemStatusResult: - """Sends get dyscom get type file system status and waits for response, returns file system ready, used size and free size""" + """Sends dyscom get type file system status and waits for response, returns file system ready, used size and free size""" p = PacketDyscomGetFileSystemStatus() - ack: PacketDyscomGetAckFileSystemStatus = await self._send_packet_and_wait(p) + ack: PacketDyscomGetAckFileSystemStatus = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetFileSystemStatus") logger().info("Dyscom get file system status, ready: %s, used size: %d, free size: %s",\ ack.file_system_ready, ack.used_size, ack.free_size) @@ -46,19 +47,19 @@ async def get_file_system_status(self) -> DyscomGetFileSystemStatusResult: async def get_list_of_measurement_meta_info(self) -> int: - """Sends get dyscom get type list of measurement meta info and waits for response, returns number of measurements""" + """Sends dyscom get type list of measurement meta info and waits for response, returns number of measurements""" p = PacketDyscomGetListOfMeasurementMetaInfo() - ack: PacketDyscomGetAckListOfMeasurementMetaInfo = await self._send_packet_and_wait(p) + ack: PacketDyscomGetAckListOfMeasurementMetaInfo = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetListOfMeasurementMetaInfo") logger().info("Dyscom get list of mmi, number of measurements: %d", ack.number_of_measurements) return ack.number_of_measurements - async def get_file_by_name(self) -> DyscomGetFileByNameResult: - """Sends get dyscom get type file by name and waits for response, returns filename, block offset, + async def get_file_by_name(self, filename: str) -> DyscomGetFileByNameResult: + """Sends dyscom get type file by name and waits for response, returns filename, block offset, filesize, number of blocks and mode""" - p = PacketDyscomGetFileByName() - ack: PacketDyscomGetAckFileByName = await self._send_packet_and_wait(p) + p = PacketDyscomGetFileByName(filename) + ack: PacketDyscomGetAckFileByName = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetFileByName") logger().info("Dyscom get file by name, filename: %s, block offset: %d, filesize: %d, number of blocks: %d, mode: %s",\ ack.filename, ack.block_offset, ack.filesize, ack.number_of_blocks, ack.mode.name) @@ -66,27 +67,27 @@ async def get_file_by_name(self) -> DyscomGetFileByNameResult: async def get_device_id(self) -> str: - """Sends get dyscom get type device id and waits for response, returns device id""" + """Sends dyscom get type device id and waits for response, returns device id""" p = PacketDyscomGetDeviceId() - ack: PacketDyscomGetAckDeviceId = await self._send_packet_and_wait(p) + ack: PacketDyscomGetAckDeviceId = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetDeviceId") logger().info("Dyscom get device id: %s", ack.device_id) return ack.device_id async def get_firmware_version(self) -> str: - """Sends get dyscom get type firmware version and waits for response, returns firmware version""" + """Sends dyscom get type firmware version and waits for response, returns firmware version""" p = PacketDyscomGetFirmwareVersion() - ack: PacketDyscomGetAckFirmwareVersion = await self._send_packet_and_wait(p) + ack: PacketDyscomGetAckFirmwareVersion = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetFirmwareVersion") logger().info("Dyscom get firmware version: %s", ack.firmware_version) return ack.firmware_version - async def get_file_info(self) -> DyscomGetFileInfoResult: - """Sends get dyscom get type file by name and waits for response, returns filename, block offset, filesize and number of blocks""" - p = PacketDyscomGetFileInfo() - ack: PacketDyscomGetAckFileInfo = await self._send_packet_and_wait(p) + async def get_file_info(self, filename: str) -> DyscomGetFileInfoResult: + """Sends dyscom get type file by name and waits for response, returns filename, block offset, filesize and number of blocks""" + p = PacketDyscomGetFileInfo(filename) + ack: PacketDyscomGetAckFileInfo = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetFileInfo") logger().info("Dyscom get file info, filename: %s, filesize: %d, checksum: %d",\ ack.filename, ack.filesize, ack.checksum) @@ -94,9 +95,9 @@ async def get_file_info(self) -> DyscomGetFileInfoResult: async def get_battery(self) -> DyscomGetBatteryResult: - """Sends get dyscom get type batter and waits for response, returns voltage, current, percentage, temperature and energy state""" + """Sends dyscom get type batter and waits for response, returns voltage, current, percentage, temperature and energy state""" p = PacketDyscomGetBatteryStatus() - ack: PacketDyscomGetAckBatteryStatus = await self._send_packet_and_wait(p) + ack: PacketDyscomGetAckBatteryStatus = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetBatteryStatus") logger().info("Dyscom get battery, voltage: %d, current: %d, percentage: %d, temperature: %d, energy state: %s",\ ack.voltage, ack.current, ack.percentage, ack.temperature, ack.energy_state) @@ -104,56 +105,71 @@ async def get_battery(self) -> DyscomGetBatteryResult: async def get_operation_mode(self) -> DyscomGetOperationModeType: - """Sends get dyscom get type operation mode and waits for response, returns operation mode""" + """Sends dyscom get type operation mode and waits for response, returns operation mode""" p = PacketDyscomGetOperationMode() - ack: PacketDyscomGetAckOperationMode = await self._send_packet_and_wait(p) + ack: PacketDyscomGetAckOperationMode = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomGetFirmwareVersion") logger().info("Dyscom get operation mode: %s", ack.operation_mode.name) return ack.operation_mode def send_get_operation_mode(self): - """Sends get dyscom get type operation mode and returns immediately without waiting for response""" + """Sends dyscom get type operation mode and returns immediately without waiting for response""" p = PacketDyscomGetOperationMode() logger().info("Dyscom send get operation mode") - self._send_packet(p) + self.send_packet(p) async def power_module(self, module: DyscomPowerModuleType, power: DyscomPowerModulePowerType) -> DyscomPowerModuleResult: - """Sends get dyscom power module and waits for response, returns module and power""" + """Sends dyscom power module and waits for response, returns module and power""" p = PacketDyscomPowerModule(module, power) - ack: PacketDyscomPowerModuleAck = await self._send_packet_and_wait(p) + ack: PacketDyscomPowerModuleAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomStart") logger().info("Dyscom power module, module: %s, power: %s", ack.module.name, ack.power.name) return DyscomPowerModuleResult(ack.module, ack.power) async def sys(self, sys_type: DyscomSysType, filename: str = "") -> DyscomSysResult: - """Sends get dyscom sys and waits for response, returns type, state and filename""" + """Sends dyscom sys and waits for response, returns type, state and filename""" p = PacketDyscomSys(sys_type, filename) - ack: PacketDyscomSysAck = await self._send_packet_and_wait(p) + ack: PacketDyscomSysAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomSys") logger().info("Dyscom sys, type: %s, state: %s, filename: %s", ack.sys_type.name, ack.state.name, ack.filename) return DyscomSysResult(ack.sys_type, ack.state, ack.filename) - def send_send_file_ack(self, block_number: int): - """Sends get dyscom send file ack and returns immediately without waiting for response""" - p = PacketDyscomSendFileAck(block_number) - self._send_packet(p) + def send_send_file(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) + self.send_packet(p) async def start(self): - """Sends get dyscom start and waits for response""" + """Sends dyscom start and waits for response""" p = PacketDyscomStart() - ack: PacketDyscomStartAck = await self._send_packet_and_wait(p) + ack: PacketDyscomStartAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomStart") logger().info("Dyscom start") async def stop(self): - """Sends get dyscom stop and waits for response""" + """Sends dyscom stop and waits for response""" p = PacketDyscomStop() - ack: PacketDyscomStopAck = await self._send_packet_and_wait(p) + ack: PacketDyscomStopAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "DyscomStop") logger().info("Dyscom stop") + + + def send_start(self): + """Sends dyscom start and returns immediately without waiting for response""" + logger().info("Dyscom start") + p = PacketDyscomStart() + self.send_packet(p) + + + def send_stop(self): + """Sends dyscom stop and returns immediately without waiting for response""" + logger().info("Dyscom stop") + p = PacketDyscomStop() + self.send_packet(p) diff --git a/src/science_mode_4/dyscom/dyscom_send_file.py b/src/science_mode_4/dyscom/dyscom_send_file.py index 3192970..b3e6522 100644 --- a/src/science_mode_4/dyscom/dyscom_send_file.py +++ b/src/science_mode_4/dyscom/dyscom_send_file.py @@ -8,8 +8,8 @@ class PacketDyscomSendFile(PacketAck): - """Packet for dyscom send file (this is technically not an acknowledge, but it is handled as such, - because it is send automatically from device)""" + """Packet for dyscom send file ack (this is technically not an acknowledge, but it is handled as such, + because it is send automatically from device). This should probably never be used on PC side""" _unpack_func = struct.Struct(">IH").unpack @@ -46,7 +46,7 @@ def data(self) -> bytes: class PacketDyscomSendFileAck(Packet): - """Packet for dyscom send file acknowledge (this is technically not apacket, but it is handled as such, + """Packet for dyscom send file acknowledge (this is technically not a packet, but it is handled as such, because it is send from PC to device)""" @@ -58,5 +58,5 @@ def __init__(self, block_number: int = 0): def get_data(self) -> bytes: bb = ByteBuilder() - bb.append_bytes(self._block_number) - return bb.get_bytes() + bb.append_value(self._block_number, 4, True) + return bb.get_bytes() \ No newline at end of file diff --git a/src/science_mode_4/dyscom/dyscom_sys.py b/src/science_mode_4/dyscom/dyscom_sys.py index ef834e0..39433ae 100644 --- a/src/science_mode_4/dyscom/dyscom_sys.py +++ b/src/science_mode_4/dyscom/dyscom_sys.py @@ -30,7 +30,7 @@ def __init__(self, sys_type: DyscomSysType = DyscomSysType.UNDEFINED, filename: def get_data(self) -> bytes: bb = ByteBuilder() - bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 129)) + bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 128)) bb.append_byte(self._sys_type) return bb.get_bytes() diff --git a/src/science_mode_4/dyscom/dyscom_types.py b/src/science_mode_4/dyscom/dyscom_types.py index d269283..e6cdac3 100644 --- a/src/science_mode_4/dyscom/dyscom_types.py +++ b/src/science_mode_4/dyscom/dyscom_types.py @@ -51,10 +51,10 @@ class DyscomSignalType(IntEnum): class DyscomFilterType(IntEnum): """Represent dyscom type for filter type""" - FILTER_OFF = 0 - PREDEFINED_FILTER_1 = 1 - PREDEFINED_FILTER_2 = 2 - PREDEFINED_FILTER_3 = 3 + FILTER_OFF = 0 # 4k sample rate + PREDEFINED_FILTER_1 = 1 # 1k sample rate + PREDEFINED_FILTER_2 = 2 # 4k sample rate + PREDEFINED_FILTER_3 = 3 # 1k sample rate class DyscomInitFlag(IntEnum): @@ -92,6 +92,7 @@ class DyscomGetOperationModeType(IntEnum): class DyscomPowerModuleType(IntEnum): """Represents dyscom power module type""" + UNDEFINED = 0 BLUETOOTH = 1 MEMORY_CARD = 2 MEASUREMENT = 3 @@ -114,8 +115,8 @@ class DyscomPowerLiveDataStatusFlag(IntEnum): class DyscomFileByNameMode(IntEnum): """Represents dyscom file by name mode type""" UNDEFINED = 0 - MULTIBLOCK = 1 - SINGLEBLOCK = 2 + MULTI_BLOCK = 1 + SINGLE_BLOCK = 2 class DyscomEnergyFlag(IntEnum): diff --git a/src/science_mode_4/general/__init__.py b/src/science_mode_4/general/__init__.py index 368bcaa..452181f 100644 --- a/src/science_mode_4/general/__init__.py +++ b/src/science_mode_4/general/__init__.py @@ -5,5 +5,6 @@ from .general_layer import * from .general_reset import * from .general_stim_status import * +from .general_types import * from .general_unknown_command import * from .general_version import * diff --git a/src/science_mode_4/general/general_layer.py b/src/science_mode_4/general/general_layer.py index eace781..9def71a 100644 --- a/src/science_mode_4/general/general_layer.py +++ b/src/science_mode_4/general/general_layer.py @@ -49,7 +49,7 @@ async def initialize(self): async def get_device_id(self) -> str: """Send get device id command and waits for response""" p = PacketGeneralGetDeviceId() - ack: PacketGeneralGetDeviceIdAck = await self._send_packet_and_wait(p) + ack: PacketGeneralGetDeviceIdAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "GetDeviceId") self._device_id = ack.device_id logger().info("Get device id: %s", ack.device_id) @@ -61,14 +61,14 @@ async def reset(self): logger().info("Reset",) p = PacketGeneralReset() # maybe we get no ack because the device resets before sending ack - ack: PacketGeneralResetAck = await self._send_packet_and_wait(p) + ack: PacketGeneralResetAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "Reset") async def get_stim_status(self) -> GetStimStatusResult: """Sends get stim status and waits for response""" p = PacketGeneralGetStimStatus() - ack: PacketGeneralGetStimStatusAck = await self._send_packet_and_wait(p) + ack: PacketGeneralGetStimStatusAck = await self.send_packet_and_wait(p) if not ack.successful: raise ValueError("Error get stim status") logger().info("Get stim status: %s, active: %r", ack.stim_status.name, ack.high_voltage_on) @@ -78,10 +78,12 @@ async def get_stim_status(self) -> GetStimStatusResult: async def get_version(self) -> GetExtendedVersionResult: """Sends get extended version and waits for response, returns firmware and science mode version""" p = PacketGeneralGetExtendedVersion() - ack: PacketGeneralGetExtendedVersionAck = await self._send_packet_and_wait(p) + ack: PacketGeneralGetExtendedVersionAck = await self.send_packet_and_wait(p) if not ack.successful: raise ValueError("Error get extended version") self._firmware_version = ack.firmware_version self._science_mode_version = ack.science_mode_version - logger().info("Get version, firmware version: %s science mode version: %s", ack.firmware_version, ack.science_mode_version) - return GetExtendedVersionResult(self._firmware_version, self._science_mode_version) + logger().info("Get version, firmware version: %s science mode version: %s, firmware hash: %s, hash type: %s, is valid hash: %r",\ + ack.firmware_version, ack.science_mode_version, ack.firmware_hash, ack.hash_type.name, ack.is_valid_hash) + return GetExtendedVersionResult(self._firmware_version, self._science_mode_version, ack.firmware_hash,\ + ack.hash_type, ack.is_valid_hash) diff --git a/src/science_mode_4/general/general_types.py b/src/science_mode_4/general/general_types.py new file mode 100644 index 0000000..e59480a --- /dev/null +++ b/src/science_mode_4/general/general_types.py @@ -0,0 +1,11 @@ +"""Provides general types""" + +from enum import IntEnum + + +class GeneralHashType(IntEnum): + """Represent general hash type""" + UNINITIALIZED = 0 + GIT = 1 + ELF_MD5 = 2 + ELF_SHA256 = 3 diff --git a/src/science_mode_4/general/general_version.py b/src/science_mode_4/general/general_version.py index f39e3aa..3a1ab59 100644 --- a/src/science_mode_4/general/general_version.py +++ b/src/science_mode_4/general/general_version.py @@ -3,16 +3,20 @@ from typing import NamedTuple from science_mode_4.protocol.commands import Commands from science_mode_4.protocol.packet import Packet, PacketAck +from .general_types import GeneralHashType class GetExtendedVersionResult(NamedTuple): """Helper class for dyscom get with type file system status""" firmware_version: str science_mode_version: str + firmware_hash: str + hash_type: GeneralHashType + is_valid_hash: bool class PacketGeneralGetExtendedVersion(Packet): - """Packet for general GetExtendetVersion""" + """Packet for general GetExtendedVersion""" def __init__(self): @@ -21,7 +25,7 @@ def __init__(self): class PacketGeneralGetExtendedVersionAck(PacketAck): - """Packet for general GetExtendetVersion acknowledge""" + """Packet for general GetExtendedVersion acknowledge""" def __init__(self, data: bytes): @@ -31,15 +35,16 @@ def __init__(self, data: bytes): self._firmware_version = "" self._science_mode_version = "" self._firmware_hash = 0 - self._hash_type = 0 + self._hash_type = GeneralHashType.UNINITIALIZED self._is_valid_hash = False if not data is None: self._successful = data[0] == 0 self._firmware_version = f"{data[1]}.{data[2]}.{data[3]}" self._science_mode_version = f"{data[4]}.{data[5]}.{data[6]}" - self._firmware_hash = int.from_bytes(data[7:10], "little") - self._hash_type = data[11] + firmware_hash = int.from_bytes(data[7:11], "big") + self._firmware_hash = f"{firmware_hash:0x}" + self._hash_type = GeneralHashType(data[11]) self._is_valid_hash = data[12] == 1 diff --git a/src/science_mode_4/layer.py b/src/science_mode_4/layer.py index da30a4d..23d35c0 100644 --- a/src/science_mode_4/layer.py +++ b/src/science_mode_4/layer.py @@ -24,14 +24,14 @@ def packet_buffer(self) -> PacketBuffer: return self._packet_buffer - def _send_packet(self, packet: Packet): + def send_packet(self, packet: Packet): """Generates a new packet number and send packet""" ack = ProtocolHelper.send_packet(packet, self._packet_number_generator.get_next_number(), self._packet_buffer) return ack - async def _send_packet_and_wait(self, packet: Packet) -> PacketAck: + async def send_packet_and_wait(self, packet: Packet) -> PacketAck: """Generates a new packet number, send packet and waits for response""" ack = await ProtocolHelper.send_packet_and_wait(packet, self._packet_number_generator.get_next_number(), self._packet_buffer) diff --git a/src/science_mode_4/low_level/low_level_layer.py b/src/science_mode_4/low_level/low_level_layer.py index 404e074..2f2c558 100644 --- a/src/science_mode_4/low_level/low_level_layer.py +++ b/src/science_mode_4/low_level/low_level_layer.py @@ -22,7 +22,7 @@ async def init(self, mode: LowLevelMode, high_voltage_source: LowLevelHighVoltag p = PacketLowLevelInit() p.mode = mode p.high_voltage_source = high_voltage_source - ack: PacketLowLevelInitAck = await self._send_packet_and_wait(p) + ack: PacketLowLevelInitAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "LowLevelInit") logger().info("Low level init") @@ -30,7 +30,7 @@ async def init(self, mode: LowLevelMode, high_voltage_source: LowLevelHighVoltag async def stop(self): """Send low level stop command and waits for response""" p = PacketLowLevelStop() - ack: PacketLowLevelStopAck = await self._send_packet_and_wait(p) + ack: PacketLowLevelStopAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "LowLevelStop") logger().info("Low level stop") @@ -40,7 +40,7 @@ def send_init(self, mode: LowLevelMode, high_voltage_source: LowLevelHighVoltage p = PacketLowLevelInit() p.mode = mode p.high_voltage_source = high_voltage_source - self._send_packet(p) + self.send_packet(p) self._packet_buffer.add_open_acknowledge(p) logger().info("Low level send init") @@ -53,7 +53,7 @@ def send_channel_config(self, execute_stimulation: bool, channel: Channel, p.channel = channel p.connector = connector p.points = points - self._send_packet(p) + self.send_packet(p) self._packet_buffer.add_open_acknowledge(p) logger().info("Low level send channel config") @@ -61,6 +61,6 @@ def send_channel_config(self, execute_stimulation: bool, channel: Channel, def send_stop(self): """Send low level stop command""" p = PacketLowLevelStop() - self._send_packet(p) + self.send_packet(p) self._packet_buffer.add_open_acknowledge(p) logger().info("Low level send stop") diff --git a/src/science_mode_4/mid_level/mid_level_layer.py b/src/science_mode_4/mid_level/mid_level_layer.py index ed4bed5..347404f 100644 --- a/src/science_mode_4/mid_level/mid_level_layer.py +++ b/src/science_mode_4/mid_level/mid_level_layer.py @@ -17,7 +17,7 @@ async def init(self, do_stop_on_all_errors: bool): """Send mid level init command and waits for response""" p = PacketMidLevelInit() p.do_stop_on_all_errors = do_stop_on_all_errors - ack: PacketMidLevelInitAck = await self._send_packet_and_wait(p) + ack: PacketMidLevelInitAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "MidLevelInit") logger().info("Mid level init") @@ -25,7 +25,7 @@ async def init(self, do_stop_on_all_errors: bool): async def stop(self): """Send mid level stop command and waits for response""" p = PacketMidLevelStop() - ack: PacketMidLevelStopAck = await self._send_packet_and_wait(p) + ack: PacketMidLevelStopAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "MidLevelStop") logger().info("Mid level stop") @@ -34,7 +34,7 @@ async def update(self, channel_configuration: list[MidLevelChannelConfiguration] """Send mid level update command and waits for response""" p = PacketMidLevelUpdate() p.channel_configuration = channel_configuration - ack: PacketMidLevelUpdateAck = await self._send_packet_and_wait(p) + ack: PacketMidLevelUpdateAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "MidLevelUpdate") logger().info("Mid level update") @@ -42,7 +42,7 @@ async def update(self, channel_configuration: list[MidLevelChannelConfiguration] async def get_current_data(self) -> list[bool]: """Send mid level get current data command and waits for response""" p = PacketMidLevelGetCurrentData() - ack: PacketMidLevelGetCurrentDataAck = await self._send_packet_and_wait(p) + ack: PacketMidLevelGetCurrentDataAck = await self.send_packet_and_wait(p) self._check_result_error(ack.result_error, "MidLevelGetCurrentData") if True in ack.channel_error: raise ValueError(f"Error mid level get current data channel error {ack.channel_error}") diff --git a/src/science_mode_4/protocol/protocol.py b/src/science_mode_4/protocol/protocol.py index b6cc72b..8b361ad 100644 --- a/src/science_mode_4/protocol/protocol.py +++ b/src/science_mode_4/protocol/protocol.py @@ -23,7 +23,7 @@ def packet_to_bytes(packet: Packet) -> bytes: # command and packet number bb.set_bit_to_position(packet.command, 0, 10) bb.set_bit_to_position(packet.number, 10, 6) - # swap command and packet number to ensure little endianness + # swap command and packet number to ensure big endianness bb.swap(0, 2) # append packet data bb.append_bytes(packet.get_data()) diff --git a/src/science_mode_4/utils/connection.py b/src/science_mode_4/utils/connection.py index 2ff7dc9..f407596 100644 --- a/src/science_mode_4/utils/connection.py +++ b/src/science_mode_4/utils/connection.py @@ -32,7 +32,8 @@ def write(self, data: bytes): 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()) + if len(result) > 0: + logger().debug("Incoming data, length: %d, bytes: %s", len(result), result.hex(" ").upper()) return result From a0544dfe036fa9363b06f3c379610ab70c583c68 Mon Sep 17 00:00:00 2001 From: Marc Hofmann Date: Mon, 28 Apr 2025 18:22:11 +0200 Subject: [PATCH 2/5] pylint hints --- .pylintrc | 2 +- HINTS.md | 2 +- src/__main__.py | 26 +++++++------------ .../dyscom/dyscom_get_battery_status.py | 2 -- .../dyscom/dyscom_get_device_id.py | 2 -- .../dyscom/dyscom_get_file_by_name.py | 2 ++ .../dyscom/dyscom_get_file_info.py | 4 +-- .../dyscom/dyscom_get_file_system_status.py | 2 -- .../dyscom/dyscom_get_firmware_version.py | 2 -- ...yscom_get_list_of_measurement_meta_info.py | 2 -- .../dyscom/dyscom_get_operation_mode.py | 2 -- src/science_mode_4/dyscom/dyscom_layer.py | 2 +- src/science_mode_4/dyscom/dyscom_send_file.py | 2 +- 13 files changed, 18 insertions(+), 34 deletions(-) diff --git a/.pylintrc b/.pylintrc index a716a25..e1a17f7 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,6 @@ [MAIN] max-line-length=140 -max-attributes=11 +max-attributes=15 [DESIGN] max-statements=100 diff --git a/HINTS.md b/HINTS.md index 613784f..fe1e0fe 100644 --- a/HINTS.md +++ b/HINTS.md @@ -80,11 +80,11 @@ This page describes implementation details. ## Dyscom commands ### Common -- 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 +- Strings are 1 byte longer than in other commands - Output data rate depends on init params filter property - Setting a filter overwrite other settings - ADS129x register channel 1-4 settings diff --git a/src/__main__.py b/src/__main__.py index 30b295c..2a2fb7c 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,27 +1,19 @@ """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_channel_settings_register import Ads129xChannelPowerMode -from science_mode_4.dyscom.ads129x.ads129x_config_register_1 import Ads129xOutputDataRate, Ads129xPowerMode -from science_mode_4.dyscom.dyscom_get_file_by_name import PacketDyscomGetAckFileByName, PacketDyscomGetFileByName +from science_mode_4.dyscom.dyscom_get_file_by_name import PacketDyscomGetAckFileByName from science_mode_4.dyscom.dyscom_get_operation_mode import PacketDyscomGetAckOperationMode from science_mode_4.dyscom.dyscom_layer import LayerDyscom from science_mode_4.dyscom.dyscom_send_file import PacketDyscomSendFile -from science_mode_4.dyscom.dyscom_send_live_data import PacketDyscomSendLiveData -from science_mode_4.dyscom.dyscom_types import DyscomFilterType, DyscomGetType, DyscomInitFlag, DyscomInitParams, DyscomPowerModulePowerType, DyscomPowerModuleType, DyscomSignalType +from science_mode_4.dyscom.dyscom_types import DyscomGetType 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 @@ -70,18 +62,18 @@ async def main() -> int: # p = PacketDyscomGetFileByName(calibration_filename) # dyscom.send_packet(p) - get_file_by_name_ack = await dyscom.get_file_by_name(calibration_filename) + await dyscom.get_file_by_name(calibration_filename) # dyscom.send_send_file(get_file_by_name_ack.block_offset) # for x in range(get_file_by_name_ack.number_of_blocks): # dyscom.send_send_file(get_file_by_name_ack.block_offset + x) - meas_info = await dyscom.get_file_info(init_ack.measurement_file_id) - await dyscom.get_operation_mode() + # meas_info = await dyscom.get_file_info(init_ack.measurement_file_id) + # await dyscom.get_operation_mode() - p = PacketDyscomGetFileByName(init_ack.measurement_file_id) - dyscom.send_packet(p) - dyscom.send_get_operation_mode() + # p = PacketDyscomGetFileByName(init_ack.measurement_file_id) + # dyscom.send_packet(p) + # dyscom.send_get_operation_mode() # get_file_by_name_ack = await dyscom.get_file_by_name(init_ack.measurement_file_id) # await dyscom.get_operation_mode() @@ -96,6 +88,7 @@ async def main() -> int: return 0 def process_ack(dyscom: LayerDyscom) -> int: + """Process all packets read from connection buffer""" offset = 0 while True: # process all available packages @@ -105,6 +98,7 @@ def process_ack(dyscom: LayerDyscom) -> int: if ack.command == Commands.DL_SEND_FILE: send_file: PacketDyscomSendFile = ack data = send_file.data + print(data) elif ack.command == Commands.DL_GET_ACK and ack.kind == DyscomGetType.OPERATION_MODE: op_mode: PacketDyscomGetAckOperationMode = ack print(op_mode.operation_mode.name) diff --git a/src/science_mode_4/dyscom/dyscom_get_battery_status.py b/src/science_mode_4/dyscom/dyscom_get_battery_status.py index 9979e06..538887e 100644 --- a/src/science_mode_4/dyscom/dyscom_get_battery_status.py +++ b/src/science_mode_4/dyscom/dyscom_get_battery_status.py @@ -3,7 +3,6 @@ from typing import NamedTuple import struct -from science_mode_4.protocol.commands import Commands from .dyscom_types import DyscomEnergyFlag, DyscomGetType from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -23,7 +22,6 @@ class PacketDyscomGetBatteryStatus(PacketDyscomGet): def __init__(self): super().__init__() - self._command = Commands.DL_GET self._type = DyscomGetType.BATTERY self._kind = int(self._type) diff --git a/src/science_mode_4/dyscom/dyscom_get_device_id.py b/src/science_mode_4/dyscom/dyscom_get_device_id.py index 0599000..468d60a 100644 --- a/src/science_mode_4/dyscom/dyscom_get_device_id.py +++ b/src/science_mode_4/dyscom/dyscom_get_device_id.py @@ -1,6 +1,5 @@ """Provides packet classes for dyscom get with type device id""" -from science_mode_4.protocol.commands import Commands from .dyscom_types import DyscomGetType from .dyscom_helper import DyscomHelper from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -12,7 +11,6 @@ class PacketDyscomGetDeviceId(PacketDyscomGet): def __init__(self): super().__init__() - self._command = Commands.DL_GET self._type = DyscomGetType.DEVICE_ID self._kind = int(self._type) 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 f34df5b..3d1610a 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 @@ -32,6 +32,8 @@ def get_data(self) -> bytes: bb = ByteBuilder() bb.append_bytes(super().get_data()) bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 128)) + # 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_get_file_info.py b/src/science_mode_4/dyscom/dyscom_get_file_info.py index 5fb27a2..962e9f4 100644 --- a/src/science_mode_4/dyscom/dyscom_get_file_info.py +++ b/src/science_mode_4/dyscom/dyscom_get_file_info.py @@ -2,7 +2,6 @@ from typing import NamedTuple -from science_mode_4.protocol.commands import Commands from science_mode_4.utils.byte_builder import ByteBuilder from .dyscom_types import DyscomGetType from .dyscom_helper import DyscomHelper @@ -22,7 +21,6 @@ class PacketDyscomGetFileInfo(PacketDyscomGet): def __init__(self, filename: str = ""): super().__init__() - self._command = Commands.DL_GET self._type = DyscomGetType.FILE_INFO self._kind = int(self._type) self._filename = filename @@ -32,6 +30,8 @@ def get_data(self) -> bytes: bb = ByteBuilder() bb.append_bytes(super().get_data()) bb.append_bytes(DyscomHelper.str_to_bytes(self._filename, 128)) + # maybe more parameters are necessary here + # file_size, file_checksum return bb.get_bytes() diff --git a/src/science_mode_4/dyscom/dyscom_get_file_system_status.py b/src/science_mode_4/dyscom/dyscom_get_file_system_status.py index 82e0a6d..93c1d56 100644 --- a/src/science_mode_4/dyscom/dyscom_get_file_system_status.py +++ b/src/science_mode_4/dyscom/dyscom_get_file_system_status.py @@ -3,7 +3,6 @@ # from dataclasses import dataclass from typing import NamedTuple -from science_mode_4.protocol.commands import Commands from .dyscom_types import DyscomGetType from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -21,7 +20,6 @@ class PacketDyscomGetFileSystemStatus(PacketDyscomGet): def __init__(self): super().__init__() - self._command = Commands.DL_GET self._type = DyscomGetType.FILESYSTEM_STATUS self._kind = int(self._type) diff --git a/src/science_mode_4/dyscom/dyscom_get_firmware_version.py b/src/science_mode_4/dyscom/dyscom_get_firmware_version.py index 5d7ba9c..5ad8037 100644 --- a/src/science_mode_4/dyscom/dyscom_get_firmware_version.py +++ b/src/science_mode_4/dyscom/dyscom_get_firmware_version.py @@ -1,6 +1,5 @@ """Provides packet classes for dyscom get with type firmware version""" -from science_mode_4.protocol.commands import Commands from .dyscom_types import DyscomGetType from .dyscom_helper import DyscomHelper from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -12,7 +11,6 @@ class PacketDyscomGetFirmwareVersion(PacketDyscomGet): def __init__(self): super().__init__() - self._command = Commands.DL_GET self._type = DyscomGetType.FIRMWARE_VERSION self._kind = int(self._type) diff --git a/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py b/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py index 7838891..a5882eb 100644 --- a/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py +++ b/src/science_mode_4/dyscom/dyscom_get_list_of_measurement_meta_info.py @@ -1,6 +1,5 @@ """Provides packet classes for dyscom get with type list of measurement meta info""" -from science_mode_4.protocol.commands import Commands from .dyscom_types import DyscomGetType from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -11,7 +10,6 @@ class PacketDyscomGetListOfMeasurementMetaInfo(PacketDyscomGet): def __init__(self): super().__init__() - self._command = Commands.DL_GET self._type = DyscomGetType.LIST_OF_MEASUREMENT_META_INFO self._kind = int(self._type) diff --git a/src/science_mode_4/dyscom/dyscom_get_operation_mode.py b/src/science_mode_4/dyscom/dyscom_get_operation_mode.py index cc52002..206d134 100644 --- a/src/science_mode_4/dyscom/dyscom_get_operation_mode.py +++ b/src/science_mode_4/dyscom/dyscom_get_operation_mode.py @@ -1,6 +1,5 @@ """Provides packet classes for dyscom get with type operation mode""" -from science_mode_4.protocol.commands import Commands from .dyscom_types import DyscomGetType, DyscomGetOperationModeType from .dyscom_get import PacketDyscomGet, PacketDyscomGetAck @@ -11,7 +10,6 @@ class PacketDyscomGetOperationMode(PacketDyscomGet): def __init__(self): super().__init__() - self._command = Commands.DL_GET self._type = DyscomGetType.OPERATION_MODE self._kind = int(self._type) diff --git a/src/science_mode_4/dyscom/dyscom_layer.py b/src/science_mode_4/dyscom/dyscom_layer.py index d097028..48c56f3 100644 --- a/src/science_mode_4/dyscom/dyscom_layer.py +++ b/src/science_mode_4/dyscom/dyscom_layer.py @@ -26,7 +26,7 @@ class LayerDyscom(Layer): """ - async def init(self, params) -> DyscomInitResult: + async def init(self, params: DyscomInitParams) -> DyscomInitResult: """Send dyscom init command and waits for response""" p = PacketDyscomInit(params) ack: PacketDyscomInitAck = await self.send_packet_and_wait(p) diff --git a/src/science_mode_4/dyscom/dyscom_send_file.py b/src/science_mode_4/dyscom/dyscom_send_file.py index b3e6522..a6cab24 100644 --- a/src/science_mode_4/dyscom/dyscom_send_file.py +++ b/src/science_mode_4/dyscom/dyscom_send_file.py @@ -59,4 +59,4 @@ def __init__(self, block_number: int = 0): def get_data(self) -> bytes: bb = ByteBuilder() bb.append_value(self._block_number, 4, True) - return bb.get_bytes() \ No newline at end of file + return bb.get_bytes() From 355f1e5d3f1ae9946c883c37f513895b387b4ce4 Mon Sep 17 00:00:00 2001 From: Marc Hofmann Date: Mon, 28 Apr 2025 20:32:46 +0200 Subject: [PATCH 3/5] pylint for examples --- .github/workflows/build-validation.yml | 6 ++++-- .pylintrc | 5 ++++- .vscode/launch.json | 4 ++-- examples/__init__.py | 7 +++++++ examples/dyscom/__init__.py | 6 ++++++ examples/general/__init__.py | 3 +++ examples/low_level/__init__.py | 4 ++++ examples/mid_level/__init__.py | 4 ++++ examples/utils/__init__.py | 7 +++++++ examples/utils/fastplotlib_utils.py | 4 ++-- examples/utils/pyplot_utils.py | 5 +++-- 11 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 examples/dyscom/__init__.py create mode 100644 examples/general/__init__.py create mode 100644 examples/low_level/__init__.py create mode 100644 examples/mid_level/__init__.py create mode 100644 examples/utils/__init__.py diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index 6a1ba32..b1734e4 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -25,5 +25,7 @@ jobs: --user - name: Build a binary wheel and a source tarball run: python3 -m build - - name: Run linter - run: pylint ./src/science_mode_4 + - name: Run linter lib + run: pylint ./src + - name: Run linter examples + run: pylint ./examples diff --git a/.pylintrc b/.pylintrc index e1a17f7..0bb322d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -9,4 +9,7 @@ max-statements=100 check-quote-consistency=yes [MESSAGES CONTROL] -disable=too-few-public-methods \ No newline at end of file +disable=too-few-public-methods + +[SIMILARITIES] +min-similarity-lines=50 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index f709071..ac009b5 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_fastplotlib", + // "program": "src/__main__.py", + "module": "examples.dyscom.example_dyscom_fastplotlib", "justMyCode": false, // "args": ["COM3"], "console": "integratedTerminal" diff --git a/examples/__init__.py b/examples/__init__.py index e69de29..3df4c04 100644 --- a/examples/__init__.py +++ b/examples/__init__.py @@ -0,0 +1,7 @@ +"""Init file for utils""" + +from .dyscom import * +from .general import * +from .low_level import * +from .mid_level import * +from .utils import * diff --git a/examples/dyscom/__init__.py b/examples/dyscom/__init__.py new file mode 100644 index 0000000..2c111cf --- /dev/null +++ b/examples/dyscom/__init__.py @@ -0,0 +1,6 @@ +"""Init file for utils""" + +from .example_dyscom_fastplotlib import * +from .example_dyscom_get import * +from .example_dyscom_pyplot import * +from .example_dyscom_write_csv import * diff --git a/examples/general/__init__.py b/examples/general/__init__.py new file mode 100644 index 0000000..38c8e24 --- /dev/null +++ b/examples/general/__init__.py @@ -0,0 +1,3 @@ +"""Init file for utils""" + +from .example_general import * diff --git a/examples/low_level/__init__.py b/examples/low_level/__init__.py new file mode 100644 index 0000000..0b38efd --- /dev/null +++ b/examples/low_level/__init__.py @@ -0,0 +1,4 @@ +"""Init file for utils""" + +from .example_low_level import * +from .example_low_level_plot import * diff --git a/examples/mid_level/__init__.py b/examples/mid_level/__init__.py new file mode 100644 index 0000000..f69a598 --- /dev/null +++ b/examples/mid_level/__init__.py @@ -0,0 +1,4 @@ +"""Init file for utils""" + +from .example_mid_level import * +from .example_mid_level_simple import * diff --git a/examples/utils/__init__.py b/examples/utils/__init__.py new file mode 100644 index 0000000..4978292 --- /dev/null +++ b/examples/utils/__init__.py @@ -0,0 +1,7 @@ +"""Init file for utils""" + +from .csv_utils import * +from .example_utils import * +from .fastplotlib_utils import * +from .plot_base import * +from .pyplot_utils import * diff --git a/examples/utils/fastplotlib_utils.py b/examples/utils/fastplotlib_utils.py index a34019e..539857f 100644 --- a/examples/utils/fastplotlib_utils.py +++ b/examples/utils/fastplotlib_utils.py @@ -5,7 +5,7 @@ import numpy as np import fastplotlib as fpl -from examples.utils.plot_base import PlotHelper, PlotValueChannel +from .plot_base import PlotHelper, PlotValueChannel class FastPlotLibValueChannel(PlotValueChannel): @@ -13,7 +13,7 @@ class FastPlotLibValueChannel(PlotValueChannel): def __init__(self, sub_plot, max_value_count: int, color: str): - """sub_plot type in Subplot from fastplotlib""" + """sub_plot type is Subplot from fastplotlib""" super().__init__(max_value_count) self._sub_plot = sub_plot diff --git a/examples/utils/pyplot_utils.py b/examples/utils/pyplot_utils.py index 8b1a0f5..73901d0 100644 --- a/examples/utils/pyplot_utils.py +++ b/examples/utils/pyplot_utils.py @@ -2,9 +2,9 @@ from queue import Empty, Full, Queue import matplotlib.pyplot as plt -import matplotlib.animation as animation +from matplotlib import animation -from examples.utils.plot_base import PlotHelper, PlotValueChannel +from .plot_base import PlotHelper, PlotValueChannel class PyPlotValueChannel(PlotValueChannel): @@ -91,6 +91,7 @@ def __init__(self, channels: dict[int, tuple[str, str]], max_value_count: int): def update(self): + """Wait for a short time to process events""" plt.pause(0.0001) From af10908feb866e5e44d6c48493e86e04f21e5c77 Mon Sep 17 00:00:00 2001 From: Marc Hofmann Date: Mon, 28 Apr 2025 20:34:44 +0200 Subject: [PATCH 4/5] wip --- .github/workflows/build-validation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-validation.yml b/.github/workflows/build-validation.yml index b1734e4..cec9d70 100644 --- a/.github/workflows/build-validation.yml +++ b/.github/workflows/build-validation.yml @@ -22,6 +22,7 @@ jobs: build pylint -r src/science_mode_4/requirements.txt + -r examples/requirements.txt --user - name: Build a binary wheel and a source tarball run: python3 -m build From e39ea7f282d57d08fe44411384aec2cfc6cb9213 Mon Sep 17 00:00:00 2001 From: Marc Hofmann Date: Mon, 28 Apr 2025 22:23:22 +0200 Subject: [PATCH 5/5] wip --- .vscode/launch.json | 2 +- examples/dyscom/example_dyscom_fastplotlib.py | 10 +++++----- examples/dyscom/example_dyscom_write_csv.py | 6 +++--- examples/mid_level/example_mid_level.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ac009b5..b484509 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "debugpy", "request": "launch", // "program": "src/__main__.py", - "module": "examples.dyscom.example_dyscom_fastplotlib", + "module": "examples.mid_level.example_mid_level", "justMyCode": false, // "args": ["COM3"], "console": "integratedTerminal" diff --git a/examples/dyscom/example_dyscom_fastplotlib.py b/examples/dyscom/example_dyscom_fastplotlib.py index 1e108a2..1cc2415 100644 --- a/examples/dyscom/example_dyscom_fastplotlib.py +++ b/examples/dyscom/example_dyscom_fastplotlib.py @@ -24,8 +24,8 @@ def main(): """Main function""" # initialize plot helper the handle plot specific things - plot_helper = FastPlotLibHelper({0: ["BI", "b"], 1: ["EMG1", "r"], 2: ["EMG2", "y"],\ - 3: ["Breathing", "g"], 4: ["Temperature", "w"]}, 1000) + plot_helper = FastPlotLibHelper({0: ["Channel 1", "b"], 1: ["Channel 2", "r"], 2: ["Channel 3", "y"],\ + 3: ["Channel 4", "g"]}, 1000) # flag to indicate is_window_open: bool = True @@ -53,7 +53,7 @@ async def device_communication() -> int: # call init with lowest sample rate and enable signal types init_params = DyscomInitParams() init_params.signal_type = [DyscomSignalType.BI, DyscomSignalType.EMG_1,\ - DyscomSignalType.EMG_2, DyscomSignalType.BREATHING, DyscomSignalType.TEMPERATURE] + DyscomSignalType.EMG_2, DyscomSignalType.BREATHING] 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) @@ -93,8 +93,8 @@ async def device_communication() -> int: print(f"SendLiveData status error {sld.samples}") break - for x, sample in enumerate(sld.samples): - plot_helper.append_value(x, sample.value) + for x in range(4): + plot_helper.append_value(x, sld.samples[x].value) plot_helper.update() else: diff --git a/examples/dyscom/example_dyscom_write_csv.py b/examples/dyscom/example_dyscom_write_csv.py index 134b1be..28b2517 100644 --- a/examples/dyscom/example_dyscom_write_csv.py +++ b/examples/dyscom/example_dyscom_write_csv.py @@ -22,7 +22,7 @@ def main(): """Main function""" - csv_helper = CsvHelper("values.csv", ["package_nr", "bi", "emg_1", "emg_2", "breathing", "temperature", "time_delta"]) + csv_helper = CsvHelper("values.csv", ["package_nr", "Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "time_delta"]) csv_helper.start() async def device_communication() -> int: @@ -52,7 +52,7 @@ async def device_communication() -> int: # call init with 4k sample rate and enable signal types init_params = DyscomInitParams() init_params.signal_type = [DyscomSignalType.BI, DyscomSignalType.EMG_1,\ - DyscomSignalType.EMG_2, DyscomSignalType.BREATHING, DyscomSignalType.TEMPERATURE] + DyscomSignalType.EMG_2, DyscomSignalType.BREATHING] 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) @@ -100,7 +100,7 @@ async def device_communication() -> int: # print(f"Live data acknowledges per iteration {live_data_counter}") break - await asyncio.sleep(0.001) + # await asyncio.sleep(0.001) # print stats end_time = timer() diff --git a/examples/mid_level/example_mid_level.py b/examples/mid_level/example_mid_level.py index 5bac1e2..31fa959 100644 --- a/examples/mid_level/example_mid_level.py +++ b/examples/mid_level/example_mid_level.py @@ -13,7 +13,7 @@ async def main() -> int: """Main function""" - # keyboard is our trigger to start specific stimulation + # keyboard is our trigger to end program def input_callback(input_value: str) -> bool: """Callback call from keyboard input thread""" # print(f"Input value {input_value}")