""" test_bno055_data.py — Issue #135: BNO055 driver unit tests. Tests data scaling, byte parsing, calibration status extraction, calibration offset packing/unpacking, and temperature handling. No HAL or ESP32-S3 hardware required — pure Python logic. """ import struct import pytest # --------------------------------------------------------------------------- # BNO055 scaling constants (mirror bno055.c) # --------------------------------------------------------------------------- EULER_SCALE = 16.0 # 1 degree = 16 LSB GYRO_SCALE = 16.0 # 1 dps = 16 LSB ACCEL_SCALE = 100.0 # 1 m/s² = 100 LSB G_MS2 = 9.80665 # --------------------------------------------------------------------------- # Calibration status bit extraction # --------------------------------------------------------------------------- BNO055_CAL_SYS_MASK = 0xC0 BNO055_CAL_GYR_MASK = 0x30 BNO055_CAL_ACC_MASK = 0x0C BNO055_CAL_MAG_MASK = 0x03 def parse_calib_status(byte): """Return (sys, gyr, acc, mag) calibration levels 0-3.""" sys = (byte & BNO055_CAL_SYS_MASK) >> 6 gyr = (byte & BNO055_CAL_GYR_MASK) >> 4 acc = (byte & BNO055_CAL_ACC_MASK) >> 2 mag = (byte & BNO055_CAL_MAG_MASK) >> 0 return sys, gyr, acc, mag # --------------------------------------------------------------------------- # Offset packing (mirrors bno055_save_offsets / bno055_restore_offsets) # --------------------------------------------------------------------------- BNO055_BKP_MAGIC = 0xB055CA10 def pack_offsets(offsets22: bytes): """Pack 22 bytes into 6 x uint32 words + magic word (7 words total).""" assert len(offsets22) == 22 words = [] for i in range(5): w = (offsets22[i * 4 + 0] | (offsets22[i * 4 + 1] << 8) | (offsets22[i * 4 + 2] << 16) | (offsets22[i * 4 + 3] << 24)) words.append(w & 0xFFFFFFFF) # Last word: only bytes 20 and 21 w5 = offsets22[20] | (offsets22[21] << 8) words.append(w5 & 0xFFFFFFFF) words.append(BNO055_BKP_MAGIC) return words # 7 uint32 words def unpack_offsets(words): """Reverse of pack_offsets. Returns 22 bytes.""" assert len(words) == 7 assert words[6] == BNO055_BKP_MAGIC result = [] for i in range(5): w = words[i] result.append(w & 0xFF) result.append((w >> 8) & 0xFF) result.append((w >> 16) & 0xFF) result.append((w >> 24) & 0xFF) # Last word: only bytes 20 and 21 result.append(words[5] & 0xFF) result.append((words[5] >> 8) & 0xFF) return bytes(result) # --------------------------------------------------------------------------- # IMUData parsing helpers (mirror bno055_read() logic) # --------------------------------------------------------------------------- def parse_euler(buf6: bytes): """Parse 6-byte Euler burst (H, R, P each int16 LE) → (heading, roll, pitch) in degrees.""" h_raw = struct.unpack_from("= 2) and (gyr >= 2) actual_ready = (acc >= 2) and (gyr >= 2) assert actual_ready == expected_ready # --------------------------------------------------------------------------- # Tests: Euler Angle Parsing # --------------------------------------------------------------------------- class TestEulerParsing: def test_zero(self): buf = struct.pack("> 16) == 0 def test_byte_order_word0(self): """First 4 bytes of offsets pack into word[0] as little-endian.""" orig = bytes([0x01, 0x02, 0x03, 0x04] + [0] * 18) words = pack_offsets(orig) assert words[0] == 0x04030201 def test_byte_order_word5_low(self): """Bytes 20-21 pack into low 16 bits of word[5].""" orig = bytes([0] * 20 + [0xAB, 0xCD]) words = pack_offsets(orig) assert words[5] == 0xCDAB # --------------------------------------------------------------------------- # Tests: Temperature # --------------------------------------------------------------------------- class TestTemperature: def test_room_temp_range(self): """Room temperature should be in –40..+85°C range.""" for t in [-40, 0, 25, 85]: assert -40 <= t <= 85 def test_temperature_is_signed_byte(self): """BNO055 TEMP register is signed int8 → –128..+127 but useful range –40..+85.""" import ctypes for raw in [25, 0x19, 0xFF]: val = ctypes.c_int8(raw).value assert -128 <= val <= 127