diff --git a/artifacts/iqpilot_emac_private/manifest.json b/artifacts/iqpilot_emac_private/manifest.json index 5acc4085..fa4179a5 100644 --- a/artifacts/iqpilot_emac_private/manifest.json +++ b/artifacts/iqpilot_emac_private/manifest.json @@ -51,8 +51,8 @@ }, "python/_iqclosure/iqpilot/common/filter_simple.py": { "mode": 420, - "sha256": "881243e472986ff0ba65c4044fa192ab5dd5e22c1d9fac3067ef953262a5fe3d", - "size": 961 + "sha256": "4b34753a3d3eea6d1578d88d21350045173a3809d38aed232875d1c5f34b4141", + "size": 1929 }, "python/_iqclosure/iqpilot/common/gpio.py": { "mode": 420, @@ -141,8 +141,8 @@ }, "python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py": { "mode": 420, - "sha256": "91008a050069861404b3f20977f5b8b6fdab4b3919e76e1ae95be49ce92f4ad8", - "size": 12298 + "sha256": "0db179f0df2dd8949493bc49a5cdecb18d11383a20239c759ece59fa9ae874cc", + "size": 11398 }, "python/_iqclosure/iqpilot/selfdrive/controls/lib/drive_helpers.py": { "mode": 420, @@ -521,8 +521,8 @@ }, "python/_iqclosure/iqpilot/system/hardware/tici/hardware.py": { "mode": 420, - "sha256": "f1ddffa9bdb5071a49fd65eb4b49958b60b1873e1ec70e6f3f2e2b2501abce2f", - "size": 25584 + "sha256": "e8eae4e8a75e1751525471cf54a8555b7bfd7f08ad84bf2e75642b5745f222db", + "size": 25601 }, "python/_iqclosure/iqpilot/system/hardware/tici/iwlist.py": { "mode": 420, @@ -576,37 +576,37 @@ }, "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "0ecdfb14d5d39ea7481d41df103330bce083439123ae1abc8a80c15201e51ae0", - "size": 333808 + "sha256": "bc5e1c99a6efca66a1f7d69ade1bd228870f965117deae86378c32e924432d39", + "size": 333944 }, "python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "6e9d753625942ba2e061d62dec251cbe838ba04bf047cd0db5ee56df0bbb3812", + "sha256": "5aa52680f9dd3ad393fabbed20e434168becf285ab5991df35dd8b3758d78419", "size": 68072 }, "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "b7e359549fea1ac8cb7e1d889b64c03cfa0d4cb6f2b0d34e0247bb7a57da7ec0", - "size": 267928 + "sha256": "0a1048e38176dce88844f20f927be2dbca151e87e05e88e707bb807ff44595cc", + "size": 333544 }, "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "445552ce49ea320ce13cacbaac3d73b6d38f5264bbd62af5be9ea47208d720f9", - "size": 404256 + "sha256": "3bbcf0e311d1fed7a8b7f8e0b995875dc1f70f89a57681fe9a507721ba61452e", + "size": 404536 }, "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "948fb552c0a0284979ae5e3fc9137d558ad7174158ffea232ae7d01466a61146", - "size": 464728 + "sha256": "23c9a32a75daab86714e127902cef5c456703838db6e178a502aff3fe6cc50f2", + "size": 531776 }, "python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "9af9e13309f6e9907804b7d40182720510ce3fe512aea0ef4b3ebffb842bfe69", + "sha256": "cb4888d0a6d31851e15bbd7d53666d01382252cf5b46f994b4eeff2960ac502c", "size": 473440 }, "python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": { "mode": 493, - "sha256": "55252943e98e971c16cce659bb0038738383b34f87d933430626bbe2ca378b96", + "sha256": "bb23a72222a72983be8cacbf13972e36c966f8c4e3d844fcf572bb54501ef8be", "size": 205848 }, "runtime": { @@ -618,12 +618,12 @@ } }, "signatures": { - "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "tmAlUdhZoUAWgQcRNuju1rSitT8oFbV6XAacY52kc2ffe3hVJQ6TgQO11YwSfKrzk9M1i93FEjCNm/y15j1lCw==", - "python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "kxlAJMSafbk3c4o/XTqxp0nvB0BgP6+StWVpzSkZLLMuqsaHTYilBUYNUPWW2/4fDVgmFsqNDt8JMiOCYXPCCw==", - "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "MZG4X/QnCy/COsuMHPJw/b0oDFgMXhGI8E3SIKxpX4c+n02bxRwFxaRYS8hkm02S8ynTzZB5mUNhW/st7FkSAA==", - "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "rVudE3ZHsquCb0gV33LcnzkGG6nZV3kXN78YbHaKqXOfdpWEKLYrUXId7qng3BUJk+Mfyq7FC/ZVWIR9vvJSCw==", - "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "pFhB5A3P8QvOTueM/hu2H128OoT6S7zQdPhFz3HrFegRhuStWQ8tpWgu4pVPacfY+zEVeWdNDtWJrX5wKZ6UAQ==", - "python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": "D58S28SrZ4SrTrGHAGDIHLpmV6ulSj0EQ4r1415/Fn8Eyb5ayknAdJtlTBiXihxRq1wc+aLBvjTbL7vepgaHCg==", - "python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": "gzJJB6tzvLI9AL6ns6zvkTS0/xKEQRbn6uhE8INr1drMCqbim81OPMz5wesJaYNVlVTK72RT5tOy62mX0h1XDg==" + "python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so": "nb58JcxhuvZb66ykqx6qtiYcxuMrT9RxYLVPrySBh2c/2lK4YAEPOW3NYXItamo3c77tp5X12OOcTenV+hRFDQ==", + "python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so": "SlLArJdgtL4q44jc1iF2qOswWn0JaYKPLHM3WX+/CzkW6s5LLjPJLIP2/X9pT/xd8yb5jrrmNlkh756J0BlwDQ==", + "python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so": "XuilHMLAoLU+Uythp7DLZyUSzh5cd+Kd/ppdar60CvfI2ddWzyxUtWzRAj89MXosOg8TZlfvdbR+izFSaQaWCQ==", + "python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so": "3kHG7y4co2JjaMyYx+Nc2gti9VXv1EkDqgPuijpjtrUXxo2/z23HzW5OFR2tS5HOdSsoPp0QKxqNSJFU2vo1CQ==", + "python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so": "MqqTLheWl49buYG1d/aJk6W23WTEpBc0+BgTUAZHRHlfbrvvJzEZxutjS5EUd36QjOcqujdXns50NVrvbaAmDQ==", + "python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so": "JI2Fqs5/uhABjC1TA5nPytJQXCWQLDYlEljLTkak6Dc8chUf2WiwBhWpcwFWhZO+QKwxERJXFdsG1ZTuPlTlDQ==", + "python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so": "BkfWTqw5Ei9EMLGwDNlDabsD/3sIXFJNkk+s/VR3Tm7+EKdkzJs4Ws/dshTDvg5DZjuwKmOdeMYZFxEpHcqvAw==" } } diff --git a/artifacts/iqpilot_emac_private/manifest.json.sig b/artifacts/iqpilot_emac_private/manifest.json.sig index 269e8c6f..dab6944b 100644 --- a/artifacts/iqpilot_emac_private/manifest.json.sig +++ b/artifacts/iqpilot_emac_private/manifest.json.sig @@ -1 +1 @@ -ugmbC4W3Qqvv6F1w1mY3z3bdZV6HsCvSX+HQ+3vjBh03AKRaz41BYjjXdFDdFz45F/sOIeZvmomkvym4GezNCg== +WG1x9JR+nNQKjx6iHX9H9cBQWxWpHVCMn3NHirf7IDe/MnGX/Xsdv1tHKunzhleUga6DF9uhIsxThBewBFNfAQ== diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/filter_simple.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/filter_simple.py index 212e1a8f..30839f66 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/filter_simple.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/common/filter_simple.py @@ -1,3 +1,8 @@ +from collections import deque + +import numpy as np + + class FirstOrderFilter: def __init__(self, x0, rc, dt, initialized=True): self.x = x0 @@ -32,3 +37,35 @@ class BounceFilter(FirstOrderFilter): self.velocity.x = 0.0 self.x += self.velocity.x return self.x + + +class MyMovingAverage: + def __init__(self, window_size, value=None): + self.window_size = window_size + if value is not None: + self.values = deque([value] * window_size, maxlen=window_size) + self.sum = value * window_size + self.result = value + else: + self.values = deque(maxlen=window_size) + self.sum = 0 + self.result = 0 + + def set(self, value): + self.values.clear() + self.values.append(value) + self.sum = value + self.result = value + return value + + def set_all(self, value): + self.values = deque([value] * self.window_size, maxlen=self.window_size) + self.sum = value * self.window_size + self.result = value + return value + + def process(self, value, median=False): + self.values.append(value) + self.sum = sum(self.values) + self.result = float(np.median(self.values)) if median else float(self.sum) / len(self.values) + return self.result diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py index ce77ab13..b54a8ce4 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/selfdrive/controls/lib/desire_helper.py @@ -25,11 +25,9 @@ NavManeuverPhase = custom.IQNavState.ManeuverPhase LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_TIME_MAX = 10.0 -TURN_DESIRE_STOP_HOLD_TIME = 3.4 -TURN_DESIRE_STOP_GAP_TIME = 0.2 -TURN_DESIRE_STOP_CYCLE_TIME = TURN_DESIRE_STOP_HOLD_TIME + TURN_DESIRE_STOP_GAP_TIME -TURN_DESIRE_CYCLE_SPEED_MAX = 5 * CV.MPH_TO_MS -TURN_DESIRE_COMMIT_YAW_RATE = 0.08 +# the model desire history spans 5 s, so one rising edge per 5 s keeps exactly one turn pulse visible +TURN_DESIRE_PULSE_HOLD_FRAMES = round(5.0 / DT_MDL) - 1 +TURN_DESIRE_PULSE_CYCLE_FRAMES = TURN_DESIRE_PULSE_HOLD_FRAMES + 1 _LANE_CHANGE_DESIRES = { (LaneChangeDirection.none, LaneChangeState.off): log.Desire.none, @@ -52,7 +50,7 @@ _TURN_DESIRES = { TurnDirection.turnRight: log.Desire.turnRight, } -_STOP_CYCLING_TURN_DESIRES = { +_PULSED_TURN_DESIRES = { log.Desire.turnLeft, log.Desire.turnRight, } @@ -111,10 +109,8 @@ class DesireHelper: self.lateral_edge_block = LateralEdgeBlock.none self.lane_turn_direction = TurnDirection.none self.nav_turn_direction = TurnDirection.none - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False + self.turn_desire_frames = 0 self.turn_desire_cycle_input = log.Desire.none - self.turn_desire_committed = False @staticmethod def get_lane_change_direction(carstate): @@ -222,43 +218,22 @@ class DesireHelper: else: self.lane_change_timer += DT_MDL - def _clear_turn_desire_stop_cycle(self) -> None: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False + def _clear_turn_desire_pulse(self) -> None: + self.turn_desire_frames = 0 self.turn_desire_cycle_input = log.Desire.none - self.turn_desire_committed = False - def _cycle_turn_desire_when_stopped(self, desired_output: log.Desire) -> log.Desire: - if desired_output not in _STOP_CYCLING_TURN_DESIRES: - self._clear_turn_desire_stop_cycle() + def _pulse_turn_desire(self, desired_output: log.Desire) -> log.Desire: + if desired_output not in _PULSED_TURN_DESIRES: + self._clear_turn_desire_pulse() return desired_output if desired_output != self.turn_desire_cycle_input: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False + self.turn_desire_frames = 0 self.turn_desire_cycle_input = desired_output - self.turn_desire_committed = False - if abs(getattr(self._last_carstate, "yawRate", 0.0)) >= TURN_DESIRE_COMMIT_YAW_RATE: - self.turn_desire_committed = True - - if self.turn_desire_committed: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False - return desired_output - - if self._last_carstate.vEgo > TURN_DESIRE_CYCLE_SPEED_MAX: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False - return desired_output - - if not self.turn_desire_stop_active: - self.turn_desire_stop_active = True - self.turn_desire_stop_timer = 0.0 - - cycle_phase = self.turn_desire_stop_timer % TURN_DESIRE_STOP_CYCLE_TIME - self.turn_desire_stop_timer += DT_MDL - if cycle_phase >= TURN_DESIRE_STOP_HOLD_TIME: + cycle_frame = self.turn_desire_frames % TURN_DESIRE_PULSE_CYCLE_FRAMES + self.turn_desire_frames += 1 + if cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES: return log.Desire.none return desired_output @@ -271,7 +246,7 @@ class DesireHelper: else: desired_output = _LANE_CHANGE_DESIRES[(self.lane_change_direction, self.lane_change_state)] - self.desire = self._cycle_turn_desire_when_stopped(desired_output) + self.desire = self._pulse_turn_desire(desired_output) def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None): self._last_carstate = carstate diff --git a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py index b9fa7fbf..2f71ed63 100644 --- a/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py +++ b/artifacts/iqpilot_emac_private/python/_iqclosure/iqpilot/system/hardware/tici/hardware.py @@ -557,7 +557,7 @@ class Tici(HardwareBase): if self.get_device_type() in ("tici", "tizi"): if initial_eps_apn: subprocess.call(["mmcli", "-m", "any", f'--3gpp-set-initial-eps-bearer-settings=apn={initial_eps_apn}']) - else: + elif is_comma_profile: subprocess.call(["mmcli", "-m", "any", '--3gpp-set-initial-eps-bearer-settings=apn=']) cmds += [ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so index ba50d6cb..020b7dfd 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/bulk_transport.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so index 1eccdff8..d5cad7e2 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/ffs_holder.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so index 82811099..2adca81e 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_client.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so index a380a3fa..8446a097 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/mac_protocol.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so index 44e6bb36..21241d57 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/maciqmodeld.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so index 21192801..e5550886 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/model_context.cpython-312-aarch64-linux-gnu.so differ diff --git a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so index ec592c64..095e305c 100755 Binary files a/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so and b/artifacts/iqpilot_emac_private/python/iqpilot_private/emac/rh_images.cpython-312-aarch64-linux-gnu.so differ diff --git a/iqpilot/common/filter_simple.py b/iqpilot/common/filter_simple.py index 212e1a8f..30839f66 100644 --- a/iqpilot/common/filter_simple.py +++ b/iqpilot/common/filter_simple.py @@ -1,3 +1,8 @@ +from collections import deque + +import numpy as np + + class FirstOrderFilter: def __init__(self, x0, rc, dt, initialized=True): self.x = x0 @@ -32,3 +37,35 @@ class BounceFilter(FirstOrderFilter): self.velocity.x = 0.0 self.x += self.velocity.x return self.x + + +class MyMovingAverage: + def __init__(self, window_size, value=None): + self.window_size = window_size + if value is not None: + self.values = deque([value] * window_size, maxlen=window_size) + self.sum = value * window_size + self.result = value + else: + self.values = deque(maxlen=window_size) + self.sum = 0 + self.result = 0 + + def set(self, value): + self.values.clear() + self.values.append(value) + self.sum = value + self.result = value + return value + + def set_all(self, value): + self.values = deque([value] * self.window_size, maxlen=self.window_size) + self.sum = value * self.window_size + self.result = value + return value + + def process(self, value, median=False): + self.values.append(value) + self.sum = sum(self.values) + self.result = float(np.median(self.values)) if median else float(self.sum) / len(self.values) + return self.result diff --git a/iqpilot/selfdrive/controls/lib/desire_helper.py b/iqpilot/selfdrive/controls/lib/desire_helper.py index ce77ab13..b54a8ce4 100644 --- a/iqpilot/selfdrive/controls/lib/desire_helper.py +++ b/iqpilot/selfdrive/controls/lib/desire_helper.py @@ -25,11 +25,9 @@ NavManeuverPhase = custom.IQNavState.ManeuverPhase LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_TIME_MAX = 10.0 -TURN_DESIRE_STOP_HOLD_TIME = 3.4 -TURN_DESIRE_STOP_GAP_TIME = 0.2 -TURN_DESIRE_STOP_CYCLE_TIME = TURN_DESIRE_STOP_HOLD_TIME + TURN_DESIRE_STOP_GAP_TIME -TURN_DESIRE_CYCLE_SPEED_MAX = 5 * CV.MPH_TO_MS -TURN_DESIRE_COMMIT_YAW_RATE = 0.08 +# the model desire history spans 5 s, so one rising edge per 5 s keeps exactly one turn pulse visible +TURN_DESIRE_PULSE_HOLD_FRAMES = round(5.0 / DT_MDL) - 1 +TURN_DESIRE_PULSE_CYCLE_FRAMES = TURN_DESIRE_PULSE_HOLD_FRAMES + 1 _LANE_CHANGE_DESIRES = { (LaneChangeDirection.none, LaneChangeState.off): log.Desire.none, @@ -52,7 +50,7 @@ _TURN_DESIRES = { TurnDirection.turnRight: log.Desire.turnRight, } -_STOP_CYCLING_TURN_DESIRES = { +_PULSED_TURN_DESIRES = { log.Desire.turnLeft, log.Desire.turnRight, } @@ -111,10 +109,8 @@ class DesireHelper: self.lateral_edge_block = LateralEdgeBlock.none self.lane_turn_direction = TurnDirection.none self.nav_turn_direction = TurnDirection.none - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False + self.turn_desire_frames = 0 self.turn_desire_cycle_input = log.Desire.none - self.turn_desire_committed = False @staticmethod def get_lane_change_direction(carstate): @@ -222,43 +218,22 @@ class DesireHelper: else: self.lane_change_timer += DT_MDL - def _clear_turn_desire_stop_cycle(self) -> None: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False + def _clear_turn_desire_pulse(self) -> None: + self.turn_desire_frames = 0 self.turn_desire_cycle_input = log.Desire.none - self.turn_desire_committed = False - def _cycle_turn_desire_when_stopped(self, desired_output: log.Desire) -> log.Desire: - if desired_output not in _STOP_CYCLING_TURN_DESIRES: - self._clear_turn_desire_stop_cycle() + def _pulse_turn_desire(self, desired_output: log.Desire) -> log.Desire: + if desired_output not in _PULSED_TURN_DESIRES: + self._clear_turn_desire_pulse() return desired_output if desired_output != self.turn_desire_cycle_input: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False + self.turn_desire_frames = 0 self.turn_desire_cycle_input = desired_output - self.turn_desire_committed = False - if abs(getattr(self._last_carstate, "yawRate", 0.0)) >= TURN_DESIRE_COMMIT_YAW_RATE: - self.turn_desire_committed = True - - if self.turn_desire_committed: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False - return desired_output - - if self._last_carstate.vEgo > TURN_DESIRE_CYCLE_SPEED_MAX: - self.turn_desire_stop_timer = 0.0 - self.turn_desire_stop_active = False - return desired_output - - if not self.turn_desire_stop_active: - self.turn_desire_stop_active = True - self.turn_desire_stop_timer = 0.0 - - cycle_phase = self.turn_desire_stop_timer % TURN_DESIRE_STOP_CYCLE_TIME - self.turn_desire_stop_timer += DT_MDL - if cycle_phase >= TURN_DESIRE_STOP_HOLD_TIME: + cycle_frame = self.turn_desire_frames % TURN_DESIRE_PULSE_CYCLE_FRAMES + self.turn_desire_frames += 1 + if cycle_frame >= TURN_DESIRE_PULSE_HOLD_FRAMES: return log.Desire.none return desired_output @@ -271,7 +246,7 @@ class DesireHelper: else: desired_output = _LANE_CHANGE_DESIRES[(self.lane_change_direction, self.lane_change_state)] - self.desire = self._cycle_turn_desire_when_stopped(desired_output) + self.desire = self._pulse_turn_desire(desired_output) def update(self, carstate, lateral_active, lane_change_prob, nav_state=None, modeldata=None, radar_state=None): self._last_carstate = carstate diff --git a/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py b/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py index ce76fa4c..3c8a88cf 100644 --- a/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py +++ b/iqpilot/selfdrive/controls/lib/tests/test_turn_desire_cycling.py @@ -1,26 +1,25 @@ from types import SimpleNamespace +import numpy as np import pytest from iqpilot.cereal import custom, log -from iqpilot.common.realtime import DT_MDL from iqpilot.selfdrive.controls.lib.desire_helper import ( DesireHelper, - TURN_DESIRE_STOP_CYCLE_TIME, - TURN_DESIRE_STOP_HOLD_TIME, + TURN_DESIRE_PULSE_CYCLE_FRAMES, + TURN_DESIRE_PULSE_HOLD_FRAMES, ) TurnDirection = custom.IQTurnSignalDirection +MODEL_HISTORY_FRAMES = 100 def helper(v_ego=0.0, yaw_rate=0.0): result = DesireHelper.__new__(DesireHelper) result._last_carstate = SimpleNamespace(vEgo=v_ego, yawRate=yaw_rate) - result.turn_desire_stop_timer = 0.0 - result.turn_desire_stop_active = False + result.turn_desire_frames = 0 result.turn_desire_cycle_input = log.Desire.none - result.turn_desire_committed = False result.nav_turn_direction = TurnDirection.none result.lane_turn_direction = TurnDirection.none result.lane_change_direction = log.LaneChangeDirection.none @@ -29,50 +28,48 @@ def helper(v_ego=0.0, yaw_rate=0.0): return result +def visible_pulses(outputs): + previous = log.Desire.none + rising = [] + for output in outputs: + rising.append(output != log.Desire.none and previous != output) + previous = output + rising = np.array(rising) + return np.array([rising[max(0, i - MODEL_HISTORY_FRAMES + 1):i + 1].sum() for i in range(len(rising))]) + + @pytest.mark.parametrize("source", ["manual", "nav"]) -def test_manual_and_nav_turn_desires_receive_rising_edges(source): - h = helper() +@pytest.mark.parametrize("v_ego,yaw_rate", [(0.0, 0.0), (6.0, 0.0), (4.0, 0.3)]) +def test_turn_desire_keeps_exactly_one_pulse_in_model_history(source, v_ego, yaw_rate): + h = helper(v_ego, yaw_rate) if source == "manual": h.lane_turn_direction = TurnDirection.turnLeft else: h.nav_turn_direction = TurnDirection.turnLeft outputs = [] - for _ in range(round((TURN_DESIRE_STOP_CYCLE_TIME + 2 * DT_MDL) / DT_MDL)): + for _ in range(4 * TURN_DESIRE_PULSE_CYCLE_FRAMES): h._pick_desire_output() outputs.append(h.desire) - gap_index = next(i for i, output in enumerate(outputs) if output == log.Desire.none) - assert gap_index * DT_MDL == pytest.approx(TURN_DESIRE_STOP_HOLD_TIME, abs=DT_MDL * 1.1) - assert log.Desire.turnLeft in outputs[gap_index + 1:] + gaps = [i for i, output in enumerate(outputs) if output == log.Desire.none] + assert gaps[0] == TURN_DESIRE_PULSE_HOLD_FRAMES + assert np.diff(gaps).tolist() == [TURN_DESIRE_PULSE_CYCLE_FRAMES] * (len(gaps) - 1) + assert set(visible_pulses(outputs)[MODEL_HISTORY_FRAMES:].tolist()) == {1} -def test_creeping_restarts_stopped_turn_cycle(): +def test_new_turn_direction_restarts_pulse_immediately(): h = helper() - for _ in range(round(TURN_DESIRE_STOP_HOLD_TIME / DT_MDL)): - h._cycle_turn_desire_when_stopped(log.Desire.turnRight) - - h._last_carstate.vEgo = 3.0 - assert h._cycle_turn_desire_when_stopped(log.Desire.turnRight) == log.Desire.turnRight - h._last_carstate.vEgo = 0.0 - assert h._cycle_turn_desire_when_stopped(log.Desire.turnRight) == log.Desire.turnRight - assert h.turn_desire_stop_timer == pytest.approx(DT_MDL) - - -def test_measured_turn_commitment_stops_cycling(): - h = helper() - h._last_carstate.yawRate = -0.1 - assert h._cycle_turn_desire_when_stopped(log.Desire.turnLeft) == log.Desire.turnLeft - h._last_carstate.yawRate = 0.0 - - outputs = [h._cycle_turn_desire_when_stopped(log.Desire.turnLeft) for _ in range(300)] - assert set(outputs) == {log.Desire.turnLeft} - - -def test_new_turn_direction_rearms_cycle_after_commitment(): - h = helper(yaw_rate=0.1) - h._cycle_turn_desire_when_stopped(log.Desire.turnLeft) - h._last_carstate.yawRate = 0.0 - h._cycle_turn_desire_when_stopped(log.Desire.turnRight) - assert h.turn_desire_committed is False + for _ in range(TURN_DESIRE_PULSE_HOLD_FRAMES // 2): + assert h._pulse_turn_desire(log.Desire.turnLeft) == log.Desire.turnLeft + assert h._pulse_turn_desire(log.Desire.turnRight) == log.Desire.turnRight assert h.turn_desire_cycle_input == log.Desire.turnRight + assert h.turn_desire_frames == 1 + + +def test_lane_change_desires_pass_through_and_reset_pulse(): + h = helper() + h._pulse_turn_desire(log.Desire.turnLeft) + assert h._pulse_turn_desire(log.Desire.laneChangeLeft) == log.Desire.laneChangeLeft + assert h.turn_desire_frames == 0 + assert h.turn_desire_cycle_input == log.Desire.none diff --git a/iqpilot/selfdrive/ui/ui_state.py b/iqpilot/selfdrive/ui/ui_state.py index 19ad00ed..24910b1c 100644 --- a/iqpilot/selfdrive/ui/ui_state.py +++ b/iqpilot/selfdrive/ui/ui_state.py @@ -226,6 +226,7 @@ class UIState(IQUIState): "carOutput", "carControl", "vehicleParameters", + "egpuDockState", ] + self.sm_services_ext ) diff --git a/iqpilot/system/hardware/tici/agnos.json b/iqpilot/system/hardware/tici/agnos.json index ef1c8837..54cb5e0f 100644 --- a/iqpilot/system/hardware/tici/agnos.json +++ b/iqpilot/system/hardware/tici/agnos.json @@ -78,14 +78,14 @@ }, { "name": "system", - "url": "https://git.konn3kt.com/IQ.Lvbs/iqos/raw/branch/master/system-aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e.img.xz", - "hash": "aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e", - "hash_raw": "aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e", + "url": "https://git.konn3kt.com/IQ.Lvbs/iqos/raw/branch/master/system-1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388.img.xz", + "hash": "1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388", + "hash_raw": "1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388", "size": 6291456000, "sparse": false, "full_check": false, "has_ab": true, - "ondevice_hash": "aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e", + "ondevice_hash": "1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388", "url_parts": 10 } ] diff --git a/iqpilot/system/hardware/tici/agnos.json.sig b/iqpilot/system/hardware/tici/agnos.json.sig index 9219f557..2a434deb 100644 --- a/iqpilot/system/hardware/tici/agnos.json.sig +++ b/iqpilot/system/hardware/tici/agnos.json.sig @@ -1 +1 @@ -GOp80wlluTRkHZnMViznUD2H1iF6Y+VrWC8JsH2pVi2VeyZTTEPhdNMP7kH4EXYT7Syfa4mAgtdPLl9jXa4wAQ== +R39gJsoN/ESDuoPJTEUrOF8FXPwyf+dzf0s4pvpx53vIsXlY89OQjyooQQHMWsK4GgU9Dvv7trTuiXo3NP0XCQ== diff --git a/iqpilot/system/hardware/tici/agnos.py b/iqpilot/system/hardware/tici/agnos.py index c44765cc..b4cbe24d 100755 --- a/iqpilot/system/hardware/tici/agnos.py +++ b/iqpilot/system/hardware/tici/agnos.py @@ -36,6 +36,9 @@ IQPILOT_MANIFEST_PUBLIC_KEY = bytes.fromhex("40ae3f81b77506ecc4982a1ca37ba1d6f87 AGNOS_MANIFEST_FILE = "system/hardware/tici/agnos.json" LFS_POINTER_MAGIC = b"version https://git-lfs" +# Cloudflare cuts a proxied response off after ~100 s, so a part slower than ~900 KB/s only completes through range resumes. +PART_RESUME_LIMIT = 50 +PART_RESUME_DELAY = 2 def _image_auth_module(): try: @@ -73,12 +76,28 @@ def _download_headers(url: str) -> dict: pass return {} -def _open_image_response(url: str) -> requests.Response: +class RangeNotSupported(requests.exceptions.RequestException): + pass + +def _ranged_get(url: str, headers: dict, offset: int) -> requests.Response: + if offset: + headers = {**headers, 'Range': f"bytes={offset}-"} + req = requests.get(url, stream=True, headers=headers, timeout=60) + req.raise_for_status() + if offset and req.status_code != 206: + req.close() + raise RangeNotSupported(f"server ignored range resume at {offset} for {url}") + return req + +def _open_image_response(url: str, offset: int = 0) -> requests.Response: auth = _download_headers(url) req = requests.get(url, stream=True, headers={'Accept-Encoding': None, **auth}, timeout=60) req.raise_for_status() if int(req.headers.get('content-length') or 0) >= 1024: - return req + if not offset: + return req + req.close() + return _ranged_get(url, {'Accept-Encoding': None, **auth}, offset) body = req.content if not body.startswith(LFS_POINTER_MAGIC): @@ -88,10 +107,10 @@ def _open_image_response(url: str) -> requests.Response: size = int(meta["size"]) lfs_base = url.split("/raw/", 1)[0] + ".git/info/lfs" - req = requests.get(f"{lfs_base}/objects/{oid}", stream=True, - headers={'Accept-Encoding': None, 'Accept': 'application/vnd.git-lfs', **auth}, timeout=60) - if req.status_code == 200: - return req + try: + return _ranged_get(f"{lfs_base}/objects/{oid}", {'Accept-Encoding': None, 'Accept': 'application/vnd.git-lfs', **auth}, offset) + except (requests.exceptions.HTTPError, RangeNotSupported): + pass batch = requests.post(f"{lfs_base}/objects/batch", data=json.dumps({"operation": "download", "transfers": ["basic"], @@ -101,10 +120,7 @@ def _open_image_response(url: str) -> requests.Response: timeout=60) batch.raise_for_status() action = batch.json()["objects"][0]["actions"]["download"] - req = requests.get(action["href"], stream=True, - headers={'Accept-Encoding': None, **action.get("header", {})}, timeout=60) - req.raise_for_status() - return req + return _ranged_get(action["href"], {'Accept-Encoding': None, **action.get("header", {})}, offset) def verify_manifest_signature(manifest_path: str) -> None: @@ -131,8 +147,21 @@ class _ChainedParts: def iter_content(self, chunk_size: int) -> Generator[bytes, None, None]: for u in self.urls: - self.req = _open_image_response(u) - yield from self.req.iter_content(chunk_size=chunk_size) + received = 0 + resumes = 0 + while True: + try: + self.req = _open_image_response(u, received) + for chunk in self.req.iter_content(chunk_size=chunk_size): + received += len(chunk) + yield chunk + break + except (requests.exceptions.ChunkedEncodingError, requests.exceptions.ConnectionError): + resumes += 1 + if resumes > PART_RESUME_LIMIT: + raise + print(f"Resuming {u} at {received} bytes ({resumes})", flush=True) + time.sleep(PART_RESUME_DELAY) class StreamingDecompressor: def __init__(self, url: str, parts: int = 0) -> None: @@ -141,7 +170,7 @@ class StreamingDecompressor: if parts > 1: self.req = _ChainedParts([f"{url}.p{i:02d}" for i in range(parts)]) else: - self.req = _open_image_response(url) + self.req = _ChainedParts([url]) self.it = self.req.iter_content(chunk_size=1024 * 1024) self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO) self.eof = False diff --git a/iqpilot/system/hardware/tici/agnos_tici_15_1.json b/iqpilot/system/hardware/tici/agnos_tici_15_1.json index f4bddf9b..9bec4532 100644 --- a/iqpilot/system/hardware/tici/agnos_tici_15_1.json +++ b/iqpilot/system/hardware/tici/agnos_tici_15_1.json @@ -67,14 +67,14 @@ }, { "name": "system", - "url": "https://git.konn3kt.com/IQ.Lvbs/iqos/raw/branch/master/system-aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e.img.xz", - "hash": "aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e", - "hash_raw": "aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e", + "url": "https://git.konn3kt.com/IQ.Lvbs/iqos/raw/branch/master/system-1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388.img.xz", + "hash": "1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388", + "hash_raw": "1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388", "size": 6291456000, "sparse": false, "full_check": false, "has_ab": true, - "ondevice_hash": "aad7a8ca1a0ec5669d46e0d5c2794214b8089361be65ecb21d7feb4d4df00c5e", + "ondevice_hash": "1a3fd2a6c5d5530b6b5dd5d25311326c8b44fba22533dcfaafb1cce35485e388", "url_parts": 10 } ] diff --git a/iqpilot/system/hardware/tici/agnos_tici_15_1.json.sig b/iqpilot/system/hardware/tici/agnos_tici_15_1.json.sig index ce2eb68f..8c727027 100644 --- a/iqpilot/system/hardware/tici/agnos_tici_15_1.json.sig +++ b/iqpilot/system/hardware/tici/agnos_tici_15_1.json.sig @@ -1 +1 @@ -+ZDnJpayd7Z4kRKRJ0LKCKKA0lzO998S/3IQFaJdozGGozP31GstEC0i0xYcaixG51ye2r8fB/GCDHGrK0kyAg== +Q7FSsS7kmbYAPqYe5W+Ht6xPNb9ekBQQlVYTBT/VNgR96+aazk2TOV/8909sS3n30bfvm/YUZc1MawSzoWGgAA== diff --git a/iqpilot/system/hardware/tici/hardware.py b/iqpilot/system/hardware/tici/hardware.py index b9fa7fbf..2f71ed63 100644 --- a/iqpilot/system/hardware/tici/hardware.py +++ b/iqpilot/system/hardware/tici/hardware.py @@ -557,7 +557,7 @@ class Tici(HardwareBase): if self.get_device_type() in ("tici", "tizi"): if initial_eps_apn: subprocess.call(["mmcli", "-m", "any", f'--3gpp-set-initial-eps-bearer-settings=apn={initial_eps_apn}']) - else: + elif is_comma_profile: subprocess.call(["mmcli", "-m", "any", '--3gpp-set-initial-eps-bearer-settings=apn=']) cmds += [ diff --git a/iqpilot/system/ui/widgets/__init__.py b/iqpilot/system/ui/widgets/__init__.py index 40aa6f04..89977445 100644 --- a/iqpilot/system/ui/widgets/__init__.py +++ b/iqpilot/system/ui/widgets/__init__.py @@ -16,14 +16,21 @@ from iqpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS W = TypeVar('W', bound='Widget') _device = None +_device_unavailable = False def device_awake() -> bool: - global _device + global _device, _device_unavailable if _device is None: + if _device_unavailable: + return True try: from iqpilot.selfdrive.ui.ui_state import device _device = device + # The setup zipapp has no msgq, and a failed import is never cached, so retrying costs ~70 ms per widget per frame. + except ModuleNotFoundError: + _device_unavailable = True + return True except Exception: return True return _device.awake diff --git a/iqpilot/ui/onroad/big_model_status.py b/iqpilot/ui/onroad/big_model_status.py index a4e1537d..fbb04fa9 100644 --- a/iqpilot/ui/onroad/big_model_status.py +++ b/iqpilot/ui/onroad/big_model_status.py @@ -43,9 +43,11 @@ def _egpu_state(params: Params, engaged: bool) -> SourceState: def resolve_source(params: Params, engaged: bool) -> tuple[str, SourceState]: + if params.get_bool("UsbGpuPresent") and not params.get_bool("IQEgpuDisabled"): + return "GPU", _egpu_state(params, engaged) if params.get_bool("IQEmacEnabled"): return "MAC", _emac_state(params, engaged) - if params.get_bool("UsbGpuPresent") or params.get_bool("IQEgpuEnabled"): + if params.get_bool("IQEgpuEnabled"): return "GPU", _egpu_state(params, engaged) return "", SourceState.HIDDEN diff --git a/iqpilot/ui/onroad/emac_status.py b/iqpilot/ui/onroad/emac_status.py index 7913e37b..b1a4231b 100644 --- a/iqpilot/ui/onroad/emac_status.py +++ b/iqpilot/ui/onroad/emac_status.py @@ -15,10 +15,13 @@ from iqpilot.selfdrive.ui.ui_state import ui_state from iqpilot.system.ui.lib.application import FontWeight, gui_app from iqpilot.system.ui.lib.text_measure import measure_text_cached from iqpilot.system.ui.widgets import Widget +from iqpilot.ui.onroad.theme import SECONDARY_TEXT _POLL_S = 1.0 _FONT_SIZE = 70 _ICON_H = 76 +_POWER_FONT_SIZE = 32 +_POWER_GAP = 10 _ICONS = {"MAC": ("mac", 62 / 46), "GPU": ("egpu", 1.0)} _GREY = rl.Color(165, 165, 170, 235) _WHITE = rl.Color(255, 255, 255, 255) @@ -75,6 +78,7 @@ class EmacStatusRenderer(Widget): super().__init__() self._params = Params() self._font = gui_app.font(FontWeight.SEMI_BOLD) + self._power_font = gui_app.font(FontWeight.MEDIUM) self._last_poll = 0.0 self._label = "" self._state = SourceState.HIDDEN @@ -95,5 +99,19 @@ class EmacStatusRenderer(Widget): y = rect.y + rect.height / 2 - size.y / 2 draw_source_label(self._font, self._label, self._state, rl.Vector2(x, y), _FONT_SIZE) return - draw_source_icon(self._label, self._state, _ICON_H, - rl.Vector2(rect.x + UI_BORDER_SIZE + BTN_SIZE // 2, rect.y + rect.height / 2)) + center = rl.Vector2(rect.x + UI_BORDER_SIZE + BTN_SIZE // 2, rect.y + rect.height / 2) + draw_source_icon(self._label, self._state, _ICON_H, center) + if self._label == "GPU": + self._draw_power_draw(center) + + def _draw_power_draw(self, icon_center: rl.Vector2) -> None: + sm = ui_state.sm + if not sm.alive["egpuDockState"] or sm.recv_frame["egpuDockState"] <= 0: + return + watts = sm["egpuDockState"].powerDrawW + if not math.isfinite(watts) or watts < 0: + return + text = f"{watts:.0f} W" + size = measure_text_cached(self._power_font, text, _POWER_FONT_SIZE) + pos = rl.Vector2(round(icon_center.x - size.x / 2), round(icon_center.y + _ICON_H / 2 + _POWER_GAP)) + rl.draw_text_ex(self._power_font, text, pos, _POWER_FONT_SIZE, 0, SECONDARY_TEXT) diff --git a/iqpilot/ui/tests/test_big_model_status.py b/iqpilot/ui/tests/test_big_model_status.py index 46af2707..76330f4b 100644 --- a/iqpilot/ui/tests/test_big_model_status.py +++ b/iqpilot/ui/tests/test_big_model_status.py @@ -33,9 +33,16 @@ def test_egpu_label_is_gpu(): assert label == "GPU" -def test_emac_wins_when_both_enabled(): - label, _ = _resolve(IQEmacEnabled=True, IQEgpuEnabled=True, - MacModelReachable=True, UsbGpuPresent=True) +def test_present_dock_wins_over_emac(): + assert _resolve(IQEmacEnabled=True, MacModelReachable=True, + UsbGpuPresent=True, UsbGpuActive=True) == ("GPU", SourceState.ACTIVE) + assert _resolve(IQEmacEnabled=True, UsbGpuPresent=True, UsbGpuActive=True) == ("GPU", SourceState.ACTIVE) + + +def test_emac_wins_without_consented_dock(): + label, _ = _resolve(IQEmacEnabled=True, IQEgpuEnabled=True, MacModelReachable=True) + assert label == "MAC" + label, _ = _resolve(IQEmacEnabled=True, MacModelReachable=True, UsbGpuPresent=True, IQEgpuDisabled=True) assert label == "MAC" diff --git a/launch_env.sh b/launch_env.sh index 88651254..2cf0d562 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -23,7 +23,7 @@ if [ -z "$AGNOS_VERSION" ]; then case "$DEVICE_MODEL" in *) - export AGNOS_VERSION="IQ.OS 4.9.9" + export AGNOS_VERSION="IQ.OS 4.9.9.1" ;; esac fi