You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
4.4 KiB
140 lines
4.4 KiB
2 weeks ago
|
#!/usr/bin/env python3
|
||
|
import os
|
||
|
import time
|
||
|
import ctypes
|
||
|
import select
|
||
|
import threading
|
||
|
|
||
|
import cereal.messaging as messaging
|
||
|
from cereal.services import SERVICE_LIST
|
||
|
from openpilot.common.util import sudo_write
|
||
|
from openpilot.common.realtime import config_realtime_process, Ratekeeper
|
||
|
from openpilot.common.swaglog import cloudlog
|
||
|
from openpilot.common.gpio import gpiochip_get_ro_value_fd, gpioevent_data
|
||
|
|
||
|
from openpilot.system.sensord.sensors.i2c_sensor import Sensor
|
||
|
from openpilot.system.sensord.sensors.lsm6ds3_accel import LSM6DS3_Accel
|
||
|
from openpilot.system.sensord.sensors.lsm6ds3_gyro import LSM6DS3_Gyro
|
||
|
from openpilot.system.sensord.sensors.lsm6ds3_temp import LSM6DS3_Temp
|
||
|
from openpilot.system.sensord.sensors.mmc5603nj_magn import MMC5603NJ_Magn
|
||
|
|
||
|
I2C_BUS_IMU = 1
|
||
|
|
||
|
def interrupt_loop(sensors: list[tuple[Sensor, str, bool]], event) -> None:
|
||
|
pm = messaging.PubMaster([service for sensor, service, interrupt in sensors if interrupt])
|
||
|
|
||
|
# Requesting both edges as the data ready pulse from the lsm6ds sensor is
|
||
|
# very short (75us) and is mostly detected as falling edge instead of rising.
|
||
|
# So if it is detected as rising the following falling edge is skipped.
|
||
|
fd = gpiochip_get_ro_value_fd("sensord", 0, 84)
|
||
|
|
||
|
# Configure IRQ affinity
|
||
|
irq_path = "/proc/irq/336/smp_affinity_list"
|
||
|
if not os.path.exists(irq_path):
|
||
|
irq_path = "/proc/irq/335/smp_affinity_list"
|
||
|
if os.path.exists(irq_path):
|
||
|
sudo_write('1\n', irq_path)
|
||
|
|
||
|
offset = time.time_ns() - time.monotonic_ns()
|
||
|
|
||
|
poller = select.poll()
|
||
|
poller.register(fd, select.POLLIN | select.POLLPRI)
|
||
|
while not event.is_set():
|
||
|
events = poller.poll(100)
|
||
|
if not events:
|
||
|
cloudlog.error("poll timed out")
|
||
|
continue
|
||
|
if not (events[0][1] & (select.POLLIN | select.POLLPRI)):
|
||
|
cloudlog.error("no poll events set")
|
||
|
continue
|
||
|
|
||
|
dat = os.read(fd, ctypes.sizeof(gpioevent_data)*16)
|
||
|
evd = gpioevent_data.from_buffer_copy(dat)
|
||
|
|
||
|
cur_offset = time.time_ns() - time.monotonic_ns()
|
||
|
if abs(cur_offset - offset) > 10 * 1e6: # ms
|
||
|
cloudlog.warning(f"time jumped: {cur_offset} {offset}")
|
||
|
offset = cur_offset
|
||
|
continue
|
||
|
|
||
|
ts = evd.timestamp - cur_offset
|
||
|
for sensor, service, interrupt in sensors:
|
||
|
if interrupt:
|
||
|
try:
|
||
|
evt = sensor.get_event(ts)
|
||
|
if not sensor.is_data_valid():
|
||
|
continue
|
||
|
msg = messaging.new_message(service, valid=True)
|
||
|
setattr(msg, service, evt)
|
||
|
pm.send(service, msg)
|
||
|
except Sensor.DataNotReady:
|
||
|
pass
|
||
|
except Exception:
|
||
|
cloudlog.exception(f"Error processing {service}")
|
||
|
|
||
|
|
||
|
def polling_loop(sensor: Sensor, service: str, event: threading.Event) -> None:
|
||
|
pm = messaging.PubMaster([service])
|
||
|
rk = Ratekeeper(SERVICE_LIST[service].frequency, print_delay_threshold=None)
|
||
|
while not event.is_set():
|
||
|
try:
|
||
|
evt = sensor.get_event()
|
||
|
if not sensor.is_data_valid():
|
||
|
continue
|
||
|
msg = messaging.new_message(service, valid=True)
|
||
|
setattr(msg, service, evt)
|
||
|
pm.send(service, msg)
|
||
|
except Exception:
|
||
|
cloudlog.exception(f"Error in {service} polling loop")
|
||
|
rk.keep_time()
|
||
|
|
||
|
def main() -> None:
|
||
|
config_realtime_process([1, ], 1)
|
||
|
|
||
|
sensors_cfg = [
|
||
|
(LSM6DS3_Accel(I2C_BUS_IMU), "accelerometer", True),
|
||
|
(LSM6DS3_Gyro(I2C_BUS_IMU), "gyroscope", True),
|
||
|
(LSM6DS3_Temp(I2C_BUS_IMU), "temperatureSensor", False),
|
||
|
(MMC5603NJ_Magn(I2C_BUS_IMU), "magnetometer", False),
|
||
|
]
|
||
|
|
||
|
# Initialize sensors
|
||
|
exit_event = threading.Event()
|
||
|
threads = [
|
||
|
threading.Thread(target=interrupt_loop, args=(sensors_cfg, exit_event), daemon=True)
|
||
|
]
|
||
|
for sensor, service, interrupt in sensors_cfg:
|
||
|
try:
|
||
|
sensor.init()
|
||
|
if not interrupt:
|
||
|
# Start polling thread for sensors without interrupts
|
||
|
threads.append(threading.Thread(
|
||
|
target=polling_loop,
|
||
|
args=(sensor, service, exit_event),
|
||
|
daemon=True
|
||
|
))
|
||
|
except Exception:
|
||
|
cloudlog.exception(f"Error initializing {service} sensor")
|
||
|
|
||
|
try:
|
||
|
for t in threads:
|
||
|
t.start()
|
||
|
while any(t.is_alive() for t in threads):
|
||
|
time.sleep(1)
|
||
|
except KeyboardInterrupt:
|
||
|
pass
|
||
|
finally:
|
||
|
exit_event.set()
|
||
|
for t in threads:
|
||
|
if t.is_alive():
|
||
|
t.join()
|
||
|
|
||
|
for sensor, _, _ in sensors_cfg:
|
||
|
try:
|
||
|
sensor.shutdown()
|
||
|
except Exception:
|
||
|
cloudlog.exception("Error shutting down sensor")
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|