From 336033b8b5bd873e7c7f4f34a69092afc20a387b Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:41:43 +0800 Subject: [PATCH 01/24] bt_api_example running --- examples/bt_api_example.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/bt_api_example.py b/examples/bt_api_example.py index d169ede..d3c39b7 100644 --- a/examples/bt_api_example.py +++ b/examples/bt_api_example.py @@ -1,4 +1,6 @@ import time +import sys +sys.path.append(r'C:\Users\Acer-User\git\pyshimmer') from serial import Serial @@ -13,7 +15,7 @@ def stream_cb(pkt: DataPacket) -> None: print('') def main(args=None): - serial = Serial('/dev/rfcomm42', DEFAULT_BAUDRATE) + serial = Serial('COM43', DEFAULT_BAUDRATE) shim_dev = ShimmerBluetooth(serial) shim_dev.initialize() From 213f707bbba03ced316dfd5b0aa61ac855176390 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:49:40 +0800 Subject: [PATCH 02/24] Revert "bt_api_example running" This reverts commit 336033b8b5bd873e7c7f4f34a69092afc20a387b. --- examples/bt_api_example.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/bt_api_example.py b/examples/bt_api_example.py index d3c39b7..d169ede 100644 --- a/examples/bt_api_example.py +++ b/examples/bt_api_example.py @@ -1,6 +1,4 @@ import time -import sys -sys.path.append(r'C:\Users\Acer-User\git\pyshimmer') from serial import Serial @@ -15,7 +13,7 @@ def stream_cb(pkt: DataPacket) -> None: print('') def main(args=None): - serial = Serial('COM43', DEFAULT_BAUDRATE) + serial = Serial('/dev/rfcomm42', DEFAULT_BAUDRATE) shim_dev = ShimmerBluetooth(serial) shim_dev.initialize() From ddaf592db9339bd7800fa641916326756676c268 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Tue, 25 Mar 2025 15:29:27 +0800 Subject: [PATCH 03/24] Implementing Retrieve HW Version --- examples/bt_api_example.py | 14 ++++++---- pyshimmer/bluetooth/bt_api.py | 25 ++++++++++++++++- pyshimmer/bluetooth/bt_commands.py | 45 +++++++++++++++++++++++++++++- pyshimmer/bluetooth/bt_const.py | 4 +++ 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/examples/bt_api_example.py b/examples/bt_api_example.py index d3c39b7..2c993af 100644 --- a/examples/bt_api_example.py +++ b/examples/bt_api_example.py @@ -15,23 +15,27 @@ def stream_cb(pkt: DataPacket) -> None: print('') def main(args=None): - serial = Serial('COM43', DEFAULT_BAUDRATE) + # serial = Serial('COM5', DEFAULT_BAUDRATE) + serial = Serial('COM14', DEFAULT_BAUDRATE) shim_dev = ShimmerBluetooth(serial) shim_dev.initialize() dev_name = shim_dev.get_device_name() print(f'My name is: {dev_name}') + + # dev_hardware_ver = shim_dev.get_device_hardware_version() + # print(f'My hardware version is: {dev_hardware_ver}') info = shim_dev.get_firmware_version() print("- firmware: [" + str(info[0]) + "]") print("- version: [" + str(info[1].major) + "." + str(info[1].minor) + "." + str(info[1].rel) + "]") - shim_dev.add_stream_callback(stream_cb) + # shim_dev.add_stream_callback(stream_cb) - shim_dev.start_streaming() - time.sleep(5.0) - shim_dev.stop_streaming() + # shim_dev.start_streaming() + # time.sleep(5.0) + # shim_dev.stop_streaming() shim_dev.shutdown() diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index 36033c2..52aba6f 100644 --- a/pyshimmer/bluetooth/bt_api.py +++ b/pyshimmer/bluetooth/bt_api.py @@ -19,7 +19,7 @@ from serial import Serial -from pyshimmer.bluetooth.bt_commands import ShimmerCommand, GetSamplingRateCommand, GetConfigTimeCommand, \ +from pyshimmer.bluetooth.bt_commands import GetShimmerHardwareVersion, ShimmerCommand, GetSamplingRateCommand, GetConfigTimeCommand, \ SetConfigTimeCommand, GetRealTimeClockCommand, SetRealTimeClockCommand, GetStatusCommand, \ GetFirmwareVersionCommand, InquiryCommand, StartStreamingCommand, StopStreamingCommand, DataPacket, \ GetEXGRegsCommand, SetEXGRegsCommand, StartLoggingCommand, StopLoggingCommand, GetExperimentIDCommand, \ @@ -33,6 +33,8 @@ from pyshimmer.serial_base import ReadAbort from pyshimmer.util import fmt_hex, PeekQueue +import time + class RequestCompletion: """ @@ -183,6 +185,12 @@ def _process_status_update(self): cb(r) def _process_resp_from_queue(self): + + # DEBUGGING + time.sleep(0.2) + if self._resp_queue.empty(): + print("DEBUG: Response queue is empty!") + cmd, return_obj = self._resp_queue.get_nowait() resp_code = cmd.get_response_code() @@ -503,6 +511,13 @@ def set_device_name(self, dev_name: str) -> None: :param dev_name: The device name to set """ self._process_and_wait(SetDeviceNameCommand(dev_name)) + + def get_device_hardware_version(self) -> any: + """Retrieve the device hardware version + + :return: The device hardware version as string + """ + return self._process_and_wait(GetShimmerHardwareVersion()) def get_experiment_id(self) -> str: """Retrieve the experiment id as string @@ -527,6 +542,14 @@ def get_inquiry(self) -> Tuple[float, int, List[EChannelType]]: - The active data channels of the device as list, does not include the TIMESTAMP channel """ return self._process_and_wait(InquiryCommand()) + + # print("DEBUG: Sending InquiryCommand()...") # Print before sending + + # response = self._process_and_wait(InquiryCommand()) # Might be hanging + + # print(f"DEBUG: Raw inquiry response: {response}") # Should print if it returns + + # return response def get_data_types(self): """Get the active data channels of the device diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index 5813fa7..78454da 100644 --- a/pyshimmer/bluetooth/bt_commands.py +++ b/pyshimmer/bluetooth/bt_commands.py @@ -391,6 +391,26 @@ def send(self, ser: BluetoothSerial) -> None: ser.write_command(INQUIRY_COMMAND) def receive(self, ser: BluetoothSerial) -> any: + + # # Read Raw Response + # raw_response = ser.read_response(INQUIRY_RESPONSE, arg_format=' None: + print("DEBUG: Sending GET_SHIMMER_VERSION_COMMAND") + ser.write_command(GET_SHIMMER_VERSION_COMMAND) + + def received(self, ser: BluetoothSerial) -> any: + # bufferbyte = ser.read(1) + # hw_version = bufferbyte[0] + + hw_version = ser.read_response(SHIMMER_VERSION_RESPONSE, arg_format=' Date: Wed, 26 Mar 2025 13:34:02 +0800 Subject: [PATCH 04/24] Retrieve HW Version --- examples/bt_api_example.py | 6 ++--- pyshimmer/bluetooth/bt_api.py | 8 ------- pyshimmer/bluetooth/bt_commands.py | 38 +++++++----------------------- pyshimmer/bluetooth/bt_const.py | 1 - 4 files changed, 11 insertions(+), 42 deletions(-) diff --git a/examples/bt_api_example.py b/examples/bt_api_example.py index 2c993af..3c97a4e 100644 --- a/examples/bt_api_example.py +++ b/examples/bt_api_example.py @@ -1,9 +1,7 @@ -import time import sys sys.path.append(r'C:\Users\Acer-User\git\pyshimmer') from serial import Serial - from pyshimmer import ShimmerBluetooth, DEFAULT_BAUDRATE, DataPacket @@ -24,8 +22,8 @@ def main(args=None): dev_name = shim_dev.get_device_name() print(f'My name is: {dev_name}') - # dev_hardware_ver = shim_dev.get_device_hardware_version() - # print(f'My hardware version is: {dev_hardware_ver}') + dev_hardware_ver = shim_dev.get_device_hardware_version() + print(f'My hardware version is: {dev_hardware_ver}') info = shim_dev.get_firmware_version() print("- firmware: [" + str(info[0]) + "]") diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index 52aba6f..bef5894 100644 --- a/pyshimmer/bluetooth/bt_api.py +++ b/pyshimmer/bluetooth/bt_api.py @@ -542,14 +542,6 @@ def get_inquiry(self) -> Tuple[float, int, List[EChannelType]]: - The active data channels of the device as list, does not include the TIMESTAMP channel """ return self._process_and_wait(InquiryCommand()) - - # print("DEBUG: Sending InquiryCommand()...") # Print before sending - - # response = self._process_and_wait(InquiryCommand()) # Might be hanging - - # print(f"DEBUG: Raw inquiry response: {response}") # Should print if it returns - - # return response def get_data_types(self): """Get the active data channels of the device diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index 78454da..05e1282 100644 --- a/pyshimmer/bluetooth/bt_commands.py +++ b/pyshimmer/bluetooth/bt_commands.py @@ -391,26 +391,6 @@ def send(self, ser: BluetoothSerial) -> None: ser.write_command(INQUIRY_COMMAND) def receive(self, ser: BluetoothSerial) -> any: - - # # Read Raw Response - # raw_response = ser.read_response(INQUIRY_RESPONSE, arg_format=' None: - print("DEBUG: Sending GET_SHIMMER_VERSION_COMMAND") ser.write_command(GET_SHIMMER_VERSION_COMMAND) - def received(self, ser: BluetoothSerial) -> any: - # bufferbyte = ser.read(1) - # hw_version = bufferbyte[0] - + def receive(self, ser: BluetoothSerial) -> any: hw_version = ser.read_response(SHIMMER_VERSION_RESPONSE, arg_format=' Date: Wed, 26 Mar 2025 14:16:57 +0800 Subject: [PATCH 05/24] reverting some changes --- examples/bt_api_example.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/bt_api_example.py b/examples/bt_api_example.py index 3c97a4e..2c8b124 100644 --- a/examples/bt_api_example.py +++ b/examples/bt_api_example.py @@ -1,5 +1,4 @@ -import sys -sys.path.append(r'C:\Users\Acer-User\git\pyshimmer') +import time from serial import Serial from pyshimmer import ShimmerBluetooth, DEFAULT_BAUDRATE, DataPacket @@ -13,8 +12,7 @@ def stream_cb(pkt: DataPacket) -> None: print('') def main(args=None): - # serial = Serial('COM5', DEFAULT_BAUDRATE) - serial = Serial('COM14', DEFAULT_BAUDRATE) + serial = Serial('/dev/rfcomm42', DEFAULT_BAUDRATE) shim_dev = ShimmerBluetooth(serial) shim_dev.initialize() @@ -29,11 +27,11 @@ def main(args=None): print("- firmware: [" + str(info[0]) + "]") print("- version: [" + str(info[1].major) + "." + str(info[1].minor) + "." + str(info[1].rel) + "]") - # shim_dev.add_stream_callback(stream_cb) + shim_dev.add_stream_callback(stream_cb) - # shim_dev.start_streaming() - # time.sleep(5.0) - # shim_dev.stop_streaming() + shim_dev.start_streaming() + time.sleep(5.0) + shim_dev.stop_streaming() shim_dev.shutdown() From cb63df96e056c4c9d6cb3797ff2665ad4367be8a Mon Sep 17 00:00:00 2001 From: JongChern Date: Wed, 26 Mar 2025 14:19:56 +0800 Subject: [PATCH 06/24] Update bt_api_example.py --- examples/bt_api_example.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/bt_api_example.py b/examples/bt_api_example.py index 2c8b124..d169ede 100644 --- a/examples/bt_api_example.py +++ b/examples/bt_api_example.py @@ -1,6 +1,7 @@ import time from serial import Serial + from pyshimmer import ShimmerBluetooth, DEFAULT_BAUDRATE, DataPacket @@ -19,9 +20,6 @@ def main(args=None): dev_name = shim_dev.get_device_name() print(f'My name is: {dev_name}') - - dev_hardware_ver = shim_dev.get_device_hardware_version() - print(f'My hardware version is: {dev_hardware_ver}') info = shim_dev.get_firmware_version() print("- firmware: [" + str(info[0]) + "]") From bb01d0edb378ac369fc143464a634b13a4249144 Mon Sep 17 00:00:00 2001 From: JongChern Date: Wed, 26 Mar 2025 14:40:10 +0800 Subject: [PATCH 07/24] minor updates --- pyshimmer/bluetooth/bt_api.py | 13 +++++-------- pyshimmer/bluetooth/bt_commands.py | 10 +--------- pyshimmer/bluetooth/bt_const.py | 10 ++++++++++ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index bef5894..32c282c 100644 --- a/pyshimmer/bluetooth/bt_api.py +++ b/pyshimmer/bluetooth/bt_api.py @@ -33,8 +33,6 @@ from pyshimmer.serial_base import ReadAbort from pyshimmer.util import fmt_hex, PeekQueue -import time - class RequestCompletion: """ @@ -186,11 +184,6 @@ def _process_status_update(self): def _process_resp_from_queue(self): - # DEBUGGING - time.sleep(0.2) - if self._resp_queue.empty(): - print("DEBUG: Response queue is empty!") - cmd, return_obj = self._resp_queue.get_nowait() resp_code = cmd.get_response_code() @@ -291,6 +284,7 @@ def __init__(self, serial: Serial, disable_status_ack: bool = True): self._fw_version: Optional[FirmwareVersion] = None self._fw_caps: Optional[FirmwareCapabilities] = None + self._hw_version = None @property def initialized(self) -> bool: @@ -319,6 +313,9 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, exc_traceback): self.shutdown() + def _set_hw_capabilities(self) -> None: + self._hw_version = self.get_device_hardware_version() + def _set_fw_capabilities(self) -> None: fw_type, fw_ver = self.get_firmware_version() self._fw_caps = FirmwareCapabilities(fw_type, fw_ver) @@ -330,7 +327,7 @@ def initialize(self) -> None: optionally disables the status acknowledgment and starts the read loop. """ self._thread.start() - + self._set_hw_capabilities() self._set_fw_capabilities() if self.capabilities.supports_ack_disable and self._disable_ack: diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index 05e1282..fd076b7 100644 --- a/pyshimmer/bluetooth/bt_commands.py +++ b/pyshimmer/bluetooth/bt_commands.py @@ -510,14 +510,6 @@ class GetShimmerHardwareVersion(ResponseCommand): """Get the device hardware version """ - - SHIMMER_VERSION_MAP = { - 0: "SHIMMER1", - 1: "SHIMMER2", - 2: "SHIMMER2R", - 3: "SHIMMER3", - 10: "SHIMMER3R" } - def __init__(self): super().__init__(SHIMMER_VERSION_RESPONSE) @@ -526,7 +518,7 @@ def send(self, ser: BluetoothSerial) -> None: def receive(self, ser: BluetoothSerial) -> any: hw_version = ser.read_response(SHIMMER_VERSION_RESPONSE, arg_format=' Date: Thu, 3 Apr 2025 15:11:52 +0800 Subject: [PATCH 08/24] Added Unittests for Hw Version Command --- test/bluetooth/test_bluetooth_api.py | 9 +++++++++ test/bluetooth/test_bt_commands.py | 11 ++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/test/bluetooth/test_bluetooth_api.py b/test/bluetooth/test_bluetooth_api.py index dde8aa3..3b67fab 100644 --- a/test/bluetooth/test_bluetooth_api.py +++ b/test/bluetooth/test_bluetooth_api.py @@ -16,6 +16,7 @@ from concurrent.futures import ThreadPoolExecutor, Future from typing import Optional, BinaryIO, List, Callable from unittest import TestCase +from urllib.request import HTTPPasswordMgrWithDefaultRealm from pyshimmer.bluetooth.bt_api import BluetoothRequestHandler, ShimmerBluetooth from pyshimmer.bluetooth.bt_commands import GetDeviceNameCommand, SetDeviceNameCommand, DataPacket, GetStatusCommand, \ @@ -440,6 +441,14 @@ def test_get_firmware_version(self): self.assertEqual(fwtype, EFirmwareType.LogAndStream) self.assertEqual(fwver, FirmwareVersion(1, 2, 3)) + def test_get_hardware_version(self): + self.do_setup() + + self._submit_req_resp_handler(1, b'\xFF\x25\x0A') + hw_version = self._sot.get_device_hardware_version() + + self.assertEqual(hw_version, "SHIMMER3R") + def test_status_ack_disable(self): self.do_setup(initialize=False) diff --git a/test/bluetooth/test_bt_commands.py b/test/bluetooth/test_bt_commands.py index 1b76393..547c35a 100644 --- a/test/bluetooth/test_bt_commands.py +++ b/test/bluetooth/test_bt_commands.py @@ -16,7 +16,7 @@ from typing import Tuple, Union from unittest import TestCase -from pyshimmer.bluetooth.bt_commands import ShimmerCommand, GetSamplingRateCommand, GetBatteryCommand, \ +from pyshimmer.bluetooth.bt_commands import GetShimmerHardwareVersion, ShimmerCommand, GetSamplingRateCommand, GetBatteryCommand, \ GetConfigTimeCommand, SetConfigTimeCommand, GetRealTimeClockCommand, SetRealTimeClockCommand, GetStatusCommand, \ GetFirmwareVersionCommand, InquiryCommand, StartStreamingCommand, StopStreamingCommand, StartLoggingCommand, \ StopLoggingCommand, GetEXGRegsCommand, SetEXGRegsCommand, GetExperimentIDCommand, SetExperimentIDCommand, \ @@ -203,6 +203,15 @@ def test_get_device_name_command(self): cmd = GetDeviceNameCommand() self.assert_cmd(cmd, b'\x7b', b'\x7a', b'\x7a\x05S_PPG', 'S_PPG') + def test_get_hardware_version(self): + cmd = GetShimmerHardwareVersion() + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x00', 'SHIMMER1') + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x01', 'SHIMMER2') + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x02', 'SHIMMER2R') + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x03', 'SHIMMER3') + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x0a', 'SHIMMER3R') + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x04', 'Unknown Version: (4)') + def test_set_device_name_command(self): cmd = SetDeviceNameCommand('S_PPG') self.assert_cmd(cmd, b'\x79\x05S_PPG') From e08ce9d2e1e94fea9b7d3db57d68f652eba92aa7 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:47:35 +0800 Subject: [PATCH 09/24] Update unittests HW Version --- test/bluetooth/test_bluetooth_api.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/bluetooth/test_bluetooth_api.py b/test/bluetooth/test_bluetooth_api.py index 3b67fab..c2de83a 100644 --- a/test/bluetooth/test_bluetooth_api.py +++ b/test/bluetooth/test_bluetooth_api.py @@ -334,6 +334,7 @@ def do_setup(self, initialize: bool = True, **kwargs) -> None: if initialize: # The Bluetooth API automatically requests the firmware version upon initialization. # We must prepare a proper response beforehand. + self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') future = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') self._sot.initialize() @@ -350,6 +351,7 @@ def test_context_manager(self): # The Bluetooth API automatically requests the firmware version upon initialization. # We must prepare a proper response beforehand. + req_future = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') req_future = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') with self._sot: # We check that the API properly asked for the firmware version @@ -443,16 +445,24 @@ def test_get_firmware_version(self): def test_get_hardware_version(self): self.do_setup() - + + self._submit_req_resp_handler(1, b'\xFF\x25\x03') + hw_version = self._sot.get_device_hardware_version() + self.assertEqual(hw_version, "SHIMMER3") + self._submit_req_resp_handler(1, b'\xFF\x25\x0A') hw_version = self._sot.get_device_hardware_version() - - self.assertEqual(hw_version, "SHIMMER3R") + self.assertEqual(hw_version, "SHIMMER3R") + + self._submit_req_resp_handler(1, b'\xFF\x25\x04') + hw_version = self._sot.get_device_hardware_version() + self.assertEqual(hw_version, "Unknown Version: (4)") def test_status_ack_disable(self): self.do_setup(initialize=False) # Queue response for version command + self._submit_req_resp_handler(1, b'\xFF\x25\x03') self._submit_req_resp_handler(1, b'\xFF\x2F\x03\x00\x00\x00\x0F\x04') # Queue response for disabling the status acknowledgment req_future = self._submit_req_resp_handler(2, b'\xFF') @@ -465,5 +475,6 @@ def test_status_ack_not_disable(self): self.do_setup(initialize=False, disable_status_ack=False) # Queue response for version command + self._submit_req_resp_handler(1, b'\xFF\x25\x03') self._submit_req_resp_handler(1, b'\xFF\x2F\x03\x00\x00\x00\x0F\x04') self._sot.initialize() From 0d83b342b3a34b9ad66ef0b1580e20ea2ddc1daa Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Mon, 7 Apr 2025 13:53:42 +0800 Subject: [PATCH 10/24] Shim3 Update Num Sensors / Bytes in AllCalibration --- pyshimmer/bluetooth/bt_api.py | 3 ++- pyshimmer/bluetooth/bt_commands.py | 6 ++++-- pyshimmer/dev/calibration.py | 18 +++++++++++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index 32c282c..6bbf9bf 100644 --- a/pyshimmer/bluetooth/bt_api.py +++ b/pyshimmer/bluetooth/bt_api.py @@ -484,7 +484,8 @@ def get_all_calibration(self) -> AllCalibration: """Gets all calibration data from sensor :return: An AllCalibration object that presents the calibration contents in an easily processable manner """ - return self._process_and_wait(GetAllCalibrationCommand()) + hw_version = self._process_and_wait(GetShimmerHardwareVersion()) + return self._process_and_wait(GetAllCalibrationCommand(hw_version)) def set_exg_register(self, chip_id: int, offset: int, data: bytes) -> None: """Configure part of the memory of the ExG registers diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index fd076b7..2312f98 100644 --- a/pyshimmer/bluetooth/bt_commands.py +++ b/pyshimmer/bluetooth/bt_commands.py @@ -358,11 +358,12 @@ class GetAllCalibrationCommand(ResponseCommand): [bytes 12-20] alignment matrix: 9 values 8-bit signed integers. """ - def __init__(self): + def __init__(self, hw_version: str): super().__init__(ALL_CALIBRATION_RESPONSE) self._offset = 0x0 self._rlen = 0x54 # 84 bytes + self.hw_version = hw_version def send(self, ser: BluetoothSerial) -> None: ser.write_command(GET_ALL_CALIBRATION_COMMAND) @@ -370,7 +371,8 @@ def send(self, ser: BluetoothSerial) -> None: def receive(self, ser: BluetoothSerial) -> any: ser.read_response(ALL_CALIBRATION_RESPONSE) reg_data = ser.read(self._rlen) - return AllCalibration(reg_data) + + return AllCalibration(reg_data, self.hw_version) class InquiryCommand(ResponseCommand): diff --git a/pyshimmer/dev/calibration.py b/pyshimmer/dev/calibration.py index 74dee01..49c1d54 100644 --- a/pyshimmer/dev/calibration.py +++ b/pyshimmer/dev/calibration.py @@ -22,16 +22,28 @@ class AllCalibration: - def __init__(self, reg_bin: bytes): - self._num_bytes = 84 + def __init__(self, reg_bin: bytes, hw_version: str): self._sensor_bytes = 21 - self._num_sensors = 4 + self._num_sensors = self._get_num_sensors_from_hw(hw_version) + self._num_bytes = self._num_sensors * self._sensor_bytes if len(reg_bin) < self._num_bytes: raise ValueError( f'All calibration data must have length {self._num_bytes}') self._reg_bin = reg_bin + + def _get_num_sensors_from_hw(self, hw_version: str) -> int: + version_map = { + "SHIMMER1": 4, + "SHIMMER2": 4, + "SHIMMER2R": 4, + "SHIMMER3": 4, + "SHIMMER3R": 6 + } + if hw_version not in version_map: + raise ValueError(f"Unsupported hardware version: {hw_version}") + return version_map[hw_version] def __str__(self) -> str: def print_sensor(sens_num: int) -> str: From f2012025c01bac26fc30b0a038a94a4e5a162173 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Mon, 7 Apr 2025 13:55:20 +0800 Subject: [PATCH 11/24] Revert "Shim3 Update Num Sensors / Bytes in AllCalibration" This reverts commit 0d83b342b3a34b9ad66ef0b1580e20ea2ddc1daa. --- pyshimmer/bluetooth/bt_api.py | 3 +-- pyshimmer/bluetooth/bt_commands.py | 6 ++---- pyshimmer/dev/calibration.py | 18 +++--------------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index 6bbf9bf..32c282c 100644 --- a/pyshimmer/bluetooth/bt_api.py +++ b/pyshimmer/bluetooth/bt_api.py @@ -484,8 +484,7 @@ def get_all_calibration(self) -> AllCalibration: """Gets all calibration data from sensor :return: An AllCalibration object that presents the calibration contents in an easily processable manner """ - hw_version = self._process_and_wait(GetShimmerHardwareVersion()) - return self._process_and_wait(GetAllCalibrationCommand(hw_version)) + return self._process_and_wait(GetAllCalibrationCommand()) def set_exg_register(self, chip_id: int, offset: int, data: bytes) -> None: """Configure part of the memory of the ExG registers diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index 2312f98..fd076b7 100644 --- a/pyshimmer/bluetooth/bt_commands.py +++ b/pyshimmer/bluetooth/bt_commands.py @@ -358,12 +358,11 @@ class GetAllCalibrationCommand(ResponseCommand): [bytes 12-20] alignment matrix: 9 values 8-bit signed integers. """ - def __init__(self, hw_version: str): + def __init__(self): super().__init__(ALL_CALIBRATION_RESPONSE) self._offset = 0x0 self._rlen = 0x54 # 84 bytes - self.hw_version = hw_version def send(self, ser: BluetoothSerial) -> None: ser.write_command(GET_ALL_CALIBRATION_COMMAND) @@ -371,8 +370,7 @@ def send(self, ser: BluetoothSerial) -> None: def receive(self, ser: BluetoothSerial) -> any: ser.read_response(ALL_CALIBRATION_RESPONSE) reg_data = ser.read(self._rlen) - - return AllCalibration(reg_data, self.hw_version) + return AllCalibration(reg_data) class InquiryCommand(ResponseCommand): diff --git a/pyshimmer/dev/calibration.py b/pyshimmer/dev/calibration.py index 49c1d54..74dee01 100644 --- a/pyshimmer/dev/calibration.py +++ b/pyshimmer/dev/calibration.py @@ -22,28 +22,16 @@ class AllCalibration: - def __init__(self, reg_bin: bytes, hw_version: str): + def __init__(self, reg_bin: bytes): + self._num_bytes = 84 self._sensor_bytes = 21 - self._num_sensors = self._get_num_sensors_from_hw(hw_version) - self._num_bytes = self._num_sensors * self._sensor_bytes + self._num_sensors = 4 if len(reg_bin) < self._num_bytes: raise ValueError( f'All calibration data must have length {self._num_bytes}') self._reg_bin = reg_bin - - def _get_num_sensors_from_hw(self, hw_version: str) -> int: - version_map = { - "SHIMMER1": 4, - "SHIMMER2": 4, - "SHIMMER2R": 4, - "SHIMMER3": 4, - "SHIMMER3R": 6 - } - if hw_version not in version_map: - raise ValueError(f"Unsupported hardware version: {hw_version}") - return version_map[hw_version] def __str__(self) -> str: def print_sensor(sens_num: int) -> str: From fc154d72d894edc2ad76a1988cb611079a828c46 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:02:50 +0800 Subject: [PATCH 12/24] Added HardwareVersion IntEnum class & updated HW version retrieval --- pyshimmer/bluetooth/bt_api.py | 12 ++++-------- pyshimmer/bluetooth/bt_commands.py | 4 ++-- pyshimmer/bluetooth/bt_const.py | 12 +----------- pyshimmer/dev/fw_version.py | 25 ++++++++++++++++++++++++- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index 32c282c..aeb4bba 100644 --- a/pyshimmer/bluetooth/bt_api.py +++ b/pyshimmer/bluetooth/bt_api.py @@ -29,7 +29,7 @@ from pyshimmer.bluetooth.bt_serial import BluetoothSerial from pyshimmer.dev.channels import ChDataTypeAssignment, ChannelDataType, EChannelType, ESensorGroup from pyshimmer.dev.exg import ExGRegister -from pyshimmer.dev.fw_version import EFirmwareType, FirmwareVersion, FirmwareCapabilities +from pyshimmer.dev.fw_version import EFirmwareType, FirmwareVersion, FirmwareCapabilities, HardwareVersion from pyshimmer.serial_base import ReadAbort from pyshimmer.util import fmt_hex, PeekQueue @@ -183,7 +183,6 @@ def _process_status_update(self): cb(r) def _process_resp_from_queue(self): - cmd, return_obj = self._resp_queue.get_nowait() resp_code = cmd.get_response_code() @@ -284,7 +283,7 @@ def __init__(self, serial: Serial, disable_status_ack: bool = True): self._fw_version: Optional[FirmwareVersion] = None self._fw_caps: Optional[FirmwareCapabilities] = None - self._hw_version = None + self._hw_version: Optional[HardwareVersion] = None @property def initialized(self) -> bool: @@ -313,12 +312,10 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, exc_traceback): self.shutdown() - def _set_hw_capabilities(self) -> None: - self._hw_version = self.get_device_hardware_version() - def _set_fw_capabilities(self) -> None: fw_type, fw_ver = self.get_firmware_version() self._fw_caps = FirmwareCapabilities(fw_type, fw_ver) + self._hw_version = self.get_device_hardware_version() def initialize(self) -> None: """Initialize the Bluetooth connection @@ -327,7 +324,6 @@ def initialize(self) -> None: optionally disables the status acknowledgment and starts the read loop. """ self._thread.start() - self._set_hw_capabilities() self._set_fw_capabilities() if self.capabilities.supports_ack_disable and self._disable_ack: @@ -509,7 +505,7 @@ def set_device_name(self, dev_name: str) -> None: """ self._process_and_wait(SetDeviceNameCommand(dev_name)) - def get_device_hardware_version(self) -> any: + def get_device_hardware_version(self) -> HardwareVersion: """Retrieve the device hardware version :return: The device hardware version as string diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index fd076b7..4f9d733 100644 --- a/pyshimmer/bluetooth/bt_commands.py +++ b/pyshimmer/bluetooth/bt_commands.py @@ -24,7 +24,7 @@ from pyshimmer.dev.channels import ChannelDataType, EChannelType, ESensorGroup, serialize_sensorlist from pyshimmer.dev.exg import ExGRegister from pyshimmer.dev.calibration import AllCalibration -from pyshimmer.dev.fw_version import get_firmware_type +from pyshimmer.dev.fw_version import HardwareVersion, get_firmware_type from pyshimmer.util import bit_is_set, resp_code_to_bytes, calibrate_u12_adc_value, battery_voltage_to_percent @@ -518,7 +518,7 @@ def send(self, ser: BluetoothSerial) -> None: def receive(self, ser: BluetoothSerial) -> any: hw_version = ser.read_response(SHIMMER_VERSION_RESPONSE, arg_format='. -from enum import Enum, auto +from enum import Enum, IntEnum, auto def ensure_firmware_version(func): @@ -99,3 +99,26 @@ def get_firmware_type(f_type: int) -> EFirmwareType: raise ValueError(f'Unknown firmware type: 0x{f_type:x}') return FirmwareTypeValueAssignment[f_type] + + +class HardwareVersion(IntEnum): + """Represents supported Shimmer device hardware versions + + Each enum member corresponds to a specific hardware version of the Shimmer device, + using an integer identifier returned by the device firmware + """ + SHIMMER1 = 0 + SHIMMER2 = 1 + SHIMMER2R = 2 + SHIMMER3 = 3 + SHIMMER3R = 10 + UNKNOWN = -1 + + @classmethod + def from_int(cls, value: int) -> 'HardwareVersion': + """Creates a HardwareVersion enum meber from an integer value + + :param value: The Integer value returned by the Shimmer device representing hardware version + :return: A corresponding HardwareVersion enum member, or UNKNOWN if unsupported + """ + return cls._value2member_map_.get(value, cls.UNKNOWN) \ No newline at end of file From 5f84ad062760a75c0994916fe0f60cc5236f2002 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:34:05 +0800 Subject: [PATCH 13/24] Edit docstrings --- pyshimmer/dev/fw_version.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyshimmer/dev/fw_version.py b/pyshimmer/dev/fw_version.py index 7e667d7..974765e 100644 --- a/pyshimmer/dev/fw_version.py +++ b/pyshimmer/dev/fw_version.py @@ -102,10 +102,8 @@ def get_firmware_type(f_type: int) -> EFirmwareType: class HardwareVersion(IntEnum): - """Represents supported Shimmer device hardware versions + """Represents the supported Shimmer device hardware versions - Each enum member corresponds to a specific hardware version of the Shimmer device, - using an integer identifier returned by the device firmware """ SHIMMER1 = 0 SHIMMER2 = 1 @@ -116,9 +114,9 @@ class HardwareVersion(IntEnum): @classmethod def from_int(cls, value: int) -> 'HardwareVersion': - """Creates a HardwareVersion enum meber from an integer value + """Converts an Integer to the corresponding HardwareVersion enum - :param value: The Integer value returned by the Shimmer device representing hardware version - :return: A corresponding HardwareVersion enum member, or UNKNOWN if unsupported + :param value: Integer representing device hardware version + :return: Corresponding HardwareVersion enum member, or UNKNOWN if unrecognised """ return cls._value2member_map_.get(value, cls.UNKNOWN) \ No newline at end of file From 80e6244d0f15200de982156c8149676192084844 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:36:18 +0800 Subject: [PATCH 14/24] Updated test files --- test/bluetooth/test_bluetooth_api.py | 25 ++++++++++++++----------- test/bluetooth/test_bt_commands.py | 14 +++++++------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/test/bluetooth/test_bluetooth_api.py b/test/bluetooth/test_bluetooth_api.py index c2de83a..478a24d 100644 --- a/test/bluetooth/test_bluetooth_api.py +++ b/test/bluetooth/test_bluetooth_api.py @@ -16,14 +16,13 @@ from concurrent.futures import ThreadPoolExecutor, Future from typing import Optional, BinaryIO, List, Callable from unittest import TestCase -from urllib.request import HTTPPasswordMgrWithDefaultRealm from pyshimmer.bluetooth.bt_api import BluetoothRequestHandler, ShimmerBluetooth from pyshimmer.bluetooth.bt_commands import GetDeviceNameCommand, SetDeviceNameCommand, DataPacket, GetStatusCommand, \ GetStringCommand, ResponseCommand from pyshimmer.bluetooth.bt_serial import BluetoothSerial from pyshimmer.dev.channels import ChDataTypeAssignment, EChannelType -from pyshimmer.dev.fw_version import FirmwareVersion, EFirmwareType +from pyshimmer.dev.fw_version import FirmwareVersion, EFirmwareType, HardwareVersion from pyshimmer.test_util import PTYSerialMockCreator @@ -334,13 +333,15 @@ def do_setup(self, initialize: bool = True, **kwargs) -> None: if initialize: # The Bluetooth API automatically requests the firmware version upon initialization. # We must prepare a proper response beforehand. - self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') - future = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') + req_future_hw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') + req_future_fw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') self._sot.initialize() # Check that it properly asked for the firmware version - result = future.result() + result = req_future_fw.result() assert result == b'\x2E' + result = req_future_hw.result() + assert result == b'\x3F' def tearDown(self) -> None: self._sot.shutdown() @@ -351,12 +352,14 @@ def test_context_manager(self): # The Bluetooth API automatically requests the firmware version upon initialization. # We must prepare a proper response beforehand. - req_future = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') - req_future = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') + req_future_hw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') + req_future_fw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') with self._sot: # We check that the API properly asked for the firmware version - req_data = req_future.result() + req_data = req_future_fw.result() self.assertEqual(req_data, b'\x2e') + req_data = req_future_hw.result() + self.assertEqual(req_data, b'\x3f') # It should now be in an initialized state self.assertTrue(self._sot.initialized) @@ -448,15 +451,15 @@ def test_get_hardware_version(self): self._submit_req_resp_handler(1, b'\xFF\x25\x03') hw_version = self._sot.get_device_hardware_version() - self.assertEqual(hw_version, "SHIMMER3") + self.assertEqual(hw_version, HardwareVersion.SHIMMER3) self._submit_req_resp_handler(1, b'\xFF\x25\x0A') hw_version = self._sot.get_device_hardware_version() - self.assertEqual(hw_version, "SHIMMER3R") + self.assertEqual(hw_version, HardwareVersion.SHIMMER3R) self._submit_req_resp_handler(1, b'\xFF\x25\x04') hw_version = self._sot.get_device_hardware_version() - self.assertEqual(hw_version, "Unknown Version: (4)") + self.assertEqual(hw_version, HardwareVersion.UNKNOWN) def test_status_ack_disable(self): self.do_setup(initialize=False) diff --git a/test/bluetooth/test_bt_commands.py b/test/bluetooth/test_bt_commands.py index 547c35a..0e5f5e6 100644 --- a/test/bluetooth/test_bt_commands.py +++ b/test/bluetooth/test_bt_commands.py @@ -24,7 +24,7 @@ SetSensorsCommand, SetSamplingRateCommand, GetAllCalibrationCommand from pyshimmer.bluetooth.bt_serial import BluetoothSerial from pyshimmer.dev.channels import ChDataTypeAssignment, EChannelType, ESensorGroup -from pyshimmer.dev.fw_version import EFirmwareType +from pyshimmer.dev.fw_version import EFirmwareType, HardwareVersion from pyshimmer.test_util import MockSerial @@ -205,12 +205,12 @@ def test_get_device_name_command(self): def test_get_hardware_version(self): cmd = GetShimmerHardwareVersion() - self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x00', 'SHIMMER1') - self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x01', 'SHIMMER2') - self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x02', 'SHIMMER2R') - self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x03', 'SHIMMER3') - self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x0a', 'SHIMMER3R') - self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x04', 'Unknown Version: (4)') + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x00', HardwareVersion.SHIMMER1) + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x01', HardwareVersion.SHIMMER2) + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x02', HardwareVersion.SHIMMER2R) + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x03', HardwareVersion.SHIMMER3) + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x0a', HardwareVersion.SHIMMER3R) + self.assert_cmd(cmd, b'\x3f', b'\x25', b'\x25\x04', HardwareVersion.UNKNOWN) def test_set_device_name_command(self): cmd = SetDeviceNameCommand('S_PPG') From d6f9ce4f4bb8a1ef68beb831040f42c44daff0ec Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:56:54 +0800 Subject: [PATCH 15/24] Update test file - fw before hw --- test/bluetooth/test_bluetooth_api.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/bluetooth/test_bluetooth_api.py b/test/bluetooth/test_bluetooth_api.py index 478a24d..790b0a6 100644 --- a/test/bluetooth/test_bluetooth_api.py +++ b/test/bluetooth/test_bluetooth_api.py @@ -333,8 +333,8 @@ def do_setup(self, initialize: bool = True, **kwargs) -> None: if initialize: # The Bluetooth API automatically requests the firmware version upon initialization. # We must prepare a proper response beforehand. - req_future_hw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') req_future_fw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') + req_future_hw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') self._sot.initialize() # Check that it properly asked for the firmware version @@ -352,14 +352,14 @@ def test_context_manager(self): # The Bluetooth API automatically requests the firmware version upon initialization. # We must prepare a proper response beforehand. - req_future_hw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') req_future_fw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') + req_future_hw = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x25\x03') with self._sot: # We check that the API properly asked for the firmware version - req_data = req_future_fw.result() - self.assertEqual(req_data, b'\x2e') - req_data = req_future_hw.result() - self.assertEqual(req_data, b'\x3f') + req_data_fw = req_future_fw.result() + self.assertEqual(req_data_fw, b'\x2e') + req_data_hw = req_future_hw.result() + self.assertEqual(req_data_hw, b'\x3f') # It should now be in an initialized state self.assertTrue(self._sot.initialized) @@ -465,8 +465,8 @@ def test_status_ack_disable(self): self.do_setup(initialize=False) # Queue response for version command - self._submit_req_resp_handler(1, b'\xFF\x25\x03') self._submit_req_resp_handler(1, b'\xFF\x2F\x03\x00\x00\x00\x0F\x04') + self._submit_req_resp_handler(1, b'\xFF\x25\x03') # Queue response for disabling the status acknowledgment req_future = self._submit_req_resp_handler(2, b'\xFF') @@ -478,6 +478,6 @@ def test_status_ack_not_disable(self): self.do_setup(initialize=False, disable_status_ack=False) # Queue response for version command - self._submit_req_resp_handler(1, b'\xFF\x25\x03') self._submit_req_resp_handler(1, b'\xFF\x2F\x03\x00\x00\x00\x0F\x04') + self._submit_req_resp_handler(1, b'\xFF\x25\x03') self._sot.initialize() From 26d888130e94a69b0dc18d7afc6e64c10fa27344 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Fri, 11 Apr 2025 14:26:03 +0800 Subject: [PATCH 16/24] Update AllCalibration --- pyshimmer/bluetooth/bt_api.py | 3 ++- pyshimmer/bluetooth/bt_commands.py | 10 +++++++--- pyshimmer/dev/calibration.py | 19 ++++++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index aeb4bba..63d4d2e 100644 --- a/pyshimmer/bluetooth/bt_api.py +++ b/pyshimmer/bluetooth/bt_api.py @@ -480,7 +480,8 @@ def get_all_calibration(self) -> AllCalibration: """Gets all calibration data from sensor :return: An AllCalibration object that presents the calibration contents in an easily processable manner """ - return self._process_and_wait(GetAllCalibrationCommand()) + hw_version = self._process_and_wait(GetShimmerHardwareVersion()) + return self._process_and_wait(GetAllCalibrationCommand(hw_version)) def set_exg_register(self, chip_id: int, offset: int, data: bytes) -> None: """Configure part of the memory of the ExG registers diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index 4f9d733..33bac17 100644 --- a/pyshimmer/bluetooth/bt_commands.py +++ b/pyshimmer/bluetooth/bt_commands.py @@ -358,11 +358,15 @@ class GetAllCalibrationCommand(ResponseCommand): [bytes 12-20] alignment matrix: 9 values 8-bit signed integers. """ - def __init__(self): + def __init__(self, hw_version: HardwareVersion): super().__init__(ALL_CALIBRATION_RESPONSE) self._offset = 0x0 - self._rlen = 0x54 # 84 bytes + if hw_version == HardwareVersion.SHIMMER3R: + self._rlen = 0x7E #126 bytes + else: + self._rlen = 0x54 #84 bytes + self.hw_version = hw_version def send(self, ser: BluetoothSerial) -> None: ser.write_command(GET_ALL_CALIBRATION_COMMAND) @@ -370,7 +374,7 @@ def send(self, ser: BluetoothSerial) -> None: def receive(self, ser: BluetoothSerial) -> any: ser.read_response(ALL_CALIBRATION_RESPONSE) reg_data = ser.read(self._rlen) - return AllCalibration(reg_data) + return AllCalibration(reg_data, self.hw_version) class InquiryCommand(ResponseCommand): diff --git a/pyshimmer/dev/calibration.py b/pyshimmer/dev/calibration.py index 74dee01..df5e51f 100644 --- a/pyshimmer/dev/calibration.py +++ b/pyshimmer/dev/calibration.py @@ -16,22 +16,35 @@ import struct from typing import List +from pyshimmer.dev.fw_version import HardwareVersion from pyshimmer.util import fmt_hex class AllCalibration: - def __init__(self, reg_bin: bytes): - self._num_bytes = 84 + def __init__(self, reg_bin: bytes, hw_version: HardwareVersion): self._sensor_bytes = 21 - self._num_sensors = 4 + self._num_sensors = self._get_num_sensors_from_hw(hw_version) + self._num_bytes = self._num_sensors * self._sensor_bytes if len(reg_bin) < self._num_bytes: raise ValueError( f'All calibration data must have length {self._num_bytes}') self._reg_bin = reg_bin + + def _get_num_sensors_from_hw(self, hw_version: HardwareVersion) -> int: + version_map = { + HardwareVersion.SHIMMER1: 4, + HardwareVersion.SHIMMER2: 4, + HardwareVersion.SHIMMER2R: 4, + HardwareVersion.SHIMMER3: 4, + HardwareVersion.SHIMMER3R: 6 + } + if hw_version not in version_map: + raise ValueError(f"Unsupported hardware version: {hw_version}") + return version_map[hw_version] def __str__(self) -> str: def print_sensor(sens_num: int) -> str: From 930edb8549bbb29cc8fa63684e71e0706a5c666c Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:18:52 +0800 Subject: [PATCH 17/24] Updated AllCalibration test files --- test/bluetooth/test_bt_commands.py | 2 +- test/dev/test_device_calibration.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/bluetooth/test_bt_commands.py b/test/bluetooth/test_bt_commands.py index 0e5f5e6..99753ba 100644 --- a/test/bluetooth/test_bt_commands.py +++ b/test/bluetooth/test_bt_commands.py @@ -183,7 +183,7 @@ def test_get_exg_reg_fail(self): self.assertRaises(ValueError, cmd.receive, serial) def test_get_allcalibration_command(self): - cmd = GetAllCalibrationCommand() + cmd = GetAllCalibrationCommand(hw_version=HardwareVersion.SHIMMER3) r = self.assert_cmd(cmd, b'\x2c', b'\x2d', b'\x2d\x08\xcd\x08\xcd\x08\xcd\x00\x5c\x00\x5c\x00\x5c\x00\x9c\x00\x9c\x00\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x00\x19\x96\x19\x96\x19\x96\x00\x9c\x00\x9c\x00\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x87\x06\x87\x06\x87\x00\x9c\x00\x64\x00\x00\x00\x00\x9c') self.assertEqual(r.binary, b'\x08\xcd\x08\xcd\x08\xcd\x00\x5c\x00\x5c\x00\x5c\x00\x9c\x00\x9c\x00\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x00\x19\x96\x19\x96\x19\x96\x00\x9c\x00\x9c\x00\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x87\x06\x87\x06\x87\x00\x9c\x00\x64\x00\x00\x00\x00\x9c') diff --git a/test/dev/test_device_calibration.py b/test/dev/test_device_calibration.py index c5cb507..16efc86 100644 --- a/test/dev/test_device_calibration.py +++ b/test/dev/test_device_calibration.py @@ -13,10 +13,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import random from unittest import TestCase from pyshimmer.dev.calibration import AllCalibration +from pyshimmer.dev.fw_version import HardwareVersion def randbytes(k: int) -> bytes: population = list(range(256)) @@ -27,7 +27,7 @@ class AllCalibrationTest(TestCase): def test_equality_operator(self): def do_assert(a: bytes, b: bytes, result: bool) -> None: - self.assertEqual(AllCalibration(a) == AllCalibration(b), result) + self.assertEqual(AllCalibration(a, hw_version=HardwareVersion.SHIMMER3) == AllCalibration(b, hw_version=HardwareVersion.SHIMMER3), result) x = randbytes(84) y = randbytes(84) @@ -45,7 +45,7 @@ def setUp(self) -> None: random.seed(0x42) def test_allcalibration_fail(self): - self.assertRaises(ValueError, AllCalibration, bytes()) + self.assertRaises(ValueError, AllCalibration, bytes(), HardwareVersion.SHIMMER3) def test_allcalibration(self): bin_reg1 = bytes([0x08, 0xcd, 0x08, 0xcd, 0x08, 0xcd, 0x00, 0x5c, 0x00, 0x5c, @@ -68,8 +68,8 @@ def test_allcalibration(self): 0x87, 0x06, 0x87, 0x06, 0x87, 0x00, 0x9c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x9c]) - allcalib1 = AllCalibration(bin_reg1) - allcalib2 = AllCalibration(bin_reg2) + allcalib1 = AllCalibration(bin_reg1, hw_version=HardwareVersion.SHIMMER3) + allcalib2 = AllCalibration(bin_reg2, hw_version=HardwareVersion.SHIMMER3) self.assertEqual(allcalib1.get_offset_bias(0), [2253, 2253, 2253] ) self.assertEqual(allcalib1.get_sensitivity(0), [92, 92, 92] ) self.assertEqual(allcalib1.get_ali_mat(0), [0, -100, 0, -100, 0, 0, 0, 0, -100] ) @@ -107,9 +107,9 @@ def test_exg_register_print(self): 0x87, 0x06, 0x87, 0x06, 0x87, 0x00, 0x9c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x9c]) - allcalib = AllCalibration(bin_reg) + allcalib = AllCalibration(bin_reg, hw_version=HardwareVersion.SHIMMER3) str_repr = str(allcalib) self.assertTrue('Offset bias: [0, 0, 0]' in str_repr) self.assertTrue('Sensitivity: [1671,' in str_repr) - + From 74bd7a38cb701e91c800169471ac842e54b18160 Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Tue, 15 Apr 2025 14:51:55 +0800 Subject: [PATCH 18/24] Add param scaling for Alignment, Sensitivity (Gyro) --- pyshimmer/dev/calibration.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyshimmer/dev/calibration.py b/pyshimmer/dev/calibration.py index df5e51f..ef69729 100644 --- a/pyshimmer/dev/calibration.py +++ b/pyshimmer/dev/calibration.py @@ -87,6 +87,9 @@ def get_sensitivity(self, sens_num: int) -> List[int]: end_offset = start_offset + 6 ans = list(struct.unpack( '>hhh', self._reg_bin[start_offset:end_offset])) + + if sens_num == 1: # Scaling for Sensitivity (Gyroscope Only) + return [round(val /100.0, 2) for val in ans] return ans def get_ali_mat(self, sens_num: int) -> List[int]: @@ -95,4 +98,7 @@ def get_ali_mat(self, sens_num: int) -> List[int]: end_offset = start_offset + 9 ans = list(struct.unpack( '>bbbbbbbbb', self._reg_bin[start_offset:end_offset])) - return ans + + # Scaling for Alignment + return [round(val / 100.0, 2) for val in ans] + \ No newline at end of file From ac40161cbb8c2f29b15569c406091c1d0ed6f10c Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:42:21 +0800 Subject: [PATCH 19/24] Update test file --- test/dev/test_device_calibration.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/dev/test_device_calibration.py b/test/dev/test_device_calibration.py index 16efc86..40d65a3 100644 --- a/test/dev/test_device_calibration.py +++ b/test/dev/test_device_calibration.py @@ -72,29 +72,29 @@ def test_allcalibration(self): allcalib2 = AllCalibration(bin_reg2, hw_version=HardwareVersion.SHIMMER3) self.assertEqual(allcalib1.get_offset_bias(0), [2253, 2253, 2253] ) self.assertEqual(allcalib1.get_sensitivity(0), [92, 92, 92] ) - self.assertEqual(allcalib1.get_ali_mat(0), [0, -100, 0, -100, 0, 0, 0, 0, -100] ) + self.assertEqual(allcalib1.get_ali_mat(0), [0.00, -1.00, 0.00, -1.00, 0.00, 0.00, 0.00, 0.00, -1.00] ) self.assertEqual(allcalib1.get_offset_bias(1), [0, 0, 0] ) - self.assertEqual(allcalib1.get_sensitivity(1), [6550, 6550, 6550] ) - self.assertEqual(allcalib1.get_ali_mat(1), [0, -100, 0, -100, 0, 0, 0, 0, -100] ) + self.assertEqual(allcalib1.get_sensitivity(1), [65.50, 65.50, 65.50] ) + self.assertEqual(allcalib1.get_ali_mat(1), [0.00, -1.00, 0.00, -1.00, 0.00, 0.00, 0.00, 0.00, -1.00] ) self.assertEqual(allcalib1.get_offset_bias(2), [0, 0, 0] ) self.assertEqual(allcalib1.get_sensitivity(2), [667, 667, 667] ) - self.assertEqual(allcalib1.get_ali_mat(2), [0, -100, 0, 100, 0, 0, 0, 0, -100] ) + self.assertEqual(allcalib1.get_ali_mat(2), [0.00, -1.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.00, -1.00] ) self.assertEqual(allcalib1.get_offset_bias(3), [0, 0, 0] ) self.assertEqual(allcalib1.get_sensitivity(3), [1671, 1671, 1671] ) - self.assertEqual(allcalib1.get_ali_mat(3), [0, -100, 0, 100, 0, 0, 0, 0, -100] ) + self.assertEqual(allcalib1.get_ali_mat(3), [0.00, -1.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.00, -1.00] ) self.assertEqual(allcalib2.get_offset_bias(0), [2253, 2253, 2253]) self.assertEqual(allcalib2.get_sensitivity(0), [92, 92, 92]) - self.assertEqual(allcalib2.get_ali_mat(0), [0, -100, 0, -100, 0, 0, 0, 0, -100]) + self.assertEqual(allcalib2.get_ali_mat(0), [0.00, -1.00, 0.00, -1.00, 0.00, 0.00, 0.00, 0.00, -1.00]) self.assertEqual(allcalib2.get_offset_bias(1), [0, 0, 0]) - self.assertEqual(allcalib2.get_sensitivity(1), [6550, 6550, 6550]) - self.assertEqual(allcalib2.get_ali_mat(1), [0, -100, 0, -100, 0, 0, 0, 0, -100]) + self.assertEqual(allcalib2.get_sensitivity(1), [65.50, 65.50, 65.50]) + self.assertEqual(allcalib2.get_ali_mat(1), [0.00, -1.00, 0.00, -1.00, 0.00, 0.00, 0.00, 0.00, -1.00]) self.assertEqual(allcalib2.get_offset_bias(2), [0, 0, 0]) self.assertEqual(allcalib2.get_sensitivity(2), [0, 0, 0]) - self.assertEqual(allcalib2.get_ali_mat(2), [0, 0, 0, 0, 0, 0, 0, 0, 0]) + self.assertEqual(allcalib2.get_ali_mat(2), [0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00]) self.assertEqual(allcalib2.get_offset_bias(3), [0, 0, 0]) self.assertEqual(allcalib2.get_sensitivity(3), [1671, 1671, 1671]) - self.assertEqual(allcalib2.get_ali_mat(3), [0, -100, 0, 100, 0, 0, 0, 0, -100]) + self.assertEqual(allcalib2.get_ali_mat(3), [0.00, -1.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.00, -1.00]) def test_exg_register_print(self): bin_reg = bytes([0x08, 0xcd, 0x08, 0xcd, 0x08, 0xcd, 0x00, 0x5c, 0x00, 0x5c, From 75855ab5638bb6983132c6eb825aa2afbb41ebfd Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:42:31 +0800 Subject: [PATCH 20/24] Implement Calibration Example --- examples/bt_calibrate_data_example.py | 112 ++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 examples/bt_calibrate_data_example.py diff --git a/examples/bt_calibrate_data_example.py b/examples/bt_calibrate_data_example.py new file mode 100644 index 0000000..1837009 --- /dev/null +++ b/examples/bt_calibrate_data_example.py @@ -0,0 +1,112 @@ +import time +import sys +import numpy as np +sys.path.append(r'C:\Users\Acer-User\git\pyshimmer') + +from serial import Serial +from pyshimmer import ShimmerBluetooth, DEFAULT_BAUDRATE, DataPacket + +def stream_cb(pkt: DataPacket) -> None: + print(f'Received new data packet: ') + for chan in pkt.channels: + print(f'channel: ' + str(chan)) + print(f'value: ' + str(pkt[chan])) + print('') + +def calibrate_inertial_sensor_data(data, alignment, sensitivity, offset): + """Applies calibration + Based on the theory outlined by Ferraris F, Grimaldi U, and Parvis M. + in "Procedure for effortless in-field calibration of three-axis rate gyros and accelerometers" Sens. Mater. 1995; 7: 311-30. + C = [R^(-1)] .[K^(-1)] .([U]-[B]) + where..... + [C] -> [3 x n] Calibrated Data Matrix + [U] -> [3 x n] Uncalibrated Data Matrix + [B] -> [3 x n] Replicated Sensor Offset Vector Matrix + [R^(-1)] -> [3 x 3] Inverse Alignment Matrix + [K^(-1)] -> [3 x 3] Inverse Sensitivity Matrix + n = Number of Samples + """ + + # [U] - [B] + data_minus_offset = np.array(data) - np.array(offset) + + # [R^(-1)] Alignment Matrix Inverse + alignment = np.array(alignment).reshape(3,3) + if np.all(alignment == 0): + am_inv = np.eye(3) # Identity Matrix + else: + try: + am_inv = np.linalg.inv(alignment) # Inverse Matrix + except np.linalg.LinAlgError: + am_inv = np.eye(3) + print("Alignment Matrix not invertible - Using Identity Matrix") + + # [K^(-1)] Sensitivity Matrix Inverse + if np.all(sensitivity == 0): + sm_inv = np.eye(3) # Identity Matrix + else: + try: + sm_inv = np.linalg.inv(np.diag(sensitivity)) # Inverse Matrix + except np.linalg.LinAlgError: + sm_inv = np.eye(3) + print("Sensitivity Matrix not invertible - Using Identity Matrix") + + # C = [R^(-1)] * [K^(-1)] * ([U] - [B]) + calibrated = am_inv @ sm_inv @ data_minus_offset + return calibrated.flatten().tolist() + +def main(args=None): + # serial = Serial('COM5', DEFAULT_BAUDRATE) + serial = Serial('COM14', DEFAULT_BAUDRATE) + shim_dev = ShimmerBluetooth(serial) + + shim_dev.initialize() + + dev_name = shim_dev.get_device_name() + print(f'My name is: {dev_name}') + + dev_hardware_ver = shim_dev.get_device_hardware_version() + print(f'My hardware version is: {dev_hardware_ver.name}') + + info = shim_dev.get_firmware_version() + print("- firmware: [" + str(info[0]) + "]") + print("- version: [" + str(info[1].major) + "." + str(info[1].minor) + "." + str(info[1].rel) + "]") + + calibration = shim_dev.get_all_calibration() + print(f'Calibration: {calibration}') + print(f'Number of Sensors: {calibration._num_sensors}') + print(f'Number of Bytes: {calibration._num_bytes}') + + # Example - Use first sensor LN_ACCEL (index 0) + sens_num = 0 + offset = calibration.get_offset_bias(sens_num) + sensitivity = calibration.get_sensitivity(sens_num) + ali_raw = calibration.get_ali_mat(sens_num) + alignment = [[ali_raw[0], ali_raw[1], ali_raw[2]], + [ali_raw[3], ali_raw[4], ali_raw[5]], + [ali_raw[6], ali_raw[7], ali_raw[8]]] + + print("Offset Bias:", offset) + print("Sensitivity:", sensitivity) + print("Alignment Matrix:") + for row in alignment: + print(" ", row) + + # Example raw accelerometer data (X, Y, Z) + raw_data = [100, 100, 100] # To-do (use stream data) + calibrated = calibrate_inertial_sensor_data(raw_data, alignment, sensitivity, offset) + + print("\nExample raw data:", raw_data) + print("Calibrated data (m/s^2 approx):", calibrated) + + # shim_dev.add_stream_callback(stream_cb) + + # shim_dev.start_streaming() + # time.sleep(0.5) + # shim_dev.stop_streaming() + + # shim_dev.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file From d5032b86efbbcf0c372903801b113843215fbd9b Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:54:00 +0800 Subject: [PATCH 21/24] Update Calibration Example --- examples/bt_calibrate_data_example.py | 43 +++++++++++++++++---------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/examples/bt_calibrate_data_example.py b/examples/bt_calibrate_data_example.py index 1837009..b4ebd0e 100644 --- a/examples/bt_calibrate_data_example.py +++ b/examples/bt_calibrate_data_example.py @@ -5,13 +5,28 @@ from serial import Serial from pyshimmer import ShimmerBluetooth, DEFAULT_BAUDRATE, DataPacket +from pyshimmer.dev.channels import EChannelType -def stream_cb(pkt: DataPacket) -> None: - print(f'Received new data packet: ') - for chan in pkt.channels: - print(f'channel: ' + str(chan)) - print(f'value: ' + str(pkt[chan])) - print('') +def make_stream_cb(alignment, sensitivity, offset): + def stream_cb(pkt: DataPacket) -> None: + print(f'\nReceived new data packet:') + raw_vector = [] + + for chan in pkt.channels: + print(f'channel: ' + str(chan)) + print(f'value: ' + str(pkt[chan])) + + # Check Accel Channels + required_channels = [EChannelType.ACCEL_LN_X, EChannelType.ACCEL_LN_Y, EChannelType.ACCEL_LN_Z] + if all(ch in pkt.channels for ch in required_channels): + raw_vector = [pkt[ch] for ch in required_channels] + calibrated = calibrate_inertial_sensor_data(raw_vector, alignment, sensitivity, offset) + print(f"Uncalibrated values: ", raw_vector) + print(f"Calibrated values: ", calibrated) + else: + print("Channels not found") + + return stream_cb def calibrate_inertial_sensor_data(data, alignment, sensitivity, offset): """Applies calibration @@ -91,19 +106,15 @@ def main(args=None): print("Alignment Matrix:") for row in alignment: print(" ", row) - - # Example raw accelerometer data (X, Y, Z) - raw_data = [100, 100, 100] # To-do (use stream data) - calibrated = calibrate_inertial_sensor_data(raw_data, alignment, sensitivity, offset) - - print("\nExample raw data:", raw_data) - print("Calibrated data (m/s^2 approx):", calibrated) # shim_dev.add_stream_callback(stream_cb) + + # Calibrated Stream Data + shim_dev.add_stream_callback(make_stream_cb(alignment, sensitivity, offset)) - # shim_dev.start_streaming() - # time.sleep(0.5) - # shim_dev.stop_streaming() + shim_dev.start_streaming() + time.sleep(0.5) + shim_dev.stop_streaming() # shim_dev.shutdown() From ed6a9bfdb7a32f19fcbbe738531794333e4f465c Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:11:14 +0800 Subject: [PATCH 22/24] Update Calib Values --- examples/bt_calibrate_data_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/bt_calibrate_data_example.py b/examples/bt_calibrate_data_example.py index b4ebd0e..38cce53 100644 --- a/examples/bt_calibrate_data_example.py +++ b/examples/bt_calibrate_data_example.py @@ -68,7 +68,7 @@ def calibrate_inertial_sensor_data(data, alignment, sensitivity, offset): # C = [R^(-1)] * [K^(-1)] * ([U] - [B]) calibrated = am_inv @ sm_inv @ data_minus_offset - return calibrated.flatten().tolist() + return [round(val, 3) for val in calibrated.flatten().tolist()] def main(args=None): # serial = Serial('COM5', DEFAULT_BAUDRATE) From f1559f752a019750bf9a355c379eceb800e668fd Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:45:20 +0800 Subject: [PATCH 23/24] Live Calibration Plot for all Shimmer3 Sensors --- examples/bt_calibrate_data_example.py | 117 +++++++++++++++++--------- 1 file changed, 79 insertions(+), 38 deletions(-) diff --git a/examples/bt_calibrate_data_example.py b/examples/bt_calibrate_data_example.py index 38cce53..d79a571 100644 --- a/examples/bt_calibrate_data_example.py +++ b/examples/bt_calibrate_data_example.py @@ -1,31 +1,77 @@ import time import sys import numpy as np +import matplotlib.pyplot as plt +import collections sys.path.append(r'C:\Users\Acer-User\git\pyshimmer') from serial import Serial from pyshimmer import ShimmerBluetooth, DEFAULT_BAUDRATE, DataPacket from pyshimmer.dev.channels import EChannelType +from matplotlib.animation import FuncAnimation +from threading import Thread -def make_stream_cb(alignment, sensitivity, offset): +fig, axs = plt.subplots(4, 1, figsize=(10, 8), sharex=True) +sensor_plot_order = ['ACCEL_LN', 'ACCEL_WR', 'GYRO', 'MAG'] + +# Plot Buffers +data_buffer = {sensor: {'X': collections.deque(maxlen=200), + 'Y': collections.deque(maxlen=200), + 'Z': collections.deque(maxlen=200)} + for sensor in sensor_plot_order} + +def init_plot(): + for ax, sensor in zip(axs, sensor_plot_order): + ax.set_xlim(0, 200) + ax.set_ylim(-10, 15) + ax.set_title(f'{sensor} Calibrated Data') + return axs + +def update_plot(_): + for ax, sensor in zip(axs, sensor_plot_order): + ax.clear() + for axis in ['X', 'Y', 'Z']: + ax.plot(data_buffer[sensor][axis], label=f'{axis}') + ax.set_ylim(-10, 15) + ax.set_title(f'{sensor} Calibrated Data') + ax.legend() + return axs + +def make_stream_cb(calibration): def stream_cb(pkt: DataPacket) -> None: - print(f'\nReceived new data packet:') - raw_vector = [] - - for chan in pkt.channels: - print(f'channel: ' + str(chan)) - print(f'value: ' + str(pkt[chan])) - - # Check Accel Channels - required_channels = [EChannelType.ACCEL_LN_X, EChannelType.ACCEL_LN_Y, EChannelType.ACCEL_LN_Z] - if all(ch in pkt.channels for ch in required_channels): - raw_vector = [pkt[ch] for ch in required_channels] - calibrated = calibrate_inertial_sensor_data(raw_vector, alignment, sensitivity, offset) - print(f"Uncalibrated values: ", raw_vector) - print(f"Calibrated values: ", calibrated) - else: - print("Channels not found") - + + # print(f'received new data packet: ') + # for chan in pkt.channels: + # print(f'channel: ' + str(chan)) + # print(f'value: ' + str(pkt[chan])) + # print('') + + sensor_map = { + 'ACCEL_LN': [EChannelType.ACCEL_LN_X, EChannelType.ACCEL_LN_Y, EChannelType.ACCEL_LN_Z], + 'GYRO': [EChannelType.GYRO_MPU9150_X, EChannelType.GYRO_MPU9150_Y, EChannelType.GYRO_MPU9150_Z], + 'MAG': [EChannelType.MAG_LSM303DLHC_X, EChannelType.MAG_LSM303DLHC_Y, EChannelType.MAG_LSM303DLHC_Z], + 'ACCEL_WR': [EChannelType.ACCEL_LSM303DLHC_X, EChannelType.ACCEL_LSM303DLHC_Y, EChannelType.ACCEL_LSM303DLHC_Z], + } + + for sensor_name, channels in sensor_map.items(): + if all(ch in pkt.channels for ch in channels): + raw = [pkt[ch] for ch in channels] + + idx = list(sensor_map).index(sensor_name) + offset = calibration.get_offset_bias(idx) + sensitivity = calibration.get_sensitivity(idx) + ali_raw = calibration.get_ali_mat(idx) + alignment = [[ali_raw[0], ali_raw[1], ali_raw[2]], + [ali_raw[3], ali_raw[4], ali_raw[5]], + [ali_raw[6], ali_raw[7], ali_raw[8]]] + + calib = calibrate_inertial_sensor_data(raw, alignment, sensitivity, offset) + # print(f"{sensor_name} Calibrated Data: {calib}") + + data_buffer[sensor_name]['X'].append(calib[0]) + data_buffer[sensor_name]['Y'].append(calib[1]) + data_buffer[sensor_name]['Z'].append(calib[2]) + # print('') return stream_cb def calibrate_inertial_sensor_data(data, alignment, sensitivity, offset): @@ -92,29 +138,24 @@ def main(args=None): print(f'Number of Sensors: {calibration._num_sensors}') print(f'Number of Bytes: {calibration._num_bytes}') - # Example - Use first sensor LN_ACCEL (index 0) - sens_num = 0 - offset = calibration.get_offset_bias(sens_num) - sensitivity = calibration.get_sensitivity(sens_num) - ali_raw = calibration.get_ali_mat(sens_num) - alignment = [[ali_raw[0], ali_raw[1], ali_raw[2]], - [ali_raw[3], ali_raw[4], ali_raw[5]], - [ali_raw[6], ali_raw[7], ali_raw[8]]] - - print("Offset Bias:", offset) - print("Sensitivity:", sensitivity) - print("Alignment Matrix:") - for row in alignment: - print(" ", row) + # Calibrated Stream Data + shim_dev.add_stream_callback(make_stream_cb(calibration)) - # shim_dev.add_stream_callback(stream_cb) + def stream_thread(): + shim_dev.start_streaming() + input('Streaming.. Press Enter to stop.\n') + shim_dev.stop_streaming() + shim_dev.shutdown() + + Thread(target=stream_thread, daemon=True).start() - # Calibrated Stream Data - shim_dev.add_stream_callback(make_stream_cb(alignment, sensitivity, offset)) + # Start real-time plotting + ani = FuncAnimation(fig, update_plot, init_func=init_plot, interval=100, blit=False) + plt.show() - shim_dev.start_streaming() - time.sleep(0.5) - shim_dev.stop_streaming() + # shim_dev.start_streaming() + # time.sleep(0.5) + # shim_dev.stop_streaming() # shim_dev.shutdown() From abb1a3eaf1d457edbbcf535f081ca92894dfac6a Mon Sep 17 00:00:00 2001 From: Danesh Mariapan <161300025+dmariapan-shimmer@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:48:49 +0800 Subject: [PATCH 24/24] Updated live plot / added UI options --- examples/bt_calibrate_data_example.py | 111 +++++++++++++++++++------- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/examples/bt_calibrate_data_example.py b/examples/bt_calibrate_data_example.py index d79a571..dc8ec18 100644 --- a/examples/bt_calibrate_data_example.py +++ b/examples/bt_calibrate_data_example.py @@ -7,35 +7,74 @@ from serial import Serial from pyshimmer import ShimmerBluetooth, DEFAULT_BAUDRATE, DataPacket -from pyshimmer.dev.channels import EChannelType +from pyshimmer.dev.channels import EChannelType, ESensorGroup from matplotlib.animation import FuncAnimation +from matplotlib.widgets import CheckButtons, Button from threading import Thread -fig, axs = plt.subplots(4, 1, figsize=(10, 8), sharex=True) -sensor_plot_order = ['ACCEL_LN', 'ACCEL_WR', 'GYRO', 'MAG'] +fig, axs = plt.subplots(4, 1, figsize=(10, 7.5), sharex=True) +plt.subplots_adjust(left=0.3, right=0.95, top=0.95, bottom=0.05, hspace=0.25) +sensor_plot_order = ['ACCEL_LN', 'GYRO', 'MAG', 'ACCEL_WR'] # Plot Buffers data_buffer = {sensor: {'X': collections.deque(maxlen=200), 'Y': collections.deque(maxlen=200), 'Z': collections.deque(maxlen=200)} for sensor in sensor_plot_order} +enabled_sensors = [] + +# UI Buttons +sensor_labels = ['ACCEL_LN', 'GYRO', 'MAG', 'ACCEL_WR'] +sensor_map = {'ACCEL_LN': ESensorGroup.ACCEL_LN, + 'GYRO': ESensorGroup.GYRO, + 'MAG': ESensorGroup.MAG, + 'ACCEL_WR': ESensorGroup.ACCEL_WR} + +check_ax = plt.axes([0.05, 0.42, 0.2, 0.15]) +check = CheckButtons(check_ax, sensor_labels, [False] * 4) + +btn_start_ax = plt.axes([0.05, 0.32, 0.1, 0.05]) +btn_stop_ax = plt.axes([0.15, 0.32, 0.1, 0.05]) +btn_clear_ax = plt.axes([0.05, 0.24, 0.2, 0.05]) +btn_start = Button(btn_start_ax, 'Start') +btn_stop = Button(btn_stop_ax, 'Stop') +btn_clear = Button(btn_clear_ax, 'Clear Plots') + +line_objs = { + sensor: { + axis: None for axis in ['X', 'Y', 'Z'] + } for sensor in sensor_plot_order +} def init_plot(): for ax, sensor in zip(axs, sensor_plot_order): ax.set_xlim(0, 200) - ax.set_ylim(-10, 15) + ax.set_ylim(-1, 1) ax.set_title(f'{sensor} Calibrated Data') - return axs + for axis in ['X', 'Y', 'Z']: + (line,) = ax.plot([], [], label=axis) + line_objs[sensor][axis] = line + ax.legend() + return [line for sensor_lines in line_objs.values() for line in sensor_lines.values()] def update_plot(_): - for ax, sensor in zip(axs, sensor_plot_order): - ax.clear() + for sensor, ax in zip(sensor_plot_order, axs): for axis in ['X', 'Y', 'Z']: - ax.plot(data_buffer[sensor][axis], label=f'{axis}') - ax.set_ylim(-10, 15) - ax.set_title(f'{sensor} Calibrated Data') - ax.legend() - return axs + buf = data_buffer[sensor][axis] + line = line_objs[sensor][axis] + line.set_data(range(len(buf)), list(buf)) + + # Adjust dynamic y-limits + all_vals = np.concatenate([data_buffer[sensor][a] for a in ['X', 'Y', 'Z']]) + if len(all_vals): + min_y, max_y = np.min(all_vals), np.max(all_vals) + margin = (max_y - min_y) * 0.1 # 10% margin + if margin == 0: + margin = 0.001 + ax.set_ylim(min_y - margin, max_y + margin) + ax.set_xlim(0, 200) + + return [line for sensor_lines in line_objs.values() for line in sensor_lines.values()] def make_stream_cb(calibration): def stream_cb(pkt: DataPacket) -> None: @@ -46,32 +85,28 @@ def stream_cb(pkt: DataPacket) -> None: # print(f'value: ' + str(pkt[chan])) # print('') - sensor_map = { + internal_map = { 'ACCEL_LN': [EChannelType.ACCEL_LN_X, EChannelType.ACCEL_LN_Y, EChannelType.ACCEL_LN_Z], 'GYRO': [EChannelType.GYRO_MPU9150_X, EChannelType.GYRO_MPU9150_Y, EChannelType.GYRO_MPU9150_Z], 'MAG': [EChannelType.MAG_LSM303DLHC_X, EChannelType.MAG_LSM303DLHC_Y, EChannelType.MAG_LSM303DLHC_Z], 'ACCEL_WR': [EChannelType.ACCEL_LSM303DLHC_X, EChannelType.ACCEL_LSM303DLHC_Y, EChannelType.ACCEL_LSM303DLHC_Z], } - for sensor_name, channels in sensor_map.items(): + for sensor_name in enabled_sensors: + channels = internal_map[sensor_name] if all(ch in pkt.channels for ch in channels): raw = [pkt[ch] for ch in channels] - - idx = list(sensor_map).index(sensor_name) + idx = list(sensor_plot_order).index(sensor_name) offset = calibration.get_offset_bias(idx) sensitivity = calibration.get_sensitivity(idx) ali_raw = calibration.get_ali_mat(idx) alignment = [[ali_raw[0], ali_raw[1], ali_raw[2]], [ali_raw[3], ali_raw[4], ali_raw[5]], [ali_raw[6], ali_raw[7], ali_raw[8]]] - calib = calibrate_inertial_sensor_data(raw, alignment, sensitivity, offset) - # print(f"{sensor_name} Calibrated Data: {calib}") - data_buffer[sensor_name]['X'].append(calib[0]) data_buffer[sensor_name]['Y'].append(calib[1]) data_buffer[sensor_name]['Z'].append(calib[2]) - # print('') return stream_cb def calibrate_inertial_sensor_data(data, alignment, sensitivity, offset): @@ -141,23 +176,39 @@ def main(args=None): # Calibrated Stream Data shim_dev.add_stream_callback(make_stream_cb(calibration)) - def stream_thread(): + # Button Callbacks + def on_checkbox_clicked(label): + if label in enabled_sensors: + enabled_sensors.remove(label) + else: + enabled_sensors.append(label) + print(f"Selected sensors: {enabled_sensors}") + + def on_start_clicked(event): + sensor_groups = [sensor_map[s] for s in enabled_sensors] + shim_dev.set_sensors(sensor_groups) shim_dev.start_streaming() - input('Streaming.. Press Enter to stop.\n') + print("Streaming started") + + def on_stop_clicked(event): shim_dev.stop_streaming() - shim_dev.shutdown() + print("Streaming stopped") - Thread(target=stream_thread, daemon=True).start() + def on_clear_clicked(event): + for sensor in data_buffer: + for axis in data_buffer[sensor]: + data_buffer[sensor][axis].clear() + print("Plot cleared") + + check.on_clicked(on_checkbox_clicked) + btn_start.on_clicked(on_start_clicked) + btn_stop.on_clicked(on_stop_clicked) + btn_clear.on_clicked(on_clear_clicked) # Start real-time plotting ani = FuncAnimation(fig, update_plot, init_func=init_plot, interval=100, blit=False) plt.show() - - # shim_dev.start_streaming() - # time.sleep(0.5) - # shim_dev.stop_streaming() - - # shim_dev.shutdown() + shim_dev.shutdown() if __name__ == '__main__':