Skip to content

Commit 7367dac

Browse files
Merge pull request #45 from pavelmachek/m_3_imu
imu: Allow access to iio on Linux
2 parents 84d3f83 + 4a9493a commit 7367dac

2 files changed

Lines changed: 207 additions & 34 deletions

File tree

internal_filesystem/lib/mpos/board/linux.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,9 @@ def adc_to_voltage(adc_value):
122122
# LightsManager will not be initialized (functions will return False)
123123

124124
# === SENSOR HARDWARE ===
125-
# Note: Desktop builds have no sensor hardware
126125
from mpos import SensorManager
127126

128-
# Initialize with no I2C bus - will detect MCU temp if available
129-
# (On Linux desktop, this will fail gracefully but set _initialized flag)
130-
SensorManager.init(None)
127+
SensorManager.init_iio()
131128

132129
# === CAMERA HARDWARE ===
133130

internal_filesystem/lib/mpos/sensor_manager.py

Lines changed: 206 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"""
2020

2121
import time
22+
import os
2223
try:
2324
import _thread
2425
_lock = _thread.allocate_lock()
@@ -28,6 +29,7 @@
2829

2930
# Sensor type constants (matching Android SensorManager)
3031
TYPE_ACCELEROMETER = 1 # Units: m/s² (meters per second squared)
32+
TYPE_MAGNETIC_FIELD = 2 # Units: μT (micro teslas)
3133
TYPE_GYROSCOPE = 4 # Units: deg/s (degrees per second)
3234
TYPE_TEMPERATURE = 13 # Units: °C (generic, returns first available - deprecated)
3335
TYPE_IMU_TEMPERATURE = 14 # Units: °C (IMU chip temperature)
@@ -101,6 +103,7 @@ class SensorManager:
101103

102104
# Class-level constants
103105
TYPE_ACCELEROMETER = TYPE_ACCELEROMETER
106+
TYPE_MAGNETIC_FIELD = TYPE_MAGNETIC_FIELD
104107
TYPE_GYROSCOPE = TYPE_GYROSCOPE
105108
TYPE_TEMPERATURE = TYPE_TEMPERATURE
106109
TYPE_IMU_TEMPERATURE = TYPE_IMU_TEMPERATURE
@@ -120,7 +123,7 @@ def get(cls):
120123
if cls._instance is None:
121124
cls._instance = cls()
122125
return cls._instance
123-
126+
124127
def init(self, i2c_bus, address=0x6B, mounted_position=FACING_SKY):
125128
"""Initialize SensorManager. MCU temperature initializes immediately, IMU initializes on first use.
126129
@@ -146,6 +149,43 @@ def init(self, i2c_bus, address=0x6B, mounted_position=FACING_SKY):
146149

147150
self._initialized = True
148151
return True
152+
153+
def init_iio(self):
154+
self._imu_driver = _IIODriver()
155+
self._sensor_list = [
156+
Sensor(
157+
name="Accelerometer",
158+
sensor_type=TYPE_ACCELEROMETER,
159+
vendor="Linux IIO",
160+
version=1,
161+
max_range="?",
162+
resolution="?",
163+
power_ma=10
164+
),
165+
Sensor(
166+
name="Gyroscope",
167+
sensor_type=TYPE_GYROSCOPE,
168+
vendor="Linux IIO",
169+
version=1,
170+
max_range="?",
171+
resolution="?",
172+
power_ma=10
173+
),
174+
Sensor(
175+
name="Temperature",
176+
sensor_type=TYPE_IMU_TEMPERATURE,
177+
vendor="Linux IIO",
178+
version=1,
179+
max_range="?",
180+
resolution="?",
181+
power_ma=10
182+
)
183+
]
184+
185+
self._load_calibration()
186+
187+
self._initialized = True
188+
return True
149189

150190
def _ensure_imu_initialized(self):
151191
"""Perform IMU initialization on first use (lazy initialization).
@@ -245,7 +285,35 @@ def get_default_sensor(self, sensor_type):
245285
if sensor.type == sensor_type:
246286
return sensor
247287
return None
248-
288+
289+
def read_sensor_once(self, sensor):
290+
if sensor.type == TYPE_ACCELEROMETER:
291+
if self._imu_driver:
292+
ax, ay, az = self._imu_driver.read_acceleration()
293+
if self._mounted_position == FACING_EARTH:
294+
az *= -1
295+
return (ax, ay, az)
296+
elif sensor.type == TYPE_GYROSCOPE:
297+
if self._imu_driver:
298+
return self._imu_driver.read_gyroscope()
299+
elif sensor.type == TYPE_IMU_TEMPERATURE:
300+
if self._imu_driver:
301+
return self._imu_driver.read_temperature()
302+
elif sensor.type == TYPE_SOC_TEMPERATURE:
303+
if self._has_mcu_temperature:
304+
import esp32
305+
return esp32.mcu_temperature()
306+
elif sensor.type == TYPE_TEMPERATURE:
307+
# Generic temperature - return first available (backward compatibility)
308+
if self._imu_driver:
309+
temp = self._imu_driver.read_temperature()
310+
if temp is not None:
311+
return temp
312+
if self._has_mcu_temperature:
313+
import esp32
314+
return esp32.mcu_temperature()
315+
return None
316+
249317
def read_sensor(self, sensor):
250318
"""Read sensor data synchronously.
251319
@@ -276,32 +344,7 @@ def read_sensor(self, sensor):
276344

277345
for attempt in range(max_retries):
278346
try:
279-
if sensor.type == TYPE_ACCELEROMETER:
280-
if self._imu_driver:
281-
ax, ay, az = self._imu_driver.read_acceleration()
282-
if self._mounted_position == FACING_EARTH:
283-
az *= -1
284-
return (ax, ay, az)
285-
elif sensor.type == TYPE_GYROSCOPE:
286-
if self._imu_driver:
287-
return self._imu_driver.read_gyroscope()
288-
elif sensor.type == TYPE_IMU_TEMPERATURE:
289-
if self._imu_driver:
290-
return self._imu_driver.read_temperature()
291-
elif sensor.type == TYPE_SOC_TEMPERATURE:
292-
if self._has_mcu_temperature:
293-
import esp32
294-
return esp32.mcu_temperature()
295-
elif sensor.type == TYPE_TEMPERATURE:
296-
# Generic temperature - return first available (backward compatibility)
297-
if self._imu_driver:
298-
temp = self._imu_driver.read_temperature()
299-
if temp is not None:
300-
return temp
301-
if self._has_mcu_temperature:
302-
import esp32
303-
return esp32.mcu_temperature()
304-
return None
347+
return self.read_sensor_once(sensor)
305348
except Exception as e:
306349
error_msg = str(e)
307350
# Retry if sensor data not ready, otherwise fail immediately
@@ -310,6 +353,7 @@ def read_sensor(self, sensor):
310353
time.sleep_ms(retry_delay_ms)
311354
continue
312355
else:
356+
print("Exception reading sensor:", error_msg)
313357
return None
314358

315359
return None
@@ -767,6 +811,138 @@ def set_calibration(self, accel_offsets, gyro_offsets):
767811
raise NotImplementedError
768812

769813

814+
class _IIODriver(_IMUDriver):
815+
"""
816+
Read sensor data via Linux IIO sysfs.
817+
818+
Typical base path:
819+
/sys/bus/iio/devices/iio:device0
820+
"""
821+
accel_path: str
822+
823+
def __init__(self):
824+
self.accel_path = self.find_iio_device_with_file("in_accel_x_raw")
825+
print("path:", self.accel_path)
826+
827+
def _p(self, name: str):
828+
return self.accel_path + "/" + name
829+
830+
def _exists(self, name):
831+
try:
832+
os.stat(name)
833+
return True
834+
except OSError:
835+
return False
836+
837+
def _is_dir(self, path):
838+
# MicroPython: stat tuple, mode is [0]
839+
try:
840+
st = os.stat(path)
841+
mode = st[0]
842+
# directory bit (POSIX): 0o040000
843+
return (mode & 0o170000) == 0o040000
844+
except OSError:
845+
return False
846+
847+
def find_iio_device_with_file(self, filename, base_dir="/sys/bus/iio/devices/"):
848+
"""
849+
Returns full path to iio:deviceX that contains given filename,
850+
e.g. "/sys/bus/iio/devices/iio:device0"
851+
852+
Returns None if not found.
853+
"""
854+
855+
print("Is dir? ", self._is_dir(base_dir), base_dir)
856+
try:
857+
entries = os.listdir(base_dir)
858+
except OSError:
859+
print("Error listing dir")
860+
return None
861+
862+
for e in entries:
863+
print("Entry:", e)
864+
if not e.startswith("iio:device"):
865+
continue
866+
867+
print("Entry:", e)
868+
869+
dev_path = base_dir + "/" + e
870+
if not self._is_dir(dev_path):
871+
continue
872+
873+
if self._exists(dev_path + "/" + filename):
874+
return dev_path
875+
876+
return None
877+
878+
def _read_text(self, name: str) -> str:
879+
p = name
880+
print("Read: ", p)
881+
f = open(p, "r")
882+
try:
883+
return f.readline().strip()
884+
finally:
885+
f.close()
886+
887+
def _read_float(self, name: str) -> float:
888+
return float(self._read_text(name))
889+
890+
def _read_int(self, name: str) -> int:
891+
return int(self._read_text(name), 10)
892+
893+
def _read_raw_scaled(self, raw_name: str, scale_name: str) -> float:
894+
raw = self._read_int(raw_name)
895+
scale = self._read_float(scale_name)
896+
return raw * scale
897+
898+
# ----------------------------
899+
# Public API (replacing I2C)
900+
# ----------------------------
901+
902+
def read_temperature(self) -> float:
903+
"""
904+
Tries common IIO patterns:
905+
- in_temp_input (already scaled, usually millidegree C)
906+
- in_temp_raw + in_temp_scale
907+
"""
908+
if False: # os.path.exists(self._p("in_temp_input")):
909+
v = self._read_float(self.accel_path + "/" + "in_temp_input")
910+
# Many drivers expose millidegree Celsius here.
911+
if abs(v) > 200: # heuristic: 25000 means 25°C
912+
return v / 1000.0
913+
return v
914+
915+
# Fallback: raw + scale
916+
return self._read_raw_scaled(self.accel_path + "/" + "in_temp_raw", self.accel_path + "/" + "in_temp_scale")
917+
918+
def read_acceleration(self) -> tuple[float, float, float]:
919+
"""
920+
Returns acceleration in m/s^2 if the kernel driver uses standard IIO scale.
921+
Common names:
922+
in_accel_{x,y,z}_raw + in_accel_scale
923+
"""
924+
scale_name = self.accel_path + "/" + "in_accel_scale"
925+
926+
ax = self._read_raw_scaled(self.accel_path + "/" + "in_accel_x_raw", scale_name)
927+
ay = self._read_raw_scaled(self.accel_path + "/" + "in_accel_y_raw", scale_name)
928+
az = self._read_raw_scaled(self.accel_path + "/" + "in_accel_z_raw", scale_name)
929+
930+
return (ax, ay, az)
931+
932+
def read_gyroscope(self) -> tuple[float, float, float]:
933+
"""
934+
Returns angular velocity in rad/s if the kernel driver uses standard IIO scale.
935+
Common names:
936+
in_anglvel_{x,y,z}_raw + in_anglvel_scale
937+
"""
938+
scale_name = self.accel_path + "/" + "in_anglvel_scale"
939+
940+
gx = self._read_raw_scaled(self.accel_path + "/" + "in_anglvel_x_raw", scale_name)
941+
gy = self._read_raw_scaled(self.accel_path + "/" + "in_anglvel_y_raw", scale_name)
942+
gz = self._read_raw_scaled(self.accel_path + "/" + "in_anglvel_z_raw", scale_name)
943+
944+
return (gx, gy, gz)
945+
770946
class _QMI8658Driver(_IMUDriver):
771947
"""Wrapper for QMI8658 IMU (Waveshare board)."""
772948

@@ -1054,8 +1230,8 @@ def set_calibration(self, accel_offsets, gyro_offsets):
10541230

10551231
_original_methods = {}
10561232
_methods_to_delegate = [
1057-
'init', 'is_available', 'get_sensor_list', 'get_default_sensor',
1058-
'read_sensor', 'calibrate_sensor', 'check_calibration_quality',
1233+
'init', 'init_iio', 'is_available', 'get_sensor_list', 'get_default_sensor',
1234+
'read_sensor', 'read_sensor_once', 'calibrate_sensor', 'check_calibration_quality',
10591235
'check_stationarity'
10601236
]
10611237

0 commit comments

Comments
 (0)