Files
rf-mapper/src/rf_mapper/bluetooth_class.py
User 52df6421be Initial commit: RF Mapper v0.3.0-dev
WiFi & Bluetooth signal mapping tool for Raspberry Pi with:
- WiFi scanning via iw command
- Bluetooth Classic/BLE device discovery
- RSSI-based distance estimation
- OUI manufacturer lookup
- Web dashboard with multiple views:
  - Radar view (polar plot)
  - 2D Map (Leaflet/OpenStreetMap)
  - 3D Map (MapLibre GL JS with building extrusion)
- Floor-based device positioning
- Live BT tracking mode (auto-starts on page load)
- SQLite database for historical device tracking:
  - RSSI time-series history
  - Device statistics (avg/min/max)
  - Movement detection and velocity estimation
  - Activity patterns (hourly/daily)
  - New device alerts
  - Automatic data retention/cleanup
- REST API for all functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 00:08:21 +01:00

127 lines
3.3 KiB
Python

"""Bluetooth Class of Device decoder"""
class BluetoothClassDecoder:
"""Decode Bluetooth Class of Device (CoD) codes"""
MAJOR_DEVICE_CLASSES = {
0: "Miscellaneous",
1: "Computer",
2: "Phone",
3: "LAN/Network",
4: "Audio/Video",
5: "Peripheral",
6: "Imaging",
7: "Wearable",
8: "Toy",
9: "Health",
31: "Uncategorized"
}
MINOR_COMPUTER = {
0: "Uncategorized",
1: "Desktop",
2: "Server",
3: "Laptop",
4: "Handheld/PDA",
5: "Palm/PDA",
6: "Wearable"
}
MINOR_PHONE = {
0: "Uncategorized",
1: "Cellular",
2: "Cordless",
3: "Smartphone",
4: "Modem/Gateway",
5: "ISDN"
}
MINOR_AV = {
0: "Uncategorized",
1: "Wearable Headset",
2: "Hands-free",
4: "Microphone",
5: "Loudspeaker",
6: "Headphones",
7: "Portable Audio",
8: "Car Audio",
9: "Set-top Box",
10: "HiFi Audio",
11: "VCR",
12: "Video Camera",
13: "Camcorder",
14: "Video Monitor",
15: "Video Display and Loudspeaker",
16: "Video Conferencing",
18: "Gaming/Toy"
}
MINOR_PERIPHERAL = {
0: "Uncategorized",
1: "Keyboard",
2: "Pointing Device",
3: "Combo Keyboard/Pointing"
}
MINOR_IMAGING = {
1: "Display",
2: "Camera",
4: "Scanner",
8: "Printer"
}
MINOR_WEARABLE = {
1: "Wristwatch",
2: "Pager",
3: "Jacket",
4: "Helmet",
5: "Glasses"
}
@classmethod
def decode(cls, class_hex: str) -> tuple[str, str]:
"""
Decode Class of Device hex string into device type and category.
Args:
class_hex: Hex string of Class of Device (e.g., "0x240404")
Returns:
Tuple of (major_class, minor_class)
"""
try:
cod = int(class_hex, 16)
major = (cod >> 8) & 0x1F
minor = (cod >> 2) & 0x3F
major_str = cls.MAJOR_DEVICE_CLASSES.get(major, f"Unknown ({major})")
minor_str = ""
if major == 1:
minor_str = cls.MINOR_COMPUTER.get(minor, f"Unknown ({minor})")
elif major == 2:
minor_str = cls.MINOR_PHONE.get(minor, f"Unknown ({minor})")
elif major == 4:
minor_str = cls.MINOR_AV.get(minor, f"Unknown ({minor})")
elif major == 5:
minor_str = cls.MINOR_PERIPHERAL.get(minor & 0x03, f"Unknown ({minor})")
elif major == 6:
minor_str = cls.MINOR_IMAGING.get(minor & 0x0F, f"Unknown ({minor})")
elif major == 7:
minor_str = cls.MINOR_WEARABLE.get(minor, f"Unknown ({minor})")
else:
minor_str = str(minor) if minor else ""
return major_str, minor_str
except (ValueError, TypeError):
return "Unknown", ""
@classmethod
def decode_to_string(cls, class_hex: str) -> str:
"""Decode to a single descriptive string"""
major, minor = cls.decode(class_hex)
if minor:
return f"{major} ({minor})"
return major