diff --git a/bridge/bridge_ios.cpp b/bridge/bridge_ios.cpp index 5ef116b5..3427fce0 100644 --- a/bridge/bridge_ios.cpp +++ b/bridge/bridge_ios.cpp @@ -62,6 +62,9 @@ static void carStateToBridge(const CarState& cs, BridgeCarState* out) { out->trunkOpen = cs.trunkOpen; out->trunkPosition = cs.trunkPosition; out->frunkOpen = cs.frunkOpen; + out->ambientLightingAvailable = cs.ambientLightingAvailable; + out->ambientLightingColor = static_cast(cs.ambientLightingColor); + out->ambientLightingBrightness = cs.ambientLightingBrightness; } static std::atomic receiveLoopRunning{false}; diff --git a/bridge/bridge_ios.h b/bridge/bridge_ios.h index c12882a2..4fba705b 100644 --- a/bridge/bridge_ios.h +++ b/bridge/bridge_ios.h @@ -73,6 +73,10 @@ typedef struct { double trunkOpen; double trunkPosition; double frunkOpen; + + double ambientLightingAvailable; + uint32_t ambientLightingColor; + double ambientLightingBrightness; } BridgeCarState; // Context diff --git a/bridge/car/can_parsers.h b/bridge/car/can_parsers.h index 60dfc2b0..0bf96d6e 100644 --- a/bridge/car/can_parsers.h +++ b/bridge/car/can_parsers.h @@ -60,7 +60,8 @@ class CANParsers { // Unscaled signal bits; false if the signal is unknown, no frame has been // seen, or the frame's multiplexor doesn't select this signal. - bool getRaw(int bus, const std::string& msgName, const std::string& sigName, uint64_t* out) const { + bool getRaw(int bus, const std::string& msgName, const std::string& sigName, uint64_t* out, + size_t minLength = 0) const { int maskedBus = bus & 3; auto busIt = busSignalCache_.find(maskedBus); if (busIt == busSignalCache_.end()) return false; @@ -70,7 +71,7 @@ class CANParsers { const auto& cached = it->second; auto frameIt = latestFrames_.find(frameKey(maskedBus, cached.address)); - if (frameIt == latestFrames_.end()) return false; + if (frameIt == latestFrames_.end() || frameIt->second.size() < minLength) return false; return cached.signal->getRawBits(frameIt->second.data(), frameIt->second.size(), out); } diff --git a/bridge/car/car_state.h b/bridge/car/car_state.h index 32f0ce5f..e6714568 100644 --- a/bridge/car/car_state.h +++ b/bridge/car/car_state.h @@ -84,7 +84,12 @@ struct CarState { static constexpr size_t TPMS_COUNT = 12; 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; + // Append fields after closures to preserve all existing array indices. + double ambientLightingAvailable = 0; + double ambientLightingColor = 0; // Packed RGB (0xRRGGBB). + double ambientLightingBrightness = 0; + static constexpr size_t AMBIENT_OFFSET = DOOR_OFFSET + DOOR_COUNT; + static constexpr size_t FIELD_COUNT = AMBIENT_OFFSET + 3; void toArray(double* out) const { // Party bus @@ -158,5 +163,9 @@ struct CarState { out[DOOR_OFFSET + 4] = trunkOpen; out[DOOR_OFFSET + 5] = trunkPosition; out[DOOR_OFFSET + 6] = frunkOpen; + + out[AMBIENT_OFFSET + 0] = ambientLightingAvailable; + out[AMBIENT_OFFSET + 1] = ambientLightingColor; + out[AMBIENT_OFFSET + 2] = ambientLightingBrightness; } }; diff --git a/bridge/car/cars/tesla.h b/bridge/car/cars/tesla.h index 6b8fc596..0444516c 100644 --- a/bridge/car/cars/tesla.h +++ b/bridge/car/cars/tesla.h @@ -138,6 +138,17 @@ static inline void updateVehicleBus(const CANParsers& cp, CarState& cs) { cs.packTMax = cp.get(1, "BMS_bmbMinMax", "BMS_thermistorTMax"); } cs.odometer = cp.get(1, "DI_odometerStatus", "DI_odometer"); + + constexpr auto ambient = "UI_ambientLightingCtrls"; + uint64_t brightness = 0; + if (cp.getRaw(1, ambient, "UI_ambientLightingBrightness", &brightness, 7)) { + const auto red = static_cast(cp.get(1, ambient, "UI_ambientLightingRed")); + const auto green = static_cast(cp.get(1, ambient, "UI_ambientLightingGreen")); + const auto blue = static_cast(cp.get(1, ambient, "UI_ambientLightingBlue")); + cs.ambientLightingColor = (red << 16) | (green << 8) | blue; + cs.ambientLightingBrightness = brightness; + cs.ambientLightingAvailable = 1; + } } @@ -291,4 +302,4 @@ class TeslaJuniperMapper : public CarStateMapper { BlinkerHold leftBlinkerHold_; BlinkerHold rightBlinkerHold_; TeslaVinUpdater vin_; -}; \ No newline at end of file +}; diff --git a/dash-apps/README.md b/dash-apps/README.md index 367d908b..c89cb2cb 100644 --- a/dash-apps/README.md +++ b/dash-apps/README.md @@ -121,6 +121,18 @@ The app plays the file's default state machine and resizes the artboard to the s To try your own `.riv` on a phone, go to Settings → Developer → **Load Rive File**. +### Tesla interior ambient lighting + +DashKit dashboards can bind these optional Rive properties: + +| Property | Type | Value | +|---|---|---| +| `ambientLightingColor` | Color | Opaque interior accent color | +| `ambientLightingBrightness` | Number | Brightness percent, independently of color | +| `ambientLightingAvailable` | Boolean | Whether the app has received a valid lighting frame | + +Use availability to select a fallback accent. Color and brightness come from the latest valid frame. + ## Saving settings Web apps get one string of storage, usually JSON. diff --git a/dashpilot-android/app/src/main/assets/vehicles/tesla/bus_1_tesla_vehicle.dbc b/dashpilot-android/app/src/main/assets/vehicles/tesla/bus_1_tesla_vehicle.dbc index 958cb5ef..ab7be151 100644 --- a/dashpilot-android/app/src/main/assets/vehicles/tesla/bus_1_tesla_vehicle.dbc +++ b/dashpilot-android/app/src/main/assets/vehicles/tesla/bus_1_tesla_vehicle.dbc @@ -9141,6 +9141,12 @@ BO_ 505 VCSEC_requests: 1 VEH SG_ VCSEC_chargePortRequest: 0|2@1+ (1,0) [0|0] "" X SG_ VCSEC_driveAttemptedWithoutAuth: 2|1@1+ (1,0) [0|0] "" X +BO_ 1657 UI_ambientLightingCtrls: 8 VEH + SG_ UI_ambientLightingRed : 8|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingGreen : 16|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBlue : 24|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBrightness : 32|7@1+ (1,0) [0|100] "%" X + CM_ "Generated by Tesla Firmware Decode @ version 923e442d6230241a259eeb3f415525373d40325c"; BA_DEF_ BO_ "GenMsgSendType" ENUM "Cyclic"; diff --git a/dashpilot-android/app/src/main/assets/vehicles/tesla/new_gen_juniper.dbc b/dashpilot-android/app/src/main/assets/vehicles/tesla/new_gen_juniper.dbc index 8ce08557..07ba6fd4 100644 --- a/dashpilot-android/app/src/main/assets/vehicles/tesla/new_gen_juniper.dbc +++ b/dashpilot-android/app/src/main/assets/vehicles/tesla/new_gen_juniper.dbc @@ -521,6 +521,12 @@ BO_ 1029 VIN_info: 8 VEH SG_ VIN_C405 m18 : 8|56@1+ (1,0) [0|7.2057594038E+016] "" X +BO_ 1657 UI_ambientLightingCtrls: 8 VEH + SG_ UI_ambientLightingRed : 8|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingGreen : 16|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBlue : 24|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBrightness : 32|7@1+ (1,0) [0|100] "%" X + CM_ "New-generation Model Y (Juniper), Tesla FW 2026.x. Reverse-engineered from comma route 077b771458fd542f/000000a9--5f9f91a356 (2026-09-22 16:49-17:05) against an annotated action timeline; names and enums cross-checked with the Tesla FW 2026.2 signal dataset. Covers the messages listed in dbc/used_messages.txt plus DI_state (for DashPilot). Node VEH = seen on the vehicle bus (comma bus 1, DashKit bus 1); node CH = seen ONLY on the chassis bus (comma bus 0/2, DashKit bus 0). Each message comment lists the buses it was observed on. Bytesum checksum = (id_lo + id_hi + sum of data bytes except the checksum byte) & 0xFF."; CM_ BO_ 585 "CHANGED. Vehicle bus only, 20 Hz. DLC 3 -> 4. Byte 0 is no longer the Tesla bytesum (0 of 19597 frames match) and no CRC-8 matched: Tesla calls it SCCM_leftStalkCrc and the firmware cannot synthesize this frame until the algorithm is known. Bits 20-31 are a new 12-bit stalk angle. SCCM_washWipeButtonStatus and SCCM_highBeamStalkStatus (bits 12-15) never left 0 in the whole log, including the wiper button press at 16:49:47.9: the wiper button now reports through VCLEFT_switchStatus.VCLEFT_swcWiperButtonState."; CM_ SG_ 585 SCCM_turnIndicatorStalkStatus "Enum changed: right tap (16:53:31) = 2 UP_1, left tap (16:53:34, 16:53:35) = 6 DOWN_1 (legacy DOWN_1 was 7)."; diff --git a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/CarState.kt b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/CarState.kt index 1cddf754..ba5d8c49 100644 --- a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/CarState.kt +++ b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/CarState.kt @@ -68,7 +68,10 @@ data class CarState( val frunkOpen: Float = 0f, // Empty until fully assembled from the CAN stream by the native mapper. - val vin: String = "" + val vin: String = "", + val ambientLightingAvailable: Boolean = false, + val ambientLightingColor: Int = 0, // Packed RGB (0xRRGGBB). + val ambientLightingBrightness: Float = 0f, ) { fun toImperial(): CarState = copy( odometer = odometer * KM_TO_MILES, @@ -80,7 +83,7 @@ data class CarState( ) companion object { - const val FIELD_COUNT = 58 + const val FIELD_COUNT = 61 private const val KM_TO_MILES = 0.621371f private val MILES_COUNTRIES = setOf("US", "GB", "MM", "LR") @@ -88,4 +91,4 @@ data class CarState( fun speedLimitSignsInKm(): Boolean = Locale.getDefault().country !in MILES_COUNTRIES } -} \ No newline at end of file +} diff --git a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/DashValues.kt b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/DashValues.kt index 03404c7f..657810f1 100644 --- a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/DashValues.kt +++ b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/datamodel/dash/DashValues.kt @@ -6,7 +6,9 @@ import java.time.ZoneId fun DashState.dashValues(systemDarkMode: Boolean? = null): Map { val c = carState val d = displaySettings - return mapOf( + val values = mutableMapOf( + "ambientLightingAvailable" to c.ambientLightingAvailable, + "ambientLightingBrightness" to c.ambientLightingBrightness, "egoSpeed" to c.egoSpeed, "gear" to c.gear, "egoSteeringAngle" to c.egoSteeringAngle, @@ -69,6 +71,10 @@ fun DashState.dashValues(systemDarkMode: Boolean? = null): Map { "dataSourceType" to dataSourceType, "speedCameraDistance" to speedCameraDistance.toFloat(), ) + if (c.ambientLightingAvailable) { + values["ambientLightingColor"] = c.ambientLightingColor or 0xFF000000.toInt() + } + return values } private fun secondsSinceLocalMidnight(epochMillis: Long): Float = diff --git a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/ui/RiveDashView.kt b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/ui/RiveDashView.kt index 0012d451..505d53ed 100644 --- a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/ui/RiveDashView.kt +++ b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/ui/RiveDashView.kt @@ -127,6 +127,7 @@ private fun BoundRive( PropertyDataType.NUMBER -> (value as? Float)?.let { vmi.setNumber(name, it) } PropertyDataType.BOOLEAN -> (value as? Boolean)?.let { vmi.setBoolean(name, it) } PropertyDataType.STRING -> vmi.setString(name, value.toString()) + PropertyDataType.COLOR -> (value as? Int)?.let { vmi.setColor(name, it) } else -> {} } } diff --git a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/vehicle/CanFrameDecoder.kt b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/vehicle/CanFrameDecoder.kt index 8fdb6122..87daf9bd 100644 --- a/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/vehicle/CanFrameDecoder.kt +++ b/dashpilot-android/app/src/main/java/com/softwiredtech/dashpilot/vehicle/CanFrameDecoder.kt @@ -86,7 +86,10 @@ class CanFrameDecoder( doorOpenRR = values[54].toFloat(), trunkOpen = values[55].toFloat(), trunkPosition = values[56].toFloat(), - frunkOpen = values[57].toFloat() + frunkOpen = values[57].toFloat(), + ambientLightingAvailable = values[58] > 0, + ambientLightingColor = values[59].toInt(), + ambientLightingBrightness = values[60].toFloat(), ) } diff --git a/dashpilot-android/bridge/CMakeLists.txt b/dashpilot-android/bridge/CMakeLists.txt index 9258d09c..baf75aac 100644 --- a/dashpilot-android/bridge/CMakeLists.txt +++ b/dashpilot-android/bridge/CMakeLists.txt @@ -94,4 +94,16 @@ add_executable(tesla_closure_mapper_test target_include_directories(tesla_closure_mapper_test PRIVATE ${MSGQ_ROOT}) target_compile_options(tesla_closure_mapper_test PRIVATE -Wall -Wextra) add_test(NAME tesla_closure_mapper COMMAND tesla_closure_mapper_test) + +add_executable(tesla_ambient_lighting_test + tests/tesla_ambient_lighting_test.cpp + ${MSGQ_ROOT}/dbc/dbc.cc + ${MSGQ_ROOT}/dbc/dbcfile.cc +) +target_include_directories(tesla_ambient_lighting_test PRIVATE ${MSGQ_ROOT}) +target_compile_options(tesla_ambient_lighting_test PRIVATE -Wall -Wextra) +set(TESLA_DBC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../app/src/main/assets/vehicles/tesla") +add_test(NAME tesla_ambient_lighting COMMAND tesla_ambient_lighting_test + "${TESLA_DBC_DIR}/bus_1_tesla_vehicle.dbc" + "${TESLA_DBC_DIR}/new_gen_juniper.dbc") endif() diff --git a/dashpilot-android/bridge/tests/tesla_ambient_lighting_test.cpp b/dashpilot-android/bridge/tests/tesla_ambient_lighting_test.cpp new file mode 100644 index 00000000..218aa7d8 --- /dev/null +++ b/dashpilot-android/bridge/tests/tesla_ambient_lighting_test.cpp @@ -0,0 +1,35 @@ +#include "common/vehicle_decoder.h" +#include +#include +#include + +static void check(const char* dbcPath, const char* profile) { + std::ifstream dbc(dbcPath); + assert(dbc); + const std::string content{std::istreambuf_iterator(dbc), {}}; + VehicleDecoder decoder({content, content}, {0, 1}, profile); + const auto& state = decoder.state(); + const uint8_t payload[] = {0xA5, 0x12, 0x34, 0x56, 0xE4, 0xAD, 0xFF, 0x5A}; + const auto feed = [&](int bus, uint32_t id, const uint8_t* data, size_t length) { + decoder.updateFrame(bus, id, data, length); + decoder.updateMapper(); + }; + feed(0, 0x679, payload, 8); + feed(1, 0x678, payload, 8); + feed(1, 0x679, payload, 6); + assert(!state.ambientLightingAvailable); + feed(1, 0x679, payload, 7); + assert(state.ambientLightingAvailable && state.ambientLightingColor == 0x123456); + assert(state.ambientLightingBrightness == 100); + const uint8_t black[8] = {}; + feed(1, 0x679, black, 6); + assert(state.ambientLightingColor == 0x123456 && state.ambientLightingBrightness == 100); + feed(1, 0x679, black, 8); + assert(state.ambientLightingAvailable && state.ambientLightingColor == 0 && state.ambientLightingBrightness == 0); +} + +int main(int argc, char** argv) { + if (argc != 3) return 1; + check(argv[1], "dashkit"); + check(argv[2], "tesla_juniper"); +} diff --git a/dashpilot-android/bridge/tests/tesla_closure_mapper_test.cpp b/dashpilot-android/bridge/tests/tesla_closure_mapper_test.cpp index dde650c5..343eb364 100644 --- a/dashpilot-android/bridge/tests/tesla_closure_mapper_test.cpp +++ b/dashpilot-android/bridge/tests/tesla_closure_mapper_test.cpp @@ -179,7 +179,7 @@ void closures_round_trip_through_the_double_array() { double out[CarState::FIELD_COUNT]; h.cs.toArray(out); - CHECK(CarState::DOOR_OFFSET == 51 && CarState::FIELD_COUNT == 58); + CHECK(CarState::DOOR_OFFSET == 51 && CarState::DOOR_COUNT == 7); CHECK(out[CarState::DOOR_OFFSET + 0] == 1); // FL CHECK(out[CarState::DOOR_OFFSET + 1] == 0); // FR CHECK(out[CarState::DOOR_OFFSET + 2] == 0); // RL diff --git a/dashpilot-ios/dashpilot/Models/CarState.swift b/dashpilot-ios/dashpilot/Models/CarState.swift index 0efeb187..e090b5eb 100644 --- a/dashpilot-ios/dashpilot/Models/CarState.swift +++ b/dashpilot-ios/dashpilot/Models/CarState.swift @@ -37,6 +37,10 @@ struct CarState: Codable { var hvacKeepClimateOn: Float = 0 var powerLevel: Float = 0 + var ambientLightingAvailable: Bool = false + var ambientLightingColor: UInt32 = 0 // Packed RGB (0xRRGGBB). + var ambientLightingBrightness: Float = 0 + // Openpilot state var madsActive: Bool = false var selfdriveActive: Bool = false @@ -102,6 +106,9 @@ struct CarState: Codable { hvacRecirc = Float(bridge.hvacRecirc) hvacKeepClimateOn = Float(bridge.hvacKeepClimateOn) powerLevel = Float(bridge.powerLevel) + ambientLightingAvailable = bridge.ambientLightingAvailable > 0 + ambientLightingColor = bridge.ambientLightingColor + ambientLightingBrightness = Float(bridge.ambientLightingBrightness) madsActive = bridge.madsActive > 0 selfdriveActive = bridge.selfdriveActive > 0 experimentalMode = bridge.experimentalMode > 0 @@ -183,6 +190,9 @@ struct CarState: Codable { hvacRecirc = try c.decodeIfPresent(Float.self, forKey: .hvacRecirc) ?? 0 hvacKeepClimateOn = try c.decodeIfPresent(Float.self, forKey: .hvacKeepClimateOn) ?? 0 powerLevel = try c.decodeIfPresent(Float.self, forKey: .powerLevel) ?? 0 + ambientLightingAvailable = try c.decodeIfPresent(Bool.self, forKey: .ambientLightingAvailable) ?? false + ambientLightingColor = try c.decodeIfPresent(UInt32.self, forKey: .ambientLightingColor) ?? 0 + ambientLightingBrightness = try c.decodeIfPresent(Float.self, forKey: .ambientLightingBrightness) ?? 0 madsActive = try c.decodeIfPresent(Bool.self, forKey: .madsActive) ?? false selfdriveActive = try c.decodeIfPresent(Bool.self, forKey: .selfdriveActive) ?? false experimentalMode = try c.decodeIfPresent(Bool.self, forKey: .experimentalMode) ?? false diff --git a/dashpilot-ios/dashpilot/Models/DashValues.swift b/dashpilot-ios/dashpilot/Models/DashValues.swift index 4994274b..4369f400 100644 --- a/dashpilot-ios/dashpilot/Models/DashValues.swift +++ b/dashpilot-ios/dashpilot/Models/DashValues.swift @@ -4,13 +4,16 @@ enum DashValue { case number(Float) case bool(Bool) case string(String) + case color(UInt32) } extension DashState { func dashValues(systemDarkMode: Bool? = nil) -> [(String, DashValue)] { let c = carState let d = displaySettings - return [ + var values: [(String, DashValue)] = [ + ("ambientLightingAvailable", .bool(c.ambientLightingAvailable)), + ("ambientLightingBrightness", .number(c.ambientLightingBrightness)), ("egoSpeed", .number(c.egoSpeed)), ("gear", .number(c.gear)), ("egoSteeringAngle", .number(c.egoSteeringAngle)), @@ -72,6 +75,10 @@ extension DashState { ("renderQuality", .number(Float(d.renderQuality))), ("dataSourceType", .string(dataSourceType?.rawValue ?? "")), ] + if c.ambientLightingAvailable { + values.append(("ambientLightingColor", .color(c.ambientLightingColor))) + } + return values } private static func secondsSinceLocalMidnight(_ epochMillis: Int64) -> Float { diff --git a/dashpilot-ios/dashpilot/Resources/vehicles/tesla/bus_1_tesla_vehicle.dbc b/dashpilot-ios/dashpilot/Resources/vehicles/tesla/bus_1_tesla_vehicle.dbc index 958cb5ef..ab7be151 100644 --- a/dashpilot-ios/dashpilot/Resources/vehicles/tesla/bus_1_tesla_vehicle.dbc +++ b/dashpilot-ios/dashpilot/Resources/vehicles/tesla/bus_1_tesla_vehicle.dbc @@ -9141,6 +9141,12 @@ BO_ 505 VCSEC_requests: 1 VEH SG_ VCSEC_chargePortRequest: 0|2@1+ (1,0) [0|0] "" X SG_ VCSEC_driveAttemptedWithoutAuth: 2|1@1+ (1,0) [0|0] "" X +BO_ 1657 UI_ambientLightingCtrls: 8 VEH + SG_ UI_ambientLightingRed : 8|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingGreen : 16|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBlue : 24|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBrightness : 32|7@1+ (1,0) [0|100] "%" X + CM_ "Generated by Tesla Firmware Decode @ version 923e442d6230241a259eeb3f415525373d40325c"; BA_DEF_ BO_ "GenMsgSendType" ENUM "Cyclic"; diff --git a/dashpilot-ios/dashpilot/Resources/vehicles/tesla/new_gen_juniper.dbc b/dashpilot-ios/dashpilot/Resources/vehicles/tesla/new_gen_juniper.dbc index 8ce08557..07ba6fd4 100644 --- a/dashpilot-ios/dashpilot/Resources/vehicles/tesla/new_gen_juniper.dbc +++ b/dashpilot-ios/dashpilot/Resources/vehicles/tesla/new_gen_juniper.dbc @@ -521,6 +521,12 @@ BO_ 1029 VIN_info: 8 VEH SG_ VIN_C405 m18 : 8|56@1+ (1,0) [0|7.2057594038E+016] "" X +BO_ 1657 UI_ambientLightingCtrls: 8 VEH + SG_ UI_ambientLightingRed : 8|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingGreen : 16|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBlue : 24|8@1+ (1,0) [0|255] "" X + SG_ UI_ambientLightingBrightness : 32|7@1+ (1,0) [0|100] "%" X + CM_ "New-generation Model Y (Juniper), Tesla FW 2026.x. Reverse-engineered from comma route 077b771458fd542f/000000a9--5f9f91a356 (2026-09-22 16:49-17:05) against an annotated action timeline; names and enums cross-checked with the Tesla FW 2026.2 signal dataset. Covers the messages listed in dbc/used_messages.txt plus DI_state (for DashPilot). Node VEH = seen on the vehicle bus (comma bus 1, DashKit bus 1); node CH = seen ONLY on the chassis bus (comma bus 0/2, DashKit bus 0). Each message comment lists the buses it was observed on. Bytesum checksum = (id_lo + id_hi + sum of data bytes except the checksum byte) & 0xFF."; CM_ BO_ 585 "CHANGED. Vehicle bus only, 20 Hz. DLC 3 -> 4. Byte 0 is no longer the Tesla bytesum (0 of 19597 frames match) and no CRC-8 matched: Tesla calls it SCCM_leftStalkCrc and the firmware cannot synthesize this frame until the algorithm is known. Bits 20-31 are a new 12-bit stalk angle. SCCM_washWipeButtonStatus and SCCM_highBeamStalkStatus (bits 12-15) never left 0 in the whole log, including the wiper button press at 16:49:47.9: the wiper button now reports through VCLEFT_switchStatus.VCLEFT_swcWiperButtonState."; CM_ SG_ 585 SCCM_turnIndicatorStalkStatus "Enum changed: right tap (16:53:31) = 2 UP_1, left tap (16:53:34, 16:53:35) = 6 DOWN_1 (legacy DOWN_1 was 7)."; diff --git a/dashpilot-ios/dashpilot/UI/RiveDashView.swift b/dashpilot-ios/dashpilot/UI/RiveDashView.swift index df10aecd..f2213e6f 100644 --- a/dashpilot-ios/dashpilot/UI/RiveDashView.swift +++ b/dashpilot-ios/dashpilot/UI/RiveDashView.swift @@ -65,6 +65,7 @@ final class RiveDashBinder { private var numbers: [String: RiveDataBindingViewModel.Instance.NumberProperty] = [:] private var bools: [String: RiveDataBindingViewModel.Instance.BooleanProperty] = [:] private var strings: [String: RiveDataBindingViewModel.Instance.StringProperty] = [:] + private var colors: [String: RiveDataBindingViewModel.Instance.ColorProperty] = [:] private var missing: Set = [] private var systemDarkMode = false @@ -118,6 +119,12 @@ final class RiveDashBinder { guard let p = strings[name] ?? instance.stringProperty(fromPath: name) else { missing.insert(name); continue } strings[name] = p p.value = v + case .color(let rgb): + guard let p = colors[name] ?? instance.colorProperty(fromPath: name) else { missing.insert(name); continue } + colors[name] = p + p.value = UIColor(red: CGFloat((rgb >> 16) & 255) / 255, + green: CGFloat((rgb >> 8) & 255) / 255, + blue: CGFloat(rgb & 255) / 255, alpha: 1) } } if !riveViewModel.isPlaying {