Compare commits

..

1 Commits

Author SHA1 Message Date
firestarsdog 6170955afc Tickle Me ELMo 2026-09-06 00:54:36 -04:00
119 changed files with 2535 additions and 1963 deletions
Binary file not shown.
-1
View File
@@ -310,7 +310,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}}, {"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}}, {"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}}, {"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_ADVANCED}},
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}}, {"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}}, {"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}}, {"DeviceShutdown", {PERSISTENT, INT, "6", "6", 1, SETTINGS_SIMPLE}},
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19]; extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-9cb1b6b1-DEBUG"; const uint8_t gitversion[19] = "DEV-cec1a0fb-DEBUG";
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
DEV-9cb1b6b1-DEBUG DEV-cec1a0fb-DEBUG
Binary file not shown.
+342 -342
View File
@@ -45,326 +45,326 @@ const static double MAHA_THRESH_31 = 3.8414588206941227;
* * * *
* This file is part of 'ekf' * * This file is part of 'ekf' *
******************************************************************************/ ******************************************************************************/
void err_fun(double *nom_x, double *delta_x, double *out_694062864366320957) { void err_fun(double *nom_x, double *delta_x, double *out_5825026556611430403) {
out_694062864366320957[0] = delta_x[0] + nom_x[0]; out_5825026556611430403[0] = delta_x[0] + nom_x[0];
out_694062864366320957[1] = delta_x[1] + nom_x[1]; out_5825026556611430403[1] = delta_x[1] + nom_x[1];
out_694062864366320957[2] = delta_x[2] + nom_x[2]; out_5825026556611430403[2] = delta_x[2] + nom_x[2];
out_694062864366320957[3] = delta_x[3] + nom_x[3]; out_5825026556611430403[3] = delta_x[3] + nom_x[3];
out_694062864366320957[4] = delta_x[4] + nom_x[4]; out_5825026556611430403[4] = delta_x[4] + nom_x[4];
out_694062864366320957[5] = delta_x[5] + nom_x[5]; out_5825026556611430403[5] = delta_x[5] + nom_x[5];
out_694062864366320957[6] = delta_x[6] + nom_x[6]; out_5825026556611430403[6] = delta_x[6] + nom_x[6];
out_694062864366320957[7] = delta_x[7] + nom_x[7]; out_5825026556611430403[7] = delta_x[7] + nom_x[7];
out_694062864366320957[8] = delta_x[8] + nom_x[8]; out_5825026556611430403[8] = delta_x[8] + nom_x[8];
} }
void inv_err_fun(double *nom_x, double *true_x, double *out_7656216431051591408) { void inv_err_fun(double *nom_x, double *true_x, double *out_8876036798993240698) {
out_7656216431051591408[0] = -nom_x[0] + true_x[0]; out_8876036798993240698[0] = -nom_x[0] + true_x[0];
out_7656216431051591408[1] = -nom_x[1] + true_x[1]; out_8876036798993240698[1] = -nom_x[1] + true_x[1];
out_7656216431051591408[2] = -nom_x[2] + true_x[2]; out_8876036798993240698[2] = -nom_x[2] + true_x[2];
out_7656216431051591408[3] = -nom_x[3] + true_x[3]; out_8876036798993240698[3] = -nom_x[3] + true_x[3];
out_7656216431051591408[4] = -nom_x[4] + true_x[4]; out_8876036798993240698[4] = -nom_x[4] + true_x[4];
out_7656216431051591408[5] = -nom_x[5] + true_x[5]; out_8876036798993240698[5] = -nom_x[5] + true_x[5];
out_7656216431051591408[6] = -nom_x[6] + true_x[6]; out_8876036798993240698[6] = -nom_x[6] + true_x[6];
out_7656216431051591408[7] = -nom_x[7] + true_x[7]; out_8876036798993240698[7] = -nom_x[7] + true_x[7];
out_7656216431051591408[8] = -nom_x[8] + true_x[8]; out_8876036798993240698[8] = -nom_x[8] + true_x[8];
} }
void H_mod_fun(double *state, double *out_2454126163912067491) { void H_mod_fun(double *state, double *out_6203949078932441352) {
out_2454126163912067491[0] = 1.0; out_6203949078932441352[0] = 1.0;
out_2454126163912067491[1] = 0.0; out_6203949078932441352[1] = 0.0;
out_2454126163912067491[2] = 0.0; out_6203949078932441352[2] = 0.0;
out_2454126163912067491[3] = 0.0; out_6203949078932441352[3] = 0.0;
out_2454126163912067491[4] = 0.0; out_6203949078932441352[4] = 0.0;
out_2454126163912067491[5] = 0.0; out_6203949078932441352[5] = 0.0;
out_2454126163912067491[6] = 0.0; out_6203949078932441352[6] = 0.0;
out_2454126163912067491[7] = 0.0; out_6203949078932441352[7] = 0.0;
out_2454126163912067491[8] = 0.0; out_6203949078932441352[8] = 0.0;
out_2454126163912067491[9] = 0.0; out_6203949078932441352[9] = 0.0;
out_2454126163912067491[10] = 1.0; out_6203949078932441352[10] = 1.0;
out_2454126163912067491[11] = 0.0; out_6203949078932441352[11] = 0.0;
out_2454126163912067491[12] = 0.0; out_6203949078932441352[12] = 0.0;
out_2454126163912067491[13] = 0.0; out_6203949078932441352[13] = 0.0;
out_2454126163912067491[14] = 0.0; out_6203949078932441352[14] = 0.0;
out_2454126163912067491[15] = 0.0; out_6203949078932441352[15] = 0.0;
out_2454126163912067491[16] = 0.0; out_6203949078932441352[16] = 0.0;
out_2454126163912067491[17] = 0.0; out_6203949078932441352[17] = 0.0;
out_2454126163912067491[18] = 0.0; out_6203949078932441352[18] = 0.0;
out_2454126163912067491[19] = 0.0; out_6203949078932441352[19] = 0.0;
out_2454126163912067491[20] = 1.0; out_6203949078932441352[20] = 1.0;
out_2454126163912067491[21] = 0.0; out_6203949078932441352[21] = 0.0;
out_2454126163912067491[22] = 0.0; out_6203949078932441352[22] = 0.0;
out_2454126163912067491[23] = 0.0; out_6203949078932441352[23] = 0.0;
out_2454126163912067491[24] = 0.0; out_6203949078932441352[24] = 0.0;
out_2454126163912067491[25] = 0.0; out_6203949078932441352[25] = 0.0;
out_2454126163912067491[26] = 0.0; out_6203949078932441352[26] = 0.0;
out_2454126163912067491[27] = 0.0; out_6203949078932441352[27] = 0.0;
out_2454126163912067491[28] = 0.0; out_6203949078932441352[28] = 0.0;
out_2454126163912067491[29] = 0.0; out_6203949078932441352[29] = 0.0;
out_2454126163912067491[30] = 1.0; out_6203949078932441352[30] = 1.0;
out_2454126163912067491[31] = 0.0; out_6203949078932441352[31] = 0.0;
out_2454126163912067491[32] = 0.0; out_6203949078932441352[32] = 0.0;
out_2454126163912067491[33] = 0.0; out_6203949078932441352[33] = 0.0;
out_2454126163912067491[34] = 0.0; out_6203949078932441352[34] = 0.0;
out_2454126163912067491[35] = 0.0; out_6203949078932441352[35] = 0.0;
out_2454126163912067491[36] = 0.0; out_6203949078932441352[36] = 0.0;
out_2454126163912067491[37] = 0.0; out_6203949078932441352[37] = 0.0;
out_2454126163912067491[38] = 0.0; out_6203949078932441352[38] = 0.0;
out_2454126163912067491[39] = 0.0; out_6203949078932441352[39] = 0.0;
out_2454126163912067491[40] = 1.0; out_6203949078932441352[40] = 1.0;
out_2454126163912067491[41] = 0.0; out_6203949078932441352[41] = 0.0;
out_2454126163912067491[42] = 0.0; out_6203949078932441352[42] = 0.0;
out_2454126163912067491[43] = 0.0; out_6203949078932441352[43] = 0.0;
out_2454126163912067491[44] = 0.0; out_6203949078932441352[44] = 0.0;
out_2454126163912067491[45] = 0.0; out_6203949078932441352[45] = 0.0;
out_2454126163912067491[46] = 0.0; out_6203949078932441352[46] = 0.0;
out_2454126163912067491[47] = 0.0; out_6203949078932441352[47] = 0.0;
out_2454126163912067491[48] = 0.0; out_6203949078932441352[48] = 0.0;
out_2454126163912067491[49] = 0.0; out_6203949078932441352[49] = 0.0;
out_2454126163912067491[50] = 1.0; out_6203949078932441352[50] = 1.0;
out_2454126163912067491[51] = 0.0; out_6203949078932441352[51] = 0.0;
out_2454126163912067491[52] = 0.0; out_6203949078932441352[52] = 0.0;
out_2454126163912067491[53] = 0.0; out_6203949078932441352[53] = 0.0;
out_2454126163912067491[54] = 0.0; out_6203949078932441352[54] = 0.0;
out_2454126163912067491[55] = 0.0; out_6203949078932441352[55] = 0.0;
out_2454126163912067491[56] = 0.0; out_6203949078932441352[56] = 0.0;
out_2454126163912067491[57] = 0.0; out_6203949078932441352[57] = 0.0;
out_2454126163912067491[58] = 0.0; out_6203949078932441352[58] = 0.0;
out_2454126163912067491[59] = 0.0; out_6203949078932441352[59] = 0.0;
out_2454126163912067491[60] = 1.0; out_6203949078932441352[60] = 1.0;
out_2454126163912067491[61] = 0.0; out_6203949078932441352[61] = 0.0;
out_2454126163912067491[62] = 0.0; out_6203949078932441352[62] = 0.0;
out_2454126163912067491[63] = 0.0; out_6203949078932441352[63] = 0.0;
out_2454126163912067491[64] = 0.0; out_6203949078932441352[64] = 0.0;
out_2454126163912067491[65] = 0.0; out_6203949078932441352[65] = 0.0;
out_2454126163912067491[66] = 0.0; out_6203949078932441352[66] = 0.0;
out_2454126163912067491[67] = 0.0; out_6203949078932441352[67] = 0.0;
out_2454126163912067491[68] = 0.0; out_6203949078932441352[68] = 0.0;
out_2454126163912067491[69] = 0.0; out_6203949078932441352[69] = 0.0;
out_2454126163912067491[70] = 1.0; out_6203949078932441352[70] = 1.0;
out_2454126163912067491[71] = 0.0; out_6203949078932441352[71] = 0.0;
out_2454126163912067491[72] = 0.0; out_6203949078932441352[72] = 0.0;
out_2454126163912067491[73] = 0.0; out_6203949078932441352[73] = 0.0;
out_2454126163912067491[74] = 0.0; out_6203949078932441352[74] = 0.0;
out_2454126163912067491[75] = 0.0; out_6203949078932441352[75] = 0.0;
out_2454126163912067491[76] = 0.0; out_6203949078932441352[76] = 0.0;
out_2454126163912067491[77] = 0.0; out_6203949078932441352[77] = 0.0;
out_2454126163912067491[78] = 0.0; out_6203949078932441352[78] = 0.0;
out_2454126163912067491[79] = 0.0; out_6203949078932441352[79] = 0.0;
out_2454126163912067491[80] = 1.0; out_6203949078932441352[80] = 1.0;
} }
void f_fun(double *state, double dt, double *out_1843607112126961388) { void f_fun(double *state, double dt, double *out_343822199527135251) {
out_1843607112126961388[0] = state[0]; out_343822199527135251[0] = state[0];
out_1843607112126961388[1] = state[1]; out_343822199527135251[1] = state[1];
out_1843607112126961388[2] = state[2]; out_343822199527135251[2] = state[2];
out_1843607112126961388[3] = state[3]; out_343822199527135251[3] = state[3];
out_1843607112126961388[4] = state[4]; out_343822199527135251[4] = state[4];
out_1843607112126961388[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5]; out_343822199527135251[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_1843607112126961388[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6]; out_343822199527135251[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_1843607112126961388[7] = state[7]; out_343822199527135251[7] = state[7];
out_1843607112126961388[8] = state[8]; out_343822199527135251[8] = state[8];
} }
void F_fun(double *state, double dt, double *out_2182262497468252920) { void F_fun(double *state, double dt, double *out_7416815269748566643) {
out_2182262497468252920[0] = 1; out_7416815269748566643[0] = 1;
out_2182262497468252920[1] = 0; out_7416815269748566643[1] = 0;
out_2182262497468252920[2] = 0; out_7416815269748566643[2] = 0;
out_2182262497468252920[3] = 0; out_7416815269748566643[3] = 0;
out_2182262497468252920[4] = 0; out_7416815269748566643[4] = 0;
out_2182262497468252920[5] = 0; out_7416815269748566643[5] = 0;
out_2182262497468252920[6] = 0; out_7416815269748566643[6] = 0;
out_2182262497468252920[7] = 0; out_7416815269748566643[7] = 0;
out_2182262497468252920[8] = 0; out_7416815269748566643[8] = 0;
out_2182262497468252920[9] = 0; out_7416815269748566643[9] = 0;
out_2182262497468252920[10] = 1; out_7416815269748566643[10] = 1;
out_2182262497468252920[11] = 0; out_7416815269748566643[11] = 0;
out_2182262497468252920[12] = 0; out_7416815269748566643[12] = 0;
out_2182262497468252920[13] = 0; out_7416815269748566643[13] = 0;
out_2182262497468252920[14] = 0; out_7416815269748566643[14] = 0;
out_2182262497468252920[15] = 0; out_7416815269748566643[15] = 0;
out_2182262497468252920[16] = 0; out_7416815269748566643[16] = 0;
out_2182262497468252920[17] = 0; out_7416815269748566643[17] = 0;
out_2182262497468252920[18] = 0; out_7416815269748566643[18] = 0;
out_2182262497468252920[19] = 0; out_7416815269748566643[19] = 0;
out_2182262497468252920[20] = 1; out_7416815269748566643[20] = 1;
out_2182262497468252920[21] = 0; out_7416815269748566643[21] = 0;
out_2182262497468252920[22] = 0; out_7416815269748566643[22] = 0;
out_2182262497468252920[23] = 0; out_7416815269748566643[23] = 0;
out_2182262497468252920[24] = 0; out_7416815269748566643[24] = 0;
out_2182262497468252920[25] = 0; out_7416815269748566643[25] = 0;
out_2182262497468252920[26] = 0; out_7416815269748566643[26] = 0;
out_2182262497468252920[27] = 0; out_7416815269748566643[27] = 0;
out_2182262497468252920[28] = 0; out_7416815269748566643[28] = 0;
out_2182262497468252920[29] = 0; out_7416815269748566643[29] = 0;
out_2182262497468252920[30] = 1; out_7416815269748566643[30] = 1;
out_2182262497468252920[31] = 0; out_7416815269748566643[31] = 0;
out_2182262497468252920[32] = 0; out_7416815269748566643[32] = 0;
out_2182262497468252920[33] = 0; out_7416815269748566643[33] = 0;
out_2182262497468252920[34] = 0; out_7416815269748566643[34] = 0;
out_2182262497468252920[35] = 0; out_7416815269748566643[35] = 0;
out_2182262497468252920[36] = 0; out_7416815269748566643[36] = 0;
out_2182262497468252920[37] = 0; out_7416815269748566643[37] = 0;
out_2182262497468252920[38] = 0; out_7416815269748566643[38] = 0;
out_2182262497468252920[39] = 0; out_7416815269748566643[39] = 0;
out_2182262497468252920[40] = 1; out_7416815269748566643[40] = 1;
out_2182262497468252920[41] = 0; out_7416815269748566643[41] = 0;
out_2182262497468252920[42] = 0; out_7416815269748566643[42] = 0;
out_2182262497468252920[43] = 0; out_7416815269748566643[43] = 0;
out_2182262497468252920[44] = 0; out_7416815269748566643[44] = 0;
out_2182262497468252920[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4])); out_7416815269748566643[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_2182262497468252920[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2)); out_7416815269748566643[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_2182262497468252920[47] = -dt*stiffness_front*state[0]/(mass*state[1]); out_7416815269748566643[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_2182262497468252920[48] = -dt*stiffness_front*state[0]/(mass*state[1]); out_7416815269748566643[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_2182262497468252920[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2))); out_7416815269748566643[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_2182262497468252920[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1; out_7416815269748566643[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_2182262497468252920[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4])); out_7416815269748566643[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_2182262497468252920[52] = dt*stiffness_front*state[0]/(mass*state[1]); out_7416815269748566643[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_2182262497468252920[53] = -9.8100000000000005*dt; out_7416815269748566643[53] = -9.8100000000000005*dt;
out_2182262497468252920[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4])); out_7416815269748566643[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_2182262497468252920[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2)); out_7416815269748566643[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_2182262497468252920[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]); out_7416815269748566643[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_2182262497468252920[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]); out_7416815269748566643[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_2182262497468252920[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2))); out_7416815269748566643[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_2182262497468252920[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]); out_7416815269748566643[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_2182262497468252920[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1; out_7416815269748566643[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_2182262497468252920[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]); out_7416815269748566643[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_2182262497468252920[62] = 0; out_7416815269748566643[62] = 0;
out_2182262497468252920[63] = 0; out_7416815269748566643[63] = 0;
out_2182262497468252920[64] = 0; out_7416815269748566643[64] = 0;
out_2182262497468252920[65] = 0; out_7416815269748566643[65] = 0;
out_2182262497468252920[66] = 0; out_7416815269748566643[66] = 0;
out_2182262497468252920[67] = 0; out_7416815269748566643[67] = 0;
out_2182262497468252920[68] = 0; out_7416815269748566643[68] = 0;
out_2182262497468252920[69] = 0; out_7416815269748566643[69] = 0;
out_2182262497468252920[70] = 1; out_7416815269748566643[70] = 1;
out_2182262497468252920[71] = 0; out_7416815269748566643[71] = 0;
out_2182262497468252920[72] = 0; out_7416815269748566643[72] = 0;
out_2182262497468252920[73] = 0; out_7416815269748566643[73] = 0;
out_2182262497468252920[74] = 0; out_7416815269748566643[74] = 0;
out_2182262497468252920[75] = 0; out_7416815269748566643[75] = 0;
out_2182262497468252920[76] = 0; out_7416815269748566643[76] = 0;
out_2182262497468252920[77] = 0; out_7416815269748566643[77] = 0;
out_2182262497468252920[78] = 0; out_7416815269748566643[78] = 0;
out_2182262497468252920[79] = 0; out_7416815269748566643[79] = 0;
out_2182262497468252920[80] = 1; out_7416815269748566643[80] = 1;
} }
void h_25(double *state, double *unused, double *out_4798003278786560335) { void h_25(double *state, double *unused, double *out_8641933420137717402) {
out_4798003278786560335[0] = state[6]; out_8641933420137717402[0] = state[6];
} }
void H_25(double *state, double *unused, double *out_7357817989096732331) { void H_25(double *state, double *unused, double *out_7339103169592445424) {
out_7357817989096732331[0] = 0; out_7339103169592445424[0] = 0;
out_7357817989096732331[1] = 0; out_7339103169592445424[1] = 0;
out_7357817989096732331[2] = 0; out_7339103169592445424[2] = 0;
out_7357817989096732331[3] = 0; out_7339103169592445424[3] = 0;
out_7357817989096732331[4] = 0; out_7339103169592445424[4] = 0;
out_7357817989096732331[5] = 0; out_7339103169592445424[5] = 0;
out_7357817989096732331[6] = 1; out_7339103169592445424[6] = 1;
out_7357817989096732331[7] = 0; out_7339103169592445424[7] = 0;
out_7357817989096732331[8] = 0; out_7339103169592445424[8] = 0;
} }
void h_24(double *state, double *unused, double *out_6970490004472512085) { void h_24(double *state, double *unused, double *out_5501280400540853584) {
out_6970490004472512085[0] = state[4]; out_5501280400540853584[0] = state[4];
out_6970490004472512085[1] = state[5]; out_5501280400540853584[1] = state[5];
} }
void H_24(double *state, double *unused, double *out_1156785309245570905) { void H_24(double *state, double *unused, double *out_9067966288769759493) {
out_1156785309245570905[0] = 0; out_9067966288769759493[0] = 0;
out_1156785309245570905[1] = 0; out_9067966288769759493[1] = 0;
out_1156785309245570905[2] = 0; out_9067966288769759493[2] = 0;
out_1156785309245570905[3] = 0; out_9067966288769759493[3] = 0;
out_1156785309245570905[4] = 1; out_9067966288769759493[4] = 1;
out_1156785309245570905[5] = 0; out_9067966288769759493[5] = 0;
out_1156785309245570905[6] = 0; out_9067966288769759493[6] = 0;
out_1156785309245570905[7] = 0; out_9067966288769759493[7] = 0;
out_1156785309245570905[8] = 0; out_9067966288769759493[8] = 0;
out_1156785309245570905[9] = 0; out_9067966288769759493[9] = 0;
out_1156785309245570905[10] = 0; out_9067966288769759493[10] = 0;
out_1156785309245570905[11] = 0; out_9067966288769759493[11] = 0;
out_1156785309245570905[12] = 0; out_9067966288769759493[12] = 0;
out_1156785309245570905[13] = 0; out_9067966288769759493[13] = 0;
out_1156785309245570905[14] = 1; out_9067966288769759493[14] = 1;
out_1156785309245570905[15] = 0; out_9067966288769759493[15] = 0;
out_1156785309245570905[16] = 0; out_9067966288769759493[16] = 0;
out_1156785309245570905[17] = 0; out_9067966288769759493[17] = 0;
} }
void h_30(double *state, double *unused, double *out_3972351172998717479) { void h_30(double *state, double *unused, double *out_5706507355292462350) {
out_3972351172998717479[0] = state[4]; out_5706507355292462350[0] = state[4];
} }
void H_30(double *state, double *unused, double *out_7228479041953492261) { void H_30(double *state, double *unused, double *out_422412828100828669) {
out_7228479041953492261[0] = 0; out_422412828100828669[0] = 0;
out_7228479041953492261[1] = 0; out_422412828100828669[1] = 0;
out_7228479041953492261[2] = 0; out_422412828100828669[2] = 0;
out_7228479041953492261[3] = 0; out_422412828100828669[3] = 0;
out_7228479041953492261[4] = 1; out_422412828100828669[4] = 1;
out_7228479041953492261[5] = 0; out_422412828100828669[5] = 0;
out_7228479041953492261[6] = 0; out_422412828100828669[6] = 0;
out_7228479041953492261[7] = 0; out_422412828100828669[7] = 0;
out_7228479041953492261[8] = 0; out_422412828100828669[8] = 0;
} }
void h_26(double *state, double *unused, double *out_1029322094662128166) { void h_26(double *state, double *unused, double *out_8743293406736795035) {
out_1029322094662128166[0] = state[7]; out_8743293406736795035[0] = state[7];
} }
void H_26(double *state, double *unused, double *out_3616314670222676107) { void H_26(double *state, double *unused, double *out_7366137585243049968) {
out_3616314670222676107[0] = 0; out_7366137585243049968[0] = 0;
out_3616314670222676107[1] = 0; out_7366137585243049968[1] = 0;
out_3616314670222676107[2] = 0; out_7366137585243049968[2] = 0;
out_3616314670222676107[3] = 0; out_7366137585243049968[3] = 0;
out_3616314670222676107[4] = 0; out_7366137585243049968[4] = 0;
out_3616314670222676107[5] = 0; out_7366137585243049968[5] = 0;
out_3616314670222676107[6] = 0; out_7366137585243049968[6] = 0;
out_3616314670222676107[7] = 1; out_7366137585243049968[7] = 1;
out_3616314670222676107[8] = 0; out_7366137585243049968[8] = 0;
} }
void h_27(double *state, double *unused, double *out_9195955360068189347) { void h_27(double *state, double *unused, double *out_6768024602127501529) {
out_9195955360068189347[0] = state[3]; out_6768024602127501529[0] = state[3];
} }
void H_27(double *state, double *unused, double *out_5053715730153067350) { void H_27(double *state, double *unused, double *out_2597176139901253580) {
out_5053715730153067350[0] = 0; out_2597176139901253580[0] = 0;
out_5053715730153067350[1] = 0; out_2597176139901253580[1] = 0;
out_5053715730153067350[2] = 0; out_2597176139901253580[2] = 0;
out_5053715730153067350[3] = 1; out_2597176139901253580[3] = 1;
out_5053715730153067350[4] = 0; out_2597176139901253580[4] = 0;
out_5053715730153067350[5] = 0; out_2597176139901253580[5] = 0;
out_5053715730153067350[6] = 0; out_2597176139901253580[6] = 0;
out_5053715730153067350[7] = 0; out_2597176139901253580[7] = 0;
out_5053715730153067350[8] = 0; out_2597176139901253580[8] = 0;
} }
void h_29(double *state, double *unused, double *out_9038768691717060202) { void h_29(double *state, double *unused, double *out_5090891545105754924) {
out_9038768691717060202[0] = state[1]; out_5090891545105754924[0] = state[1];
} }
void H_29(double *state, double *unused, double *out_3340353003283516317) { void H_29(double *state, double *unused, double *out_4310538866770804613) {
out_3340353003283516317[0] = 0; out_4310538866770804613[0] = 0;
out_3340353003283516317[1] = 1; out_4310538866770804613[1] = 1;
out_3340353003283516317[2] = 0; out_4310538866770804613[2] = 0;
out_3340353003283516317[3] = 0; out_4310538866770804613[3] = 0;
out_3340353003283516317[4] = 0; out_4310538866770804613[4] = 0;
out_3340353003283516317[5] = 0; out_4310538866770804613[5] = 0;
out_3340353003283516317[6] = 0; out_4310538866770804613[6] = 0;
out_3340353003283516317[7] = 0; out_4310538866770804613[7] = 0;
out_3340353003283516317[8] = 0; out_4310538866770804613[8] = 0;
} }
void h_28(double *state, double *unused, double *out_4249115445980516624) { void h_28(double *state, double *unused, double *out_5824514749225839094) {
out_4249115445980516624[0] = state[0]; out_5824514749225839094[0] = state[0];
} }
void H_28(double *state, double *unused, double *out_5303983274848842568) { void H_28(double *state, double *unused, double *out_9053806189869216429) {
out_5303983274848842568[0] = 1; out_9053806189869216429[0] = 1;
out_5303983274848842568[1] = 0; out_9053806189869216429[1] = 0;
out_5303983274848842568[2] = 0; out_9053806189869216429[2] = 0;
out_5303983274848842568[3] = 0; out_9053806189869216429[3] = 0;
out_5303983274848842568[4] = 0; out_9053806189869216429[4] = 0;
out_5303983274848842568[5] = 0; out_9053806189869216429[5] = 0;
out_5303983274848842568[6] = 0; out_9053806189869216429[6] = 0;
out_5303983274848842568[7] = 0; out_9053806189869216429[7] = 0;
out_5303983274848842568[8] = 0; out_9053806189869216429[8] = 0;
} }
void h_31(double *state, double *unused, double *out_5644913485036226869) { void h_31(double *state, double *unused, double *out_8917127482422223291) {
out_5644913485036226869[0] = state[8]; out_8917127482422223291[0] = state[8];
} }
void H_31(double *state, double *unused, double *out_7388463950973692759) { void H_31(double *state, double *unused, double *out_7308457207715484996) {
out_7388463950973692759[0] = 0; out_7308457207715484996[0] = 0;
out_7388463950973692759[1] = 0; out_7308457207715484996[1] = 0;
out_7388463950973692759[2] = 0; out_7308457207715484996[2] = 0;
out_7388463950973692759[3] = 0; out_7308457207715484996[3] = 0;
out_7388463950973692759[4] = 0; out_7308457207715484996[4] = 0;
out_7388463950973692759[5] = 0; out_7308457207715484996[5] = 0;
out_7388463950973692759[6] = 0; out_7308457207715484996[6] = 0;
out_7388463950973692759[7] = 0; out_7308457207715484996[7] = 0;
out_7388463950973692759[8] = 1; out_7308457207715484996[8] = 1;
} }
#include <eigen3/Eigen/Dense> #include <eigen3/Eigen/Dense>
#include <iostream> #include <iostream>
@@ -518,68 +518,68 @@ void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) { void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_31, H_31, NULL, in_z, in_R, in_ea, MAHA_THRESH_31); update<1, 3, 0>(in_x, in_P, h_31, H_31, NULL, in_z, in_R, in_ea, MAHA_THRESH_31);
} }
void car_err_fun(double *nom_x, double *delta_x, double *out_694062864366320957) { void car_err_fun(double *nom_x, double *delta_x, double *out_5825026556611430403) {
err_fun(nom_x, delta_x, out_694062864366320957); err_fun(nom_x, delta_x, out_5825026556611430403);
} }
void car_inv_err_fun(double *nom_x, double *true_x, double *out_7656216431051591408) { void car_inv_err_fun(double *nom_x, double *true_x, double *out_8876036798993240698) {
inv_err_fun(nom_x, true_x, out_7656216431051591408); inv_err_fun(nom_x, true_x, out_8876036798993240698);
} }
void car_H_mod_fun(double *state, double *out_2454126163912067491) { void car_H_mod_fun(double *state, double *out_6203949078932441352) {
H_mod_fun(state, out_2454126163912067491); H_mod_fun(state, out_6203949078932441352);
} }
void car_f_fun(double *state, double dt, double *out_1843607112126961388) { void car_f_fun(double *state, double dt, double *out_343822199527135251) {
f_fun(state, dt, out_1843607112126961388); f_fun(state, dt, out_343822199527135251);
} }
void car_F_fun(double *state, double dt, double *out_2182262497468252920) { void car_F_fun(double *state, double dt, double *out_7416815269748566643) {
F_fun(state, dt, out_2182262497468252920); F_fun(state, dt, out_7416815269748566643);
} }
void car_h_25(double *state, double *unused, double *out_4798003278786560335) { void car_h_25(double *state, double *unused, double *out_8641933420137717402) {
h_25(state, unused, out_4798003278786560335); h_25(state, unused, out_8641933420137717402);
} }
void car_H_25(double *state, double *unused, double *out_7357817989096732331) { void car_H_25(double *state, double *unused, double *out_7339103169592445424) {
H_25(state, unused, out_7357817989096732331); H_25(state, unused, out_7339103169592445424);
} }
void car_h_24(double *state, double *unused, double *out_6970490004472512085) { void car_h_24(double *state, double *unused, double *out_5501280400540853584) {
h_24(state, unused, out_6970490004472512085); h_24(state, unused, out_5501280400540853584);
} }
void car_H_24(double *state, double *unused, double *out_1156785309245570905) { void car_H_24(double *state, double *unused, double *out_9067966288769759493) {
H_24(state, unused, out_1156785309245570905); H_24(state, unused, out_9067966288769759493);
} }
void car_h_30(double *state, double *unused, double *out_3972351172998717479) { void car_h_30(double *state, double *unused, double *out_5706507355292462350) {
h_30(state, unused, out_3972351172998717479); h_30(state, unused, out_5706507355292462350);
} }
void car_H_30(double *state, double *unused, double *out_7228479041953492261) { void car_H_30(double *state, double *unused, double *out_422412828100828669) {
H_30(state, unused, out_7228479041953492261); H_30(state, unused, out_422412828100828669);
} }
void car_h_26(double *state, double *unused, double *out_1029322094662128166) { void car_h_26(double *state, double *unused, double *out_8743293406736795035) {
h_26(state, unused, out_1029322094662128166); h_26(state, unused, out_8743293406736795035);
} }
void car_H_26(double *state, double *unused, double *out_3616314670222676107) { void car_H_26(double *state, double *unused, double *out_7366137585243049968) {
H_26(state, unused, out_3616314670222676107); H_26(state, unused, out_7366137585243049968);
} }
void car_h_27(double *state, double *unused, double *out_9195955360068189347) { void car_h_27(double *state, double *unused, double *out_6768024602127501529) {
h_27(state, unused, out_9195955360068189347); h_27(state, unused, out_6768024602127501529);
} }
void car_H_27(double *state, double *unused, double *out_5053715730153067350) { void car_H_27(double *state, double *unused, double *out_2597176139901253580) {
H_27(state, unused, out_5053715730153067350); H_27(state, unused, out_2597176139901253580);
} }
void car_h_29(double *state, double *unused, double *out_9038768691717060202) { void car_h_29(double *state, double *unused, double *out_5090891545105754924) {
h_29(state, unused, out_9038768691717060202); h_29(state, unused, out_5090891545105754924);
} }
void car_H_29(double *state, double *unused, double *out_3340353003283516317) { void car_H_29(double *state, double *unused, double *out_4310538866770804613) {
H_29(state, unused, out_3340353003283516317); H_29(state, unused, out_4310538866770804613);
} }
void car_h_28(double *state, double *unused, double *out_4249115445980516624) { void car_h_28(double *state, double *unused, double *out_5824514749225839094) {
h_28(state, unused, out_4249115445980516624); h_28(state, unused, out_5824514749225839094);
} }
void car_H_28(double *state, double *unused, double *out_5303983274848842568) { void car_H_28(double *state, double *unused, double *out_9053806189869216429) {
H_28(state, unused, out_5303983274848842568); H_28(state, unused, out_9053806189869216429);
} }
void car_h_31(double *state, double *unused, double *out_5644913485036226869) { void car_h_31(double *state, double *unused, double *out_8917127482422223291) {
h_31(state, unused, out_5644913485036226869); h_31(state, unused, out_8917127482422223291);
} }
void car_H_31(double *state, double *unused, double *out_7388463950973692759) { void car_H_31(double *state, double *unused, double *out_7308457207715484996) {
H_31(state, unused, out_7388463950973692759); H_31(state, unused, out_7308457207715484996);
} }
void car_predict(double *in_x, double *in_P, double *in_Q, double dt) { void car_predict(double *in_x, double *in_P, double *in_Q, double dt) {
predict(in_x, in_P, in_Q, dt); predict(in_x, in_P, in_Q, dt);
+21 -21
View File
@@ -9,27 +9,27 @@ void car_update_27(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_err_fun(double *nom_x, double *delta_x, double *out_694062864366320957); void car_err_fun(double *nom_x, double *delta_x, double *out_5825026556611430403);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_7656216431051591408); void car_inv_err_fun(double *nom_x, double *true_x, double *out_8876036798993240698);
void car_H_mod_fun(double *state, double *out_2454126163912067491); void car_H_mod_fun(double *state, double *out_6203949078932441352);
void car_f_fun(double *state, double dt, double *out_1843607112126961388); void car_f_fun(double *state, double dt, double *out_343822199527135251);
void car_F_fun(double *state, double dt, double *out_2182262497468252920); void car_F_fun(double *state, double dt, double *out_7416815269748566643);
void car_h_25(double *state, double *unused, double *out_4798003278786560335); void car_h_25(double *state, double *unused, double *out_8641933420137717402);
void car_H_25(double *state, double *unused, double *out_7357817989096732331); void car_H_25(double *state, double *unused, double *out_7339103169592445424);
void car_h_24(double *state, double *unused, double *out_6970490004472512085); void car_h_24(double *state, double *unused, double *out_5501280400540853584);
void car_H_24(double *state, double *unused, double *out_1156785309245570905); void car_H_24(double *state, double *unused, double *out_9067966288769759493);
void car_h_30(double *state, double *unused, double *out_3972351172998717479); void car_h_30(double *state, double *unused, double *out_5706507355292462350);
void car_H_30(double *state, double *unused, double *out_7228479041953492261); void car_H_30(double *state, double *unused, double *out_422412828100828669);
void car_h_26(double *state, double *unused, double *out_1029322094662128166); void car_h_26(double *state, double *unused, double *out_8743293406736795035);
void car_H_26(double *state, double *unused, double *out_3616314670222676107); void car_H_26(double *state, double *unused, double *out_7366137585243049968);
void car_h_27(double *state, double *unused, double *out_9195955360068189347); void car_h_27(double *state, double *unused, double *out_6768024602127501529);
void car_H_27(double *state, double *unused, double *out_5053715730153067350); void car_H_27(double *state, double *unused, double *out_2597176139901253580);
void car_h_29(double *state, double *unused, double *out_9038768691717060202); void car_h_29(double *state, double *unused, double *out_5090891545105754924);
void car_H_29(double *state, double *unused, double *out_3340353003283516317); void car_H_29(double *state, double *unused, double *out_4310538866770804613);
void car_h_28(double *state, double *unused, double *out_4249115445980516624); void car_h_28(double *state, double *unused, double *out_5824514749225839094);
void car_H_28(double *state, double *unused, double *out_5303983274848842568); void car_H_28(double *state, double *unused, double *out_9053806189869216429);
void car_h_31(double *state, double *unused, double *out_5644913485036226869); void car_h_31(double *state, double *unused, double *out_8917127482422223291);
void car_H_31(double *state, double *unused, double *out_7388463950973692759); void car_H_31(double *state, double *unused, double *out_7308457207715484996);
void car_predict(double *in_x, double *in_P, double *in_Q, double dt); void car_predict(double *in_x, double *in_P, double *in_Q, double dt);
void car_set_mass(double x); void car_set_mass(double x);
void car_set_rotational_inertia(double x); void car_set_rotational_inertia(double x);
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -5,18 +5,18 @@ void pose_update_4(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void pose_update_10(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void pose_update_10(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_13(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void pose_update_13(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_14(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void pose_update_14(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_err_fun(double *nom_x, double *delta_x, double *out_6507930050433723456); void pose_err_fun(double *nom_x, double *delta_x, double *out_4860250714300227404);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_4828699442560272840); void pose_inv_err_fun(double *nom_x, double *true_x, double *out_6437944255548658708);
void pose_H_mod_fun(double *state, double *out_6044345162965507958); void pose_H_mod_fun(double *state, double *out_22797630451996970);
void pose_f_fun(double *state, double dt, double *out_2707422486897363634); void pose_f_fun(double *state, double dt, double *out_2362837524605346595);
void pose_F_fun(double *state, double dt, double *out_2783420399776033045); void pose_F_fun(double *state, double dt, double *out_4166989473592392013);
void pose_h_4(double *state, double *unused, double *out_6625322830489649201); void pose_h_4(double *state, double *unused, double *out_800785631088066019);
void pose_H_4(double *state, double *unused, double *out_891826777926427223); void pose_H_4(double *state, double *unused, double *out_2555861026626074297);
void pose_h_10(double *state, double *unused, double *out_495585793918053605); void pose_h_10(double *state, double *unused, double *out_7585285090603906809);
void pose_H_10(double *state, double *unused, double *out_8682725662931185277); void pose_H_10(double *state, double *unused, double *out_6341548427213940478);
void pose_h_13(double *state, double *unused, double *out_6437595282912769194); void pose_h_13(double *state, double *unused, double *out_7888682643875678910);
void pose_H_13(double *state, double *unused, double *out_2320447047405905578); void pose_H_13(double *state, double *unused, double *out_5768134851958407098);
void pose_h_14(double *state, double *unused, double *out_1731855561045144303); void pose_h_14(double *state, double *unused, double *out_3441729687180753414);
void pose_H_14(double *state, double *unused, double *out_1326943304571310822); void pose_H_14(double *state, double *unused, double *out_4925284788653666127);
void pose_predict(double *in_x, double *in_P, double *in_Q, double dt); void pose_predict(double *in_x, double *in_P, double *in_Q, double dt);
} }
Binary file not shown.
Binary file not shown.
@@ -4914,15 +4914,6 @@
"is_parent_toggle": true, "is_parent_toggle": true,
"settings_tier": "simple" "settings_tier": "simple"
}, },
{
"key": "GalaxyMobileDefault",
"label": "Try the Big Dipper Web UI",
"description": "Open the Big Dipper at the top-level Galaxy link instead of the classic Galaxy. The classic UI remains available at /classic and Big Dipper at /mobile regardless of this toggle.",
"picker_description": "Serve the Big Dipper as the default landing page.",
"data_type": "bool",
"ui_type": "toggle",
"settings_tier": "advanced"
},
{ {
"key": "AlphaLongitudinalEnabled", "key": "AlphaLongitudinalEnabled",
"label": "openpilot Longitudinal Control (Alpha)", "label": "openpilot Longitudinal Control (Alpha)",
@@ -444,26 +444,15 @@ class SpeedLimitController:
return return
current_road_name = sm["mapdOut"].roadName if desired_source == "Map Data" else "" current_road_name = sm["mapdOut"].roadName if desired_source == "Map Data" else ""
current_speed = self.target if (self.source != "None" and self.target > 0) else self.last_valid_limit
# Do not trigger alerts when shifting to fallback or when re-obtaining the same speed limit if abs(desired_target - self.previous_target) >= 1 or (current_road_name != self.previous_road_name and current_road_name != ""):
is_fallback = desired_source == "None" or desired_target == 0
same_speed = desired_target > 0 and current_speed > 0 and abs(desired_target - current_speed) < 1
if not is_fallback and not same_speed and (abs(desired_target - self.previous_target) >= 1 or current_speed == 0):
self.handle_limit_change(desired_source, desired_target, current_road_name, v_ego, sm) self.handle_limit_change(desired_source, desired_target, current_road_name, v_ego, sm)
elif desired_source != self.source and (abs(desired_target - self.target) < 1 or self.target == 0):
self.source = desired_source
self.target = desired_target
else: else:
self.speed_limit_changed_timer = 0 self.speed_limit_changed_timer = 0
self.unconfirmed_speed_limit = 0 self.unconfirmed_speed_limit = 0
if desired_source != self.source or desired_target != self.target:
self.source = desired_source
self.target = desired_target
if desired_source != "None" and desired_target > 0:
self.previous_source = desired_source
self.previous_target = desired_target
if current_road_name != self.previous_road_name and current_road_name != "":
self.previous_road_name = current_road_name
self.denied_target = 0
if self.source != "None" and self.target > 0: if self.source != "None" and self.target > 0:
self.last_valid_limit = self.target self.last_valid_limit = self.target
+3 -2
View File
@@ -235,7 +235,7 @@ class BlueZClient:
continue continue
props = interfaces[DEVICE_IFACE] props = interfaces[DEVICE_IFACE]
uuids = [str(value).lower() for value in props.get("UUIDs", [])] uuids = [str(value).lower() for value in props.get("UUIDs", [])]
audio, controller = device_capabilities(uuids, int(props.get("Class", 0)), str(props.get("Icon", ""))) audio, controller, serial = device_capabilities(uuids, int(props.get("Class", 0)), str(props.get("Icon", "")))
device = { device = {
"path": path, "path": path,
"address": str(props.get("Address", "")), "address": str(props.get("Address", "")),
@@ -248,9 +248,10 @@ class BlueZClient:
"uuids": uuids, "uuids": uuids,
"audio": audio, "audio": audio,
"controller": controller, "controller": controller,
"serial": serial,
} }
if include_hidden or show_pairing_device(device["address"], device["name"], device["paired"], device["trusted"], device["connected"], if include_hidden or show_pairing_device(device["address"], device["name"], device["paired"], device["trusted"], device["connected"],
device["blocked"], audio, controller, include_discovering): device["blocked"], audio, controller, serial, include_discovering):
devices.append(device) devices.append(device)
return sorted(devices, key=lambda device: (not device["connected"], not device["paired"], -(device["rssi"] or -127), device["name"].lower())) return sorted(devices, key=lambda device: (not device["connected"], not device["paired"], -(device["rssi"] or -127), device["name"].lower()))
+86 -2
View File
@@ -9,11 +9,13 @@ from typing import Any
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.starpilot.system.bluetooth.bluez import BlueZClient from openpilot.starpilot.system.bluetooth.bluez import BlueZClient
from openpilot.starpilot.system.bluetooth.elm327 import ELM327Session
from openpilot.starpilot.system.bluetooth.protocol import BLUETOOTH_SOCKET_PATH from openpilot.starpilot.system.bluetooth.protocol import BLUETOOTH_SOCKET_PATH
from openpilot.starpilot.system.bluetooth.radio import BluetoothRadio from openpilot.starpilot.system.bluetooth.radio import BluetoothRadio
OFFROAD_COMMANDS = {"set_power", "start_scan", "stop_scan", "pair", "forget", "test_audio", "pairing_response"} OFFROAD_COMMANDS = {"set_power", "start_scan", "stop_scan", "pair", "forget", "test_audio", "pairing_response",
"elm_open", "elm_command", "elm_read_dtcs"}
SCAN_DURATION = 20.0 SCAN_DURATION = 20.0
AUDIO_TEST_START_DELAY = 3.0 AUDIO_TEST_START_DELAY = 3.0
AUDIO_TEST_HOLD_TIME = 3.0 AUDIO_TEST_HOLD_TIME = 3.0
@@ -24,13 +26,15 @@ MANUAL_DISCONNECT_SUPPRESSION_SECONDS = 300.0
class BluetoothController: class BluetoothController:
def __init__(self, params: Params | None = None, bluez_factory=BlueZClient, radio: BluetoothRadio | None = None, def __init__(self, params: Params | None = None, bluez_factory=BlueZClient, radio: BluetoothRadio | None = None,
params_memory: Params | None = None, sleep=time.sleep): params_memory: Params | None = None, sleep=time.sleep, elm_factory=ELM327Session):
self.params = params or Params() self.params = params or Params()
self.params_memory = params_memory or Params(memory=True) self.params_memory = params_memory or Params(memory=True)
self._bluez_factory = bluez_factory self._bluez_factory = bluez_factory
self._elm_factory = elm_factory
self._radio = radio or BluetoothRadio() self._radio = radio or BluetoothRadio()
self._lock = threading.RLock() self._lock = threading.RLock()
self._bluez: BlueZClient | None = None self._bluez: BlueZClient | None = None
self._elm: ELM327Session | None = None
self._pairing_address = "" self._pairing_address = ""
self._pairing_error = "" self._pairing_error = ""
self._last_reconnect = 0.0 self._last_reconnect = 0.0
@@ -46,6 +50,7 @@ class BluetoothController:
self.params.remove("BluetoothAudioTestActive") self.params.remove("BluetoothAudioTestActive")
self.params_memory.remove("TestAlert") self.params_memory.remove("TestAlert")
with self._lock: with self._lock:
self._close_elm()
if self._bluez is not None: if self._bluez is not None:
self._bluez.close() self._bluez.close()
self._bluez = None self._bluez = None
@@ -80,6 +85,7 @@ class BluetoothController:
def _reset_client(self) -> None: def _reset_client(self) -> None:
with self._lock: with self._lock:
self._close_elm()
if self._bluez is not None: if self._bluez is not None:
try: try:
self._bluez.close() self._bluez.close()
@@ -87,6 +93,32 @@ class BluetoothController:
pass pass
self._bluez = None self._bluez = None
def _close_elm(self) -> None:
with self._lock:
session = self._elm
self._elm = None
if session is None:
return
try:
session.close()
except Exception:
cloudlog.warning("ELM327 session close failed")
def _invalidate_elm(self, session: ELM327Session) -> None:
with self._lock:
if self._elm is not session:
return
self._close_elm()
def _active_elm(self, address: str) -> ELM327Session:
with self._lock:
session = self._elm
if session is None:
raise RuntimeError("ELM327 session is not open")
if str(session.address).upper() != address.upper():
raise RuntimeError("ELM327 session is open for another device")
return session
def _offroad(self) -> bool: def _offroad(self) -> bool:
return self.params.get_bool("IsOffroad") return self.params.get_bool("IsOffroad")
@@ -183,6 +215,7 @@ class BluetoothController:
pass pass
raise raise
else: else:
self._close_elm()
try: try:
client = self._bluez client = self._bluez
if client is not None: if client is not None:
@@ -231,6 +264,9 @@ class BluetoothController:
self._manual_disconnect_until.pop(normalized_address, None) self._manual_disconnect_until.pop(normalized_address, None)
raise raise
elif command == "forget": elif command == "forget":
with self._lock:
if self._elm is not None and str(self._elm.address).upper() == address.upper():
self._close_elm()
self._client().remove(address) self._client().remove(address)
self._reconnect_backoff.pop(address.upper(), None) self._reconnect_backoff.pop(address.upper(), None)
self._manual_disconnect_until.pop(address.upper(), None) self._manual_disconnect_until.pop(address.upper(), None)
@@ -258,6 +294,50 @@ class BluetoothController:
self._audio_test_deadline = deadline self._audio_test_deadline = deadline
threading.Thread(target=self._test_audio_worker, args=(address, deadline), daemon=True).start() threading.Thread(target=self._test_audio_worker, args=(address, deadline), daemon=True).start()
return {"audio_test_delay_ms": max(0, round((deadline - time.monotonic()) * 1000))} return {"audio_test_delay_ms": max(0, round((deadline - time.monotonic()) * 1000))}
elif command == "elm_open":
if not address:
raise RuntimeError("Bluetooth device address is required")
if not self.params.get_bool("BluetoothEnabled"):
raise RuntimeError("Bluetooth is disabled")
with self._lock:
if self._elm is not None and str(self._elm.address).upper() == address.upper():
return {"adapter": self._elm.adapter_name}
device = self._client().device_for_address(address)
if not device.get("paired"):
raise RuntimeError("Pair the Bluetooth device before opening ELM327")
if not device.get("serial"):
raise RuntimeError("Bluetooth device does not advertise Serial Port Profile")
self._close_elm()
session = self._elm_factory(address)
try:
adapter = str(session.open())
except Exception:
try:
session.close()
except Exception:
pass
raise
session.adapter_name = adapter
self._elm = session
return {"adapter": adapter}
elif command == "elm_close":
with self._lock:
if self._elm is not None and str(self._elm.address).upper() == address.upper():
self._close_elm()
elif command == "elm_command":
session = self._active_elm(address)
try:
return {"response": session.command(str(request.get("value", "")))}
except Exception:
self._invalidate_elm(session)
raise
elif command == "elm_read_dtcs":
session = self._active_elm(address)
try:
return session.read_dtcs()
except Exception:
self._invalidate_elm(session)
raise
elif command == "pairing_response": elif command == "pairing_response":
if not self._client().agent.respond(str(request.get("prompt_id", "")), bool(request.get("accepted", False)), str(request.get("value", ""))): if not self._client().agent.respond(str(request.get("prompt_id", "")), bool(request.get("accepted", False)), str(request.get("value", ""))):
raise RuntimeError("Pairing request is no longer active") raise RuntimeError("Pairing request is no longer active")
@@ -276,10 +356,14 @@ class BluetoothController:
while True: while True:
time.sleep(2) time.sleep(2)
if not self.params.get_bool("BluetoothEnabled"): if not self.params.get_bool("BluetoothEnabled"):
self._close_elm()
continue continue
try: try:
status = self.status() status = self.status()
if self._elm is not None and not status["offroad"]:
self._close_elm()
if not status["available"] or not status["powered"]: if not status["available"] or not status["powered"]:
self._close_elm()
continue continue
now = time.monotonic() now = time.monotonic()
self._maintain_scan(status, now) self._maintain_scan(status, now)
+166
View File
@@ -0,0 +1,166 @@
from __future__ import annotations
import re
import socket
import threading
DEFAULT_CHANNEL = 1
OPEN_TIMEOUT = 10.0
DEFAULT_COMMAND_TIMEOUT = 10.0
DTC_COMMAND_TIMEOUT = 25.0
MAX_COMMAND_LENGTH = 256
MAX_RESPONSE_SIZE = 64 * 1024
RECV_SIZE = 4096
class DTCParseError(ValueError):
def __init__(self, message: str, raw: str):
super().__init__(message)
self.raw = raw
def decode_dtc(first: int, second: int) -> str:
prefixes = "PCBU"
prefix = prefixes[(first >> 6) & 0x03]
return f"{prefix}{(first >> 4) & 0x03:X}{first & 0x0F:X}{second >> 4:X}{second & 0x0F:X}"
_HEX_BYTE = re.compile(r"(?i)(?<![0-9a-f])([0-9a-f]{2})(?![0-9a-f])")
def parse_dtcs(raw: str) -> list[str]:
codes = []
seen = set()
for line in raw.splitlines():
values = [int(match, 16) for match in _HEX_BYTE.findall(line)]
try:
response_index = values.index(0x43)
except ValueError:
continue
payload = values[response_index + 1:]
if len(payload) % 2:
count = payload[0]
expected_length = count * 2
if len(payload) - 1 < expected_length:
raise DTCParseError(f"Mode 03 response claims {expected_length} DTC bytes, received {len(payload) - 1}", raw)
payload = payload[1:1 + expected_length]
for index in range(0, len(payload) - 1, 2):
first, second = payload[index:index + 2]
if first == 0 and second == 0:
continue
code = decode_dtc(first, second)
if code not in seen:
seen.add(code)
codes.append(code)
return codes
class ELM327Session:
def __init__(self, address: str, channel: int = DEFAULT_CHANNEL):
self.address = address
self.channel = channel
self.socket: socket.socket | None = None
self.lock = threading.RLock()
self.adapter_name = ""
def _close_unlocked(self) -> None:
client_socket = self.socket
self.socket = None
self.adapter_name = ""
if client_socket is not None:
try:
client_socket.close()
except Exception:
pass
def close(self) -> None:
with self.lock:
self._close_unlocked()
def _receive_until_prompt_unlocked(self, timeout: float) -> bytes:
if self.socket is None:
raise RuntimeError("ELM327 session is not open")
self.socket.settimeout(timeout)
response = bytearray()
while True:
chunk = self.socket.recv(RECV_SIZE)
if not chunk:
raise RuntimeError("ELM327 connection closed")
response.extend(chunk)
if len(response) > MAX_RESPONSE_SIZE:
raise RuntimeError("ELM327 response exceeded 64 KiB")
if b">" in response:
return bytes(response)
@staticmethod
def _clean_response(raw: bytes, command: str) -> str:
response = raw.split(b">", 1)[0].decode("ascii", errors="replace")
response = response.replace("\r\n", "\n").replace("\r", "\n")
lines = response.split("\n")
while lines and not lines[0].strip():
lines.pop(0)
if lines and lines[0].strip() == command:
lines.pop(0)
return "\n".join(lines).strip()
def _exchange_unlocked(self, command: str, timeout: float) -> str:
if self.socket is None:
raise RuntimeError("ELM327 session is not open")
try:
self.socket.settimeout(timeout)
self.socket.sendall(command.encode("ascii") + b"\r")
return self._clean_response(self._receive_until_prompt_unlocked(timeout), command)
except Exception as error:
self._close_unlocked()
raise RuntimeError(f"ELM327 transport failed: {error}") from error
def open(self) -> str:
with self.lock:
if self.socket is not None:
return self.adapter_name
try:
self.socket = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)
self.socket.settimeout(OPEN_TIMEOUT)
self.socket.connect((self.address, self.channel))
adapter_name = self._exchange_unlocked("ATI", OPEN_TIMEOUT)
if not adapter_name or adapter_name.strip().upper() in {"?", "ERROR", "COMMAND UNKNOWN", "UNKNOWN COMMAND", "NO DATA"}:
raise RuntimeError("ELM327 adapter rejected ATI")
self.adapter_name = adapter_name
for setup_command in ("ATE0", "ATL0", "ATH0"):
self._exchange_unlocked(setup_command, OPEN_TIMEOUT)
return self.adapter_name
except Exception as error:
self._close_unlocked()
if isinstance(error, RuntimeError) and str(error).startswith("ELM327 open failed:"):
raise
raise RuntimeError(f"ELM327 open failed: {error}") from error
def command(self, command: str, timeout: float = DEFAULT_COMMAND_TIMEOUT) -> str:
if not isinstance(command, str):
raise ValueError("ELM327 command must be text")
if "\r" in command or "\n" in command:
raise ValueError("ELM327 command cannot contain carriage returns or newlines")
command = command.strip()
if not command:
raise ValueError("ELM327 command cannot be empty")
if len(command) > MAX_COMMAND_LENGTH:
raise ValueError("ELM327 command is too long")
try:
command.encode("ascii")
except UnicodeEncodeError as error:
raise ValueError("ELM327 command must contain ASCII characters") from error
if timeout <= 0:
raise ValueError("ELM327 command timeout must be positive")
with self.lock:
return self._exchange_unlocked(command, timeout)
def read_dtcs(self) -> dict[str, str | list[str]]:
with self.lock:
for setup_command in ("ATE0", "ATL0", "ATH0", "ATSP0"):
self.command(setup_command)
raw = self.command("03", timeout=DTC_COMMAND_TIMEOUT)
return {"codes": parse_dtcs(raw), "raw": raw}
+24 -4
View File
@@ -16,6 +16,7 @@ BLUETOOTH_RADIO_HELPER = "/usr/comma/bluetooth-radio"
A2DP_SINK_UUID = "0000110b-0000-1000-8000-00805f9b34fb" A2DP_SINK_UUID = "0000110b-0000-1000-8000-00805f9b34fb"
HID_UUID = "00001124-0000-1000-8000-00805f9b34fb" HID_UUID = "00001124-0000-1000-8000-00805f9b34fb"
HOG_UUID = "00001812-0000-1000-8000-00805f9b34fb" HOG_UUID = "00001812-0000-1000-8000-00805f9b34fb"
SPP_UUID = "00001101-0000-1000-8000-00805f9b34fb"
COMMAND_TIMEOUTS = { COMMAND_TIMEOUTS = {
"set_power": 90.0, "set_power": 90.0,
"start_scan": 20.0, "start_scan": 20.0,
@@ -24,6 +25,10 @@ COMMAND_TIMEOUTS = {
"disconnect": 20.0, "disconnect": 20.0,
"forget": 20.0, "forget": 20.0,
"test_audio": 10.0, "test_audio": 10.0,
"elm_open": 15.0,
"elm_close": 5.0,
"elm_command": 20.0,
"elm_read_dtcs": 30.0,
} }
TRUE_VALUES = {"1", "true", "yes", "on"} TRUE_VALUES = {"1", "true", "yes", "on"}
@@ -40,6 +45,7 @@ class BluetoothDevice:
uuids: tuple[str, ...] = () uuids: tuple[str, ...] = ()
audio: bool = False audio: bool = False
controller: bool = False controller: bool = False
serial: bool = False
@classmethod @classmethod
def from_dict(cls, value: dict[str, Any]) -> "BluetoothDevice": def from_dict(cls, value: dict[str, Any]) -> "BluetoothDevice":
@@ -54,6 +60,7 @@ class BluetoothDevice:
uuids=tuple(str(uuid).lower() for uuid in value.get("uuids", ())), uuids=tuple(str(uuid).lower() for uuid in value.get("uuids", ())),
audio=bool(value.get("audio", False)), audio=bool(value.get("audio", False)),
controller=bool(value.get("controller", False)), controller=bool(value.get("controller", False)),
serial=bool(value.get("serial", False)),
) )
@@ -86,21 +93,22 @@ class BluetoothStatus:
) )
def device_capabilities(uuids: list[str] | tuple[str, ...], bluetooth_class: int = 0, icon: str = "") -> tuple[bool, bool]: def device_capabilities(uuids: list[str] | tuple[str, ...], bluetooth_class: int = 0, icon: str = "") -> tuple[bool, bool, bool]:
normalized = {str(uuid).lower() for uuid in uuids} normalized = {str(uuid).lower() for uuid in uuids}
major_class = (int(bluetooth_class) >> 8) & 0x1F major_class = (int(bluetooth_class) >> 8) & 0x1F
audio = A2DP_SINK_UUID in normalized or major_class == 0x04 or icon in {"audio-card", "audio-headphones", "audio-headset"} audio = A2DP_SINK_UUID in normalized or major_class == 0x04 or icon in {"audio-card", "audio-headphones", "audio-headset"}
controller = HID_UUID in normalized or HOG_UUID in normalized or major_class == 0x05 or icon in {"input-gaming", "input-mouse", "input-keyboard"} controller = HID_UUID in normalized or HOG_UUID in normalized or major_class == 0x05 or icon in {"input-gaming", "input-mouse", "input-keyboard"}
return audio, controller serial = SPP_UUID in normalized
return audio, controller, serial
def show_pairing_device(address: str, name: str, paired: bool, trusted: bool, connected: bool, blocked: bool, def show_pairing_device(address: str, name: str, paired: bool, trusted: bool, connected: bool, blocked: bool,
audio: bool, controller: bool, discovering: bool = False) -> bool: audio: bool, controller: bool, serial: bool = False, discovering: bool = False) -> bool:
known = paired or trusted or connected known = paired or trusted or connected
normalized_address = "".join(character for character in address.upper() if character.isalnum()) normalized_address = "".join(character for character in address.upper() if character.isalnum())
normalized_name = "".join(character for character in name.upper() if character.isalnum()) normalized_name = "".join(character for character in name.upper() if character.isalnum())
named = bool(name) and name != "Unknown device" and normalized_name != normalized_address named = bool(name) and name != "Unknown device" and normalized_name != normalized_address
return known or (named and not blocked and (audio or controller)) return known or (named and not blocked and (audio or controller or serial))
class _DesktopFakeBluetooth: class _DesktopFakeBluetooth:
@@ -304,5 +312,17 @@ class BluetoothClient:
result = self.call("test_audio", address=address) result = self.call("test_audio", address=address)
return max(0.0, float(result.get("audio_test_delay_ms", 0)) / 1000.0) return max(0.0, float(result.get("audio_test_delay_ms", 0)) / 1000.0)
def elm_open(self, address: str) -> dict[str, Any]:
return self.call("elm_open", address=address)
def elm_close(self, address: str) -> dict[str, Any]:
return self.call("elm_close", address=address)
def elm_command(self, address: str, value: str) -> dict[str, Any]:
return self.call("elm_command", address=address, value=value)
def elm_read_dtcs(self, address: str) -> dict[str, Any]:
return self.call("elm_read_dtcs", address=address)
def respond(self, prompt_id: str, accepted: bool, value: str = "") -> None: def respond(self, prompt_id: str, accepted: bool, value: str = "") -> None:
self.call("pairing_response", prompt_id=prompt_id, accepted=accepted, value=value) self.call("pairing_response", prompt_id=prompt_id, accepted=accepted, value=value)
@@ -6,10 +6,11 @@ import numpy as np
import pytest import pytest
from openpilot.starpilot.system.bluetooth.audio import BluetoothAudioSink from openpilot.starpilot.system.bluetooth.audio import BluetoothAudioSink
import openpilot.starpilot.system.bluetooth.daemon as bluetooth_daemon
from openpilot.starpilot.system.bluetooth.bluez import PairingAgent from openpilot.starpilot.system.bluetooth.bluez import PairingAgent
from openpilot.starpilot.system.bluetooth.daemon import BluetoothController from openpilot.starpilot.system.bluetooth.daemon import BluetoothController
from openpilot.starpilot.system.bluetooth.protocol import (A2DP_SINK_UUID, HID_UUID, BluetoothClient, BluetoothDevice, BluetoothStatus, from openpilot.starpilot.system.bluetooth.protocol import (A2DP_SINK_UUID, HID_UUID, BluetoothClient, BluetoothDevice, BluetoothStatus,
device_capabilities, show_pairing_device) SPP_UUID, device_capabilities, show_pairing_device)
from openpilot.system import hardware from openpilot.system import hardware
from openpilot.system.ui.lib.bluetooth_manager import BluetoothManager from openpilot.system.ui.lib.bluetooth_manager import BluetoothManager
@@ -64,6 +65,7 @@ class FakeBlueZ:
"connected": False, "connected": False,
"audio": True, "audio": True,
"controller": False, "controller": False,
"serial": False,
} }
def close(self): def close(self):
@@ -163,9 +165,38 @@ class FakeProcess:
self.stopped = True self.stopped = True
class FakeELM:
instances = []
def __init__(self, address):
self.address = address
self.adapter_name = "Fake ELM327"
self.closed = False
self.commands = []
self.opened = False
FakeELM.instances.append(self)
def open(self):
self.opened = True
return self.adapter_name
def close(self):
self.closed = True
def command(self, value):
self.commands.append(value)
if value == "fail":
raise RuntimeError("transport failed")
return f"response for {value}"
def read_dtcs(self):
return {"codes": ["P0133"], "raw": "43 01 33"}
def test_protocol_round_trip_and_capabilities(): def test_protocol_round_trip_and_capabilities():
audio, controller = device_capabilities([A2DP_SINK_UUID, HID_UUID]) audio, controller, serial = device_capabilities([A2DP_SINK_UUID, HID_UUID])
assert audio and controller assert audio and controller
assert not serial
status = BluetoothStatus.from_dict({ status = BluetoothStatus.from_dict({
"available": True, "available": True,
"enabled": True, "enabled": True,
@@ -174,12 +205,155 @@ def test_protocol_round_trip_and_capabilities():
assert status.devices == (BluetoothDevice("00:11:22:33:44:55", "Combo", uuids=(A2DP_SINK_UUID, HID_UUID), audio=True, controller=True),) assert status.devices == (BluetoothDevice("00:11:22:33:44:55", "Combo", uuids=(A2DP_SINK_UUID, HID_UUID), audio=True, controller=True),)
def test_serial_capability_round_trips_and_is_discoverable():
audio, controller, serial = device_capabilities([SPP_UUID.upper()])
assert not audio and not controller and serial
status = BluetoothStatus.from_dict({
"devices": [{"address": "00:11:22:33:44:55", "name": "OBDII", "serial": True}],
})
assert status.devices[0].serial
assert show_pairing_device("00:11:22:33:44:55", "OBDII", False, False, False, False,
audio=False, controller=False, serial=True)
def test_serial_device_is_not_auto_reconnected_but_audio_device_is(monkeypatch):
class StopMaintenance(Exception):
pass
params = FakeParams(IsOffroad=True, BluetoothEnabled=True)
client = FakeBlueZ()
client.powered = True
client.device.update(paired=True, trusted=True, connected=False, audio=False, controller=False, serial=True)
sleeps = 0
def sleep(_delay):
nonlocal sleeps
sleeps += 1
if sleeps > 1:
raise StopMaintenance
monkeypatch.setattr(bluetooth_daemon.time, "sleep", sleep)
controller = BluetoothController(params, lambda: client, FakeRadio(), sleep=sleep, elm_factory=FakeELM)
controller._bluez = client
controller._last_reconnect = -100.0
with pytest.raises(StopMaintenance):
controller.maintain_connections()
assert client.actions == []
client.device.update(audio=True, serial=False)
sleeps = 0
controller._last_reconnect = -100.0
with pytest.raises(StopMaintenance):
controller.maintain_connections()
assert client.actions == [("connect", client.device["address"])]
def make_elm_controller(elm_factory=FakeELM):
params = FakeParams(IsOffroad=True, BluetoothEnabled=True)
client = FakeBlueZ()
client.device.update(paired=True, trusted=True, serial=True)
controller = BluetoothController(params, lambda: client, FakeRadio(), elm_factory=elm_factory)
return controller, client, params
def test_elm_is_lazy_and_requires_a_paired_serial_device():
FakeELM.instances = []
controller, client, params = make_elm_controller()
assert FakeELM.instances == []
controller.status()
assert FakeELM.instances == []
result = controller.handle({"command": "elm_open", "address": client.device["address"]})
assert result == {"adapter": "Fake ELM327"}
assert len(FakeELM.instances) == 1
params.values["IsOffroad"] = False
with pytest.raises(RuntimeError, match="offroad"):
controller.handle({"command": "elm_command", "address": client.device["address"], "value": "ATI"})
params.values["IsOffroad"] = True
client.device["paired"] = False
controller.handle({"command": "elm_close", "address": client.device["address"]})
with pytest.raises(RuntimeError, match="Pair"):
controller.handle({"command": "elm_open", "address": client.device["address"]})
client.device.update(paired=True, serial=False)
with pytest.raises(RuntimeError, match="Serial Port Profile"):
controller.handle({"command": "elm_open", "address": client.device["address"]})
def test_elm_commands_use_one_session_and_close_on_transport_failure():
FakeELM.instances = []
controller, client, _ = make_elm_controller()
address = client.device["address"]
controller.handle({"command": "elm_open", "address": address})
assert controller.handle({"command": "elm_open", "address": address}) == {"adapter": "Fake ELM327"}
assert controller.handle({"command": "elm_command", "address": address, "value": "ATI"}) == {"response": "response for ATI"}
assert controller.handle({"command": "elm_read_dtcs", "address": address}) == {"codes": ["P0133"], "raw": "43 01 33"}
with pytest.raises(RuntimeError, match="transport"):
controller.handle({"command": "elm_command", "address": address, "value": "fail"})
assert controller._elm is None
assert FakeELM.instances[0].closed
def test_elm_open_replaces_a_different_session_and_close_is_allowed_onroad():
FakeELM.instances = []
controller, client, params = make_elm_controller()
first = client.device["address"]
second = "AA:BB:CC:DD:EE:FF"
controller.handle({"command": "elm_open", "address": first})
controller.handle({"command": "elm_open", "address": second})
assert len(FakeELM.instances) == 2
assert FakeELM.instances[0].closed
assert not FakeELM.instances[1].closed
params.values["IsOffroad"] = False
controller.handle({"command": "elm_close", "address": second})
assert FakeELM.instances[1].closed and controller._elm is None
def test_elm_cleanup_happens_before_poweroff_forget_shutdown_and_onroad(monkeypatch):
class StopMaintenance(Exception):
pass
for cleanup in ("power", "forget", "shutdown", "onroad"):
FakeELM.instances = []
controller, client, params = make_elm_controller()
address = client.device["address"]
controller.handle({"command": "elm_open", "address": address})
session = FakeELM.instances[0]
if cleanup == "power":
controller.handle({"command": "set_power", "enabled": False})
elif cleanup == "forget":
controller.handle({"command": "forget", "address": address})
elif cleanup == "shutdown":
controller.close()
else:
params.values["IsOffroad"] = False
sleeps = 0
def sleep(_delay):
nonlocal sleeps
sleeps += 1
if sleeps > 1:
raise StopMaintenance
monkeypatch.setattr(bluetooth_daemon.time, "sleep", sleep)
controller._sleep = sleep
with pytest.raises(StopMaintenance):
controller.maintain_connections()
assert session.closed and controller._elm is None
def test_pairing_list_filters_anonymous_and_irrelevant_advertisements(): def test_pairing_list_filters_anonymous_and_irrelevant_advertisements():
assert not show_pairing_device("00:11:22:33:44:55", "00:11:22:33:44:55", False, False, False, False, False, False) assert not show_pairing_device("00:11:22:33:44:55", "00:11:22:33:44:55", False, False, False, False, False, False)
assert not show_pairing_device("00:11:22:33:44:55", "Nearby sensor", False, False, False, False, False, False) assert not show_pairing_device("00:11:22:33:44:55", "Nearby sensor", False, False, False, False, False, False)
assert show_pairing_device("00:11:22:33:44:55", "Media Remote", False, False, False, False, False, True) assert show_pairing_device("00:11:22:33:44:55", "Media Remote", False, False, False, False, False, True)
assert show_pairing_device("00:11:22:33:44:55", "Media Remote", False, False, False, False, False, True, True) assert show_pairing_device("00:11:22:33:44:55", "Media Remote", False, False, False, False, False, True, discovering=True)
assert not show_pairing_device("00:11:22:33:44:55", "Nearby sensor", False, False, False, False, False, False, True) assert not show_pairing_device("00:11:22:33:44:55", "Nearby sensor", False, False, False, False, False, False, discovering=True)
assert show_pairing_device("00:11:22:33:44:55", "Known device", True, True, False, False, False, False) assert show_pairing_device("00:11:22:33:44:55", "Known device", True, True, False, False, False, False)
@@ -0,0 +1,227 @@
from collections import deque
import threading
import pytest
from openpilot.starpilot.system.bluetooth import elm327
ADDRESS = "00:11:22:33:44:55"
class FakeSocket:
def __init__(self, responses):
self.responses = deque(deque(response) for response in responses)
self.pending = deque()
self.sent = []
self.connected_to = None
self.timeouts = []
self.closed = False
self.close_calls = 0
self.recv_error = None
self.send_error = None
def settimeout(self, timeout):
self.timeouts.append(timeout)
def connect(self, address):
self.connected_to = address
def sendall(self, value):
if self.send_error is not None:
raise self.send_error
self.sent.append(value)
self.pending = self.responses.popleft() if self.responses else deque()
def recv(self, _size):
if self.recv_error is not None:
raise self.recv_error
return self.pending.popleft() if self.pending else b""
def close(self):
self.close_calls += 1
self.closed = True
def startup_responses(identity=b"ELM327 v1.5"):
return [
[b"ATI\r\n", identity + b"\r\n>"],
[b"ATE0\r\nOK\r\n>"],
[b"ATL0\r\nOK\r\n>"],
[b"ATH0\r\nOK\r\n>"],
]
def make_session(monkeypatch, responses):
fake = FakeSocket(responses)
monkeypatch.setattr(elm327.socket, "AF_BLUETOOTH", 31, raising=False)
monkeypatch.setattr(elm327.socket, "BTPROTO_RFCOMM", 3, raising=False)
monkeypatch.setattr(elm327.socket, "socket", lambda *args: fake)
return elm327.ELM327Session(ADDRESS), fake
def test_open_uses_rfccomm_channel_one_and_validates_ati(monkeypatch):
session, fake = make_session(monkeypatch, startup_responses())
assert session.open() == "ELM327 v1.5"
assert fake.connected_to == (ADDRESS, 1)
assert fake.sent == [b"ATI\r", b"ATE0\r", b"ATL0\r", b"ATH0\r"]
assert session.adapter_name == "ELM327 v1.5"
def test_open_rejects_empty_or_obviously_rejected_ati(monkeypatch):
for identity in (b"", b"?", b"ERROR"):
session, fake = make_session(monkeypatch, startup_responses(identity))
with pytest.raises(RuntimeError, match="ATI"):
session.open()
assert session.socket is None and fake.closed
def test_command_removes_exact_echo_and_reads_split_prompt_response(monkeypatch):
responses = startup_responses() + [[b"ATR", b"V\r\n12.4V\r", b"\n>"]]
session, fake = make_session(monkeypatch, responses)
session.open()
assert session.command(" ATRV ") == "12.4V"
assert fake.sent[-1] == b"ATRV\r"
def test_malformed_response_bytes_decode_with_replacement(monkeypatch):
responses = startup_responses() + [[b"ATI\r\n\xffOK\r\n>"]]
session, _ = make_session(monkeypatch, responses)
session.open()
assert session.command("ATI") == "OK"
@pytest.mark.parametrize("command", ["", " ", "ATI\r", "ATI\n", "AT\r\nI", "A" * (elm327.MAX_COMMAND_LENGTH + 1)])
def test_command_rejects_invalid_input(monkeypatch, command):
session, _ = make_session(monkeypatch, [])
with pytest.raises(ValueError):
session.command(command)
def test_command_rejects_non_ascii_input(monkeypatch):
session, _ = make_session(monkeypatch, [])
with pytest.raises(ValueError, match="ASCII"):
session.command("ATé")
def test_response_size_limit_closes_session(monkeypatch):
responses = startup_responses() + [[b"x" * (elm327.MAX_RESPONSE_SIZE + 1)]]
session, fake = make_session(monkeypatch, responses)
session.open()
with pytest.raises(RuntimeError, match="64 KiB"):
session.command("ATI")
assert session.socket is None and fake.closed
@pytest.mark.parametrize("error", [TimeoutError("timed out"), OSError("disconnected")])
def test_timeout_or_eof_closes_session(monkeypatch, error):
responses = startup_responses() + [[]]
session, fake = make_session(monkeypatch, responses)
session.open()
fake.recv_error = error if isinstance(error, TimeoutError) else None
if isinstance(error, TimeoutError):
with pytest.raises(RuntimeError, match="transport"):
session.command("ATI")
else:
with pytest.raises(RuntimeError, match="connection closed"):
session.command("ATI")
assert session.socket is None and fake.closed
def test_send_failure_closes_session(monkeypatch):
responses = startup_responses() + [[b"OK>"]]
session, fake = make_session(monkeypatch, responses)
session.open()
fake.send_error = OSError("send failed")
with pytest.raises(RuntimeError, match="transport"):
session.command("ATI")
assert session.socket is None and fake.closed
def test_close_is_idempotent(monkeypatch):
session, fake = make_session(monkeypatch, startup_responses())
session.open()
session.close()
session.close()
assert fake.close_calls == 1
assert session.socket is None
def test_simultaneous_commands_are_serialized(monkeypatch):
class SerializedSocket(FakeSocket):
def __init__(self, responses):
super().__init__(responses)
self.command_started = threading.Event()
self.release_command = threading.Event()
self._command_sends = 0
def sendall(self, value):
super().sendall(value)
self._command_sends += 1
if self._command_sends == 5:
self.command_started.set()
assert self.release_command.wait(timeout=1.0)
fake = SerializedSocket(startup_responses() + [[b"VALUE1>"], [b"VALUE2>"]])
monkeypatch.setattr(elm327.socket, "AF_BLUETOOTH", 31, raising=False)
monkeypatch.setattr(elm327.socket, "BTPROTO_RFCOMM", 3, raising=False)
monkeypatch.setattr(elm327.socket, "socket", lambda *args: fake)
session = elm327.ELM327Session(ADDRESS)
session.open()
results = []
first = threading.Thread(target=lambda: results.append(session.command("ONE")))
second = threading.Thread(target=lambda: results.append(session.command("TWO")))
first.start()
assert fake.command_started.wait(timeout=1.0)
second.start()
assert len(fake.sent) == 5
fake.release_command.set()
first.join(timeout=1.0)
second.join(timeout=1.0)
assert sorted(results) == ["VALUE1", "VALUE2"]
assert len(fake.sent) == 6
def test_read_dtcs_runs_known_setup_and_returns_mode_three_result(monkeypatch):
responses = startup_responses() + [
[b"OK>"], [b"OK>"], [b"OK>"], [b"OK>"],
[b"43 01 33 04 20 00 00>"],
]
session, fake = make_session(monkeypatch, responses)
session.open()
assert session.read_dtcs() == {"codes": ["P0133", "P0420"], "raw": "43 01 33 04 20 00 00"}
assert fake.sent[-5:] == [b"ATE0\r", b"ATL0\r", b"ATH0\r", b"ATSP0\r", b"03\r"]
def test_non_can_dtc_is_decoded_and_padding_ignored():
assert elm327.parse_dtcs("43 01 33 00 00 00 00") == ["P0133"]
def test_can_dtc_count_byte_is_skipped():
assert elm327.parse_dtcs("43 02 01 33 04 20") == ["P0133", "P0420"]
def test_no_data_returns_no_codes():
assert elm327.parse_dtcs("NO DATA") == []
def test_multiple_ecu_lines_are_ordered_and_deduplicated():
raw = "43 01 33 00 00\n43 04 20 00 00\n43 01 33 00 00"
assert elm327.parse_dtcs(raw) == ["P0133", "P0420"]
def test_malformed_can_count_raises_with_raw_response():
raw = "43 02 01 33"
with pytest.raises(elm327.DTCParseError) as error:
elm327.parse_dtcs(raw)
assert error.value.raw == raw
@@ -398,6 +398,116 @@
animation-delay: 0.28s; animation-delay: 0.28s;
} }
.bluetoothElmPanel {
background: var(--secondary-bg);
border: 1px solid rgba(169, 140, 229, 0.45);
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-sm);
padding: 18px;
}
.bluetoothElmHeader,
.bluetoothElmActions,
.bluetoothElmCommand > div {
align-items: center;
display: flex;
gap: 10px;
}
.bluetoothElmHeader {
justify-content: space-between;
gap: 18px;
}
.bluetoothElmHeader h3,
.bluetoothElmHeader p,
.bluetoothElmCodes p,
.bluetoothElmResponse pre,
.bluetoothElmCommand label {
margin: 0;
}
.bluetoothElmHeader p,
.bluetoothElmCodes p {
color: var(--text-muted);
margin-top: 4px;
}
.bluetoothElmHeader strong {
color: #cbb2fa;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bluetoothElmActions {
margin-top: 16px;
}
.bluetoothElmActions button,
.bluetoothElmCommand button {
background: linear-gradient(135deg, #765bb6, #9474ce);
border: 0;
border-radius: var(--border-radius-md);
color: #fff;
cursor: pointer;
font-weight: 700;
padding: 10px 14px;
}
.bluetoothElmActions .bluetoothSecondaryButton {
background: var(--input-bg);
border: 1px solid var(--sidebar-border-color);
color: var(--text-color);
}
.bluetoothElmActions button:disabled,
.bluetoothElmCommand button:disabled,
.bluetoothElmCommand input:disabled {
cursor: not-allowed;
opacity: 0.45;
}
.bluetoothElmCodes,
.bluetoothElmCommand,
.bluetoothElmResponse {
margin-top: 16px;
}
.bluetoothElmCommand label {
color: var(--text-muted);
display: block;
font-size: 0.86rem;
margin-bottom: 6px;
}
.bluetoothElmCommand > div {
align-items: stretch;
}
.bluetoothElmCommand input {
background: var(--input-bg);
border: 1px solid var(--sidebar-border-color);
border-radius: var(--border-radius-md);
color: var(--text-color);
flex: 1;
font: inherit;
min-width: 0;
padding: 10px 12px;
}
.bluetoothElmResponse pre {
background: var(--input-bg);
border: 1px solid var(--sidebar-border-color);
border-radius: var(--border-radius-md);
color: var(--text-color);
margin-top: 6px;
max-height: 220px;
overflow: auto;
padding: 12px;
white-space: pre-wrap;
}
@keyframes bluetoothSpin { @keyframes bluetoothSpin {
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
@@ -20,6 +20,11 @@ const state = reactive({
prompt: null, prompt: null,
audioTestAddress: "", audioTestAddress: "",
audioTestLabel: "", audioTestLabel: "",
elmAddress: "",
elmName: "",
elmAdapter: "",
elmResponse: "",
elmCodes: null,
error: "", error: "",
}) })
@@ -46,7 +51,9 @@ function schedulePoll(delay = pollDelay()) {
pollTimer = setTimeout(async () => { pollTimer = setTimeout(async () => {
pollTimer = null pollTimer = null
try { try {
if (bluetoothPageActive() && document.visibilityState !== "hidden" && state.busy !== "power") { if (state.elmAddress && (document.visibilityState === "hidden" || !bluetoothPageActive())) {
closeElm()
} else if (bluetoothPageActive() && document.visibilityState !== "hidden" && state.busy !== "power") {
await refresh() await refresh()
} }
} finally { } finally {
@@ -97,8 +104,10 @@ async function request(operation, body = {}) {
} }
state.error = "" state.error = ""
await refresh() await refresh()
return payload
} catch (error) { } catch (error) {
state.error = error?.message || "Bluetooth operation failed" state.error = error?.message || "Bluetooth operation failed"
return null
} finally { } finally {
state.busy = "" state.busy = ""
if (operation === "power") state.powerTarget = null if (operation === "power") state.powerTarget = null
@@ -106,6 +115,54 @@ async function request(operation, body = {}) {
} }
} }
function clearElmState() {
state.elmAddress = ""
state.elmName = ""
state.elmAdapter = ""
state.elmResponse = ""
state.elmCodes = null
}
function closeElm() {
const address = state.elmAddress
if (!address) return
clearElmState()
request("elm_close", { address })
}
async function openElm(address) {
const device = state.devices.find((item) => normalizedAddress(item) === String(address || "").toUpperCase())
const payload = await request("elm_open", { address })
if (!payload || !device) return
state.elmAddress = address
state.elmName = device.name || address
state.elmAdapter = String(payload.adapter || "")
state.elmResponse = ""
state.elmCodes = null
}
async function readElmCodes() {
const address = state.elmAddress
if (!address) return
const payload = await request("elm_read_dtcs", { address })
if (!payload || state.elmAddress !== address) return
state.elmCodes = Array.isArray(payload.codes) ? payload.codes.map(String) : []
state.elmResponse = String(payload.raw || "")
}
async function sendElmCommand() {
const address = state.elmAddress
const input = document.getElementById("bluetoothElmCommand")
const command = input?.value.trim() || ""
if (!address || !command) {
state.error = "Enter an ELM327 command."
return
}
const payload = await request("elm_command", { address, value: command })
if (!payload || state.elmAddress !== address) return
state.elmResponse = `${command}\n${String(payload.response || "")}`.trim()
}
async function refreshOnce() { async function refreshOnce() {
const statusUrl = `${galaxyPath("/api/bluetooth/status")}?_=${Date.now()}` const statusUrl = `${galaxyPath("/api/bluetooth/status")}?_=${Date.now()}`
const response = await fetch(statusUrl, { cache: "no-store" }) const response = await fetch(statusUrl, { cache: "no-store" })
@@ -126,6 +183,10 @@ async function refreshOnce() {
devices, devices,
}) })
state.devices = devices state.devices = devices
if (state.elmAddress && (!state.enabled || !state.offroad ||
!devices.some((device) => normalizedAddress(device) === state.elmAddress.toUpperCase() && device.paired))) {
closeElm()
}
if (state.deviceSignature !== deviceSignature) { if (state.deviceSignature !== deviceSignature) {
state.deviceSignature = deviceSignature state.deviceSignature = deviceSignature
state.revision++ state.revision++
@@ -223,7 +284,11 @@ function initialize() {
window.addEventListener("focus", refresh) window.addEventListener("focus", refresh)
window.addEventListener("pageshow", refresh) window.addEventListener("pageshow", refresh)
document.addEventListener("visibilitychange", () => { document.addEventListener("visibilitychange", () => {
if (document.visibilityState !== "hidden" && bluetoothPageActive()) refresh() if (document.visibilityState === "hidden" || !bluetoothPageActive()) {
closeElm()
} else {
refresh()
}
}) })
refresh() refresh()
schedulePoll(0) schedulePoll(0)
@@ -248,6 +313,7 @@ function deviceCapabilities(device) {
const capabilities = [] const capabilities = []
if (device.audio) capabilities.push("Audio") if (device.audio) capabilities.push("Audio")
if (device.controller) capabilities.push("Controller") if (device.controller) capabilities.push("Controller")
if (device.serial) capabilities.push("Serial")
return capabilities.join(" · ") || "Bluetooth device" return capabilities.join(" · ") || "Bluetooth device"
} }
@@ -353,7 +419,14 @@ function renderDeviceActions(device) {
actions.push("<button data-bluetooth-operation=\"pair\" data-address=\"" + address + "\"" + actions.push("<button data-bluetooth-operation=\"pair\" data-address=\"" + address + "\"" +
renderDisabledAttribute(!state.offroad || !!state.busy || pairing) + ">" + (pairing ? "Pairing…" : "Pair") + "</button>") renderDisabledAttribute(!state.offroad || !!state.busy || pairing) + ">" + (pairing ? "Pairing…" : "Pair") + "</button>")
} }
if (device.paired || device.connected) { if (device.paired && device.serial) {
actions.push("<button data-bluetooth-operation=\"elm_open\" data-address=\"" + address + "\"" +
renderDisabledAttribute(!state.offroad || !!state.busy) + ">ELM327</button>")
actions.push("<button class=\"bluetoothIconButton bluetoothForgetButton\" data-bluetooth-operation=\"forget\" data-address=\"" +
address + "\" data-device-name=\"" + name + "\" title=\"Forget device\" aria-label=\"Forget " + name + "\"" +
renderDisabledAttribute(!state.offroad || !!state.busy) + "><i class=\"bi bi-trash3\" aria-hidden=\"true\"></i></button>")
}
if ((device.paired || device.connected) && !device.serial) {
const operation = device.connected ? "disconnect" : "connect" const operation = device.connected ? "disconnect" : "connect"
actions.push("<button data-bluetooth-operation=\"" + operation + "\" data-address=\"" + address + "\"" + actions.push("<button data-bluetooth-operation=\"" + operation + "\" data-address=\"" + address + "\"" +
renderDisabledAttribute(!!state.busy) + ">" + (device.connected ? "Disconnect" : "Connect") + "</button>") renderDisabledAttribute(!!state.busy) + ">" + (device.connected ? "Disconnect" : "Connect") + "</button>")
@@ -407,7 +480,48 @@ function handleDeviceListClick(event) {
const operation = button.dataset.bluetoothOperation const operation = button.dataset.bluetoothOperation
const address = button.dataset.address || "" const address = button.dataset.address || ""
if (operation === "forget" && !window.confirm("Forget " + (button.dataset.deviceName || "this device") + "?")) return if (operation === "forget" && !window.confirm("Forget " + (button.dataset.deviceName || "this device") + "?")) return
request(operation, { address }) if (operation === "elm_open") {
openElm(address)
} else {
request(operation, { address })
}
}
function renderElmPanel() {
if (!state.elmAddress) return ""
return html`
<section class="bluetoothElmPanel">
<div class="bluetoothElmHeader">
<div>
<h3>ELM327</h3>
<p>${() => state.elmName || state.elmAddress}</p>
</div>
<strong>${() => state.elmAdapter || "Connecting…"}</strong>
</div>
<div class="bluetoothElmActions">
<button disabled="${() => !state.offroad || !!state.busy}" @click="${readElmCodes}">Read Codes</button>
<button class="bluetoothSecondaryButton" disabled="${() => !!state.busy}" @click="${closeElm}">Close</button>
</div>
${() => state.elmCodes !== null ? html`
<div class="bluetoothElmCodes">
<strong>Stored Codes</strong>
<p>${() => state.elmCodes.length ? state.elmCodes.join(" · ") : "No stored codes reported."}</p>
</div>
` : ""}
<div class="bluetoothElmCommand">
<label for="bluetoothElmCommand">Command</label>
<div>
<input id="bluetoothElmCommand" type="text" autocomplete="off" placeholder="ATI"
disabled="${() => !state.offroad || !!state.busy}" />
<button disabled="${() => !state.offroad || !!state.busy}" @click="${sendElmCommand}">Send</button>
</div>
</div>
<div class="bluetoothElmResponse">
<strong>Response</strong>
<pre>${() => state.elmResponse || "—"}</pre>
</div>
</section>
`
} }
export function Bluetooth() { export function Bluetooth() {
@@ -440,6 +554,7 @@ export function Bluetooth() {
<span>The test sound is sent at NOW. The audible gap is Bluetooth latency.</span> <span>The test sound is sent at NOW. The audible gap is Bluetooth latency.</span>
</div> </div>
` : ""} ` : ""}
${() => renderElmPanel()}
<div class="bluetoothToolbar"> <div class="bluetoothToolbar">
<button disabled="${() => !state.offroad || !state.enabled || !!state.busy}" <button disabled="${() => !state.offroad || !state.enabled || !!state.busy}"
@@ -342,10 +342,6 @@ ul { list-style: none; margin: 0; padding: 0; }
.gx-appbar__back:active { transform: scale(0.92); } .gx-appbar__back:active { transform: scale(0.92); }
.gx-appbar__title { .gx-appbar__title {
background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
font-size: var(--fs-lg); font-size: var(--fs-lg);
font-weight: var(--fw-bold); font-weight: var(--fw-bold);
white-space: nowrap; white-space: nowrap;
@@ -7,7 +7,7 @@
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Big Dipper"> <meta name="apple-mobile-web-app-title" content="Galaxy">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<meta name="theme-color" content="#8b6cc5" /> <meta name="theme-color" content="#8b6cc5" />
<link rel="manifest" href="/assets/mobile/manifest.json" crossorigin="use-credentials"> <link rel="manifest" href="/assets/mobile/manifest.json" crossorigin="use-credentials">
@@ -26,7 +26,7 @@
} }
</script> </script>
<title>Big Dipper</title> <title>Galaxy</title>
</head> </head>
<body> <body>
@@ -105,38 +105,11 @@ export const api = {
deleteAllRoutes(includePreserved) { return request(`/api/routes/delete_all?include_preserved=${includePreserved}`, { method: "DELETE" }) }, deleteAllRoutes(includePreserved) { return request(`/api/routes/delete_all?include_preserved=${includePreserved}`, { method: "DELETE" }) },
getRouteLogs(name) { return request(`/api/routes/${encodeURIComponent(name)}/logs`) }, getRouteLogs(name) { return request(`/api/routes/${encodeURIComponent(name)}/logs`) },
async screenRecordingsStream({ onProgress, onRecordings, signal } = {}) { getScreenRecordings() { return request("/api/screen_recordings/list") },
const res = await fetch("/api/screen_recordings/list", { signal })
if (!res.ok || !res.body) throw new Error(`Screen recordings request failed (${res.status})`)
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ""
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const events = buffer.split(/\r?\n\r?\n/)
buffer = events.pop() || ""
for (const event of events) {
const lines = event.split(/\r?\n/).filter((l) => l.startsWith("data:"))
if (!lines.length) continue
try {
const payload = JSON.parse(lines.map((l) => l.slice(5).trimStart()).join("\n"))
if (Number.isFinite(payload.progress)) onProgress?.(payload.progress)
onRecordings?.(Array.isArray(payload.recordings) ? payload.recordings : [])
} catch (e) { }
}
}
},
screenRecordingVideoUrl(filename) { return `/api/screen_recordings/download/${encodeURIComponent(filename)}` },
deleteScreenRecording(filename) { return request(`/api/screen_recordings/delete/${encodeURIComponent(filename)}`, { method: "DELETE" }) }, deleteScreenRecording(filename) { return request(`/api/screen_recordings/delete/${encodeURIComponent(filename)}`, { method: "DELETE" }) },
deleteAllScreenRecordings() { return request("/api/screen_recordings/delete_all", { method: "DELETE" }) }, deleteAllScreenRecordings() { return request("/api/screen_recordings/delete_all", { method: "DELETE" }) },
renameScreenRecording(oldName, newName) { return request("/api/screen_recordings/rename", { method: "POST", data: { old: oldName, new: newName } }) }, renameScreenRecording(oldName, newName) { return request("/api/screen_recordings/rename", { method: "POST", data: { old: oldName, new: newName } }) },
getModelLab() { return request("/api/model-laboratory", { cache: "no-store" }) },
saveModelLab(config) { return request("/api/model-laboratory", { method: "PUT", data: config }) },
prepareModelLabArtifact(model) { return request("/api/model-laboratory/download", { method: "POST", data: { model } }) },
getErrorLogs() { return request("/api/error_logs", { headers: { Accept: "application/json" } }) }, getErrorLogs() { return request("/api/error_logs", { headers: { Accept: "application/json" } }) },
getErrorLog(filename) { return fetch(`/api/error_logs/${encodeURIComponent(filename)}`).then((r) => r.text()) }, getErrorLog(filename) { return fetch(`/api/error_logs/${encodeURIComponent(filename)}`).then((r) => r.text()) },
deleteErrorLog(filename) { return delOk(`/api/error_logs/${encodeURIComponent(filename)}`) }, deleteErrorLog(filename) { return delOk(`/api/error_logs/${encodeURIComponent(filename)}`) },
@@ -18,7 +18,6 @@ import { ModelManager } from "./views/ModelManager.js"
import { Plots } from "./views/Plots.js" import { Plots } from "./views/Plots.js"
import { TestingGround } from "./views/TestingGround.js" import { TestingGround } from "./views/TestingGround.js"
import { ThemeMaker } from "./views/ThemeMaker.js" import { ThemeMaker } from "./views/ThemeMaker.js"
import { ModelLaboratory } from "./views/ModelLaboratory.js"
import { Cameras } from "./views/Cameras.js" import { Cameras } from "./views/Cameras.js"
import { store, initRouter, navigate } from "./store.js" import { store, initRouter, navigate } from "./store.js"
import { showSnackbar } from "./api.js" import { showSnackbar } from "./api.js"
@@ -53,7 +52,6 @@ const VIEWS = {
"/plots": Plots, "/plots": Plots,
"/testing_ground": TestingGround, "/testing_ground": TestingGround,
"/theme_maker": ThemeMaker, "/theme_maker": ThemeMaker,
"/model_laboratory": ModelLaboratory,
"/cameras": Cameras, "/cameras": Cameras,
} }
@@ -102,7 +102,7 @@ export const AppShell = {
<button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true"> <button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true">
<i class="bi bi-list"></i> <i class="bi bi-list"></i>
</button> </button>
<span class="gx-appbar__title">Big Dipper</span> <span class="gx-appbar__title">Galaxy</span>
<div class="gx-searchwrap"> <div class="gx-searchwrap">
<input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..." <input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..."
v-model="search" aria-label="Search toggles" /> v-model="search" aria-label="Search toggles" />
@@ -128,8 +128,8 @@ export const AppShell = {
</transition> </transition>
<aside class="gx-drawer" :class="{ open: store.drawerOpen }"> <aside class="gx-drawer" :class="{ open: store.drawerOpen }">
<div class="gx-drawer__header"> <div class="gx-drawer__header">
<img class="gx-logo" src="/assets/images/main_logo.png" alt="Big Dipper logo" /> <img class="gx-logo" src="/assets/images/main_logo.png" alt="Galaxy logo" />
<span class="gx-drawer-title">Big Dipper</span> <span class="gx-drawer-title">Galaxy</span>
</div> </div>
<div class="gx-nav-section"> <div class="gx-nav-section">
<div class="gx-nav-section__title">Main</div> <div class="gx-nav-section__title">Main</div>

Some files were not shown because too many files have changed in this diff Show More