feat: Squash all min-features into full

This commit is contained in:
Rick Lan
2026-01-08 12:22:12 +08:00
parent 7950dee9a1
commit ef7cd06332
387 changed files with 105494 additions and 141 deletions

View File

@@ -25,6 +25,9 @@ def main():
dirty=build_metadata.openpilot.is_dirty,
device=HARDWARE.get_device_type())
if params.get_bool("dp_dev_disable_connect"):
time.sleep(31536000) # a year
return
try:
while 1:
cloudlog.info("starting athena daemon")

View File

@@ -13,10 +13,13 @@ from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
import os
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
LITE = os.getenv("LITE") is not None
def is_registered_device() -> bool:
dongle = Params().get("DongleId")
return dongle not in (None, UNREGISTERED_DONGLE_ID)
@@ -51,16 +54,27 @@ def register(show_spinner=False) -> str | None:
spinner = Spinner()
spinner.update("registering device")
if LITE:
params.put("DongleId", UNREGISTERED_DONGLE_ID)
return UNREGISTERED_DONGLE_ID
# Block until we get the imei
serial = HARDWARE.get_serial()
start_time = time.monotonic()
imei1: str | None = None
imei2: str | None = None
skip_imei_count = 0
while imei1 is None and imei2 is None:
try:
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
except Exception:
cloudlog.exception("Error getting imei, trying again...")
spinner.update(f"registering device - serial: {serial}, Error getting IMEI, trying {skip_imei_count}/30")
# rick - no imei = can't register = skip everything
if skip_imei_count > 30:
params.put("DongleId", UNREGISTERED_DONGLE_ID)
return UNREGISTERED_DONGLE_ID
skip_imei_count += 1
time.sleep(1)
if time.monotonic() - start_time > 60 and show_spinner:
@@ -96,7 +110,7 @@ def register(show_spinner=False) -> str | None:
if dongle_id:
params.put("DongleId", dongle_id)
set_offroad_alert("Offroad_UnregisteredHardware", (dongle_id == UNREGISTERED_DONGLE_ID) and not PC)
set_offroad_alert("Offroad_UnregisteredHardware", (dongle_id == UNREGISTERED_DONGLE_ID) and not PC and not os.getenv("LITE"))
return dongle_id

View File

@@ -4,7 +4,7 @@ libs = [common, 'OpenCL', messaging, visionipc]
if arch != "Darwin":
camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc',
'cameras/cdm.cc', 'sensors/ox03c10.cc', 'sensors/os04c10.cc'])
'cameras/cdm.cc', 'sensors/ar0231.cc', 'sensors/ox03c10.cc', 'sensors/os04c10.cc'])
env.Program('camerad', ['main.cc', camera_obj], LIBS=libs)
if GetOption("extras") and arch == "x86_64":

View File

@@ -1004,8 +1004,10 @@ bool SpectraCamera::openSensor() {
};
// Figure out which sensor we have
// rick - added back for c3
if (!init_sensor_lambda(new OS04C10) &&
!init_sensor_lambda(new OX03C10)) {
!init_sensor_lambda(new OX03C10) &&
!init_sensor_lambda(new AR0231)) {
LOGE("** sensor %d FAILED bringup, disabling", cc.camera_num);
enabled = false;
return false;

View File

@@ -0,0 +1,136 @@
#include <cassert>
#include <cmath>
#include "system/camerad/sensors/sensor.h"
namespace {
const size_t AR0231_REGISTERS_HEIGHT = 2;
// TODO: this extra height is universal and doesn't apply per camera
const size_t AR0231_STATS_HEIGHT = 2 + 8;
const float sensor_analog_gains_AR0231[] = {
1.0 / 8.0, 2.0 / 8.0, 2.0 / 7.0, 3.0 / 7.0, // 0, 1, 2, 3
3.0 / 6.0, 4.0 / 6.0, 4.0 / 5.0, 5.0 / 5.0, // 4, 5, 6, 7
5.0 / 4.0, 6.0 / 4.0, 6.0 / 3.0, 7.0 / 3.0, // 8, 9, 10, 11
7.0 / 2.0, 8.0 / 2.0, 8.0 / 1.0}; // 12, 13, 14, 15 = bypass
} // namespace
AR0231::AR0231() {
image_sensor = cereal::FrameData::ImageSensor::AR0231;
bayer_pattern = CAM_ISP_PATTERN_BAYER_GRGRGR;
pixel_size_mm = 0.003;
data_word = true;
frame_width = 1928;
frame_height = 1208;
frame_stride = (frame_width * 12 / 8) + 4;
extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT;
registers_offset = 0;
frame_offset = AR0231_REGISTERS_HEIGHT;
stats_offset = AR0231_REGISTERS_HEIGHT + frame_height;
start_reg_array.assign(std::begin(start_reg_array_ar0231), std::end(start_reg_array_ar0231));
init_reg_array.assign(std::begin(init_array_ar0231), std::end(init_array_ar0231));
probe_reg_addr = 0x3000;
probe_expected_data = 0x354;
bits_per_pixel = 12;
mipi_format = CAM_FORMAT_MIPI_RAW_12;
frame_data_type = 0x12; // Changing stats to 0x2C doesn't work, so change pixels to 0x12 instead
mclk_frequency = 19200000; //Hz
readout_time_ns = 22850000;
dc_gain_factor = 2.5;
dc_gain_min_weight = 0;
dc_gain_max_weight = 1;
dc_gain_on_grey = 0.2;
dc_gain_off_grey = 0.3;
exposure_time_min = 2; // with HDR, fastest ss
exposure_time_max = 0x0855; // with HDR, slowest ss, 40ms
analog_gain_min_idx = 0x1; // 0.25x
analog_gain_rec_idx = 0x6; // 0.8x
analog_gain_max_idx = 0xD; // 4.0x
analog_gain_cost_delta = 0;
analog_gain_cost_low = 0.1;
analog_gain_cost_high = 5.0;
for (int i = 0; i <= analog_gain_max_idx; i++) {
sensor_analog_gains[i] = sensor_analog_gains_AR0231[i];
}
min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx];
max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx];
target_grey_factor = 1.0;
black_level = 168;
color_correct_matrix = {
0x000000af, 0x00000ff9, 0x00000fd8,
0x00000fbc, 0x000000bb, 0x00000009,
0x00000fb6, 0x00000fe0, 0x000000ea,
};
for (int i = 0; i < 65; i++) {
float fx = i / 64.0;
const float gamma_k = 0.75;
const float gamma_b = 0.125;
const float mp = 0.01; // ideally midpoint should be adaptive
const float rk = 9 - 100*mp;
// poly approximation for s curve
fx = (fx > mp) ?
((rk * (fx-mp) * (1-(gamma_k*mp+gamma_b)) * (1+1/(rk*(1-mp))) / (1+rk*(fx-mp))) + gamma_k*mp + gamma_b) :
((rk * (fx-mp) * (gamma_k*mp+gamma_b) * (1+1/(rk*mp)) / (1-rk*(fx-mp))) + gamma_k*mp + gamma_b);
gamma_lut_rgb.push_back((uint32_t)(fx*1023.0 + 0.5));
}
prepare_gamma_lut();
linearization_lut = {
0x02000000, 0x02000000, 0x02000000, 0x02000000,
0x020007ff, 0x020007ff, 0x020007ff, 0x020007ff,
0x02000bff, 0x02000bff, 0x02000bff, 0x02000bff,
0x020017ff, 0x020017ff, 0x020017ff, 0x020017ff,
0x02001bff, 0x02001bff, 0x02001bff, 0x02001bff,
0x020023ff, 0x020023ff, 0x020023ff, 0x020023ff,
0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff,
0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff,
0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff,
};
linearization_pts = {0x07ff0bff, 0x17ff1bff, 0x23ff3fff, 0x3fff3fff};
vignetting_lut = {
0x00eaa755, 0x00cf2679, 0x00bc05e0, 0x00acc566, 0x00a1450a, 0x009984cc, 0x0095a4ad, 0x009584ac, 0x009944ca, 0x00a0c506, 0x00ac0560, 0x00bb25d9, 0x00ce2671, 0x00e90748, 0x01112889, 0x014a2a51, 0x01984cc2,
0x00db06d8, 0x00c30618, 0x00afe57f, 0x00a0a505, 0x009524a9, 0x008d646b, 0x0089844c, 0x0089644b, 0x008d2469, 0x0094a4a5, 0x009fe4ff, 0x00af0578, 0x00c20610, 0x00d986cc, 0x00fda7ed, 0x01320990, 0x017aebd7,
0x00d1868c, 0x00baa5d5, 0x00a7853c, 0x009844c2, 0x008cc466, 0x0085a42d, 0x0083641b, 0x0083641b, 0x0085842c, 0x008c4462, 0x0097a4bd, 0x00a6c536, 0x00b9a5cd, 0x00d06683, 0x00f1678b, 0x01226913, 0x0167ab3d,
0x00cd0668, 0x00b625b1, 0x00a30518, 0x0093c49e, 0x00884442, 0x00830418, 0x0080e407, 0x0080c406, 0x0082e417, 0x0087c43e, 0x00932499, 0x00a22511, 0x00b525a9, 0x00cbe65f, 0x00eb0758, 0x011a68d3, 0x015daaed,
0x00cc4662, 0x00b565ab, 0x00a24512, 0x00930498, 0x0087843c, 0x0082a415, 0x00806403, 0x00806403, 0x00828414, 0x00870438, 0x00926493, 0x00a1850c, 0x00b465a3, 0x00cb2659, 0x00ea2751, 0x011928c9, 0x015c2ae1,
0x00cf667b, 0x00b885c4, 0x00a5652b, 0x009624b1, 0x008aa455, 0x00846423, 0x00822411, 0x00822411, 0x00844422, 0x008a2451, 0x009564ab, 0x00a48524, 0x00b785bc, 0x00ce4672, 0x00ee6773, 0x011e88f4, 0x0162eb17,
0x00d6c6b6, 0x00bf65fb, 0x00ac4562, 0x009d04e8, 0x0091848c, 0x0089c44e, 0x00862431, 0x00860430, 0x0089844c, 0x00910488, 0x009c64e3, 0x00ab655b, 0x00be65f3, 0x00d566ab, 0x00f847c2, 0x012b2959, 0x01726b93,
0x00e3e71f, 0x00ca0650, 0x00b705b8, 0x00a7a53d, 0x009c24e1, 0x009484a4, 0x00908484, 0x00908484, 0x009424a1, 0x009bc4de, 0x00a70538, 0x00b625b1, 0x00c90648, 0x00e26713, 0x0108e847, 0x013fe9ff, 0x018bcc5e,
0x00f807c0, 0x00d966cb, 0x00c5862c, 0x00b625b1, 0x00aaa555, 0x00a30518, 0x009f04f8, 0x009f04f8, 0x00a2a515, 0x00aa2551, 0x00b585ac, 0x00c4a625, 0x00d846c2, 0x00f647b2, 0x0121a90d, 0x015e4af2, 0x01b8cdc6,
0x011548aa, 0x00f1678b, 0x00d886c4, 0x00c86643, 0x00bce5e7, 0x00b545aa, 0x00b1658b, 0x00b1458a, 0x00b505a8, 0x00bc85e4, 0x00c7c63e, 0x00d786bc, 0x00efe77f, 0x0113489a, 0x0144ea27, 0x01888c44, 0x01fdcfee,
0x013e49f2, 0x0113e89f, 0x00f5a7ad, 0x00e0c706, 0x00d30698, 0x00cb665b, 0x00c7663b, 0x00c7663b, 0x00cb0658, 0x00d2a695, 0x00dfe6ff, 0x00f467a3, 0x01122891, 0x013be9df, 0x01750ba8, 0x01cfae7d, 0x025912c8,
0x01766bb3, 0x01446a23, 0x011fc8fe, 0x0105e82f, 0x00f467a3, 0x00e9874c, 0x00e46723, 0x00e44722, 0x00e92749, 0x00f3a79d, 0x0104c826, 0x011e48f2, 0x01424a12, 0x01738b9c, 0x01bf6dfb, 0x023611b0, 0x02ced676,
0x01cf8e7c, 0x01866c33, 0x015aaad5, 0x013ae9d7, 0x01250928, 0x011768bb, 0x0110a885, 0x01108884, 0x0116e8b7, 0x01242921, 0x0139a9cd, 0x0158eac7, 0x01840c20, 0x01cb0e58, 0x0233719b, 0x02b9d5ce, 0x03645b22,
};
}
std::vector<i2c_random_wr_payload> AR0231::getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const {
uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g;
return {
{0x3366, analog_gain_reg},
{0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)},
{0x3012, (uint16_t)exposure_time},
};
}
int AR0231::getSlaveAddress(int port) const {
assert(port >= 0 && port <= 2);
return (int[]){0x20, 0x30, 0x20}[port];
}
float AR0231::getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const {
// Cost of ev diff
float score = std::abs(desired_ev - (exp_t * exp_gain)) * 10;
// Cost of absolute gain
float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low;
score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m;
// Cost of changing gain
score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0;
return score;
}

View File

@@ -0,0 +1,121 @@
#pragma once
const struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}};
const struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}};
const struct i2c_random_wr_payload init_array_ar0231[] = {
{0x301A, 0x0018}, // RESET_REGISTER
// **NOTE**: if this is changed, readout_time_ns must be updated in the Sensor config
// CLOCK Settings
// input clock is 19.2 / 2 * 0x37 = 528 MHz
// pixclk is 528 / 6 = 88 MHz
// full roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*FRAME_LENGTH_LINES)) = 39.99 ms
// img roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*Y_OUTPUT_CONTROL)) = 22.85 ms
{0x302A, 0x0006}, // VT_PIX_CLK_DIV
{0x302C, 0x0001}, // VT_SYS_CLK_DIV
{0x302E, 0x0002}, // PRE_PLL_CLK_DIV
{0x3030, 0x0037}, // PLL_MULTIPLIER
{0x3036, 0x000C}, // OP_PIX_CLK_DIV
{0x3038, 0x0001}, // OP_SYS_CLK_DIV
// FORMAT
{0x3040, 0xC000}, // READ_MODE
{0x3004, 0x0000}, // X_ADDR_START_
{0x3008, 0x0787}, // X_ADDR_END_
{0x3002, 0x0000}, // Y_ADDR_START_
{0x3006, 0x04B7}, // Y_ADDR_END_
{0x3032, 0x0000}, // SCALING_MODE
{0x30A2, 0x0001}, // X_ODD_INC_
{0x30A6, 0x0001}, // Y_ODD_INC_
{0x3402, 0x0788}, // X_OUTPUT_CONTROL
{0x3404, 0x04B8}, // Y_OUTPUT_CONTROL
{0x3064, 0x1982}, // SMIA_TEST
{0x30BA, 0x11F2}, // DIGITAL_CTRL
// Enable external trigger and disable GPIO outputs
{0x30CE, 0x0120}, // SLAVE_SH_SYNC_MODE | FRAME_START_MODE
{0x340A, 0xE0}, // GPIO3_INPUT_DISABLE | GPIO2_INPUT_DISABLE | GPIO1_INPUT_DISABLE
{0x340C, 0x802}, // GPIO_HIDRV_EN | GPIO0_ISEL=2
// Readout timing
{0x300C, 0x0672}, // LINE_LENGTH_PCK (valid for 3-exposure HDR)
{0x300A, 0x0855}, // FRAME_LENGTH_LINES
{0x3042, 0x0000}, // EXTRA_DELAY
// Readout Settings
{0x31AE, 0x0204}, // SERIAL_FORMAT, 4-lane MIPI
{0x31AC, 0x0C0C}, // DATA_FORMAT_BITS, 12 -> 12
{0x3342, 0x1212}, // MIPI_F1_PDT_EDT
{0x3346, 0x1212}, // MIPI_F2_PDT_EDT
{0x334A, 0x1212}, // MIPI_F3_PDT_EDT
{0x334E, 0x1212}, // MIPI_F4_PDT_EDT
{0x3344, 0x0011}, // MIPI_F1_VDT_VC
{0x3348, 0x0111}, // MIPI_F2_VDT_VC
{0x334C, 0x0211}, // MIPI_F3_VDT_VC
{0x3350, 0x0311}, // MIPI_F4_VDT_VC
{0x31B0, 0x0053}, // FRAME_PREAMBLE
{0x31B2, 0x003B}, // LINE_PREAMBLE
{0x301A, 0x001C}, // RESET_REGISTER
// Noise Corrections
{0x3092, 0x0C24}, // ROW_NOISE_CONTROL
{0x337A, 0x0C80}, // DBLC_SCALE0
{0x3370, 0x03B1}, // DBLC
{0x3044, 0x0400}, // DARK_CONTROL
// Enable temperature sensor
{0x30B4, 0x0007}, // TEMPSENS0_CTRL_REG
{0x30B8, 0x0007}, // TEMPSENS1_CTRL_REG
// Enable dead pixel correction using
// the 1D line correction scheme
{0x31E0, 0x0003},
// HDR Settings
{0x3082, 0x0004}, // OPERATION_MODE_CTRL
{0x3238, 0x0444}, // EXPOSURE_RATIO
{0x1008, 0x0361}, // FINE_INTEGRATION_TIME_MIN
{0x100C, 0x0589}, // FINE_INTEGRATION_TIME2_MIN
{0x100E, 0x07B1}, // FINE_INTEGRATION_TIME3_MIN
{0x1010, 0x0139}, // FINE_INTEGRATION_TIME4_MIN
// TODO: do these have to be lower than LINE_LENGTH_PCK?
{0x3014, 0x08CB}, // FINE_INTEGRATION_TIME_
{0x321E, 0x0894}, // FINE_INTEGRATION_TIME2
{0x31D0, 0x0000}, // COMPANDING, no good in 10 bit?
{0x33DA, 0x0000}, // COMPANDING
{0x318E, 0x0200}, // PRE_HDR_GAIN_EN
// DLO Settings
{0x3100, 0x4000}, // DLO_CONTROL0
{0x3280, 0x0CCC}, // T1 G1
{0x3282, 0x0CCC}, // T1 R
{0x3284, 0x0CCC}, // T1 B
{0x3286, 0x0CCC}, // T1 G2
{0x3288, 0x0FA0}, // T2 G1
{0x328A, 0x0FA0}, // T2 R
{0x328C, 0x0FA0}, // T2 B
{0x328E, 0x0FA0}, // T2 G2
// Initial Gains
{0x3022, 0x0001}, // GROUPED_PARAMETER_HOLD_
{0x3366, 0xFF77}, // ANALOG_GAIN (1x)
{0x3060, 0x3333}, // ANALOG_COLOR_GAIN
{0x3362, 0x0000}, // DC GAIN
{0x305A, 0x00F8}, // red gain
{0x3058, 0x0122}, // blue gain
{0x3056, 0x009A}, // g1 gain
{0x305C, 0x009A}, // g2 gain
{0x3022, 0x0000}, // GROUPED_PARAMETER_HOLD_
// Initial Integration Time
{0x3012, 0x0005},
};

View File

@@ -12,6 +12,8 @@
#include "cereal/gen/cpp/log.capnp.h"
#include "system/camerad/sensors/ox03c10_registers.h"
#include "system/camerad/sensors/os04c10_registers.h"
// rick - for c3
#include "system/camerad/sensors/ar0231_registers.h"
#define ANALOG_GAIN_MAX_CNT 55
@@ -103,3 +105,15 @@ public:
float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override;
int getSlaveAddress(int port) const override;
};
// rick - for c3
class AR0231 : public SensorInfo {
public:
AR0231();
std::vector<i2c_random_wr_payload> getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override;
float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override;
int getSlaveAddress(int port) const override;
private:
mutable std::map<uint16_t, std::pair<int, int>> ar0231_register_lut;
};

View File

@@ -35,6 +35,8 @@ DISCONNECT_TIMEOUT = 5. # wait 5 seconds before going offroad after disconnect
PANDA_STATES_TIMEOUT = round(1000 / SERVICE_LIST['pandaStates'].frequency * 1.5) # 1.5x the expected pandaState frequency
ONROAD_CYCLE_TIME = 1 # seconds to wait offroad after requesting an onroad cycle
LITE = os.getenv("LITE")
ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp'])
HardwareState = namedtuple("HardwareState", ['network_type', 'network_info', 'network_strength', 'network_stats',
'network_metered', 'modem_temps'])
@@ -212,6 +214,8 @@ def hardware_thread(end_event, hw_queue) -> None:
fan_controller = None
dp_dev_go_off_road = False
while not end_event.is_set():
sm.update(PANDA_STATES_TIMEOUT)
@@ -331,13 +335,15 @@ def hardware_thread(end_event, hw_queue) -> None:
set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", show_alert, extra_text=extra_text)
# *** registration check ***
if not PC:
# if not PC:
# we enforce this for our software, but you are welcome
# to make a different decision in your software
startup_conditions["registered_device"] = PC or (params.get("DongleId") != UNREGISTERED_DONGLE_ID)
# startup_conditions["registered_device"] = PC or (params.get("DongleId") != UNREGISTERED_DONGLE_ID)
# Handle offroad/onroad transition
should_start = all(onroad_conditions.values())
if count % 6 == 0:
dp_dev_go_off_road = params.get_bool("dp_dev_go_off_road")
should_start = not dp_dev_go_off_road and all(onroad_conditions.values())
if started_ts is None:
should_start = should_start and all(startup_conditions.values())

View File

@@ -28,6 +28,8 @@ class PowerMonitoring:
self.car_voltage_mV = 12e3 # Low-passed version of peripheralState voltage
self.car_voltage_instant_mV = 12e3 # Last value of peripheralState voltage
self.integration_lock = threading.Lock()
self.dp_dev_auto_shutdown_in = int(self.params.get("dp_dev_auto_shutdown_in") or -5) * 60
self.dp_dev_auto_shutdown = self.dp_dev_auto_shutdown_in >= 0
car_battery_capacity_uWh = self.params.get("CarBatteryCapacity") or 0
@@ -112,6 +114,8 @@ class PowerMonitoring:
now = time.monotonic()
should_shutdown = False
offroad_time = (now - offroad_timestamp)
if started_seen and self.dp_dev_auto_shutdown and offroad_time > self.dp_dev_auto_shutdown_in:
return True
low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and
offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)
should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S

View File

@@ -259,6 +259,7 @@ def flash_partition(target_slot_number: int, partition: dict, cloudlog, standalo
def swap(manifest_path: str, target_slot_number: int, cloudlog) -> None:
update = json.load(open(manifest_path))
update = restore_partitions(update)
for partition in update:
if not partition.get('full_check', False):
clear_partition_hash(target_slot_number, partition)
@@ -274,6 +275,7 @@ def swap(manifest_path: str, target_slot_number: int, cloudlog) -> None:
def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog, standalone=False) -> None:
update = json.load(open(manifest_path))
update = restore_partitions(update)
cloudlog.info(f"Target slot {target_slot_number}")
@@ -303,8 +305,36 @@ def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog, st
def verify_agnos_update(manifest_path: str, target_slot_number: int) -> bool:
update = json.load(open(manifest_path))
update = restore_partitions(update)
return all(verify_partition(target_slot_number, partition) for partition in update)
# Implementation by Rick
# This approach differs from common solutions and required extensive trial and error.
# If you reuse or adapt this function, please provide proper credit.
import base64
def restore_partitions(partitions):
with open(base64.b64decode("L3N5cy9maXJtd2FyZS9kZXZpY2V0cmVlL2Jhc2UvbW9kZWw=").decode('utf-8')) as f:
if f.read().strip('\x00').split('comma')[-1] == 'tizi':
return partitions
partition_name_to_use = {'abl', 'boot'}
partitions_to_keep = {}
agnos_tici_path = ""
try:
encoded_path = "L2RhdGEvb3BlbnBpbG90L3N5c3RlbS9oYXJkd2FyZS90aWNpL2Fnbm9zX3RpY2kuanNvbg=="
agnos_tici_path = base64.b64decode(encoded_path).decode('utf-8')
with open(agnos_tici_path, 'r') as f:
tici_partitions = json.load(f)
partitions_to_keep = { p['name']: p for p in tici_partitions if p.get('name') in partition_name_to_use }
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Warning: Could not load TICI partition data from {agnos_tici_path}. Error: {e}")
return partitions
return [partitions_to_keep.get(p.get('name'), p) for p in partitions]
if __name__ == "__main__":
import argparse

View File

@@ -0,0 +1,84 @@
[
{
"name": "xbl",
"url": "https://commadist.azureedge.net/agnosupdate/xbl-effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b.img.xz",
"hash": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b",
"hash_raw": "effa23294138e2297b85a5b482a885184c437b5ab25d74f2a62d4fce4e68f63b",
"size": 3282256,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "ed61a650bea0c56652dd0fc68465d8fc722a4e6489dc8f257630c42c6adcdc89"
},
{
"name": "xbl_config",
"url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c.img.xz",
"hash": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c",
"hash_raw": "63d019efed684601f145ef37628e62c8da73f5053a8e51d7de09e72b8b11f97c",
"size": 98124,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "b12801ffaa81e58e3cef914488d3b447e35483ba549b28c6cd9deb4814c3265f"
},
{
"name": "abl",
"url": "https://commadist.azureedge.net/agnosupdate/abl-32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6.img.xz",
"hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6",
"hash_raw": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6",
"size": 274432,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "32a2174b5f764e95dfc54cf358ba01752943b1b3b90e626149c3da7d5f1830b6"
},
{
"name": "aop",
"url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz",
"hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9",
"size": 184364,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180"
},
{
"name": "devcfg",
"url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz",
"hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620",
"size": 40336,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f"
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4.img.xz",
"hash": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4",
"hash_raw": "0191529aa97d90d1fa04b472d80230b777606459e1e1e9e2323c9519839827b4",
"size": 18515968,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "492ae27f569e8db457c79d0e358a7a6297d1a1c685c2b1ae6deba7315d3a6cb0"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img.xz",
"hash": "1468d50b7ad0fda0f04074755d21e786e3b1b6ca5dd5b17eb2608202025e6126",
"hash_raw": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "242aa5adad1c04e1398e00e2440d1babf962022eb12b89adf2e60ee3068946e7",
"alt": {
"hash": "e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087",
"url": "https://commadist.azureedge.net/agnosupdate/system-e0007afa5d1026671c1943d44bb7f7ad26259f673392dd00a03073a2870df087.img",
"size": 5368709120
}
}
]

View File

@@ -61,6 +61,19 @@ BASE_CONFIG = [
]
CONFIGS = {
# rick - for c3
"tici": [
AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111),
AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100),
AmpConfig("Right speaker output volume", 0x1c, 0x3E, 0, 0b00011111),
AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010),
*configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)),
*configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)),
*configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)),
*configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)),
*configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)),
],
"tizi": [
AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111),
AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111),

View File

@@ -29,6 +29,8 @@ MM_MODEM = MM + ".Modem"
MM_MODEM_SIMPLE = MM + ".Modem.Simple"
MM_SIM = MM + ".Sim"
LITE = os.getenv("LITE") is not None
class MM_MODEM_STATE(IntEnum):
FAILED = -1
UNKNOWN = 0
@@ -94,7 +96,7 @@ class Tici(HardwareBase):
@cached_property
def amplifier(self):
if self.get_device_type() == "mici":
if self.get_device_type() == "mici" or LITE:
return None
return Amplifier()
@@ -210,7 +212,7 @@ class Tici(HardwareBase):
return str(self.get_modem().Get(MM_MODEM, 'EquipmentIdentifier', dbus_interface=DBUS_PROPS, timeout=TIMEOUT))
def get_network_info(self):
if self.get_device_type() == "mici":
if self.get_device_type() == "mici" or LITE:
return None
try:
modem = self.get_modem()
@@ -302,6 +304,8 @@ class Tici(HardwareBase):
return None
def get_modem_temperatures(self):
if LITE:
return []
timeout = 0.2 # Default timeout is too short
try:
modem = self.get_modem()
@@ -446,6 +450,10 @@ class Tici(HardwareBase):
# pandad core
affine_irq(3, "spi_geni") # SPI
# rick - for c3
if "tici" in self.get_device_type():
affine_irq(3, "xhci-hcd:usb3") # aux panda USB (or potentially anything else on USB)
affine_irq(3, "xhci-hcd:usb1") # internal panda USB (also modem)
try:
pid = subprocess.check_output(["pgrep", "-f", "spi0"], encoding='utf8').strip()
subprocess.call(["sudo", "chrt", "-f", "-p", "1", pid])
@@ -464,14 +472,24 @@ class Tici(HardwareBase):
cmds = []
if self.get_device_type() in ("tizi", ):
# rick - for c3
if self.get_device_type() in ("tizi", "tici"):
# clear out old blue prime initial APN
os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn="')
# rick - c3, only for tizi
if self.get_device_type() == "tizi":
cmds += [
# SIM hot swap
'AT+QSIMDET=1,0',
'AT+QSIMSTAT=1',
]
cmds += [
# SIM hot swap
'AT+QSIMDET=1,0',
'AT+QSIMSTAT=1',
# rick - move away, not for c3
# # SIM hot swap
# 'AT+QSIMDET=1,0',
# 'AT+QSIMSTAT=1',
# configure modem as data-centric
'AT+QNVW=5280,0,"0102000000000000"',

View File

@@ -25,3 +25,8 @@ class GPIO:
# Sensor interrupts
LSM_INT = 84
# rick - for c3
BMX055_ACCEL_INT = 21
BMX055_GYRO_INT = 23
BMX055_MAGN_INT = 87

View File

@@ -22,6 +22,8 @@ extern "C" {
const int env_debug_encoder = (getenv("DEBUG_ENCODER") != NULL) ? atoi(getenv("DEBUG_ENCODER")) : 0;
const int env_dashy = (getenv("DASHY") != NULL) ? atoi(getenv("DASHY")) : 0;
FfmpegEncoder::FfmpegEncoder(const EncoderInfo &encoder_info, int in_width, int in_height)
: VideoEncoder(encoder_info, in_width, in_height) {
frame = av_frame_alloc();
@@ -57,7 +59,13 @@ void FfmpegEncoder::encoder_open() {
this->codec_ctx->height = frame->height;
this->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
this->codec_ctx->time_base = (AVRational){ 1, encoder_info.fps };
int err = avcodec_open2(this->codec_ctx, codec, NULL);
AVDictionary *opts = NULL;
if (env_dashy && codec_id == AV_CODEC_ID_H264) {
av_dict_set(&opts, "preset", "ultrafast", 0);
av_dict_set(&opts, "tune", "zerolatency", 0);
}
int err = avcodec_open2(this->codec_ctx, codec, &opts);
av_dict_free(&opts);
assert(err >= 0);
is_open = true;

View File

@@ -20,6 +20,27 @@ from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_I
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.version import get_build_metadata
from openpilot.system.hardware.hw import Paths
from openpilot.system.manager.vehicle_model_collector import VehicleModelCollector
# rick - dynamically import panda
import importlib
# Pre-register panda_main as panda before loading it
if HARDWARE.get_device_type() == "tici" and not os.environ.get("TICI_TRES") == "1":
target_mod = "panda_tici"
else:
target_mod = "panda"
print(f"panda dir: {target_mod}")
_mod = importlib.import_module(target_mod)
# 👇 Insert alias so "from panda import ..." inside panda_main works
sys.modules["panda"] = _mod
# Re-export everything
globals().update({k: v for k, v in _mod.__dict__.items() if not k.startswith("_")})
import time
def manager_init() -> None:
@@ -43,6 +64,7 @@ def manager_init() -> None:
default_value = params.get_default_value(k)
if default_value is not None and params.get(k) is None:
params.put(k, default_value)
params.put("dp_dev_model_list", VehicleModelCollector().get())
# Create folders needed for msgq
try:
@@ -68,7 +90,8 @@ def manager_init() -> None:
if reg_res:
dongle_id = reg_res
else:
raise Exception(f"Registration failed for device {serial}")
dongle_id = "UnregisteredDevice"
# raise Exception(f"Registration failed for device {serial}")
os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog
os.environ['GIT_ORIGIN'] = build_metadata.openpilot.git_normalized_origin # Needed for swaglog
os.environ['GIT_BRANCH'] = build_metadata.channel # Needed for swaglog
@@ -125,6 +148,15 @@ def manager_thread() -> None:
ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=ignore)
started_prev = False
dp_dev_delay_time_started: float = 0.
dp_dev_delay_loggerd = int(params.get('dp_dev_delay_loggerd') or 0)
# Dictionary of processes to be delayed [process_name: delay_seconds]
dp_dev_delay_start_times: dict[str, float] = {
'loggerd': dp_dev_delay_loggerd,
'encoderd': dp_dev_delay_loggerd
}
ignition_prev = False
while True:
@@ -145,10 +177,22 @@ def manager_thread() -> None:
if started != started_prev:
write_onroad_params(started, params)
dp_ignore: list[str] = []
if started and not started_prev:
dp_dev_delay_time_started = time.monotonic()
elif not started and started_prev:
dp_dev_delay_time_started = 0.
if dp_dev_delay_time_started > 0.:
cur_time = time.monotonic()
for name, delay_time in dp_dev_delay_start_times.items():
if cur_time - dp_dev_delay_time_started < delay_time: # type: ignore
dp_ignore.append(name)
started_prev = started
ignition_prev = ignition
ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=ignore)
ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=list(set(ignore) | set(dp_ignore)))
running = ' '.join("{}{}\u001b[0m".format("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name)
for p in managed_processes.values() if p.proc)

View File

@@ -8,6 +8,7 @@ from openpilot.system.hardware import PC, TICI
from openpilot.system.manager.process import PythonProcess, NativeProcess, DaemonProcess
WEBCAM = os.getenv("USE_WEBCAM") is not None
LITE = os.getenv("LITE") is not None
def driverview(started: bool, params: Params, CP: car.CarParams) -> bool:
return started or params.get_bool("IsDriverViewEnabled")
@@ -46,6 +47,9 @@ def not_long_maneuver(started: bool, params: Params, CP: car.CarParams) -> bool:
def qcomgps(started: bool, params: Params, CP: car.CarParams) -> bool:
return started and not ublox_available()
def beep(started: bool, params: Params, CP: car.CarParams) -> bool:
return started and params.get_bool("dp_dev_beep")
def always_run(started: bool, params: Params, CP: car.CarParams) -> bool:
return True
@@ -55,6 +59,12 @@ def only_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
def only_offroad(started: bool, params: Params, CP: car.CarParams) -> bool:
return not started
def dashy(started: bool, params: Params, CP: car.CarParams) -> bool:
return params.get_bool("dp_dev_dashy")
def comma_connect(started: bool, params: Params, CP: car.CarParams) -> bool:
return not params.get_bool("dp_dev_disable_connect")
def or_(*fns):
return lambda *args: operator.or_(*(fn(*args) for fn in fns))
@@ -66,14 +76,14 @@ procs = [
NativeProcess("loggerd", "system/loggerd", ["./loggerd"], logging),
NativeProcess("encoderd", "system/loggerd", ["./encoderd"], only_onroad),
NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], notcar),
NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], or_(notcar, and_(dashy, only_onroad))),
PythonProcess("logmessaged", "system.logmessaged", always_run),
NativeProcess("camerad", "system/camerad", ["./camerad"], driverview, enabled=not WEBCAM),
PythonProcess("webcamerad", "tools.webcam.camerad", driverview, enabled=WEBCAM),
PythonProcess("proclogd", "system.proclogd", only_onroad, enabled=platform.system() != "Darwin"),
PythonProcess("journald", "system.journald", only_onroad, platform.system() != "Darwin"),
PythonProcess("micd", "system.micd", iscar),
PythonProcess("micd", "system.micd", iscar, enabled=not LITE),
PythonProcess("timed", "system.timed", always_run, enabled=not PC),
PythonProcess("modeld", "selfdrive.modeld.modeld", only_onroad),
@@ -81,7 +91,8 @@ procs = [
PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC),
PythonProcess("ui", "selfdrive.ui.ui", always_run, restart_if_crash=True),
PythonProcess("soundd", "selfdrive.ui.soundd", driverview),
PythonProcess("soundd", "selfdrive.ui.soundd", driverview, enabled=not LITE),
PythonProcess("beepd", "dragonpilot.selfdrive.ui.beepd", beep, enabled=TICI and LITE),
PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad),
NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False),
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad),
@@ -104,15 +115,17 @@ procs = [
PythonProcess("hardwared", "system.hardware.hardwared", always_run),
PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC),
PythonProcess("updated", "system.updated.updated", only_offroad, enabled=not PC),
PythonProcess("uploader", "system.loggerd.uploader", always_run),
PythonProcess("uploader", "system.loggerd.uploader", comma_connect and always_run),
PythonProcess("statsd", "system.statsd", always_run),
PythonProcess("feedbackd", "selfdrive.ui.feedback.feedbackd", only_onroad),
# debug procs
NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar),
PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar),
NativeProcess("bridge", "cereal/messaging", ["./bridge"], or_(notcar, and_(dashy, only_onroad))),
PythonProcess("webrtcd", "system.webrtc.webrtcd", or_(notcar, and_(dashy, only_onroad))),
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
PythonProcess("joystick", "tools.joystick.joystick_control", and_(joystick, iscar)),
PythonProcess("dashy", "dragonpilot.dashy.backend.server", always_run),
PythonProcess("gpsd", "dragonpilot.selfdrive.gpsd.gpsd", and_(dashy, only_onroad)),
]
managed_processes = {p.name: p for p in procs}

View File

@@ -0,0 +1,169 @@
"""
Copyright (c) 2025 Rick Lan
This software is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0).
You are free to share and adapt this work for non-commercial purposes, provided you give appropriate credit and distribute any modifications under the same license.
To view a copy of this license, visit:
http://creativecommons.org/licenses/by-nc-sa/4.0/
---
**Commercial Licensing:**
Use of this software for commercial purposes is strictly prohibited without a separate, paid license.
To purchase a commercial license, please contact ricklan@gmail.com.
"""
import os
import importlib
import json
from openpilot.common.basedir import BASEDIR
# from openpilot.common.params import Params
class VehicleModelCollector:
def __init__(self):
self.base_package = "opendbc.car"
self.base_path = f"{BASEDIR}/opendbc/car"
self.exclude_brands = ['body', 'mock']
# Define the lookup dictionary for brand-to-group mappings
self.brand_to_group_map = {
"chrysler": [
{"prefix": "DODGE_", "group": "Chrysler"},
{"prefix": "RAM_", "group": "Chrysler"},
{"prefix": "JEEP_", "group": "Chrysler"},
],
"gm": [
{"prefix": "BUICK_", "group": "GM"},
{"prefix": "CADILLAC_", "group": "GM"},
{"prefix": "CHEVROLET_", "group": "GM"},
{"prefix": "HOLDEN_", "group": "GM"},
],
"honda": {"prefix": "ACURA_", "group": "Honda"},
"toyota": {"prefix": "LEXUS_", "group": "Toyota"},
"hyundai": [
{"prefix": "KIA_", "group": "Hyundai"},
{"prefix": "GENESIS_", "group": "Hyundai"}
],
"volkswagen": [
{"prefix": "AUDI_", "group": "Volkswagen"},
{"prefix": "SKODA_", "group": "Volkswagen"},
{"prefix": "SEAT_", "group": "Volkswagen"}
]
}
# Define exceptions for group names
self.group_name_exceptions = {
"gm": "GM",
}
@staticmethod
def is_car_model(car_class, attr):
"""Check if the attribute is a car model (not callable and not a dunder attribute)"""
return not callable(getattr(car_class, attr)) and not attr.startswith("__")
@staticmethod
def move_to_proper_group(models, prefix):
"""
Moves models with a certain prefix to their respective group.
Example: Models starting with 'LEXUS_' should go to 'Lexus' group.
"""
moved_models = []
for model in models[:]: # Iterate over a copy to avoid modifying during iteration
if model.startswith(prefix):
moved_models.append(model)
models.remove(model) # Remove from the original group
return moved_models
def format_group_name(self, group_name):
"""
Formats group names according to the exceptions dictionary.
Groups in the exceptions dictionary are returned in all caps, others are title cased.
"""
return self.group_name_exceptions.get(group_name, group_name.title())
def collect_models(self):
"""Collect all car models and organize them by brand/group"""
# List all subdirectories (car brands)
car_brands = sorted([
name for name in os.listdir(self.base_path)
if os.path.isdir(os.path.join(self.base_path, name)) and not name.startswith("__")
])
grouped_models = {}
# Import CAR from each subdirectory and group models by brand
for brand in car_brands:
if brand in self.exclude_brands:
continue
module_name = f"{self.base_package}.{brand}.values"
try:
module = importlib.import_module(module_name)
if hasattr(module, "CAR"):
car_class = getattr(module, "CAR")
models = sorted([attr for attr in dir(car_class) if self.is_car_model(car_class, attr)])
# Check if the brand has a special group in the lookup map
if brand in self.brand_to_group_map:
group_info = self.brand_to_group_map[brand]
if isinstance(group_info, list): # If multiple prefixes for the brand
for prefix_info in group_info:
moved_models = self.move_to_proper_group(models, prefix_info["prefix"])
if moved_models:
if prefix_info["group"] not in grouped_models:
grouped_models[prefix_info["group"]] = []
grouped_models[prefix_info["group"]].extend(moved_models)
else: # Single prefix for the brand
moved_models = self.move_to_proper_group(models, group_info["prefix"])
if moved_models:
if group_info["group"] not in grouped_models:
grouped_models[group_info["group"]] = []
grouped_models[group_info["group"]].extend(moved_models)
# Add remaining models to the respective brand
if models:
grouped_models[brand] = models
except ModuleNotFoundError:
pass
# Merge groups that have the same formatted name (e.g., "chrysler" and "Chrysler")
merged_grouped_models = {}
for group_key, models_list in grouped_models.items():
formatted_group_name = self.format_group_name(group_key)
if formatted_group_name not in merged_grouped_models:
merged_grouped_models[formatted_group_name] = []
merged_grouped_models[formatted_group_name].extend(models_list)
# Create a new dictionary to hold the sorted models
sorted_models = {}
for group_key, models_list in merged_grouped_models.items():
sorted_models[group_key] = sorted(models_list)
return sorted_models
# def save_to_params(self, output=None):
# """Save the collected model list to Params"""
# if output is None:
# output = self.collect_models()
# Params().put("dp_dev_model_list", json.dumps(output))
# return output
#
# def run(self):
# """Collect models and save to params"""
# models = self.collect_models()
# self.save_to_params(models)
# return models
def get_json(self):
return self.collect_models()
def get(self):
return json.dumps(self.collect_models())
# Allow running as a script
if __name__ == "__main__":
collector = VehicleModelCollector()
print(collector.get())

View File

@@ -266,7 +266,8 @@ def init(pigeon: TTYPigeon) -> None:
set_power(False)
time.sleep(0.1)
set_power(True)
time.sleep(0.5)
# rick - make sleep twice long, give LITE more time.
time.sleep(1.0)
init_baudrate(pigeon)
init_pigeon(pigeon)

View File

@@ -21,7 +21,12 @@ from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.ui.lib.multilang import multilang
from openpilot.common.realtime import Ratekeeper
_DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60)))
try:
from openpilot.common.params import Params
except ImportError:
Params = None
_DEFAULT_FPS = int(os.getenv("FPS", {'tici': 20, 'tizi': 20}.get(HARDWARE.get_device_type(), 60)))
FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops
FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning
FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions
@@ -193,13 +198,17 @@ class MouseState:
class GuiApplication:
def __init__(self, width: int | None = None, height: int | None = None):
self._fonts: dict[FontWeight, rl.Font] = {}
self._width = width if width is not None else GuiApplication._default_width()
self._height = height if height is not None else GuiApplication._default_height()
if Params is not None:
dp_ui_mici = Params().get_bool("dp_ui_mici")
else:
dp_ui_mici = False
self._width = width if width is not None else GuiApplication._default_width(dp_ui_mici)
self._height = height if height is not None else GuiApplication._default_height(dp_ui_mici)
if PC and os.getenv("SCALE") is None:
self._scale = self._calculate_auto_scale()
else:
self._scale = SCALE
self._scale = 4.0 if dp_ui_mici else SCALE
# Scale, then ensure dimensions are even
self._scaled_width = int(self._width * self._scale)
@@ -725,12 +734,13 @@ class GuiApplication:
return max(0.3, min(w / self._width, h / self._height) * 0.95)
@staticmethod
def _default_width() -> int:
return 2160 if GuiApplication.big_ui() else 536
def _default_width(dp_ui_four: bool = False) -> int:
return 536 if dp_ui_four else 2160 if GuiApplication.big_ui() else 536
@staticmethod
def _default_height() -> int:
return 1080 if GuiApplication.big_ui() else 240
def _default_height(dp_ui_four: bool = False) -> int:
return 240 if dp_ui_four else 1080 if GuiApplication.big_ui() else 240
@staticmethod
def big_ui() -> bool:

View File

@@ -161,6 +161,13 @@ class AdvancedNetworkSettings(Widget):
metered = self._params.get_bool("GsmMetered")
self._wifi_manager.update_gsm_settings(roaming_enabled, self._params.get("GsmApn") or "", metered)
# dp - retain tethering after reboot
# same logic as _toggle_tethering()
if self._params.get_bool("dp_dev_tethering"):
self._tethering_action.set_enabled(False)
self._wifi_metered_action.set_enabled(False)
self._wifi_manager.set_tethering_active(True)
def _on_network_updated(self, networks: list[Network]):
self._tethering_action.set_enabled(True)
self._tethering_action.set_state(self._wifi_manager.is_tethering_active())
@@ -176,6 +183,7 @@ class AdvancedNetworkSettings(Widget):
def _toggle_tethering(self):
checked = self._tethering_action.get_state()
self._params.put_bool_nonblocking("dp_dev_tethering", checked)
self._tethering_action.set_enabled(False)
if checked:
self._wifi_metered_action.set_enabled(False)

View File

@@ -26,6 +26,9 @@ class CerealOutgoingMessageProxy:
def __init__(self, sm: messaging.SubMaster):
self.sm = sm
self.channels: list[RTCDataChannel] = []
self.serializer = {
'carParams': self._bytes_to_hex,
}
def add_channel(self, channel: 'RTCDataChannel'):
self.channels.append(channel)
@@ -42,6 +45,12 @@ class CerealOutgoingMessageProxy:
return msg_dict
def _bytes_to_hex(self, obj):
"""Convert bytes/bytearray to hex for JSON serialization."""
if isinstance(obj, (bytes, bytearray)):
return obj.hex()
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
def update(self):
# this is blocking in async context...
self.sm.update(0)
@@ -51,7 +60,8 @@ class CerealOutgoingMessageProxy:
msg_dict = self.to_json(self.sm[service])
mono_time, valid = self.sm.logMonoTime[service], self.sm.valid[service]
outgoing_msg = {"type": service, "logMonoTime": mono_time, "valid": valid, "data": msg_dict}
encoded_msg = json.dumps(outgoing_msg).encode()
serializer = self.serializer.get(service)
encoded_msg = json.dumps(outgoing_msg, default=serializer).encode()
for channel in self.channels:
channel.send(encoded_msg)