Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bridge/bridge_ios.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ static void carStateToBridge(const CarState& cs, BridgeCarState* out) {
out->tpmsBattFR = cs.tpmsBattFR;
out->tpmsBattRL = cs.tpmsBattRL;
out->tpmsBattRR = cs.tpmsBattRR;
out->doorOpenFL = cs.doorOpenFL;
out->doorOpenFR = cs.doorOpenFR;
out->doorOpenRL = cs.doorOpenRL;
out->doorOpenRR = cs.doorOpenRR;
out->trunkOpen = cs.trunkOpen;
out->trunkPosition = cs.trunkPosition;
out->frunkOpen = cs.frunkOpen;
}

static std::atomic<bool> receiveLoopRunning{false};
Expand Down
9 changes: 9 additions & 0 deletions bridge/bridge_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ typedef struct {
double tpmsBattFR;
double tpmsBattRL;
double tpmsBattRR;

// Closures: doors/trunk 0/1 (1 = latch not closed), trunk angle in degrees.
double doorOpenFL;
double doorOpenFR;
double doorOpenRL;
double doorOpenRR;
double trunkOpen;
double trunkPosition;
double frunkOpen;
} BridgeCarState;

// Context
Expand Down
26 changes: 25 additions & 1 deletion bridge/car/car_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ struct CarState {
double tpmsBattRL = -1;
double tpmsBattRR = -1;

// Closures from VCLEFT_doorStatus / VCRIGHT_doorStatus latch state: 1 while
// the latch is anything but closed (opened, opening, ajar, closing), else 0.
double doorOpenFL = 0;
double doorOpenFR = 0;
double doorOpenRL = 0;
double doorOpenRR = 0;
double trunkOpen = 0;
// Powered liftgate angle in degrees from VCLEFT_liftgateStatus (mux 1);
// 0 when closed or when the car has no powered liftgate.
double trunkPosition = 0;
// Frunk latch from VCFRONT_status (mux 0), same rule as the doors; it has no position.
double frunkOpen = 0;

// Vehicle VIN, empty until fully assembled by the mapper.
static constexpr size_t VIN_LENGTH = 17;
char vin[VIN_LENGTH + 1] = {};
Expand All @@ -69,7 +82,9 @@ struct CarState {
static constexpr size_t VIN_OFFSET = 36;
static constexpr size_t TPMS_OFFSET = VIN_OFFSET + VIN_DOUBLE_COUNT;
static constexpr size_t TPMS_COUNT = 12;
static constexpr size_t FIELD_COUNT = TPMS_OFFSET + TPMS_COUNT;
static constexpr size_t DOOR_OFFSET = TPMS_OFFSET + TPMS_COUNT;
static constexpr size_t DOOR_COUNT = 7;
static constexpr size_t FIELD_COUNT = DOOR_OFFSET + DOOR_COUNT;

void toArray(double* out) const {
// Party bus
Expand Down Expand Up @@ -134,5 +149,14 @@ struct CarState {
out[TPMS_OFFSET + 9] = tpmsBattFR;
out[TPMS_OFFSET + 10] = tpmsBattRL;
out[TPMS_OFFSET + 11] = tpmsBattRR;

// Closures (after TPMS): doors FL FR RL RR, trunk open, trunk position, frunk open.
out[DOOR_OFFSET + 0] = doorOpenFL;
out[DOOR_OFFSET + 1] = doorOpenFR;
out[DOOR_OFFSET + 2] = doorOpenRL;
out[DOOR_OFFSET + 3] = doorOpenRR;
out[DOOR_OFFSET + 4] = trunkOpen;
out[DOOR_OFFSET + 5] = trunkPosition;
out[DOOR_OFFSET + 6] = frunkOpen;
}
};
29 changes: 29 additions & 0 deletions bridge/car/cars/tesla.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,33 @@ static inline double powerLevel(const CarState& cs) {
return std::clamp(kw / limit, -1.0, 1.0) * 100.0;
}

// VCLEFT/VCRIGHT_*LatchStatus: 0 SNA, 1 OPENED, 2 CLOSED, 3 CLOSING, 4 OPENING,
// 5 AJAR, 6 TIMEOUT, 7 DEFAULT, 8 FAULT. Open until the latch reports closed;
// unknown/fault states read as closed so a stale bus doesn't light a warning.
static inline double latchOpen(double latchStatus) {
int s = static_cast<int>(latchStatus);
return (s == 1 || s == 3 || s == 4 || s == 5) ? 1.0 : 0.0;
}

// Per-door, trunk and frunk latch state plus the powered-liftgate angle, from
// the vehicle bus (DashKit forwards 0x102, 0x103, 0x142 and 0x2E1).
static inline void updateClosures(const CANParsers& cp, CarState& cs) {
cs.doorOpenFL = latchOpen(cp.get(1, "VCLEFT_doorStatus", "VCLEFT_frontLatchStatus"));
cs.doorOpenRL = latchOpen(cp.get(1, "VCLEFT_doorStatus", "VCLEFT_rearLatchStatus"));
cs.doorOpenFR = latchOpen(cp.get(1, "VCRIGHT_doorStatus", "VCRIGHT_frontLatchStatus"));
cs.doorOpenRR = latchOpen(cp.get(1, "VCRIGHT_doorStatus", "VCRIGHT_rearLatchStatus"));
cs.trunkOpen = latchOpen(cp.get(1, "VCRIGHT_doorStatus", "VCRIGHT_trunkLatchStatus"));
// VCLEFT_liftgateStatus is multiplexed: the angle lives on mux 1. Keep the
// last angle across mux-0 frames. Closed reads slightly negative on some cars.
if (static_cast<int>(cp.get(1, "VCLEFT_liftgateStatus", "VCLEFT_liftgateStatusIndex")) == 1) {
cs.trunkPosition = std::max(0.0, cp.get(1, "VCLEFT_liftgateStatus", "VCLEFT_liftgatePosition"));
}
// VCFRONT_status is multiplexed: the frunk latch lives on mux 0.
if (static_cast<int>(cp.get(1, "VCFRONT_status", "VCFRONT_statusIndex")) == 0) {
cs.frunkOpen = latchOpen(cp.get(1, "VCFRONT_status", "VCFRONT_frunkLatchStatus"));
}
}

static inline void updateVehicleBus(const CANParsers& cp, CarState& cs) {
// Vehicle bus specific signals
// BMS_energyStatus is multiplexed: mux 0 has pack/remaining energy, mux 1 has energyBuffer
Expand Down Expand Up @@ -175,6 +202,8 @@ class TeslaDashKitMapper : public CarStateMapper {
cs.buckleStatus = cp.get(1, "VCLEFT_switchStatus", "VCLEFT_frontBuckleSwitch") == 2.0 ? 1.0 : 0.0;
}

updateClosures(cp, cs);

// Climate setpoint temperature (degC) from the vehicle bus.
cs.acTemp = cp.get(1, "UI_hvacRequest", "UI_hvacReqTempSetpointLeft");
cs.acTempRight = cp.get(1, "UI_hvacRequest", "UI_hvacReqTempSetpointRight");
Expand Down
4 changes: 4 additions & 0 deletions dash-apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ Values not received yet are `0` / `false`. For speed limit, stop line and energy
| `egoSteeringAngle` | degrees | steering wheel angle, negative = left |
| `leftBlinker`, `rightBlinker` | 0–2 | 0 off, 1 blinking (lamp dark), 2 blinking (lamp lit) |
| `anyDoorOpen` | 0/1 | |
| `doorOpenFL`, `doorOpenFR`, `doorOpenRL`, `doorOpenRR` | 0/1 | per door, 1 until the latch reports closed. **DashKit only** |
| `trunkOpen` | 0/1 | rear trunk latch. **DashKit only** |
| `trunkPosition` | degrees | powered liftgate angle, 0 when closed or not powered. **DashKit only** |
| `frunkOpen` | 0/1 | frunk latch, same rule as the doors (no position). **DashKit only** |
| `buckleStatus` | 0/1 | 1 = driver buckled |
| `odometer` | number | km, or miles when `isImperial` |

Expand Down
7 changes: 7 additions & 0 deletions dash-apps/web-compose/core/host.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
stopLineDist: "getStopLineDist",
trafficLightColor: "getTrafficLightColor",
anyDoorOpen: "getAnyDoorOpen",
doorOpenFL: "getDoorOpenFL",
doorOpenFR: "getDoorOpenFR",
doorOpenRL: "getDoorOpenRL",
doorOpenRR: "getDoorOpenRR",
trunkOpen: "getTrunkOpen",
trunkPosition: "getTrunkPosition",
frunkOpen: "getFrunkOpen",
buckleStatus: "getBuckleStatus",
accSetSpeed: "getAccSetSpeed",
fullPackEnergy: "getFullPackEnergy",
Expand Down
21 changes: 21 additions & 0 deletions dash-apps/web-compose/core/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
"signal.leftBlindSpot": "Left blind spot",
"signal.rightBlindSpot": "Right blind spot",
"signal.anyDoorOpen": "Door open",
"signal.doorOpenFL": "Driver door",
"signal.doorOpenFR": "Passenger door",
"signal.doorOpenRL": "Rear left door",
"signal.doorOpenRR": "Rear right door",
"signal.trunkOpen": "Trunk open",
"signal.trunkPosition": "Trunk angle",
"signal.frunkOpen": "Frunk open",
"signal.seatbeltUnbuckled": "Seatbelt",
"widget.unknown": "Unavailable widget",
"editor.title": "Edit dashboard",
Expand Down Expand Up @@ -100,6 +107,13 @@
"signal.leftBlindSpot": "Bal holttér",
"signal.rightBlindSpot": "Jobb holttér",
"signal.anyDoorOpen": "Nyitott ajtó",
"signal.doorOpenFL": "Vezetőajtó",
"signal.doorOpenFR": "Utasajtó",
"signal.doorOpenRL": "Bal hátsó ajtó",
"signal.doorOpenRR": "Jobb hátsó ajtó",
"signal.trunkOpen": "Csomagtartó nyitva",
"signal.trunkPosition": "Csomagtartó szöge",
"signal.frunkOpen": "Frunk nyitva",
"signal.seatbeltUnbuckled": "Biztonsági öv",
"widget.unknown": "Nem elérhető elem",
"editor.title": "Műszerfal szerkesztése",
Expand Down Expand Up @@ -161,6 +175,13 @@
"signal.leftBlindSpot": "Toter Winkel links",
"signal.rightBlindSpot": "Toter Winkel rechts",
"signal.anyDoorOpen": "Tür offen",
"signal.doorOpenFL": "Fahrertür",
"signal.doorOpenFR": "Beifahrertür",
"signal.doorOpenRL": "Tür hinten links",
"signal.doorOpenRR": "Tür hinten rechts",
"signal.trunkOpen": "Kofferraum offen",
"signal.trunkPosition": "Kofferraumwinkel",
"signal.frunkOpen": "Frunk offen",
"signal.seatbeltUnbuckled": "Sicherheitsgurt",
"widget.unknown": "Element nicht verfügbar",
"editor.title": "Dashboard bearbeiten",
Expand Down
8 changes: 8 additions & 0 deletions dash-apps/web-compose/core/signals.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@
read: (r) => num(r.maxDischargePower) || undefined },
{ id: "odometer", kind: "number", unit: "dist", precision: 0, category: "vehicle", bus: "vehicle",
read: (r) => num(r.odometer) || undefined },
{ id: "trunkPosition", kind: "number", unit: "deg", precision: 0, range: () => [0, 95], category: "vehicle", bus: "vehicle",
read: (r) => num(r.trunkPosition) },

// surroundings
{ id: "stopLineDist", kind: "number", unit: "meter", precision: 0, range: () => [0, 200], category: "assist", bus: "any",
Expand All @@ -116,6 +118,12 @@
{ id: "leftBlindSpot", kind: "bool", unit: "none", category: "signals", bus: "any", read: (r) => flag(r.leftBlindSpot) },
{ id: "rightBlindSpot", kind: "bool", unit: "none", category: "signals", bus: "any", read: (r) => flag(r.rightBlindSpot) },
{ id: "anyDoorOpen", kind: "bool", unit: "none", category: "warnings", bus: "any", read: (r) => flag(r.anyDoorOpen) },
{ id: "doorOpenFL", kind: "bool", unit: "none", category: "warnings", bus: "vehicle", read: (r) => flag(r.doorOpenFL) },
{ id: "doorOpenFR", kind: "bool", unit: "none", category: "warnings", bus: "vehicle", read: (r) => flag(r.doorOpenFR) },
{ id: "doorOpenRL", kind: "bool", unit: "none", category: "warnings", bus: "vehicle", read: (r) => flag(r.doorOpenRL) },
{ id: "doorOpenRR", kind: "bool", unit: "none", category: "warnings", bus: "vehicle", read: (r) => flag(r.doorOpenRR) },
{ id: "trunkOpen", kind: "bool", unit: "none", category: "warnings", bus: "vehicle", read: (r) => flag(r.trunkOpen) },
{ id: "frunkOpen", kind: "bool", unit: "none", category: "warnings", bus: "vehicle", read: (r) => flag(r.frunkOpen) },
{ id: "seatbeltUnbuckled", kind: "bool", unit: "none", category: "warnings", bus: "any",
read: (r) => (r.buckleStatus === undefined ? undefined : Number(r.buckleStatus) === 0) },
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ object VehicleControl {
const val CMD_AC_SWING_INTENSITY: Int = 0x4D
const val CMD_AC_SWING_TOGGLE: Int = 0x4E

// --- Door unlatch (VCSEC_requests 0x1F9); value = door index ---
const val CMD_DOOR_UNLATCH: Int = 0x50
const val DOOR_FRONT_LEFT: Int = 0
const val DOOR_FRONT_RIGHT: Int = 1
const val DOOR_REAR_LEFT: Int = 2
const val DOOR_REAR_RIGHT: Int = 3

/** Bind (or clear, with actionValue 0) an N-finger tap to a control action. */
fun sendFingerAction(manager: DashKitBleManager, fingers: Int, actionValue: Int): Boolean =
send(manager, CMD_MULTI_FINGER_ACTION, (fingers shl 8) or (actionValue and 0xFF))
Expand All @@ -119,6 +126,10 @@ object VehicleControl {
fun sendSportKickdownThreshold(manager: DashKitBleManager, percent: Int): Boolean =
send(manager, CMD_SPORT_KICKDOWN_THRESHOLD, percent)

/** Pop one door latch (firmware refuses it in R/N/D or while moving). */
fun sendDoorUnlatch(manager: DashKitBleManager, door: Int): Boolean =
send(manager, CMD_DOOR_UNLATCH, door)

fun sendAcSwingToggle(manager: DashKitBleManager): Boolean =
send(manager, CMD_AC_SWING_TOGGLE, 0)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ data class CarState(
val tpmsBattRL: Float = -1f,
val tpmsBattRR: Float = -1f,

// Closures: 1 while the door/trunk latch is not closed, else 0. Trunk angle
// in degrees from the powered liftgate (0 when closed or not powered).
val doorOpenFL: Float = 0f,
val doorOpenFR: Float = 0f,
val doorOpenRL: Float = 0f,
val doorOpenRR: Float = 0f,
val trunkOpen: Float = 0f,
val trunkPosition: Float = 0f,
val frunkOpen: Float = 0f,

// Empty until fully assembled from the CAN stream by the native mapper.
val vin: String = ""
) {
Expand All @@ -70,7 +80,7 @@ data class CarState(
)

companion object {
const val FIELD_COUNT = 51
const val FIELD_COUNT = 58
private const val KM_TO_MILES = 0.621371f

private val MILES_COUNTRIES = setOf("US", "GB", "MM", "LR")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ fun DashState.dashValues(systemDarkMode: Boolean? = null): Map<String, Any> {
"leftBlinker" to c.leftBlinker,
"rightBlinker" to c.rightBlinker,
"anyDoorOpen" to c.anyDoorOpen,
"doorOpenFL" to c.doorOpenFL,
"doorOpenFR" to c.doorOpenFR,
"doorOpenRL" to c.doorOpenRL,
"doorOpenRR" to c.doorOpenRR,
"trunkOpen" to c.trunkOpen,
"trunkPosition" to c.trunkPosition,
"frunkOpen" to c.frunkOpen,
"buckleStatus" to c.buckleStatus,
"odometer" to c.odometer,
"adasOn" to c.adasOn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class CarStateBridge(
@Volatile private var trafficLightColor: Float = 0f
@Volatile private var buckleStatus: Float = 0f
@Volatile private var anyDoorOpen: Float = 0f
@Volatile private var doorOpenFL: Float = 0f
@Volatile private var doorOpenFR: Float = 0f
@Volatile private var doorOpenRL: Float = 0f
@Volatile private var doorOpenRR: Float = 0f
@Volatile private var trunkOpen: Float = 0f
@Volatile private var trunkPosition: Float = 0f
@Volatile private var frunkOpen: Float = 0f
@Volatile private var accSetSpeed: Float = 0f
@Volatile private var fullPackEnergy: Float = 0f
@Volatile private var nominalEnergyRemaining: Float = 0f
Expand Down Expand Up @@ -79,6 +86,13 @@ class CarStateBridge(
trafficLightColor = state.trafficLightColor
buckleStatus = state.buckleStatus
anyDoorOpen = state.anyDoorOpen
doorOpenFL = state.doorOpenFL
doorOpenFR = state.doorOpenFR
doorOpenRL = state.doorOpenRL
doorOpenRR = state.doorOpenRR
trunkOpen = state.trunkOpen
trunkPosition = state.trunkPosition
frunkOpen = state.frunkOpen
accSetSpeed = state.accSetSpeed
fullPackEnergy = state.fullPackEnergy
nominalEnergyRemaining = state.nominalEnergyRemaining
Expand Down Expand Up @@ -121,6 +135,13 @@ class CarStateBridge(
@JavascriptInterface fun getTrafficLightColor(): Float = trafficLightColor
@JavascriptInterface fun getBuckleStatus(): Float = buckleStatus
@JavascriptInterface fun getAnyDoorOpen(): Float = anyDoorOpen
@JavascriptInterface fun getDoorOpenFL(): Float = doorOpenFL
@JavascriptInterface fun getDoorOpenFR(): Float = doorOpenFR
@JavascriptInterface fun getDoorOpenRL(): Float = doorOpenRL
@JavascriptInterface fun getDoorOpenRR(): Float = doorOpenRR
@JavascriptInterface fun getTrunkOpen(): Float = trunkOpen
@JavascriptInterface fun getTrunkPosition(): Float = trunkPosition
@JavascriptInterface fun getFrunkOpen(): Float = frunkOpen
@JavascriptInterface fun getAccSetSpeed(): Float = accSetSpeed
@JavascriptInterface fun getFullPackEnergy(): Float = fullPackEnergy
@JavascriptInterface fun getNominalEnergyRemaining(): Float = nominalEnergyRemaining
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.material.icons.rounded.SouthEast
import androidx.compose.material.icons.rounded.EvStation
import androidx.compose.material.icons.rounded.Flip
import androidx.compose.material.icons.rounded.Inbox
import androidx.compose.material.icons.rounded.MeetingRoom
import androidx.compose.material.icons.rounded.PushPin
import androidx.compose.material.icons.rounded.SwapHoriz
import androidx.compose.material.icons.rounded.Thermostat
Expand Down Expand Up @@ -175,6 +176,48 @@ val vehicleControls: List<ControlAction> = listOf(
manager?.let { VehicleControl.sendMirrorDipToggle(it) }
}
),
// Door unlatch: a momentary latch release per door, like the Tesla app's
// "Unlatch Door". The firmware refuses it in R/N/D or while moving.
ControlAction(
id = "unlatch_front_left",
icon = Icons.Rounded.MeetingRoom,
labelRes = { R.string.control_unlatch_front_left },
active = { false },
gestureValue = 10,
perform = { manager ->
manager?.let { VehicleControl.sendDoorUnlatch(it, VehicleControl.DOOR_FRONT_LEFT) }
}
),
ControlAction(
id = "unlatch_front_right",
icon = Icons.Rounded.MeetingRoom,
labelRes = { R.string.control_unlatch_front_right },
active = { false },
gestureValue = 11,
perform = { manager ->
manager?.let { VehicleControl.sendDoorUnlatch(it, VehicleControl.DOOR_FRONT_RIGHT) }
}
),
ControlAction(
id = "unlatch_rear_left",
icon = Icons.Rounded.MeetingRoom,
labelRes = { R.string.control_unlatch_rear_left },
active = { false },
gestureValue = 12,
perform = { manager ->
manager?.let { VehicleControl.sendDoorUnlatch(it, VehicleControl.DOOR_REAR_LEFT) }
}
),
ControlAction(
id = "unlatch_rear_right",
icon = Icons.Rounded.MeetingRoom,
labelRes = { R.string.control_unlatch_rear_right },
active = { false },
gestureValue = 13,
perform = { manager ->
manager?.let { VehicleControl.sendDoorUnlatch(it, VehicleControl.DOOR_REAR_RIGHT) }
}
),
// Glovebox is an electronic latch release: open only (closed by hand).
ControlAction(
id = "glovebox",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@ class CanFrameDecoder(
tpmsBattFL = values[47].toFloat(),
tpmsBattFR = values[48].toFloat(),
tpmsBattRL = values[49].toFloat(),
tpmsBattRR = values[50].toFloat()
tpmsBattRR = values[50].toFloat(),
// Closures after TPMS (see CarState::DOOR_OFFSET): doors FL FR RL RR, trunk open, trunk angle, frunk open.
doorOpenFL = values[51].toFloat(),
doorOpenFR = values[52].toFloat(),
doorOpenRL = values[53].toFloat(),
doorOpenRR = values[54].toFloat(),
trunkOpen = values[55].toFloat(),
trunkPosition = values[56].toFloat(),
frunkOpen = values[57].toFloat()
)
}

Expand Down
4 changes: 4 additions & 0 deletions dashpilot-android/app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@
<string name="control_mirror_dip">Spiegelabsenkung umschalten</string>
<string name="control_ac_swing">Klima-Schwenkmodus umschalten</string>
<string name="control_glovebox">Handschuhfach öffnen</string>
<string name="control_unlatch_front_left">Fahrertür entriegeln</string>
<string name="control_unlatch_front_right">Beifahrertür entriegeln</string>
<string name="control_unlatch_rear_left">Tür hinten links entriegeln</string>
<string name="control_unlatch_rear_right">Tür hinten rechts entriegeln</string>

<!-- Automations -->
<string name="automations_title">Automatisierungen</string>
Expand Down
Loading
Loading