bumble 0.0.214__py3-none-any.whl → 0.0.215__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. bumble/_version.py +16 -3
  2. bumble/a2dp.py +15 -16
  3. bumble/apps/auracast.py +13 -38
  4. bumble/apps/bench.py +9 -10
  5. bumble/apps/ble_rpa_tool.py +1 -0
  6. bumble/apps/console.py +22 -25
  7. bumble/apps/controller_info.py +19 -19
  8. bumble/apps/controller_loopback.py +2 -2
  9. bumble/apps/controllers.py +1 -1
  10. bumble/apps/device_info.py +3 -3
  11. bumble/apps/gatt_dump.py +1 -1
  12. bumble/apps/gg_bridge.py +5 -6
  13. bumble/apps/hci_bridge.py +3 -3
  14. bumble/apps/l2cap_bridge.py +3 -3
  15. bumble/apps/lea_unicast/app.py +15 -25
  16. bumble/apps/pair.py +30 -43
  17. bumble/apps/pandora_server.py +5 -4
  18. bumble/apps/player/player.py +19 -22
  19. bumble/apps/rfcomm_bridge.py +3 -8
  20. bumble/apps/scan.py +16 -6
  21. bumble/apps/show.py +3 -4
  22. bumble/apps/speaker/speaker.py +22 -22
  23. bumble/apps/unbond.py +2 -1
  24. bumble/apps/usb_probe.py +1 -2
  25. bumble/att.py +241 -246
  26. bumble/audio/io.py +5 -9
  27. bumble/avc.py +2 -2
  28. bumble/avctp.py +6 -7
  29. bumble/avdtp.py +19 -22
  30. bumble/avrcp.py +1096 -588
  31. bumble/codecs.py +2 -0
  32. bumble/controller.py +52 -13
  33. bumble/core.py +567 -248
  34. bumble/crypto/__init__.py +2 -2
  35. bumble/crypto/builtin.py +1 -1
  36. bumble/crypto/cryptography.py +2 -4
  37. bumble/data_types.py +1025 -0
  38. bumble/device.py +280 -278
  39. bumble/drivers/__init__.py +3 -2
  40. bumble/drivers/intel.py +3 -4
  41. bumble/drivers/rtk.py +26 -9
  42. bumble/gap.py +4 -4
  43. bumble/gatt.py +3 -2
  44. bumble/gatt_adapters.py +3 -11
  45. bumble/gatt_client.py +69 -81
  46. bumble/gatt_server.py +124 -124
  47. bumble/hci.py +67 -18
  48. bumble/helpers.py +19 -26
  49. bumble/hfp.py +10 -21
  50. bumble/hid.py +22 -16
  51. bumble/host.py +181 -103
  52. bumble/keys.py +5 -3
  53. bumble/l2cap.py +121 -74
  54. bumble/link.py +8 -9
  55. bumble/pairing.py +7 -6
  56. bumble/pandora/__init__.py +8 -7
  57. bumble/pandora/config.py +3 -1
  58. bumble/pandora/device.py +3 -2
  59. bumble/pandora/host.py +38 -36
  60. bumble/pandora/l2cap.py +22 -21
  61. bumble/pandora/security.py +15 -15
  62. bumble/pandora/utils.py +5 -3
  63. bumble/profiles/aics.py +11 -11
  64. bumble/profiles/ams.py +7 -8
  65. bumble/profiles/ancs.py +6 -7
  66. bumble/profiles/ascs.py +4 -9
  67. bumble/profiles/asha.py +8 -12
  68. bumble/profiles/bap.py +11 -23
  69. bumble/profiles/bass.py +2 -7
  70. bumble/profiles/battery_service.py +3 -4
  71. bumble/profiles/cap.py +1 -2
  72. bumble/profiles/csip.py +2 -6
  73. bumble/profiles/device_information_service.py +2 -2
  74. bumble/profiles/gap.py +4 -4
  75. bumble/profiles/gatt_service.py +1 -4
  76. bumble/profiles/gmap.py +5 -5
  77. bumble/profiles/hap.py +62 -59
  78. bumble/profiles/heart_rate_service.py +5 -4
  79. bumble/profiles/le_audio.py +3 -1
  80. bumble/profiles/mcp.py +3 -7
  81. bumble/profiles/pacs.py +3 -6
  82. bumble/profiles/pbp.py +2 -0
  83. bumble/profiles/tmap.py +2 -3
  84. bumble/profiles/vcs.py +2 -8
  85. bumble/profiles/vocs.py +8 -8
  86. bumble/rfcomm.py +11 -14
  87. bumble/rtp.py +1 -0
  88. bumble/sdp.py +10 -8
  89. bumble/smp.py +142 -153
  90. bumble/snoop.py +5 -5
  91. bumble/tools/generate_company_id_list.py +1 -0
  92. bumble/tools/intel_fw_download.py +3 -3
  93. bumble/tools/intel_util.py +4 -4
  94. bumble/tools/rtk_fw_download.py +6 -3
  95. bumble/tools/rtk_util.py +24 -7
  96. bumble/transport/__init__.py +19 -15
  97. bumble/transport/android_emulator.py +8 -13
  98. bumble/transport/android_netsim.py +19 -18
  99. bumble/transport/common.py +12 -15
  100. bumble/transport/file.py +1 -1
  101. bumble/transport/hci_socket.py +4 -6
  102. bumble/transport/pty.py +5 -6
  103. bumble/transport/pyusb.py +7 -10
  104. bumble/transport/serial.py +2 -1
  105. bumble/transport/tcp_client.py +2 -2
  106. bumble/transport/tcp_server.py +11 -14
  107. bumble/transport/udp.py +3 -3
  108. bumble/transport/unix.py +67 -1
  109. bumble/transport/usb.py +6 -6
  110. bumble/transport/vhci.py +0 -1
  111. bumble/transport/ws_client.py +2 -1
  112. bumble/transport/ws_server.py +3 -2
  113. bumble/utils.py +20 -5
  114. bumble/vendor/android/hci.py +1 -2
  115. bumble/vendor/zephyr/hci.py +0 -1
  116. {bumble-0.0.214.dist-info → bumble-0.0.215.dist-info}/METADATA +2 -1
  117. bumble-0.0.215.dist-info/RECORD +183 -0
  118. bumble-0.0.214.dist-info/RECORD +0 -182
  119. {bumble-0.0.214.dist-info → bumble-0.0.215.dist-info}/WHEEL +0 -0
  120. {bumble-0.0.214.dist-info → bumble-0.0.215.dist-info}/entry_points.txt +0 -0
  121. {bumble-0.0.214.dist-info → bumble-0.0.215.dist-info}/licenses/LICENSE +0 -0
  122. {bumble-0.0.214.dist-info → bumble-0.0.215.dist-info}/top_level.txt +0 -0
bumble/core.py CHANGED
@@ -17,14 +17,26 @@
17
17
  # -----------------------------------------------------------------------------
18
18
  from __future__ import annotations
19
19
 
20
+ import dataclasses
20
21
  import enum
21
22
  import struct
22
- from typing import cast, overload, Literal, Union, Optional
23
+ from typing import (
24
+ TYPE_CHECKING,
25
+ Any,
26
+ ClassVar,
27
+ Iterable,
28
+ Literal,
29
+ Optional,
30
+ Type,
31
+ Union,
32
+ cast,
33
+ overload,
34
+ )
35
+
23
36
  from typing_extensions import Self
24
37
 
25
- from bumble.company_ids import COMPANY_IDENTIFIERS
26
38
  from bumble import utils
27
-
39
+ from bumble.company_ids import COMPANY_IDENTIFIERS
28
40
 
29
41
  # -----------------------------------------------------------------------------
30
42
  # Constants
@@ -331,6 +343,9 @@ class UUID:
331
343
  result += f' ({self.name})'
332
344
  return result
333
345
 
346
+ def __repr__(self) -> str:
347
+ return self.to_hex_str()
348
+
334
349
 
335
350
  # -----------------------------------------------------------------------------
336
351
  # Common UUID constants
@@ -447,26 +462,25 @@ BT_HDP_SINK_SERVICE = UUID.from_16_bits(0x1402,
447
462
 
448
463
 
449
464
  # -----------------------------------------------------------------------------
450
- # DeviceClass
465
+ # ClassOfDevice
466
+ # See Bluetooth - Assigned Numbers - 2.8 Class of Device
451
467
  # -----------------------------------------------------------------------------
452
- class DeviceClass:
468
+ @dataclasses.dataclass
469
+ class ClassOfDevice:
453
470
  # fmt: off
454
- # pylint: disable=line-too-long
455
-
456
- # Major Service Classes (flags combined with OR)
457
- LIMITED_DISCOVERABLE_MODE_SERVICE_CLASS = (1 << 0)
458
- LE_AUDIO_SERVICE_CLASS = (1 << 1)
459
- RESERVED = (1 << 2)
460
- POSITIONING_SERVICE_CLASS = (1 << 3)
461
- NETWORKING_SERVICE_CLASS = (1 << 4)
462
- RENDERING_SERVICE_CLASS = (1 << 5)
463
- CAPTURING_SERVICE_CLASS = (1 << 6)
464
- OBJECT_TRANSFER_SERVICE_CLASS = (1 << 7)
465
- AUDIO_SERVICE_CLASS = (1 << 8)
466
- TELEPHONY_SERVICE_CLASS = (1 << 9)
467
- INFORMATION_SERVICE_CLASS = (1 << 10)
468
-
469
- SERVICE_CLASS_LABELS = [
471
+ class MajorServiceClasses(utils.CompatibleIntFlag):
472
+ LIMITED_DISCOVERABLE_MODE = (1 << 0)
473
+ LE_AUDIO = (1 << 1)
474
+ POSITIONING = (1 << 3)
475
+ NETWORKING = (1 << 4)
476
+ RENDERING = (1 << 5)
477
+ CAPTURING = (1 << 6)
478
+ OBJECT_TRANSFER = (1 << 7)
479
+ AUDIO = (1 << 8)
480
+ TELEPHONY = (1 << 9)
481
+ INFORMATION = (1 << 10)
482
+
483
+ MAJOR_SERVICE_CLASS_LABELS: ClassVar[list[str]] = [
470
484
  'Limited Discoverable Mode',
471
485
  'LE audio',
472
486
  '(reserved)',
@@ -477,220 +491,440 @@ class DeviceClass:
477
491
  'Object Transfer',
478
492
  'Audio',
479
493
  'Telephony',
480
- 'Information'
494
+ 'Information',
481
495
  ]
482
496
 
483
- # Major Device Classes
484
- MISCELLANEOUS_MAJOR_DEVICE_CLASS = 0x00
485
- COMPUTER_MAJOR_DEVICE_CLASS = 0x01
486
- PHONE_MAJOR_DEVICE_CLASS = 0x02
487
- LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS = 0x03
488
- AUDIO_VIDEO_MAJOR_DEVICE_CLASS = 0x04
489
- PERIPHERAL_MAJOR_DEVICE_CLASS = 0x05
490
- IMAGING_MAJOR_DEVICE_CLASS = 0x06
491
- WEARABLE_MAJOR_DEVICE_CLASS = 0x07
492
- TOY_MAJOR_DEVICE_CLASS = 0x08
493
- HEALTH_MAJOR_DEVICE_CLASS = 0x09
494
- UNCATEGORIZED_MAJOR_DEVICE_CLASS = 0x1F
495
-
496
- MAJOR_DEVICE_CLASS_NAMES = {
497
- MISCELLANEOUS_MAJOR_DEVICE_CLASS: 'Miscellaneous',
498
- COMPUTER_MAJOR_DEVICE_CLASS: 'Computer',
499
- PHONE_MAJOR_DEVICE_CLASS: 'Phone',
500
- LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS: 'LAN/Network Access Point',
501
- AUDIO_VIDEO_MAJOR_DEVICE_CLASS: 'Audio/Video',
502
- PERIPHERAL_MAJOR_DEVICE_CLASS: 'Peripheral',
503
- IMAGING_MAJOR_DEVICE_CLASS: 'Imaging',
504
- WEARABLE_MAJOR_DEVICE_CLASS: 'Wearable',
505
- TOY_MAJOR_DEVICE_CLASS: 'Toy',
506
- HEALTH_MAJOR_DEVICE_CLASS: 'Health',
507
- UNCATEGORIZED_MAJOR_DEVICE_CLASS: 'Uncategorized'
497
+ class MajorDeviceClass(utils.OpenIntEnum):
498
+ MISCELLANEOUS = 0x00
499
+ COMPUTER = 0x01
500
+ PHONE = 0x02
501
+ LAN_NETWORK_ACCESS_POINT = 0x03
502
+ AUDIO_VIDEO = 0x04
503
+ PERIPHERAL = 0x05
504
+ IMAGING = 0x06
505
+ WEARABLE = 0x07
506
+ TOY = 0x08
507
+ HEALTH = 0x09
508
+ UNCATEGORIZED = 0x1F
509
+
510
+ MAJOR_DEVICE_CLASS_LABELS: ClassVar[dict[MajorDeviceClass, str]] = {
511
+ MajorDeviceClass.MISCELLANEOUS: 'Miscellaneous',
512
+ MajorDeviceClass.COMPUTER: 'Computer',
513
+ MajorDeviceClass.PHONE: 'Phone',
514
+ MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: 'LAN/Network Access Point',
515
+ MajorDeviceClass.AUDIO_VIDEO: 'Audio/Video',
516
+ MajorDeviceClass.PERIPHERAL: 'Peripheral',
517
+ MajorDeviceClass.IMAGING: 'Imaging',
518
+ MajorDeviceClass.WEARABLE: 'Wearable',
519
+ MajorDeviceClass.TOY: 'Toy',
520
+ MajorDeviceClass.HEALTH: 'Health',
521
+ MajorDeviceClass.UNCATEGORIZED: 'Uncategorized',
522
+ }
523
+
524
+ class ComputerMinorDeviceClass(utils.OpenIntEnum):
525
+ UNCATEGORIZED = 0x00
526
+ DESKTOP_WORKSTATION = 0x01
527
+ SERVER_CLASS_COMPUTER = 0x02
528
+ LAPTOP_COMPUTER = 0x03
529
+ HANDHELD_PC_PDA = 0x04
530
+ PALM_SIZE_PC_PDA = 0x05
531
+ WEARABLE_COMPUTER = 0x06
532
+ TABLET = 0x07
533
+
534
+ COMPUTER_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[ComputerMinorDeviceClass, str]] = {
535
+ ComputerMinorDeviceClass.UNCATEGORIZED: 'Uncategorized',
536
+ ComputerMinorDeviceClass.DESKTOP_WORKSTATION: 'Desktop workstation',
537
+ ComputerMinorDeviceClass.SERVER_CLASS_COMPUTER: 'Server-class computer',
538
+ ComputerMinorDeviceClass.LAPTOP_COMPUTER: 'Laptop',
539
+ ComputerMinorDeviceClass.HANDHELD_PC_PDA: 'Handheld PC/PDA',
540
+ ComputerMinorDeviceClass.PALM_SIZE_PC_PDA: 'Palm-size PC/PDA',
541
+ ComputerMinorDeviceClass.WEARABLE_COMPUTER: 'Wearable computer',
542
+ ComputerMinorDeviceClass.TABLET: 'Tablet',
508
543
  }
509
544
 
510
- COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
511
- COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS = 0x01
512
- COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS = 0x02
513
- COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS = 0x03
514
- COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS = 0x04
515
- COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS = 0x05
516
- COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS = 0x06
517
- COMPUTER_TABLET_MINOR_DEVICE_CLASS = 0x07
518
-
519
- COMPUTER_MINOR_DEVICE_CLASS_NAMES = {
520
- COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
521
- COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS: 'Desktop workstation',
522
- COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS: 'Server-class computer',
523
- COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS: 'Laptop',
524
- COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS: 'Handheld PC/PDA',
525
- COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS: 'Palm-size PC/PDA',
526
- COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS: 'Wearable computer',
527
- COMPUTER_TABLET_MINOR_DEVICE_CLASS: 'Tablet'
545
+ class PhoneMinorDeviceClass(utils.OpenIntEnum):
546
+ UNCATEGORIZED = 0x00
547
+ CELLULAR = 0x01
548
+ CORDLESS = 0x02
549
+ SMARTPHONE = 0x03
550
+ WIRED_MODEM_OR_VOICE_GATEWAY = 0x04
551
+ COMMON_ISDN = 0x05
552
+
553
+ PHONE_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[PhoneMinorDeviceClass, str]] = {
554
+ PhoneMinorDeviceClass.UNCATEGORIZED: 'Uncategorized',
555
+ PhoneMinorDeviceClass.CELLULAR: 'Cellular',
556
+ PhoneMinorDeviceClass.CORDLESS: 'Cordless',
557
+ PhoneMinorDeviceClass.SMARTPHONE: 'Smartphone',
558
+ PhoneMinorDeviceClass.WIRED_MODEM_OR_VOICE_GATEWAY: 'Wired modem or voice gateway',
559
+ PhoneMinorDeviceClass.COMMON_ISDN: 'Common ISDN access',
528
560
  }
529
561
 
530
- PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
531
- PHONE_CELLULAR_MINOR_DEVICE_CLASS = 0x01
532
- PHONE_CORDLESS_MINOR_DEVICE_CLASS = 0x02
533
- PHONE_SMARTPHONE_MINOR_DEVICE_CLASS = 0x03
534
- PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS = 0x04
535
- PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS = 0x05
536
-
537
- PHONE_MINOR_DEVICE_CLASS_NAMES = {
538
- PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
539
- PHONE_CELLULAR_MINOR_DEVICE_CLASS: 'Cellular',
540
- PHONE_CORDLESS_MINOR_DEVICE_CLASS: 'Cordless',
541
- PHONE_SMARTPHONE_MINOR_DEVICE_CLASS: 'Smartphone',
542
- PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS: 'Wired modem or voice gateway',
543
- PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS: 'Common ISDN access'
562
+ class LanNetworkMinorDeviceClass(utils.OpenIntEnum):
563
+ FULLY_AVAILABLE = 0x00
564
+ _1_TO_17_PERCENT_UTILIZED = 0x01
565
+ _17_TO_33_PERCENT_UTILIZED = 0x02
566
+ _33_TO_50_PERCENT_UTILIZED = 0x03
567
+ _50_TO_67_PERCENT_UTILIZED = 0x04
568
+ _67_TO_83_PERCENT_UTILIZED = 0x05
569
+ _83_TO_99_PERCENT_UTILIZED = 0x06
570
+ _NO_SERVICE_AVAILABLE = 0x07
571
+
572
+ LAN_NETWORK_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[LanNetworkMinorDeviceClass, str]] = {
573
+ LanNetworkMinorDeviceClass.FULLY_AVAILABLE: 'Fully availbable',
574
+ LanNetworkMinorDeviceClass._1_TO_17_PERCENT_UTILIZED: '1% to 17% utilized',
575
+ LanNetworkMinorDeviceClass._17_TO_33_PERCENT_UTILIZED: '17% to 33% utilized',
576
+ LanNetworkMinorDeviceClass._33_TO_50_PERCENT_UTILIZED: '33% to 50% utilized',
577
+ LanNetworkMinorDeviceClass._50_TO_67_PERCENT_UTILIZED: '50% to 67% utilized',
578
+ LanNetworkMinorDeviceClass._67_TO_83_PERCENT_UTILIZED: '67% to 83% utilized',
579
+ LanNetworkMinorDeviceClass._83_TO_99_PERCENT_UTILIZED: '83% to 99% utilized',
580
+ LanNetworkMinorDeviceClass._NO_SERVICE_AVAILABLE: 'No service available',
544
581
  }
545
582
 
546
- AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
547
- AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS = 0x01
548
- AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS = 0x02
549
- # (RESERVED) = 0x03
550
- AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS = 0x04
551
- AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS = 0x05
552
- AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS = 0x06
553
- AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS = 0x07
554
- AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS = 0x08
555
- AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS = 0x09
556
- AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS = 0x0A
557
- AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS = 0x0B
558
- AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS = 0x0C
559
- AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS = 0x0D
560
- AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS = 0x0E
561
- AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS = 0x0F
562
- AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS = 0x10
563
- # (RESERVED) = 0x11
564
- AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS = 0x12
565
-
566
- AUDIO_VIDEO_MINOR_DEVICE_CLASS_NAMES = {
567
- AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
568
- AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS: 'Wearable Headset Device',
569
- AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS: 'Hands-free Device',
570
- AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS: 'Microphone',
571
- AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS: 'Loudspeaker',
572
- AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS: 'Headphones',
573
- AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS: 'Portable Audio',
574
- AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS: 'Car audio',
575
- AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS: 'Set-top box',
576
- AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS: 'HiFi Audio Device',
577
- AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS: 'VCR',
578
- AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS: 'Video Camera',
579
- AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS: 'Camcorder',
580
- AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS: 'Video Monitor',
581
- AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS: 'Video Display and Loudspeaker',
582
- AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS: 'Video Conferencing',
583
- AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS: 'Gaming/Toy'
583
+ class AudioVideoMinorDeviceClass(utils.OpenIntEnum):
584
+ UNCATEGORIZED = 0x00
585
+ WEARABLE_HEADSET_DEVICE = 0x01
586
+ HANDS_FREE_DEVICE = 0x02
587
+ # (RESERVED) = 0x03
588
+ MICROPHONE = 0x04
589
+ LOUDSPEAKER = 0x05
590
+ HEADPHONES = 0x06
591
+ PORTABLE_AUDIO = 0x07
592
+ CAR_AUDIO = 0x08
593
+ SET_TOP_BOX = 0x09
594
+ HIFI_AUDIO_DEVICE = 0x0A
595
+ VCR = 0x0B
596
+ VIDEO_CAMERA = 0x0C
597
+ CAMCORDER = 0x0D
598
+ VIDEO_MONITOR = 0x0E
599
+ VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x0F
600
+ VIDEO_CONFERENCING = 0x10
601
+ # (RESERVED) = 0x11
602
+ GAMING_OR_TOY = 0x12
603
+
604
+ AUDIO_VIDEO_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[AudioVideoMinorDeviceClass, str]] = {
605
+ AudioVideoMinorDeviceClass.UNCATEGORIZED: 'Uncategorized',
606
+ AudioVideoMinorDeviceClass.WEARABLE_HEADSET_DEVICE: 'Wearable Headset Device',
607
+ AudioVideoMinorDeviceClass.HANDS_FREE_DEVICE: 'Hands-free Device',
608
+ AudioVideoMinorDeviceClass.MICROPHONE: 'Microphone',
609
+ AudioVideoMinorDeviceClass.LOUDSPEAKER: 'Loudspeaker',
610
+ AudioVideoMinorDeviceClass.HEADPHONES: 'Headphones',
611
+ AudioVideoMinorDeviceClass.PORTABLE_AUDIO: 'Portable Audio',
612
+ AudioVideoMinorDeviceClass.CAR_AUDIO: 'Car audio',
613
+ AudioVideoMinorDeviceClass.SET_TOP_BOX: 'Set-top box',
614
+ AudioVideoMinorDeviceClass.HIFI_AUDIO_DEVICE: 'HiFi Audio Device',
615
+ AudioVideoMinorDeviceClass.VCR: 'VCR',
616
+ AudioVideoMinorDeviceClass.VIDEO_CAMERA: 'Video Camera',
617
+ AudioVideoMinorDeviceClass.CAMCORDER: 'Camcorder',
618
+ AudioVideoMinorDeviceClass.VIDEO_MONITOR: 'Video Monitor',
619
+ AudioVideoMinorDeviceClass.VIDEO_DISPLAY_AND_LOUDSPEAKER: 'Video Display and Loudspeaker',
620
+ AudioVideoMinorDeviceClass.VIDEO_CONFERENCING: 'Video Conferencing',
621
+ AudioVideoMinorDeviceClass.GAMING_OR_TOY: 'Gaming/Toy',
584
622
  }
585
623
 
586
- PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
587
- PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS = 0x10
588
- PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS = 0x20
589
- PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS = 0x30
590
- PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS = 0x01
591
- PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS = 0x02
592
- PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS = 0x03
593
- PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS = 0x04
594
- PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS = 0x05
595
- PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS = 0x06
596
- PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS = 0x07
597
- PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS = 0x08
598
- PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS = 0x09
599
-
600
- PERIPHERAL_MINOR_DEVICE_CLASS_NAMES = {
601
- PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
602
- PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS: 'Keyboard',
603
- PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS: 'Pointing device',
604
- PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS: 'Combo keyboard/pointing device',
605
- PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS: 'Joystick',
606
- PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS: 'Gamepad',
607
- PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS: 'Remote control',
608
- PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS: 'Sensing device',
609
- PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS: 'Digitizer tablet',
610
- PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS: 'Card Reader',
611
- PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS: 'Digital Pen',
612
- PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS: 'Handheld scanner',
613
- PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS: 'Handheld gestural input device'
624
+ class PeripheralMinorDeviceClass(utils.OpenIntEnum):
625
+ UNCATEGORIZED = 0x00
626
+ KEYBOARD = 0x10
627
+ POINTING_DEVICE = 0x20
628
+ COMBO_KEYBOARD_POINTING_DEVICE = 0x30
629
+ JOYSTICK = 0x01
630
+ GAMEPAD = 0x02
631
+ REMOTE_CONTROL = 0x03
632
+ SENSING_DEVICE = 0x04
633
+ DIGITIZER_TABLET = 0x05
634
+ CARD_READER = 0x06
635
+ DIGITAL_PEN = 0x07
636
+ HANDHELD_SCANNER = 0x08
637
+ HANDHELD_GESTURAL_INPUT_DEVICE = 0x09
638
+
639
+ PERIPHERAL_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[PeripheralMinorDeviceClass, str]] = {
640
+ PeripheralMinorDeviceClass.UNCATEGORIZED: 'Uncategorized',
641
+ PeripheralMinorDeviceClass.KEYBOARD: 'Keyboard',
642
+ PeripheralMinorDeviceClass.POINTING_DEVICE: 'Pointing device',
643
+ PeripheralMinorDeviceClass.COMBO_KEYBOARD_POINTING_DEVICE: 'Combo keyboard/pointing device',
644
+ PeripheralMinorDeviceClass.JOYSTICK: 'Joystick',
645
+ PeripheralMinorDeviceClass.GAMEPAD: 'Gamepad',
646
+ PeripheralMinorDeviceClass.REMOTE_CONTROL: 'Remote control',
647
+ PeripheralMinorDeviceClass.SENSING_DEVICE: 'Sensing device',
648
+ PeripheralMinorDeviceClass.DIGITIZER_TABLET: 'Digitizer tablet',
649
+ PeripheralMinorDeviceClass.CARD_READER: 'Card Reader',
650
+ PeripheralMinorDeviceClass.DIGITAL_PEN: 'Digital Pen',
651
+ PeripheralMinorDeviceClass.HANDHELD_SCANNER: 'Handheld scanner',
652
+ PeripheralMinorDeviceClass.HANDHELD_GESTURAL_INPUT_DEVICE: 'Handheld gestural input device',
614
653
  }
615
654
 
616
- WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
617
- WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS = 0x01
618
- WEARABLE_PAGER_MINOR_DEVICE_CLASS = 0x02
619
- WEARABLE_JACKET_MINOR_DEVICE_CLASS = 0x03
620
- WEARABLE_HELMET_MINOR_DEVICE_CLASS = 0x04
621
- WEARABLE_GLASSES_MINOR_DEVICE_CLASS = 0x05
622
-
623
- WEARABLE_MINOR_DEVICE_CLASS_NAMES = {
624
- WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
625
- WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS: 'Wristwatch',
626
- WEARABLE_PAGER_MINOR_DEVICE_CLASS: 'Pager',
627
- WEARABLE_JACKET_MINOR_DEVICE_CLASS: 'Jacket',
628
- WEARABLE_HELMET_MINOR_DEVICE_CLASS: 'Helmet',
629
- WEARABLE_GLASSES_MINOR_DEVICE_CLASS: 'Glasses',
655
+ class WearableMinorDeviceClass(utils.OpenIntEnum):
656
+ UNCATEGORIZED = 0x00
657
+ WRISTWATCH = 0x01
658
+ PAGER = 0x02
659
+ JACKET = 0x03
660
+ HELMET = 0x04
661
+ GLASSES = 0x05
662
+
663
+ WEARABLE_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[WearableMinorDeviceClass, str]] = {
664
+ WearableMinorDeviceClass.UNCATEGORIZED: 'Uncategorized',
665
+ WearableMinorDeviceClass.WRISTWATCH: 'Wristwatch',
666
+ WearableMinorDeviceClass.PAGER: 'Pager',
667
+ WearableMinorDeviceClass.JACKET: 'Jacket',
668
+ WearableMinorDeviceClass.HELMET: 'Helmet',
669
+ WearableMinorDeviceClass.GLASSES: 'Glasses',
630
670
  }
631
671
 
632
- TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
633
- TOY_ROBOT_MINOR_DEVICE_CLASS = 0x01
634
- TOY_VEHICLE_MINOR_DEVICE_CLASS = 0x02
635
- TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS = 0x03
636
- TOY_CONTROLLER_MINOR_DEVICE_CLASS = 0x04
637
- TOY_GAME_MINOR_DEVICE_CLASS = 0x05
638
-
639
- TOY_MINOR_DEVICE_CLASS_NAMES = {
640
- TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
641
- TOY_ROBOT_MINOR_DEVICE_CLASS: 'Robot',
642
- TOY_VEHICLE_MINOR_DEVICE_CLASS: 'Vehicle',
643
- TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS: 'Doll/Action figure',
644
- TOY_CONTROLLER_MINOR_DEVICE_CLASS: 'Controller',
645
- TOY_GAME_MINOR_DEVICE_CLASS: 'Game',
672
+ class ToyMinorDeviceClass(utils.OpenIntEnum):
673
+ UNCATEGORIZED = 0x00
674
+ ROBOT = 0x01
675
+ VEHICLE = 0x02
676
+ DOLL_ACTION_FIGURE = 0x03
677
+ CONTROLLER = 0x04
678
+ GAME = 0x05
679
+
680
+ TOY_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[ToyMinorDeviceClass, str]] = {
681
+ ToyMinorDeviceClass.UNCATEGORIZED: 'Uncategorized',
682
+ ToyMinorDeviceClass.ROBOT: 'Robot',
683
+ ToyMinorDeviceClass.VEHICLE: 'Vehicle',
684
+ ToyMinorDeviceClass.DOLL_ACTION_FIGURE: 'Doll/Action figure',
685
+ ToyMinorDeviceClass.CONTROLLER: 'Controller',
686
+ ToyMinorDeviceClass.GAME: 'Game',
646
687
  }
647
688
 
648
- HEALTH_UNDEFINED_MINOR_DEVICE_CLASS = 0x00
649
- HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS = 0x01
650
- HEALTH_THERMOMETER_MINOR_DEVICE_CLASS = 0x02
651
- HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS = 0x03
652
- HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS = 0x04
653
- HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS = 0x05
654
- HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS = 0x06
655
- HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS = 0x07
656
- HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS = 0x08
657
- HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS = 0x09
658
- HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS = 0x0A
659
- HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS = 0x0B
660
- HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS = 0x0C
661
- HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS = 0x0D
662
- HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS = 0x0E
663
- HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS = 0x0F
664
-
665
- HEALTH_MINOR_DEVICE_CLASS_NAMES = {
666
- HEALTH_UNDEFINED_MINOR_DEVICE_CLASS: 'Undefined',
667
- HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS: 'Blood Pressure Monitor',
668
- HEALTH_THERMOMETER_MINOR_DEVICE_CLASS: 'Thermometer',
669
- HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS: 'Weighing Scale',
670
- HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS: 'Glucose Meter',
671
- HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS: 'Pulse Oximeter',
672
- HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS: 'Heart/Pulse Rate Monitor',
673
- HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS: 'Health Data Display',
674
- HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS: 'Step Counter',
675
- HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS: 'Body Composition Analyzer',
676
- HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS: 'Peak Flow Monitor',
677
- HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS: 'Medication Monitor',
678
- HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS: 'Knee Prosthesis',
679
- HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS: 'Ankle Prosthesis',
680
- HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS: 'Generic Health Manager',
681
- HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS: 'Personal Mobility Device',
689
+ class HealthMinorDeviceClass(utils.OpenIntEnum):
690
+ UNDEFINED = 0x00
691
+ BLOOD_PRESSURE_MONITOR = 0x01
692
+ THERMOMETER = 0x02
693
+ WEIGHING_SCALE = 0x03
694
+ GLUCOSE_METER = 0x04
695
+ PULSE_OXIMETER = 0x05
696
+ HEART_PULSE_RATE_MONITOR = 0x06
697
+ HEALTH_DATA_DISPLAY = 0x07
698
+ STEP_COUNTER = 0x08
699
+ BODY_COMPOSITION_ANALYZER = 0x09
700
+ PEAK_FLOW_MONITOR = 0x0A
701
+ MEDICATION_MONITOR = 0x0B
702
+ KNEE_PROSTHESIS = 0x0C
703
+ ANKLE_PROSTHESIS = 0x0D
704
+ GENERIC_HEALTH_MANAGER = 0x0E
705
+ PERSONAL_MOBILITY_DEVICE = 0x0F
706
+
707
+ HEALTH_MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[HealthMinorDeviceClass, str]] = {
708
+ HealthMinorDeviceClass.UNDEFINED: 'Undefined',
709
+ HealthMinorDeviceClass.BLOOD_PRESSURE_MONITOR: 'Blood Pressure Monitor',
710
+ HealthMinorDeviceClass.THERMOMETER: 'Thermometer',
711
+ HealthMinorDeviceClass.WEIGHING_SCALE: 'Weighing Scale',
712
+ HealthMinorDeviceClass.GLUCOSE_METER: 'Glucose Meter',
713
+ HealthMinorDeviceClass.PULSE_OXIMETER: 'Pulse Oximeter',
714
+ HealthMinorDeviceClass.HEART_PULSE_RATE_MONITOR: 'Heart/Pulse Rate Monitor',
715
+ HealthMinorDeviceClass.HEALTH_DATA_DISPLAY: 'Health Data Display',
716
+ HealthMinorDeviceClass.STEP_COUNTER: 'Step Counter',
717
+ HealthMinorDeviceClass.BODY_COMPOSITION_ANALYZER: 'Body Composition Analyzer',
718
+ HealthMinorDeviceClass.PEAK_FLOW_MONITOR: 'Peak Flow Monitor',
719
+ HealthMinorDeviceClass.MEDICATION_MONITOR: 'Medication Monitor',
720
+ HealthMinorDeviceClass.KNEE_PROSTHESIS: 'Knee Prosthesis',
721
+ HealthMinorDeviceClass.ANKLE_PROSTHESIS: 'Ankle Prosthesis',
722
+ HealthMinorDeviceClass.GENERIC_HEALTH_MANAGER: 'Generic Health Manager',
723
+ HealthMinorDeviceClass.PERSONAL_MOBILITY_DEVICE: 'Personal Mobility Device',
682
724
  }
683
725
 
684
- MINOR_DEVICE_CLASS_NAMES = {
685
- COMPUTER_MAJOR_DEVICE_CLASS: COMPUTER_MINOR_DEVICE_CLASS_NAMES,
686
- PHONE_MAJOR_DEVICE_CLASS: PHONE_MINOR_DEVICE_CLASS_NAMES,
687
- AUDIO_VIDEO_MAJOR_DEVICE_CLASS: AUDIO_VIDEO_MINOR_DEVICE_CLASS_NAMES,
688
- PERIPHERAL_MAJOR_DEVICE_CLASS: PERIPHERAL_MINOR_DEVICE_CLASS_NAMES,
689
- WEARABLE_MAJOR_DEVICE_CLASS: WEARABLE_MINOR_DEVICE_CLASS_NAMES,
690
- TOY_MAJOR_DEVICE_CLASS: TOY_MINOR_DEVICE_CLASS_NAMES,
691
- HEALTH_MAJOR_DEVICE_CLASS: HEALTH_MINOR_DEVICE_CLASS_NAMES,
726
+ MINOR_DEVICE_CLASS_LABELS: ClassVar[dict[MajorDeviceClass, dict[Any, str]]] = {
727
+ MajorDeviceClass.COMPUTER: COMPUTER_MINOR_DEVICE_CLASS_LABELS,
728
+ MajorDeviceClass.PHONE: PHONE_MINOR_DEVICE_CLASS_LABELS,
729
+ MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: LAN_NETWORK_MINOR_DEVICE_CLASS_LABELS,
730
+ MajorDeviceClass.AUDIO_VIDEO: AUDIO_VIDEO_MINOR_DEVICE_CLASS_LABELS,
731
+ MajorDeviceClass.PERIPHERAL: PERIPHERAL_MINOR_DEVICE_CLASS_LABELS,
732
+ MajorDeviceClass.WEARABLE: WEARABLE_MINOR_DEVICE_CLASS_LABELS,
733
+ MajorDeviceClass.TOY: TOY_MINOR_DEVICE_CLASS_LABELS,
734
+ MajorDeviceClass.HEALTH: HEALTH_MINOR_DEVICE_CLASS_LABELS,
735
+ }
736
+
737
+ _MINOR_DEVICE_CLASSES: ClassVar[dict[MajorDeviceClass, Type]] = {
738
+ MajorDeviceClass.COMPUTER: ComputerMinorDeviceClass,
739
+ MajorDeviceClass.PHONE: PhoneMinorDeviceClass,
740
+ MajorDeviceClass.LAN_NETWORK_ACCESS_POINT: LanNetworkMinorDeviceClass,
741
+ MajorDeviceClass.AUDIO_VIDEO: AudioVideoMinorDeviceClass,
742
+ MajorDeviceClass.PERIPHERAL: PeripheralMinorDeviceClass,
743
+ MajorDeviceClass.WEARABLE: WearableMinorDeviceClass,
744
+ MajorDeviceClass.TOY: ToyMinorDeviceClass,
745
+ MajorDeviceClass.HEALTH: HealthMinorDeviceClass,
692
746
  }
693
747
 
748
+ # fmt: on
749
+
750
+ major_service_classes: MajorServiceClasses
751
+ major_device_class: MajorDeviceClass
752
+ minor_device_class: Union[
753
+ ComputerMinorDeviceClass,
754
+ PhoneMinorDeviceClass,
755
+ LanNetworkMinorDeviceClass,
756
+ AudioVideoMinorDeviceClass,
757
+ PeripheralMinorDeviceClass,
758
+ WearableMinorDeviceClass,
759
+ ToyMinorDeviceClass,
760
+ HealthMinorDeviceClass,
761
+ int,
762
+ ]
763
+
764
+ @classmethod
765
+ def from_int(cls, class_of_device: int) -> Self:
766
+ major_service_classes = cls.MajorServiceClasses(class_of_device >> 13 & 0x7FF)
767
+ major_device_class = cls.MajorDeviceClass(class_of_device >> 8 & 0x1F)
768
+ minor_device_class_int = class_of_device >> 2 & 0x3F
769
+ if minor_device_class_object := cls._MINOR_DEVICE_CLASSES.get(
770
+ major_device_class
771
+ ):
772
+ minor_device_class = minor_device_class_object(minor_device_class_int)
773
+ else:
774
+ minor_device_class = minor_device_class_int
775
+ return cls(major_service_classes, major_device_class, minor_device_class)
776
+
777
+ def __int__(self) -> int:
778
+ return (
779
+ self.major_service_classes << 13
780
+ | self.major_device_class << 8
781
+ | self.minor_device_class << 2
782
+ )
783
+
784
+ def __str__(self) -> str:
785
+ minor_device_class_name = (
786
+ self.minor_device_class.name
787
+ if hasattr(self.minor_device_class, 'name')
788
+ else hex(self.minor_device_class)
789
+ )
790
+ return (
791
+ f"ClassOfDevice({self.major_service_classes.composite_name},"
792
+ f"{self.major_device_class.name}/{minor_device_class_name})"
793
+ )
794
+
795
+ def major_service_classes_labels(self) -> str:
796
+ return "|".join(
797
+ bit_flags_to_strings(
798
+ self.major_service_classes, self.MAJOR_SERVICE_CLASS_LABELS
799
+ )
800
+ )
801
+
802
+ def major_device_class_label(self) -> str:
803
+ return name_or_number(
804
+ cast(dict[int, str], self.MAJOR_DEVICE_CLASS_LABELS),
805
+ self.major_device_class,
806
+ )
807
+
808
+ def minor_device_class_label(self) -> str:
809
+ class_names = self.MINOR_DEVICE_CLASS_LABELS.get(self.major_device_class)
810
+ if class_names is None:
811
+ return f'#{self.minor_device_class:02X}'
812
+ return name_or_number(class_names, self.minor_device_class)
813
+
814
+
815
+ # -----------------------------------------------------------------------------
816
+ # DeviceClass
817
+ # -----------------------------------------------------------------------------
818
+ class DeviceClass:
819
+ """Legacy only. Use ClassOfDevice instead"""
820
+
821
+ # fmt: off
822
+ # pylint: disable=line-too-long
823
+
824
+ # Major Service Classes (flags combined with OR)
825
+ LIMITED_DISCOVERABLE_MODE_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.LIMITED_DISCOVERABLE_MODE
826
+ LE_AUDIO_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.LE_AUDIO
827
+ POSITIONING_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.POSITIONING
828
+ NETWORKING_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.NETWORKING
829
+ RENDERING_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.RENDERING
830
+ CAPTURING_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.CAPTURING
831
+ OBJECT_TRANSFER_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.OBJECT_TRANSFER
832
+ AUDIO_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.AUDIO
833
+ TELEPHONY_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.TELEPHONY
834
+ INFORMATION_SERVICE_CLASS = ClassOfDevice.MajorServiceClasses.INFORMATION
835
+
836
+ # Major Device Classes
837
+ MISCELLANEOUS_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.MISCELLANEOUS
838
+ COMPUTER_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.COMPUTER
839
+ PHONE_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.PHONE
840
+ LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.LAN_NETWORK_ACCESS_POINT
841
+ AUDIO_VIDEO_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.AUDIO_VIDEO
842
+ PERIPHERAL_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.PERIPHERAL
843
+ IMAGING_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.IMAGING
844
+ WEARABLE_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.WEARABLE
845
+ TOY_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.TOY
846
+ HEALTH_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.HEALTH
847
+ UNCATEGORIZED_MAJOR_DEVICE_CLASS = ClassOfDevice.MajorDeviceClass.UNCATEGORIZED
848
+
849
+ COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.UNCATEGORIZED
850
+ COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.DESKTOP_WORKSTATION
851
+ COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.SERVER_CLASS_COMPUTER
852
+ COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.LAPTOP_COMPUTER
853
+ COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.HANDHELD_PC_PDA
854
+ COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.PALM_SIZE_PC_PDA
855
+ COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.WEARABLE_COMPUTER
856
+ COMPUTER_TABLET_MINOR_DEVICE_CLASS = ClassOfDevice.ComputerMinorDeviceClass.TABLET
857
+
858
+ PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS = ClassOfDevice.PhoneMinorDeviceClass.UNCATEGORIZED
859
+ PHONE_CELLULAR_MINOR_DEVICE_CLASS = ClassOfDevice.PhoneMinorDeviceClass.CELLULAR
860
+ PHONE_CORDLESS_MINOR_DEVICE_CLASS = ClassOfDevice.PhoneMinorDeviceClass.CORDLESS
861
+ PHONE_SMARTPHONE_MINOR_DEVICE_CLASS = ClassOfDevice.PhoneMinorDeviceClass.SMARTPHONE
862
+ PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS = ClassOfDevice.PhoneMinorDeviceClass.WIRED_MODEM_OR_VOICE_GATEWAY
863
+ PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS = ClassOfDevice.PhoneMinorDeviceClass.COMMON_ISDN
864
+
865
+ AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.UNCATEGORIZED
866
+ AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.WEARABLE_HEADSET_DEVICE
867
+ AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.HANDS_FREE_DEVICE
868
+ AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.MICROPHONE
869
+ AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.LOUDSPEAKER
870
+ AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.HEADPHONES
871
+ AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.PORTABLE_AUDIO
872
+ AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.CAR_AUDIO
873
+ AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.SET_TOP_BOX
874
+ AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.HIFI_AUDIO_DEVICE
875
+ AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.VCR
876
+ AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_CAMERA
877
+ AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.CAMCORDER
878
+ AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_MONITOR
879
+ AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_DISPLAY_AND_LOUDSPEAKER
880
+ AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.VIDEO_CONFERENCING
881
+ AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS = ClassOfDevice.AudioVideoMinorDeviceClass.GAMING_OR_TOY
882
+
883
+ PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.UNCATEGORIZED
884
+ PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.KEYBOARD
885
+ PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.POINTING_DEVICE
886
+ PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.COMBO_KEYBOARD_POINTING_DEVICE
887
+ PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.JOYSTICK
888
+ PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.GAMEPAD
889
+ PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.REMOTE_CONTROL
890
+ PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.SENSING_DEVICE
891
+ PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.DIGITIZER_TABLET
892
+ PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.CARD_READER
893
+ PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.DIGITAL_PEN
894
+ PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.HANDHELD_SCANNER
895
+ PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.PeripheralMinorDeviceClass.HANDHELD_GESTURAL_INPUT_DEVICE
896
+
897
+ WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS = ClassOfDevice.WearableMinorDeviceClass.UNCATEGORIZED
898
+ WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS = ClassOfDevice.WearableMinorDeviceClass.WRISTWATCH
899
+ WEARABLE_PAGER_MINOR_DEVICE_CLASS = ClassOfDevice.WearableMinorDeviceClass.PAGER
900
+ WEARABLE_JACKET_MINOR_DEVICE_CLASS = ClassOfDevice.WearableMinorDeviceClass.JACKET
901
+ WEARABLE_HELMET_MINOR_DEVICE_CLASS = ClassOfDevice.WearableMinorDeviceClass.HELMET
902
+ WEARABLE_GLASSES_MINOR_DEVICE_CLASS = ClassOfDevice.WearableMinorDeviceClass.GLASSES
903
+
904
+ TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS = ClassOfDevice.ToyMinorDeviceClass.UNCATEGORIZED
905
+ TOY_ROBOT_MINOR_DEVICE_CLASS = ClassOfDevice.ToyMinorDeviceClass.ROBOT
906
+ TOY_VEHICLE_MINOR_DEVICE_CLASS = ClassOfDevice.ToyMinorDeviceClass.VEHICLE
907
+ TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS = ClassOfDevice.ToyMinorDeviceClass.DOLL_ACTION_FIGURE
908
+ TOY_CONTROLLER_MINOR_DEVICE_CLASS = ClassOfDevice.ToyMinorDeviceClass.CONTROLLER
909
+ TOY_GAME_MINOR_DEVICE_CLASS = ClassOfDevice.ToyMinorDeviceClass.GAME
910
+
911
+ HEALTH_UNDEFINED_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.UNDEFINED
912
+ HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.BLOOD_PRESSURE_MONITOR
913
+ HEALTH_THERMOMETER_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.THERMOMETER
914
+ HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.WEIGHING_SCALE
915
+ HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.GLUCOSE_METER
916
+ HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.PULSE_OXIMETER
917
+ HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.HEART_PULSE_RATE_MONITOR
918
+ HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.HEALTH_DATA_DISPLAY
919
+ HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.STEP_COUNTER
920
+ HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.BODY_COMPOSITION_ANALYZER
921
+ HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.PEAK_FLOW_MONITOR
922
+ HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.MEDICATION_MONITOR
923
+ HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.KNEE_PROSTHESIS
924
+ HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.ANKLE_PROSTHESIS
925
+ HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.GENERIC_HEALTH_MANAGER
926
+ HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS = ClassOfDevice.HealthMinorDeviceClass.PERSONAL_MOBILITY_DEVICE
927
+
694
928
  # fmt: on
695
929
  # pylint: enable=line-too-long
696
930
 
@@ -711,16 +945,16 @@ class DeviceClass:
711
945
  @staticmethod
712
946
  def service_class_labels(service_class_flags):
713
947
  return bit_flags_to_strings(
714
- service_class_flags, DeviceClass.SERVICE_CLASS_LABELS
948
+ service_class_flags, ClassOfDevice.MAJOR_SERVICE_CLASS_LABELS
715
949
  )
716
950
 
717
951
  @staticmethod
718
952
  def major_device_class_name(device_class):
719
- return name_or_number(DeviceClass.MAJOR_DEVICE_CLASS_NAMES, device_class)
953
+ return name_or_number(ClassOfDevice.MAJOR_DEVICE_CLASS_LABELS, device_class)
720
954
 
721
955
  @staticmethod
722
956
  def minor_device_class_name(major_device_class, minor_device_class):
723
- class_names = DeviceClass.MINOR_DEVICE_CLASS_NAMES.get(major_device_class)
957
+ class_names = ClassOfDevice.MINOR_DEVICE_CLASS_LABELS.get(major_device_class)
724
958
  if class_names is None:
725
959
  return f'#{minor_device_class:02X}'
726
960
  return name_or_number(class_names, minor_device_class)
@@ -1255,6 +1489,10 @@ class Appearance:
1255
1489
  category = cls.Category(appearance >> 6)
1256
1490
  return cls(category, appearance & 0x3F)
1257
1491
 
1492
+ @classmethod
1493
+ def from_bytes(cls, data: bytes):
1494
+ return cls.from_int(int.from_bytes(data, byteorder="little"))
1495
+
1258
1496
  def __init__(self, category: Category, subcategory: int) -> None:
1259
1497
  self.category = category
1260
1498
  if subcategory_class := self.SUBCATEGORY_CLASSES.get(category):
@@ -1265,6 +1503,9 @@ class Appearance:
1265
1503
  def __int__(self) -> int:
1266
1504
  return self.category << 6 | self.subcategory
1267
1505
 
1506
+ def __bytes__(self) -> bytes:
1507
+ return int(self).to_bytes(2, byteorder="little")
1508
+
1268
1509
  def __repr__(self) -> str:
1269
1510
  return (
1270
1511
  'Appearance('
@@ -1276,6 +1517,61 @@ class Appearance:
1276
1517
  def __str__(self) -> str:
1277
1518
  return f'{self.category.name}/{self.subcategory.name}'
1278
1519
 
1520
+ def __eq__(self, value: Any) -> bool:
1521
+ return (
1522
+ isinstance(value, Appearance)
1523
+ and self.category == value.category
1524
+ and self.subcategory == value.subcategory
1525
+ )
1526
+
1527
+
1528
+ # -----------------------------------------------------------------------------
1529
+ # Classes representing "Data Types" defined in
1530
+ # "Supplement to the Bluetooth Core Specification", Part A
1531
+ # -----------------------------------------------------------------------------
1532
+ # TODO: use ABC, figure out multiple base classes with metaclasses
1533
+ class DataType:
1534
+ # Human-reable label/name for the type
1535
+ label = ""
1536
+
1537
+ # Advertising Data type ID for this data type.
1538
+ ad_type: AdvertisingData.Type = 0 # type: ignore
1539
+
1540
+ def value_string(self) -> str:
1541
+ """Human-reable string representation of the value."""
1542
+ raise NotImplementedError()
1543
+
1544
+ def to_string(self, use_label: bool = False) -> str:
1545
+ if use_label:
1546
+ return f"[{self.label}]: {self.value_string()}"
1547
+
1548
+ return f"{self.__class__.__name__}({self.value_string()})"
1549
+
1550
+ @classmethod
1551
+ def from_advertising_data(cls, advertising_data: AdvertisingData) -> Optional[Self]:
1552
+ if (data := advertising_data.get(cls.ad_type, raw=True)) is None:
1553
+ return None
1554
+
1555
+ return cls.from_bytes(data)
1556
+
1557
+ @classmethod
1558
+ def all_from_advertising_data(cls, advertising_data: AdvertisingData) -> list[Self]:
1559
+ return [
1560
+ cls.from_bytes(data)
1561
+ for data in advertising_data.get_all(cls.ad_type, raw=True)
1562
+ ]
1563
+
1564
+ @classmethod
1565
+ def from_bytes(cls, data: bytes) -> Self:
1566
+ """Create an instance from a serialized form."""
1567
+ raise NotImplementedError()
1568
+
1569
+ def __bytes__(self) -> bytes:
1570
+ raise NotImplementedError()
1571
+
1572
+ def __str__(self) -> str:
1573
+ return self.to_string()
1574
+
1279
1575
 
1280
1576
  # -----------------------------------------------------------------------------
1281
1577
  # Advertising Data
@@ -1351,7 +1647,7 @@ class AdvertisingData:
1351
1647
  THREE_D_INFORMATION_DATA = 0x3D
1352
1648
  MANUFACTURER_SPECIFIC_DATA = 0xFF
1353
1649
 
1354
- class Flags(enum.IntFlag):
1650
+ class Flags(utils.CompatibleIntFlag):
1355
1651
  LE_LIMITED_DISCOVERABLE_MODE = 1 << 0
1356
1652
  LE_GENERAL_DISCOVERABLE_MODE = 1 << 1
1357
1653
  BR_EDR_NOT_SUPPORTED = 1 << 2
@@ -1419,15 +1715,25 @@ class AdvertisingData:
1419
1715
  BR_EDR_CONTROLLER_FLAG = Flags.SIMULTANEOUS_LE_BR_EDR_CAPABLE
1420
1716
  BR_EDR_HOST_FLAG = 0x10 # Deprecated
1421
1717
 
1422
- ad_structures: list[tuple[int, bytes]]
1718
+ ad_structures: list[tuple[AdvertisingData.Type, bytes]]
1423
1719
 
1424
1720
  # fmt: on
1425
1721
  # pylint: enable=line-too-long
1426
1722
 
1427
- def __init__(self, ad_structures: Optional[list[tuple[int, bytes]]] = None) -> None:
1723
+ def __init__(
1724
+ self,
1725
+ ad_structures: Optional[Iterable[Union[tuple[int, bytes], DataType]]] = None,
1726
+ ) -> None:
1428
1727
  if ad_structures is None:
1429
1728
  ad_structures = []
1430
- self.ad_structures = ad_structures[:]
1729
+ self.ad_structures = [
1730
+ (
1731
+ (element.ad_type, bytes(element))
1732
+ if isinstance(element, DataType)
1733
+ else (AdvertisingData.Type(element[0]), element[1])
1734
+ )
1735
+ for element in ad_structures
1736
+ ]
1431
1737
 
1432
1738
  @classmethod
1433
1739
  def from_bytes(cls, data: bytes) -> AdvertisingData:
@@ -1444,11 +1750,10 @@ class AdvertisingData:
1444
1750
  'LE Limited Discoverable Mode',
1445
1751
  'LE General Discoverable Mode',
1446
1752
  'BR/EDR Not Supported',
1447
- 'Simultaneous LE and BR/EDR (Controller)',
1448
- 'Simultaneous LE and BR/EDR (Host)',
1753
+ 'Simultaneous LE and BR/EDR',
1449
1754
  ]
1450
1755
  )
1451
- return ','.join(bit_flags_to_strings(flags, flag_names))
1756
+ return ', '.join(bit_flags_to_strings(flags, flag_names))
1452
1757
 
1453
1758
  @staticmethod
1454
1759
  def uuid_list_to_objects(ad_data: bytes, uuid_size: int) -> list[UUID]:
@@ -1604,7 +1909,7 @@ class AdvertisingData:
1604
1909
  if length > 0:
1605
1910
  ad_type = data[offset]
1606
1911
  ad_data = data[offset + 1 : offset + length]
1607
- self.ad_structures.append((ad_type, ad_data))
1912
+ self.ad_structures.append((AdvertisingData.Type(ad_type), ad_data))
1608
1913
  offset += length
1609
1914
 
1610
1915
  @overload
@@ -1623,6 +1928,7 @@ class AdvertisingData:
1623
1928
  ],
1624
1929
  raw: Literal[False] = False,
1625
1930
  ) -> list[list[UUID]]: ...
1931
+
1626
1932
  @overload
1627
1933
  def get_all(
1628
1934
  self,
@@ -1633,6 +1939,7 @@ class AdvertisingData:
1633
1939
  ],
1634
1940
  raw: Literal[False] = False,
1635
1941
  ) -> list[tuple[UUID, bytes]]: ...
1942
+
1636
1943
  @overload
1637
1944
  def get_all(
1638
1945
  self,
@@ -1644,6 +1951,7 @@ class AdvertisingData:
1644
1951
  ],
1645
1952
  raw: Literal[False] = False,
1646
1953
  ) -> list[str]: ...
1954
+
1647
1955
  @overload
1648
1956
  def get_all(
1649
1957
  self,
@@ -1655,26 +1963,31 @@ class AdvertisingData:
1655
1963
  ],
1656
1964
  raw: Literal[False] = False,
1657
1965
  ) -> list[int]: ...
1966
+
1658
1967
  @overload
1659
1968
  def get_all(
1660
1969
  self,
1661
1970
  type_id: Literal[AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE,],
1662
1971
  raw: Literal[False] = False,
1663
1972
  ) -> list[tuple[int, int]]: ...
1973
+
1664
1974
  @overload
1665
1975
  def get_all(
1666
1976
  self,
1667
1977
  type_id: Literal[AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA,],
1668
1978
  raw: Literal[False] = False,
1669
1979
  ) -> list[tuple[int, bytes]]: ...
1980
+
1670
1981
  @overload
1671
1982
  def get_all(
1672
1983
  self,
1673
1984
  type_id: Literal[AdvertisingData.Type.APPEARANCE,],
1674
1985
  raw: Literal[False] = False,
1675
1986
  ) -> list[Appearance]: ...
1987
+
1676
1988
  @overload
1677
1989
  def get_all(self, type_id: int, raw: Literal[True]) -> list[bytes]: ...
1990
+
1678
1991
  @overload
1679
1992
  def get_all(
1680
1993
  self, type_id: int, raw: bool = False
@@ -1682,7 +1995,7 @@ class AdvertisingData:
1682
1995
 
1683
1996
  def get_all(self, type_id: int, raw: bool = False) -> list[AdvertisingDataObject]: # type: ignore[misc]
1684
1997
  '''
1685
- Get Advertising Data Structure(s) with a given type
1998
+ Get all advertising data elements as simple AdvertisingDataObject objects.
1686
1999
 
1687
2000
  Returns a (possibly empty) list of matches.
1688
2001
  '''
@@ -1708,6 +2021,7 @@ class AdvertisingData:
1708
2021
  ],
1709
2022
  raw: Literal[False] = False,
1710
2023
  ) -> Optional[list[UUID]]: ...
2024
+
1711
2025
  @overload
1712
2026
  def get(
1713
2027
  self,
@@ -1718,6 +2032,7 @@ class AdvertisingData:
1718
2032
  ],
1719
2033
  raw: Literal[False] = False,
1720
2034
  ) -> Optional[tuple[UUID, bytes]]: ...
2035
+
1721
2036
  @overload
1722
2037
  def get(
1723
2038
  self,
@@ -1729,6 +2044,7 @@ class AdvertisingData:
1729
2044
  ],
1730
2045
  raw: Literal[False] = False,
1731
2046
  ) -> Optional[Optional[str]]: ...
2047
+
1732
2048
  @overload
1733
2049
  def get(
1734
2050
  self,
@@ -1740,26 +2056,31 @@ class AdvertisingData:
1740
2056
  ],
1741
2057
  raw: Literal[False] = False,
1742
2058
  ) -> Optional[int]: ...
2059
+
1743
2060
  @overload
1744
2061
  def get(
1745
2062
  self,
1746
2063
  type_id: Literal[AdvertisingData.Type.PERIPHERAL_CONNECTION_INTERVAL_RANGE,],
1747
2064
  raw: Literal[False] = False,
1748
2065
  ) -> Optional[tuple[int, int]]: ...
2066
+
1749
2067
  @overload
1750
2068
  def get(
1751
2069
  self,
1752
2070
  type_id: Literal[AdvertisingData.Type.MANUFACTURER_SPECIFIC_DATA,],
1753
2071
  raw: Literal[False] = False,
1754
2072
  ) -> Optional[tuple[int, bytes]]: ...
2073
+
1755
2074
  @overload
1756
2075
  def get(
1757
2076
  self,
1758
2077
  type_id: Literal[AdvertisingData.Type.APPEARANCE,],
1759
2078
  raw: Literal[False] = False,
1760
2079
  ) -> Optional[Appearance]: ...
2080
+
1761
2081
  @overload
1762
2082
  def get(self, type_id: int, raw: Literal[True]) -> Optional[bytes]: ...
2083
+
1763
2084
  @overload
1764
2085
  def get(
1765
2086
  self, type_id: int, raw: bool = False
@@ -1767,7 +2088,7 @@ class AdvertisingData:
1767
2088
 
1768
2089
  def get(self, type_id: int, raw: bool = False) -> Optional[AdvertisingDataObject]:
1769
2090
  '''
1770
- Get Advertising Data Structure(s) with a given type
2091
+ Get advertising data as a simple AdvertisingDataObject object.
1771
2092
 
1772
2093
  Returns the first entry, or None if no structure matches.
1773
2094
  '''
@@ -1789,23 +2110,6 @@ class AdvertisingData:
1789
2110
  return self.to_string()
1790
2111
 
1791
2112
 
1792
- # -----------------------------------------------------------------------------
1793
- # Connection Parameters
1794
- # -----------------------------------------------------------------------------
1795
- class ConnectionParameters:
1796
- def __init__(self, connection_interval, peripheral_latency, supervision_timeout):
1797
- self.connection_interval = connection_interval
1798
- self.peripheral_latency = peripheral_latency
1799
- self.supervision_timeout = supervision_timeout
1800
-
1801
- def __str__(self):
1802
- return (
1803
- f'ConnectionParameters(connection_interval={self.connection_interval}, '
1804
- f'peripheral_latency={self.peripheral_latency}, '
1805
- f'supervision_timeout={self.supervision_timeout}'
1806
- )
1807
-
1808
-
1809
2113
  # -----------------------------------------------------------------------------
1810
2114
  # Connection PHY
1811
2115
  # -----------------------------------------------------------------------------
@@ -1822,7 +2126,22 @@ class ConnectionPHY:
1822
2126
  # LE Role
1823
2127
  # -----------------------------------------------------------------------------
1824
2128
  class LeRole(enum.IntEnum):
1825
- PERIPHERAL_ONLY = 0x00
1826
- CENTRAL_ONLY = 0x01
2129
+ # fmt: off
2130
+ PERIPHERAL_ONLY = 0x00
2131
+ CENTRAL_ONLY = 0x01
1827
2132
  BOTH_PERIPHERAL_PREFERRED = 0x02
1828
- BOTH_CENTRAL_PREFERRED = 0x03
2133
+ BOTH_CENTRAL_PREFERRED = 0x03
2134
+
2135
+
2136
+ # -----------------------------------------------------------------------------
2137
+ # Security Manager OOB Flag
2138
+ # -----------------------------------------------------------------------------
2139
+ class SecurityManagerOutOfBandFlag(utils.CompatibleIntFlag):
2140
+ """
2141
+ See Supplement to the Bluetooth Core Specification, Part A
2142
+ 1.7 SECURITY MANAGER OUT OF BAND (OOB)
2143
+ """
2144
+
2145
+ OOB_FLAGS_FIELD = 1 << 0
2146
+ LE_SUPPORTED = 1 << 1
2147
+ ADDRESS_TYPE = 1 << 3