diff --git a/examples/bt_calibrate_data_example.py b/examples/bt_calibrate_data_example.py new file mode 100644 index 0000000..dc8ec18 --- /dev/null +++ b/examples/bt_calibrate_data_example.py @@ -0,0 +1,215 @@ +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, ESensorGroup +from matplotlib.animation import FuncAnimation +from matplotlib.widgets import CheckButtons, Button +from threading import Thread + +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(-1, 1) + ax.set_title(f'{sensor} Calibrated Data') + 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 sensor, ax in zip(sensor_plot_order, axs): + for axis in ['X', 'Y', 'Z']: + 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: + + # print(f'received new data packet: ') + # for chan in pkt.channels: + # print(f'channel: ' + str(chan)) + # print(f'value: ' + str(pkt[chan])) + # print('') + + 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 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_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) + data_buffer[sensor_name]['X'].append(calib[0]) + data_buffer[sensor_name]['Y'].append(calib[1]) + data_buffer[sensor_name]['Z'].append(calib[2]) + return stream_cb + +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 [round(val, 3) for val in 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}') + + # Calibrated Stream Data + shim_dev.add_stream_callback(make_stream_cb(calibration)) + + # 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() + print("Streaming started") + + def on_stop_clicked(event): + shim_dev.stop_streaming() + print("Streaming stopped") + + 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.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/pyshimmer/bluetooth/bt_api.py b/pyshimmer/bluetooth/bt_api.py index 36033c2..63d4d2e 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, \ @@ -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 @@ -283,6 +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: Optional[HardwareVersion] = None @property def initialized(self) -> bool: @@ -314,6 +315,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): 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 @@ -322,7 +324,6 @@ def initialize(self) -> None: optionally disables the status acknowledgment and starts the read loop. """ self._thread.start() - self._set_fw_capabilities() if self.capabilities.supports_ack_disable and self._disable_ack: @@ -479,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 @@ -503,6 +505,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) -> HardwareVersion: + """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 diff --git a/pyshimmer/bluetooth/bt_commands.py b/pyshimmer/bluetooth/bt_commands.py index 5813fa7..33bac17 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 @@ -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): @@ -503,7 +507,22 @@ class GetDeviceNameCommand(GetStringCommand): """ def __init__(self): - super().__init__(GET_SHIMMERNAME_COMMAND, SHIMMERNAME_RESPONSE) + super().__init__(GET_SHIMMERNAME_COMMAND, SHIMMERNAME_RESPONSE) + + +class GetShimmerHardwareVersion(ResponseCommand): + """Get the device hardware version + + """ + def __init__(self): + super().__init__(SHIMMER_VERSION_RESPONSE) + + def send(self, ser: BluetoothSerial) -> None: + ser.write_command(GET_SHIMMER_VERSION_COMMAND) + + def receive(self, ser: BluetoothSerial) -> any: + hw_version = ser.read_response(SHIMMER_VERSION_RESPONSE, arg_format=' 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: @@ -74,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]: @@ -82,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 diff --git a/pyshimmer/dev/fw_version.py b/pyshimmer/dev/fw_version.py index 3c3ddb9..974765e 100644 --- a/pyshimmer/dev/fw_version.py +++ b/pyshimmer/dev/fw_version.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from enum import Enum, auto +from enum import Enum, IntEnum, auto def ensure_firmware_version(func): @@ -99,3 +99,24 @@ 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 the supported Shimmer device hardware versions + + """ + SHIMMER1 = 0 + SHIMMER2 = 1 + SHIMMER2R = 2 + SHIMMER3 = 3 + SHIMMER3R = 10 + UNKNOWN = -1 + + @classmethod + def from_int(cls, value: int) -> 'HardwareVersion': + """Converts an Integer to the corresponding HardwareVersion enum + + :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 diff --git a/test/bluetooth/test_bluetooth_api.py b/test/bluetooth/test_bluetooth_api.py index dde8aa3..790b0a6 100644 --- a/test/bluetooth/test_bluetooth_api.py +++ b/test/bluetooth/test_bluetooth_api.py @@ -22,7 +22,7 @@ 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 @@ -333,12 +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. - future = self._submit_req_resp_handler(req_len=1, resp=b'\xff\x2f\x03\x00\x00\x00\x0b\x00') + 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 - 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() @@ -349,11 +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\x2f\x03\x00\x00\x00\x0b\x00') + 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.result() - self.assertEqual(req_data, b'\x2e') + 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) @@ -440,11 +446,27 @@ 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\x03') + hw_version = self._sot.get_device_hardware_version() + 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, HardwareVersion.SHIMMER3R) + + self._submit_req_resp_handler(1, b'\xFF\x25\x04') + hw_version = self._sot.get_device_hardware_version() + self.assertEqual(hw_version, HardwareVersion.UNKNOWN) + def test_status_ack_disable(self): self.do_setup(initialize=False) # Queue response for version command 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') @@ -457,4 +479,5 @@ def test_status_ack_not_disable(self): # Queue response for version command 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() diff --git a/test/bluetooth/test_bt_commands.py b/test/bluetooth/test_bt_commands.py index 1b76393..99753ba 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, \ @@ -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 @@ -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') @@ -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', 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') self.assert_cmd(cmd, b'\x79\x05S_PPG') diff --git a/test/dev/test_device_calibration.py b/test/dev/test_device_calibration.py index c5cb507..40d65a3 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,33 +68,33 @@ 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] ) + 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, @@ -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) - +