diff --git a/jc/parsers/bluetoothctl.py b/jc/parsers/bluetoothctl.py index 6848b886..84de1528 100644 --- a/jc/parsers/bluetoothctl.py +++ b/jc/parsers/bluetoothctl.py @@ -65,7 +65,8 @@ a controller and a device but there might be fields corresponding to one entity. "rssi": int, "txpower": int, "uuids": array, - "modalias": string + "modalias": string, + "battery_percentage": int } ] @@ -96,7 +97,8 @@ Examples: "Headset HS (00001831-0000-1000-8000-00805f9b34fb)" ], "rssi": -52, - "txpower": 4 + "txpower": 4, + "battery_percentage": 70 } ] """ @@ -161,7 +163,8 @@ try: "rssi": int, "txpower": int, "uuids": List[str], - "modalias": str + "modalias": str, + "battery_percentage": int }, ) except ImportError: @@ -280,6 +283,7 @@ _device_line_pattern = ( + r"|\s*Modalias:\s*(?P.+)" + r"|\s*RSSI:\s*(?P.+)" + r"|\s*TxPower:\s*(?P.+)" + + r"|\s*Battery\sPercentage:\s*0[xX][0-9a-fA-F]*\s*\((?P[0-9]+)\)" + r"|\s*UUID:\s*(?P.+))" ) @@ -317,7 +321,8 @@ def _parse_device(next_lines: List[str], quiet: bool) -> Optional[Device]: "rssi": 0, "txpower": 0, "uuids": [], - "modalias": '' + "modalias": '', + "battery_percentage": 0 } if name.endswith("(public)"): @@ -381,6 +386,13 @@ def _parse_device(next_lines: List[str], quiet: bool) -> Optional[Device]: device["uuids"].append(matches["uuid"]) elif matches["modalias"]: device["modalias"] = matches["modalias"] + elif matches["battery_percentage"]: + battery_percentage = matches["battery_percentage"] + try: + device["battery_percentage"] = int(battery_percentage) + except ValueError: + if not quiet: + jc.utils.warning_message([f"{next_line} : battery_percentage - {battery_percentage} is not int-able"]) return device diff --git a/tests/fixtures/generic/bluetoothctl_device_with_battery.out b/tests/fixtures/generic/bluetoothctl_device_with_battery.out new file mode 100644 index 00000000..ab56b4d3 --- /dev/null +++ b/tests/fixtures/generic/bluetoothctl_device_with_battery.out @@ -0,0 +1,17 @@ +Device 67:F6:B4:0E:5C:94 (public) + Name: WH-1000XM3 + Alias: WH-1000XM3 + Class: 0x11240404 (2360324) + Icon: audio-headset + Paired: yes + Bonded: yes + Trusted: yes + Blocked: no + Connected: yes + LegacyPairing: no + UUID: Vendor specific (fd096fad-eed7-4504-943b-5fa1c0e761b2) + UUID: Vendor specific (03c57488-f7b6-45a3-8a23-ed4a890075cd) + UUID: Vendor specific (77a369ae-e453-4ff7-bc84-dc8f411eaa6a) + UUID: Vendor specific (8c274bd0-e7bd-4ed0-a391-55465e38005c) + Modalias: usb:v052Cp0DC3d1426 + Battery Percentage: 0x46 (70) \ No newline at end of file diff --git a/tests/test_bluetoothctl.py b/tests/test_bluetoothctl.py index 937f5616..f573f950 100644 --- a/tests/test_bluetoothctl.py +++ b/tests/test_bluetoothctl.py @@ -177,6 +177,49 @@ class BluetoothctlTests(unittest.TestCase): "txpower": 4 } + if actual: + for k, v in expected.items(): + self.assertEqual(v, actual[0][k], f"Device regex failed on {k}") + def test_bluetoothctl_device_with_battery(self): + """ + Test 'bluetoothctl' with device that has a battery + """ + + with open("tests/fixtures/generic/bluetoothctl_device_with_battery.out", "r") as f: + output = f.read() + + actual = parse(output, quiet=True) + + self.assertIsNotNone(actual) + self.assertIsNotNone(actual[0], actual) + + expected = { + "name": "WH-1000XM3", + "is_public": True, + "is_random": False, + "address": "67:F6:B4:0E:5C:94", + "alias": "WH-1000XM3", + "appearance": "", + "class": "0x11240404 (2360324)", + "icon": "audio-headset", + "paired": "yes", + "bonded": "yes", + "trusted": "yes", + "blocked": "no", + "connected": "yes", + "legacy_pairing": "no", + "rssi": 0, + "txpower": 0, + "uuids": [ + "Vendor specific (fd096fad-eed7-4504-943b-5fa1c0e761b2)", + "Vendor specific (03c57488-f7b6-45a3-8a23-ed4a890075cd)", + "Vendor specific (77a369ae-e453-4ff7-bc84-dc8f411eaa6a)", + "Vendor specific (8c274bd0-e7bd-4ed0-a391-55465e38005c)" + ], + "modalias": "usb:v052Cp0DC3d1426", + "battery_percentage": 70 + } + if actual: for k, v in expected.items(): self.assertEqual(v, actual[0][k], f"Device regex failed on {k}")