mirror of
https://gitlvb.teallvbs.xyz/IQ.Lvbs/IQ.Pilot.git
synced 2026-09-18 13:33:49 +08:00
IQ.Pilot Release Commit @ 45bb3db
This commit is contained in:
@@ -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=="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
ugmbC4W3Qqvv6F1w1mY3z3bdZV6HsCvSX+HQ+3vjBh03AKRaz41BYjjXdFDdFz45F/sOIeZvmomkvym4GezNCg==
|
||||
WG1x9JR+nNQKjx6iHX9H9cBQWxWpHVCMn3NHirf7IDe/MnGX/Xsdv1tHKunzhleUga6DF9uhIsxThBewBFNfAQ==
|
||||
|
||||
@@ -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
|
||||
|
||||
+15
-40
@@ -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
|
||||
|
||||
+1
-1
@@ -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 += [
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -226,6 +226,7 @@ class UIState(IQUIState):
|
||||
"carOutput",
|
||||
"carControl",
|
||||
"vehicleParameters",
|
||||
"egpuDockState",
|
||||
] + self.sm_services_ext
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
GOp80wlluTRkHZnMViznUD2H1iF6Y+VrWC8JsH2pVi2VeyZTTEPhdNMP7kH4EXYT7Syfa4mAgtdPLl9jXa4wAQ==
|
||||
R39gJsoN/ESDuoPJTEUrOF8FXPwyf+dzf0s4pvpx53vIsXlY89OQjyooQQHMWsK4GgU9Dvv7trTuiXo3NP0XCQ==
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
+ZDnJpayd7Z4kRKRJ0LKCKKA0lzO998S/3IQFaJdozGGozP31GstEC0i0xYcaixG51ye2r8fB/GCDHGrK0kyAg==
|
||||
Q7FSsS7kmbYAPqYe5W+Ht6xPNb9ekBQQlVYTBT/VNgR96+aazk2TOV/8909sS3n30bfvm/YUZc1MawSzoWGgAA==
|
||||
|
||||
@@ -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 += [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user