IQ.Pilot Release Commit @ 9fe0487
This commit is contained in:
@@ -200,6 +200,9 @@ class HardwareBase(ABC):
|
||||
def reboot_modem(self):
|
||||
pass
|
||||
|
||||
def recover_sim_detection(self) -> bool:
|
||||
return False
|
||||
|
||||
def get_networks(self):
|
||||
return None
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ def hw_state_thread(end_event, hw_queue):
|
||||
modem_configured = False
|
||||
modem_missing_count = 0
|
||||
modem_restart_count = 0
|
||||
sim_detection_recovered = False
|
||||
|
||||
while not end_event.is_set():
|
||||
# these are expensive calls. update every 10s
|
||||
@@ -254,6 +255,10 @@ def hw_state_thread(end_event, hw_queue):
|
||||
HARDWARE.configure_modem()
|
||||
modem_configured = True
|
||||
|
||||
if modem_configured and not sim_detection_recovered and HARDWARE.recover_sim_detection():
|
||||
cloudlog.event("sim missing with hot-swap detect armed, rebooting modem with detect disabled", error=True)
|
||||
sim_detection_recovered = True
|
||||
|
||||
prev_hw_state = hw_state
|
||||
except Exception:
|
||||
cloudlog.exception("Error getting hardware state")
|
||||
@@ -549,15 +554,17 @@ def hardware_thread(end_event, hw_queue) -> None:
|
||||
som_power_draw = HARDWARE.get_som_power_draw()
|
||||
msg.deviceState.somPowerDrawW = som_power_draw
|
||||
|
||||
# FastSleep deep standby: shed heavy processes at low battery instead of shutting down,
|
||||
# recover on ignition or once the alternator is charging
|
||||
# FastSleep deep standby: shed heavy processes once parked with the screen idled off
|
||||
# (or at low battery) instead of shutting down, recover on ignition or once the
|
||||
# alternator is charging
|
||||
fast_sleep = params.get_bool("FastSleep")
|
||||
if fast_sleep and not tesla_no_sleep:
|
||||
if low_power:
|
||||
if onroad_conditions["ignition"] or power_monitor.car_voltage_mV >= (VBATT_LOW_POWER_EXIT * 1e3):
|
||||
low_power = False
|
||||
else:
|
||||
low_power = power_monitor.should_enter_low_power(onroad_conditions["ignition"], in_car, off_ts)
|
||||
screen_off = msg.deviceState.screenBrightnessPercent < 1e-3
|
||||
low_power = power_monitor.should_enter_low_power(onroad_conditions["ignition"], in_car, off_ts, screen_off)
|
||||
else:
|
||||
low_power = False
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ CAR_CHARGING_RATE_W = 45
|
||||
|
||||
VBATT_PAUSE_CHARGING = 11.8 # Lower limit on the LPF car battery voltage
|
||||
|
||||
# FastSleep (deep standby): enter low power at the normal shutdown voltage, shut down
|
||||
# at a lower floor, exit once the alternator is charging
|
||||
# FastSleep (deep standby): enter low power once parked with the screen idled off, or
|
||||
# immediately at the normal shutdown voltage; shut down at a lower floor, exit once the
|
||||
# alternator is charging
|
||||
VBATT_LOW_POWER_ENTRY = 11.8
|
||||
VBATT_LOW_POWER_EXIT = 12.8
|
||||
VBATT_HARD_SHUTDOWN = 11.5
|
||||
LOW_POWER_ENTRY_TIME_S = 300
|
||||
MAX_TIME_OFFROAD_S = 30*3600
|
||||
MIN_ON_TIME_S = 3600
|
||||
DELAY_SHUTDOWN_TIME_S = 300 # Wait at least DELAY_SHUTDOWN_TIME_S seconds after offroad_time to shutdown.
|
||||
@@ -124,14 +126,18 @@ class PowerMonitoring:
|
||||
return 0 < iq_max_time_val_s <= offroad_time
|
||||
|
||||
# FastSleep: see if we should enter low power mode instead of shutting down
|
||||
def should_enter_low_power(self, ignition: bool, in_car: bool, offroad_timestamp: float | None) -> bool:
|
||||
def should_enter_low_power(self, ignition: bool, in_car: bool, offroad_timestamp: float | None, screen_off: bool) -> bool:
|
||||
if offroad_timestamp is None or ignition or not in_car:
|
||||
return False
|
||||
if not self.params.get_bool("FastSleep"):
|
||||
return False
|
||||
offroad_time = time.monotonic() - offroad_timestamp
|
||||
return (self.car_voltage_mV < (VBATT_LOW_POWER_ENTRY * 1e3) and
|
||||
offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)
|
||||
# a healthy battery rests above VBATT_LOW_POWER_ENTRY, so parked entry must be
|
||||
# time-based; the voltage trigger stays as the sagging-battery fast path
|
||||
low_voltage = (self.car_voltage_mV < (VBATT_LOW_POWER_ENTRY * 1e3) and
|
||||
offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)
|
||||
parked_idle = screen_off and offroad_time > LOW_POWER_ENTRY_TIME_S
|
||||
return low_voltage or parked_idle
|
||||
|
||||
# See if we need to shutdown
|
||||
def should_shutdown(self, ignition: bool, in_car: bool, offroad_timestamp: float | None, started_seen: bool):
|
||||
@@ -141,12 +147,15 @@ class PowerMonitoring:
|
||||
now = time.monotonic()
|
||||
should_shutdown = False
|
||||
offroad_time = (now - offroad_timestamp)
|
||||
vbatt_min = VBATT_HARD_SHUTDOWN if self.params.get_bool("FastSleep") else VBATT_PAUSE_CHARGING
|
||||
fast_sleep = self.params.get_bool("FastSleep")
|
||||
vbatt_min = VBATT_HARD_SHUTDOWN if fast_sleep else VBATT_PAUSE_CHARGING
|
||||
low_voltage_shutdown = (self.car_voltage_mV < (vbatt_min * 1e3) and
|
||||
offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)
|
||||
should_shutdown |= self.max_time_offroad_exceeded(offroad_time)
|
||||
should_shutdown |= low_voltage_shutdown
|
||||
should_shutdown |= (self.car_battery_capacity_uWh <= 0)
|
||||
# the 30 Wh bookkeeping model empties within hours at offroad draw regardless of the
|
||||
# real battery state; under FastSleep the measured voltage floors govern instead
|
||||
should_shutdown |= (self.car_battery_capacity_uWh <= 0) and not fast_sleep
|
||||
should_shutdown &= not ignition
|
||||
should_shutdown &= (not self.params.get_bool("DisablePowerDown"))
|
||||
should_shutdown &= in_car
|
||||
|
||||
@@ -2,7 +2,8 @@ import pytest
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.hardware.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \
|
||||
CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S, MAX_TIME_OFFROAD_S
|
||||
CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S, MAX_TIME_OFFROAD_S, \
|
||||
VBATT_HARD_SHUTDOWN, LOW_POWER_ENTRY_TIME_S
|
||||
|
||||
# Create fake time
|
||||
ssb = 0.
|
||||
@@ -232,3 +233,59 @@ class TestPowerMonitoring:
|
||||
result = pm.max_time_offroad_exceeded(offroad_time_s)
|
||||
|
||||
assert result == expected_result
|
||||
|
||||
# FastSleep must not shut down on the empty bookkeeping model while voltage is healthy
|
||||
def test_fast_sleep_ignores_battery_capacity_model(self, mocker):
|
||||
self.params.put_bool("FastSleep", True)
|
||||
self.params.put("MaxTimeOffroad", 0)
|
||||
try:
|
||||
pm_patch(mocker, "HARDWARE.get_current_power_draw", 0)
|
||||
pm = PowerMonitoring()
|
||||
pm.car_battery_capacity_uWh = 0
|
||||
start_time = ssb
|
||||
for _ in range(DELAY_SHUTDOWN_TIME_S + 100):
|
||||
pm.calculate(GOOD_VOLTAGE, False)
|
||||
assert not pm.should_shutdown(False, True, start_time, True)
|
||||
finally:
|
||||
self.params.put_bool("FastSleep", False)
|
||||
|
||||
# FastSleep still shuts down below the hard voltage floor
|
||||
def test_fast_sleep_hard_voltage_floor(self, mocker):
|
||||
self.params.put_bool("FastSleep", True)
|
||||
try:
|
||||
pm_patch(mocker, "HARDWARE.get_current_power_draw", 0)
|
||||
pm = PowerMonitoring()
|
||||
pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh
|
||||
start_time = ssb
|
||||
for _ in range(DELAY_SHUTDOWN_TIME_S + 100):
|
||||
pm.calculate((VBATT_HARD_SHUTDOWN - 0.5) * 1e3, False)
|
||||
assert pm.should_shutdown(False, True, start_time, True)
|
||||
finally:
|
||||
self.params.put_bool("FastSleep", False)
|
||||
|
||||
def test_fast_sleep_low_power_entry(self, mocker):
|
||||
self.params.put_bool("FastSleep", True)
|
||||
try:
|
||||
pm_patch(mocker, "HARDWARE.get_current_power_draw", 0)
|
||||
|
||||
# parked with the screen idled off: time-based entry at healthy voltage
|
||||
pm = PowerMonitoring()
|
||||
start_time = ssb
|
||||
for _ in range(LOW_POWER_ENTRY_TIME_S + 10):
|
||||
pm.calculate(GOOD_VOLTAGE, False)
|
||||
assert pm.should_enter_low_power(False, True, start_time, screen_off=True)
|
||||
assert not pm.should_enter_low_power(False, True, start_time, screen_off=False)
|
||||
assert not pm.should_enter_low_power(True, True, start_time, screen_off=True)
|
||||
assert not pm.should_enter_low_power(False, False, start_time, screen_off=True)
|
||||
|
||||
# sagging battery: voltage entry regardless of screen state
|
||||
pm = PowerMonitoring()
|
||||
start_time = ssb
|
||||
for _ in range(100):
|
||||
pm.calculate((VBATT_HARD_SHUTDOWN + 0.1) * 1e3, False)
|
||||
assert pm.should_enter_low_power(False, True, start_time, screen_off=False)
|
||||
|
||||
self.params.put_bool("FastSleep", False)
|
||||
assert not pm.should_enter_low_power(False, True, start_time, screen_off=True)
|
||||
finally:
|
||||
self.params.put_bool("FastSleep", False)
|
||||
|
||||
@@ -78,17 +78,17 @@
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img.xz",
|
||||
"hash": "654de83dd64190701c5a9dc7297fb569ba106cc63217725893920df6b8c9eaa0",
|
||||
"hash_raw": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz",
|
||||
"hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9",
|
||||
"hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"size": 6291456000,
|
||||
"sparse": true,
|
||||
"full_check": false,
|
||||
"has_ab": true,
|
||||
"ondevice_hash": "8269bd017b18a1a4fd5e093b96efa181fd803e200fa92364fe9307f25445f596",
|
||||
"ondevice_hash": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff",
|
||||
"alt": {
|
||||
"hash": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img",
|
||||
"hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img",
|
||||
"size": 6291456000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
6ZRxEduDaLm1coUVvI/0QAZtmbf+tY0TWMZtaEaxV7I2fKFfGPG/6oOizuFsmd/SSQls84kap9MwKDEc8XfIDA==
|
||||
+LyP/p4uBBxbTwRwPrwR/2qPaHkAXmlFkg0GdxOBuujPY89GxP4t5SNKxirqiw9LmMcfA0JfVwMeR3QD9rQ6Dg==
|
||||
|
||||
@@ -67,17 +67,17 @@
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img.xz",
|
||||
"hash": "654de83dd64190701c5a9dc7297fb569ba106cc63217725893920df6b8c9eaa0",
|
||||
"hash_raw": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz",
|
||||
"hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9",
|
||||
"hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"size": 6291456000,
|
||||
"sparse": true,
|
||||
"full_check": false,
|
||||
"has_ab": true,
|
||||
"ondevice_hash": "8269bd017b18a1a4fd5e093b96efa181fd803e200fa92364fe9307f25445f596",
|
||||
"ondevice_hash": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff",
|
||||
"alt": {
|
||||
"hash": "756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-756453317d208f873d44e14e9429d466c90cdcee70de52fede0bfdd6f6571c7b.img",
|
||||
"hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
|
||||
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img",
|
||||
"size": 6291456000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
+gqhJbKMr9i9lW7WrtKebqKXlM0Asr/i7ZodOzIAHzAgzGEvPCWbfco2jo0/jO7O2UzZwKJUl/WzHlsOtJvnCw==
|
||||
RCYk34Ifjx7S9PIhPm6p49vFMhamPwvw8Lfbb6a6HO8sUOi3J2PcY4VDUcqQZWU6+Am++HP3nJSea8OmAsckAg==
|
||||
|
||||
@@ -69,6 +69,9 @@ NetworkStrength = log.DeviceState.NetworkStrength
|
||||
MM_MODEM_ACCESS_TECHNOLOGY_UMTS = 1 << 5
|
||||
MM_MODEM_ACCESS_TECHNOLOGY_LTE = 1 << 14
|
||||
|
||||
# MMModemStateFailedReason
|
||||
MM_MODEM_STATE_FAILED_REASON_SIM_MISSING = 2
|
||||
|
||||
|
||||
def affine_irq(val, action):
|
||||
irqs = get_irqs_for_action(action)
|
||||
@@ -605,6 +608,32 @@ class Tici(HardwareBase):
|
||||
os.system(f"sudo cp {tf.name} {dest}")
|
||||
os.system(f"sudo nmcli con load {dest}")
|
||||
|
||||
def recover_sim_detection(self) -> bool:
|
||||
# A worn SIM-tray presence switch can read "removed" while the SIM pads still make
|
||||
# contact; with hot-swap detect armed (AT+QSIMDET=1) the modem never powers the SIM
|
||||
# and lands in failed/sim-missing. Disabling detect and rebooting the modem makes it
|
||||
# probe the SIM electrically. Safe to retry on failure: firing disarms the QSIMDET
|
||||
# gate, so a genuinely SIM-less device gets at most one extra modem reboot per boot.
|
||||
if self.get_device_type() not in ("tici", "tizi"):
|
||||
return False
|
||||
|
||||
try:
|
||||
modem = self.get_modem()
|
||||
state = modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if state != MM_MODEM_STATE.FAILED:
|
||||
return False
|
||||
reason = modem.Get(MM_MODEM, 'StateFailedReason', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
if reason != MM_MODEM_STATE_FAILED_REASON_SIM_MISSING:
|
||||
return False
|
||||
detect = str(modem.Command('AT+QSIMDET?', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)).strip()
|
||||
if not detect.startswith('+QSIMDET: 1'):
|
||||
return False
|
||||
modem.Command('AT+QSIMDET=0,0', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
modem.Command('AT+CFUN=1,1', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def reboot_modem(self):
|
||||
modem = None
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user