/// A Bluetooth Classic device already paired with the tablet. /// /// [deviceClass] is Android's class-of-device value. CarDash uses it to keep /// phones, headsets and other non-SPP devices out of the default OBD-II /// adapter list. Devices with no class information stay visible; hiding a real /// adapter would be worse than showing an extra device. class BluetoothDeviceInfo { const BluetoothDeviceInfo({ required this.name, required this.address, this.deviceClass, this.uuids = const [], }); static const _sppUuid = '00001101-0000-1000-8000-00805f9b34fb'; /// Names commonly used by ELM327 and compatible OBD-II adapters. static const _obdNameHints = [ 'obd', 'elm', 'vlink', 'v-link', 'vgate', 'v-gate', 'veepeak', 'bafx', 'obdlink', 'scantool', 'konnwei', 'autel', 'car scanner', ]; /// Android major device classes that can plausibly carry an SPP ELM327. static const _likelyMajorClasses = { 0x0000, // Miscellaneous 0x0100, // Computer 0x0300, // Networking / LAN access 0x1F00, // Uncategorized }; final String name; final String address; final int? deviceClass; /// Cached SDP UUIDs, when Android already has them. final List uuids; factory BluetoothDeviceInfo.fromMap(Map map) { final rawUuids = map['uuids']; return BluetoothDeviceInfo( name: (map['name'] as String?)?.trim() ?? 'Unknown device', address: (map['address'] as String?) ?? '', deviceClass: (map['deviceClass'] as num?)?.toInt(), uuids: rawUuids is List ? rawUuids .whereType() .map((uuid) => uuid.toLowerCase()) .toList(growable: false) : const [], ); } int? get majorClass => deviceClass == null ? null : deviceClass! & 0x1F00; bool get hasSppService => uuids.contains(_sppUuid); bool get hasObdName { final normalized = name.toLowerCase(); return _obdNameHints.any(normalized.contains); } /// Whether the adapter is likely to be a Bluetooth Classic OBD-II device. /// /// A recognised name or the SPP service UUID always wins. Otherwise the /// Android major class decides; unknown class information is treated as /// possibly usable so a real adapter is never hidden. bool get isLikelyObd { if (hasObdName || hasSppService) return true; final major = majorClass; if (major == null) return true; return _likelyMajorClasses.contains(major); } String get label => name.isEmpty ? address : '$name ยท $address'; }