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
17 changes: 17 additions & 0 deletions bridge/car/can_parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ class CANParsers {
return value;
}

// 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 {
int maskedBus = bus & 3;
auto busIt = busSignalCache_.find(maskedBus);
if (busIt == busSignalCache_.end()) return false;

auto it = busIt->second.find(msgName + "::" + sigName);
if (it == busIt->second.end()) return false;

const auto& cached = it->second;
auto frameIt = latestFrames_.find(frameKey(maskedBus, cached.address));
if (frameIt == latestFrames_.end()) return false;

return cached.signal->getRawBits(frameIt->second.data(), frameIt->second.size(), out);
}

private:
struct CachedSignal {
uint32_t address;
Expand Down
14 changes: 13 additions & 1 deletion bridge/car/car_state.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <cstddef>
#include <cstring>

struct CarState {
// Party bus
Expand Down Expand Up @@ -40,7 +41,12 @@ struct CarState {
double experimentalMode = 0;
double changingLane = 0;

static constexpr size_t FIELD_COUNT = 31;
// Vehicle VIN, empty until fully assembled by the mapper.
static constexpr size_t VIN_LENGTH = 17;
char vin[VIN_LENGTH + 1] = {};

static constexpr size_t VIN_DOUBLE_COUNT = 3;
static constexpr size_t FIELD_COUNT = 31 + VIN_DOUBLE_COUNT;

void toArray(double* out) const {
// Party bus
Expand Down Expand Up @@ -80,5 +86,11 @@ struct CarState {
out[29] = changingLane;

out[30] = acTemp;

// VIN chars ride the double array as raw bit patterns, 8 bytes per
// double; decoded by CanFrameDecoder.arrayToCarState.
char padded[VIN_DOUBLE_COUNT * sizeof(double)] = {};
std::memcpy(padded, vin, VIN_LENGTH);
std::memcpy(out + 31, padded, sizeof(padded));
}
};
65 changes: 65 additions & 0 deletions bridge/car/cars/tesla.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,68 @@
#pragma once

#include <array>
#include <chrono>
#include <cstdint>
#include <cstring>

#include "car/car_state_mapper.h"

// Assembles CarState::vin from VIN_info (0x405) on the vehicle bus. The VIN
// arrives as three muxed segments in any order; each is read as unscaled bits
// because the 56-bit VIN signals are wider than double's 53-bit mantissa, so
// the normal cp.get() path would silently corrupt the top byte(s).
class TeslaVinUpdater {
public:
void update(const CANParsers& cp, CarState& cs) {
if (seen_ == kAllSegments) return;
for (size_t i = 0; i < kSegments.size(); i++) {
if (seen_ & (1u << i)) continue;
const Segment& seg = kSegments[i];

uint64_t raw;
if (!cp.getRaw(1, "VIN_info", seg.signal, &raw)) continue;

char chars[7];
for (int b = 0; b < 7; b++) chars[b] = static_cast<char>(raw >> (8 * b));

bool valid = true;
for (int b = 0; b < seg.textStart; b++) valid &= chars[b] == '\0';
for (int b = seg.textStart; b < 7; b++) valid &= isVinChar(chars[b]);
if (!valid) continue;

std::memcpy(pending_ + seg.vinOffset, chars + seg.textStart, 7 - seg.textStart);
seen_ |= 1u << i;
}
if (seen_ == kAllSegments) {
std::memcpy(cs.vin, pending_, CarState::VIN_LENGTH);
}
}

private:
struct Segment {
const char* signal;
int textStart; // first payload byte; earlier bytes must be zero
int vinOffset;
};

static constexpr std::array<Segment, 3> kSegments{{
{"VIN_A405", 4, 0},
{"VIN_B405", 0, 3},
{"VIN_C405", 0, 10},
}};
static constexpr uint8_t kAllSegments = 0b111;

// ISO 3779: digits and letters except I, O, Q.
static bool isVinChar(char c) {
if (c >= '0' && c <= '9') return true;
if (c >= 'A' && c <= 'Z') return c != 'I' && c != 'O' && c != 'Q';
return false;
}

uint8_t seen_ = 0;
char pending_[CarState::VIN_LENGTH] = {};
};

static inline void updatePartyBus(const CANParsers& cp, CarState& cs) {
cs.egoSteeringAngle = cp.get(2, "SCCM_steeringAngleSensor", "SCCM_steeringAngle");
cs.gear = cp.get(2, "DI_systemStatus", "DI_gear");
Expand Down Expand Up @@ -59,7 +118,11 @@ class TeslaCommaExtraMapper : public CarStateMapper {
void update(const CANParsers& cp, CarState& cs) override {
updatePartyBus(cp, cs);
updateVehicleBus(cp, cs);
vin_.update(cp, cs);
}

private:
TeslaVinUpdater vin_;
};

class TeslaDashKitMapper : public CarStateMapper {
Expand Down Expand Up @@ -101,6 +164,7 @@ class TeslaDashKitMapper : public CarStateMapper {
cs.acTemp = cp.get(1, "UI_hvacRequest", "UI_hvacReqTempSetpointLeft");

updateVehicleBus(cp, cs);
vin_.update(cp, cs);
}

private:
Expand All @@ -127,4 +191,5 @@ class TeslaDashKitMapper : public CarStateMapper {

BlinkerHold leftBlinkerHold_;
BlinkerHold rightBlinkerHold_;
TeslaVinUpdater vin_;
};
16 changes: 15 additions & 1 deletion bridge/dbc/dbc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ bool cabana::Signal::getValue(const uint8_t *data, size_t data_size, double *val
return true;
}

bool cabana::Signal::getRawBits(const uint8_t *data, size_t data_size, uint64_t *val) const {
if (multiplexor && get_raw_value(data, data_size, *multiplexor) != multiplex_value) {
return false;
}
*val = get_raw_bits(data, data_size, *this);
return true;
}

bool cabana::Signal::operator==(const cabana::Signal &other) const {
return name == other.name && size == other.size &&
start_bit == other.start_bit &&
Expand All @@ -150,7 +158,7 @@ bool cabana::Signal::operator==(const cabana::Signal &other) const {

// helper functions

double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig) {
uint64_t get_raw_bits(const uint8_t *data, size_t data_size, const cabana::Signal &sig) {
const int msb_byte = sig.msb / 8;
if (msb_byte >= (int)data_size) return 0;

Expand All @@ -175,6 +183,12 @@ double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal
}
}

return val;
}

double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig) {
uint64_t val = get_raw_bits(data, data_size, sig);

// Sign extension (if needed)
if (sig.is_signed && (val & (1ULL << (sig.size - 1)))) {
val |= ~((1ULL << sig.size) - 1);
Expand Down
3 changes: 3 additions & 0 deletions bridge/dbc/dbc.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class Signal {
Signal(const Signal &other) = default;
void update();
bool getValue(const uint8_t *data, size_t data_size, double *val) const;
// Unscaled signal bits, for signals wider than double's 53-bit mantissa.
bool getRawBits(const uint8_t *data, size_t data_size, uint64_t *val) const;
bool operator==(const cabana::Signal &other) const;
inline bool operator!=(const cabana::Signal &other) const { return !(*this == other); }

Expand Down Expand Up @@ -132,5 +134,6 @@ class Msg {

// Helper functions
double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig);
uint64_t get_raw_bits(const uint8_t *data, size_t data_size, const cabana::Signal &sig);
void updateMsbLsb(cabana::Signal &s);
inline int flipBitPos(int start_bit) { return 8 * (start_bit / 8) + 7 - start_bit % 8; }
1 change: 1 addition & 0 deletions dashpilot-android/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/.idea/assetWizardSettings.xml
.DS_Store
/build
bridge/build-host
/captures
.externalNativeBuild
.cxx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ data class CarState(
val experimentalMode: Boolean = false,
val madsActive: Boolean = false,

val changingLane: Boolean = false
val changingLane: Boolean = false,

// Empty until fully assembled from the CAN stream by the native mapper.
val vin: String = ""
) {
fun toImperial(): CarState = copy(
odometer = odometer * KM_TO_MILES,
Expand All @@ -47,7 +50,7 @@ data class CarState(
)

companion object {
const val FIELD_COUNT = 31
const val FIELD_COUNT = 34
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
@@ -0,0 +1,40 @@
package com.softwiredtech.dashpilot.datasource

class RawCanFrame(
val bus: Int,
val address: Int,
val data: ByteArray,
)

// Wire format from firmware (build_ble_packet):
// [count : 1]
// per frame:
// [timestamp_us : LE32]
// [bus : 1]
// [addr : LE32]
// [len : 1]
// [data : len bytes]
internal fun parseCanPacket(payload: ByteArray): List<RawCanFrame> {
if (payload.isEmpty()) return emptyList()
val frames = ArrayList<RawCanFrame>(payload[0].toInt() and 0xFF)
var offset = 1
val count = payload[0].toInt() and 0xFF
for (i in 0 until count) {
if (offset + 4 > payload.size) break
offset += 4
if (offset >= payload.size) break
val bus = payload[offset].toInt() and 0xFF
offset += 1
if (offset + 4 > payload.size) break
val addr = java.nio.ByteBuffer.wrap(payload, offset, 4)
.order(java.nio.ByteOrder.LITTLE_ENDIAN).int
offset += 4
if (offset >= payload.size) break
val len = payload[offset].toInt() and 0xFF
offset += 1
if (offset + len > payload.size) break
frames.add(RawCanFrame(bus, addr, payload.copyOfRange(offset, offset + len)))
offset += len
}
return frames
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,23 @@ import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.util.Log
import com.softwiredtech.dashpilot.datamodel.dash.CarState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.UUID
import com.softwiredtech.dashpilot.vehicle.CanFrameDecoder
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.sample
import java.util.UUID

@SuppressLint("MissingPermission")
class DashKitDataSource(
private val manager: DashKitBleManager,
private val decoder: CanFrameDecoder
private val decoder: CanFrameDecoder,
) : IDataSource, GattListener {

companion object {
private const val TAG = "DashKitDataSource"
private val SERVICE_UUID = UUID.fromString("CADA0000-CA00-B1E0-B0D6-C000AA0100A1")
private val CHAR_UUID = UUID.fromString("CADA0001-CA00-B1E0-B0D6-C000AA0100A1")
// The firmware now applies CAN acceptance filtering in hardware (per-bus
// MCP251xFD filters), so the app no longer pushes a BLE filter list.
}

private val _incoming = MutableSharedFlow<CarState>(replay = 1)
Expand Down Expand Up @@ -68,35 +64,9 @@ class DashKitDataSource(
}

private fun parseAndEmit(payload: ByteArray) {
// Wire format from firmware (build_ble_packet):
// [count : 1]
// per frame:
// [timestamp_us : LE32]
// [bus : 1]
// [addr : LE32]
// [len : 1]
// [data : len bytes]
if (payload.isEmpty()) return
val count = payload[0].toInt() and 0xFF
var offset = 1
for (i in 0 until count) {
if (offset + 4 > payload.size) break
// Timestamp is currently unused by the decoder; skip it.
offset += 4
if (offset >= payload.size) break
val bus = payload[offset].toInt() and 0xFF
offset += 1
if (offset + 4 > payload.size) break
val addr = ByteBuffer.wrap(payload, offset, 4).order(ByteOrder.LITTLE_ENDIAN).int
offset += 4
if (offset >= payload.size) break
val len = payload[offset].toInt() and 0xFF
offset += 1
if (offset + len > payload.size) break
val data = payload.copyOfRange(offset, offset + len)
offset += len

currentState = decoder.decodeFrame(bus, addr, data)
for (frame in parseCanPacket(payload)) {
currentState = decoder.decodeFrame(frame.bus, frame.address, frame.data)
}
_incoming.tryEmit(currentState)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,25 @@ class CanFrameDecoder(
experimentalMode = values[27] > 0,
madsActive = values[28] > 0,
changingLane = values[29] > 0,
acTemp = values[30].toFloat()
acTemp = values[30].toFloat(),
vin = decodeVin(values, offset = 31)
)
}

// VIN chars arrive bit-cast into doubles, 8 ASCII bytes per double,
// little-endian; see CarState::toArray in bridge/car/car_state.h.
private fun decodeVin(values: DoubleArray, offset: Int): String {
val bytes = ByteArray(VIN_DOUBLE_COUNT * 8)
for (chunk in 0 until VIN_DOUBLE_COUNT) {
val bits = values[offset + chunk].toRawBits()
for (b in 0 until 8) {
bytes[chunk * 8 + b] = (bits ushr (8 * b)).toByte()
}
}
val length = bytes.indexOf(0).let { if (it < 0) bytes.size else it }
return String(bytes, 0, length, Charsets.US_ASCII)
}

private const val VIN_DOUBLE_COUNT = 3
}
}
Loading
Loading