From 1eea76fe934fc59b5d2b1a56635d0cde695a39a5 Mon Sep 17 00:00:00 2001 From: codeskyblue Date: Mon, 6 May 2024 15:03:39 +0800 Subject: [PATCH] add test for battery() --- README.md | 3 ++ adbutils/_proto.py | 43 ++++++++-------- adbutils/shell.py | 105 ++++++++++++++++++++------------------- tests/adb_server.py | 20 -------- tests/test_adb_server.py | 5 +- tests/test_adb_shell.py | 51 +++++++++++++++++++ 6 files changed, 133 insertions(+), 94 deletions(-) create mode 100644 tests/test_adb_shell.py diff --git a/README.md b/README.md index 4e89733..61ec8c1 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,9 @@ d.root() # adb tcpip d.tcpip(5555) + +print(d.battery()) +BatteryInfo(ac_powered=False, usb_powered=False, wireless_powered=False, dock_powered=False, max_charging_current=0, max_charging_voltage=0, charge_counter=10000, status=4, health=2, present=True, level=100, scale=100, voltage=5000, temperature=25.0, technology='Li-ion') ``` Screenrecord (mp4) diff --git a/adbutils/_proto.py b/adbutils/_proto.py index 4e96148..06baab8 100644 --- a/adbutils/_proto.py +++ b/adbutils/_proto.py @@ -12,7 +12,7 @@ import enum import datetime import pathlib -import typing +from typing import List, NamedTuple, Optional, Union from dataclasses import dataclass @@ -58,35 +58,36 @@ class FileInfo: @dataclass class AppInfo: package_name: str - version_name: typing.Optional[str] - version_code: typing.Optional[int] - flags: typing.Union[str, list] + version_name: Optional[str] + version_code: Optional[int] + flags: Union[str, list] first_install_time: datetime.datetime last_update_time: datetime.datetime signature: str path: str - sub_apk_paths: typing.List[str] + sub_apk_paths: List[str] @dataclass class BatteryInfo: ac_powered: bool usb_powered: bool - wireless_powered: bool - max_charging_current: typing.Optional[int] - max_charging_voltage: typing.Optional[int] - charge_counter: typing.Optional[int] - status: typing.Optional[int] - health: typing.Optional[int] - present: bool - level: typing.Optional[int] - scale: typing.Optional[int] - voltage: typing.Optional[int, float] - temperature: typing.Optional[int, float] - technology: typing.Optional[str] - - -class WindowSize(typing.NamedTuple): + wireless_powered: Optional[bool] + dock_powered: Optional[bool] + max_charging_current: Optional[int] + max_charging_voltage: Optional[int] + charge_counter: Optional[int] + status: Optional[int] + health: Optional[int] + present: Optional[bool] + level: Optional[int] + scale: Optional[int] + voltage: Optional[int] # mV + temperature: Optional[float] # e.g. 25.0 + technology: Optional[str] + + +class WindowSize(NamedTuple): width: int height: int @@ -117,4 +118,4 @@ class AdbDeviceInfo: state: str -StrOrPathLike = typing.Union[str, pathlib.Path] +StrOrPathLike = Union[str, pathlib.Path] diff --git a/adbutils/shell.py b/adbutils/shell.py index a8664ae..e3a1ae5 100644 --- a/adbutils/shell.py +++ b/adbutils/shell.py @@ -441,58 +441,64 @@ def dump_hierarchy(self) -> str: raise AdbError("dump output is not xml", xml_data) return xml_data - def battery(self) -> Optional[BatteryInfo]: + def battery(self) -> BatteryInfo: """ Get battery info - AC powered - Indicates that the device is currently not powered by AC power. If true, it indicates that the device is connected to an AC power adapter. - USB powered - Indicates that the device is currently being powered or charged through the USB interface. - Wireless powered - Indicates that the device is not powered through wireless charging. If wireless charging is supported and currently in use, this will be true. - Max charging current - The maximum charging current supported by the device, usually in microamperes( μ A). - Max charging voltage - The maximum charging voltage supported by the device may be in millivolts (mV). - Charge counter - The cumulative charge count of a battery, usually measured in milliampere hours (mAh) - Status - Battery status code. - Health - Battery health status code. - Present - indicates that the battery is currently detected and installed in the device. - Level - The percentage of current battery level. - Scale - The full scale of the percentage of battery charge, indicating that the battery level is measured using 100 as the standard for full charge. - Voltage - The current voltage of the battery, usually measured in millivolts (mV). - Temperature - Battery temperature, usually measured in degrees Celsius (° C) - Technology - Battery type, like (Li-ion) battery - Returns: BatteryInfo + + Returns: + BatteryInfo + + Details: + AC powered - Indicates that the device is currently not powered by AC power. If true, it indicates that the device is connected to an AC power adapter. + USB powered - Indicates that the device is currently being powered or charged through the USB interface. + Wireless powered - Indicates that the device is not powered through wireless charging. If wireless charging is supported and currently in use, this will be true. + Max charging current - The maximum charging current supported by the device, usually in microamperes( μ A). + Max charging voltage - The maximum charging voltage supported by the device may be in millivolts (mV). + Charge counter - The cumulative charge count of a battery, usually measured in milliampere hours (mAh) + Status - Battery status code. + Health - Battery health status code. + Present - indicates that the battery is currently detected and installed in the device. + Level - The percentage of current battery level. + Scale - The full scale of the percentage of battery charge, indicating that the battery level is measured using 100 as the standard for full charge. + Voltage - The current voltage of the battery, usually measured in millivolts (mV). + Temperature - Battery temperature, usually measured in degrees Celsius (° C) + Technology - Battery type, like (Li-ion) battery """ + def to_bool(v: str) -> bool: + return v == "true" + output = self.shell(["dumpsys", "battery"]) - m_ac_powered = re.search(r"AC powered: (\w+)", output) - ac_powered_status = m_ac_powered.group(1) if m_ac_powered else None - m_usb_powered = re.search(r"USB powered: (\w+)", output) - usb_powered_status = m_usb_powered.group(1) if m_usb_powered else None - m_wireless_powered = re.search(r"Wireless powered: (\w+)", output) - wireless_powered_status = m_wireless_powered.group(1) if m_wireless_powered else None - m_max_charging_current = re.search(r"Max charging current: (\d+)", output) - max_charging_current = m_max_charging_current.group(1) if m_max_charging_current else None - m_max_charging_voltage = re.search(r"Max charging voltage: (\d+)", output) - max_charging_voltage = m_max_charging_voltage.group(1) if m_max_charging_voltage else None - m_charge_counter = re.search(r"Charge counter: (\d+)", output) - charge_counter = m_charge_counter.group(1) if m_charge_counter else None - m_status = re.search(r"status: (\d+)", output) - status = m_status.group(1) if m_status else None - m_health = re.search(r"health: (\d+)", output) - health = m_health.group(1) if m_health else None - m_present = re.search(r"present: (\w+)", output) - present = m_present.group(1) if m_present else None - m_level = re.search(r"level: (\d+)", output) - level = int(m_level.group(1)) if m_level else None - m_scale = re.search(r"scale: (\d+)", output) - scale = int(m_scale.group(1)) if m_scale else None - m_voltage = re.search(r"voltage: (\d+)", output) - voltage = int(m_scale.group(1)) if m_voltage else None - m_temperature = re.search(r"temperature: (\d+)", output) - temperature = m_temperature.group(1) if m_temperature else None - m_technology = re.search(r"technology: \s*(.*)$", output) - technology = m_technology.group(1).strip() if m_technology else None - battery_info = BatteryInfo( - ac_powered=ac_powered_status, - usb_powered=usb_powered_status, - wireless_powered=wireless_powered_status, + shell_kvs = {} + for line in output.splitlines(): + key, val = line.strip().split(':', 1) + shell_kvs[key.strip()] = val.strip() + + def get_key(k: str, map_function): + v = shell_kvs.get(k) + if v is not None: + return map_function(v) + return None + + ac_powered = get_key("AC powered", to_bool) + usb_powered = get_key("USB powered", to_bool) + wireless_powered = get_key("Wireless powered", to_bool) + dock_powered = get_key("Dock powered", to_bool) + max_charging_current = get_key("Max charging current", int) + max_charging_voltage = get_key("Max charging voltage", int) + charge_counter = get_key("Charge counter", int) + status = get_key("status", int) + health = get_key("health", int) + present = get_key("present", to_bool) + level = get_key("level", int) + scale = get_key("scale", int) + voltage = get_key("voltage", int) + temperature = get_key("temperature", lambda x: int(x) / 10) + technology = shell_kvs.get("technology", str) + return BatteryInfo( + ac_powered=ac_powered, + usb_powered=usb_powered, + wireless_powered=wireless_powered, + dock_powered=dock_powered, max_charging_current=max_charging_current, max_charging_voltage=max_charging_voltage, charge_counter=charge_counter, @@ -504,5 +510,4 @@ def battery(self) -> Optional[BatteryInfo]: voltage=voltage, temperature=temperature, technology=technology, - ) - return battery_info + ) \ No newline at end of file diff --git a/tests/adb_server.py b/tests/adb_server.py index b6c38e7..f277be5 100644 --- a/tests/adb_server.py +++ b/tests/adb_server.py @@ -94,27 +94,7 @@ async def host_kill(ctx: Context): await ctx.server.stop() # os.kill(os.getpid(), signal.SIGINT) - -_DUMPSYS_BATTERY_ = """Current Battery Service state: - AC powered: false - USB powered: false - Wireless powered: false - Dock powered: false - Max charging current: 0 - Max charging voltage: 0 - Charge counter: 10000 - status: 4 - health: 2 - present: true - level: 90 - scale: 100 - voltage: 5000 - temperature: 250 - technology: Li-ion""" - - SHELL_OUTPUTS = { - "dumpsys battery": _DUMPSYS_BATTERY_, "pwd": "/", } diff --git a/tests/test_adb_server.py b/tests/test_adb_server.py index e0fa04b..1821d9c 100644 --- a/tests/test_adb_server.py +++ b/tests/test_adb_server.py @@ -29,6 +29,5 @@ def test_host_tport_serial(adb: adbutils.AdbClient): d.open_transport() -def test_shell_pwd(adb: adbutils.AdbClient): - d = adb.device(serial="123456") - assert d.shell("pwd") == "/" \ No newline at end of file + + diff --git a/tests/test_adb_shell.py b/tests/test_adb_shell.py new file mode 100644 index 0000000..35a0ec8 --- /dev/null +++ b/tests/test_adb_shell.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +"""Created on Mon May 06 2024 14:41:10 by codeskyblue +""" + +import adbutils + + +def test_shell_pwd(adb: adbutils.AdbClient): + d = adb.device(serial="123456") + assert d.shell("pwd") == "/" + + +def test_shell_battery(adb: adbutils.AdbClient): + d = adb.device(serial="123456") + + _DUMPSYS_BATTERY_ = """Current Battery Service state: + AC powered: false + USB powered: true + Wireless powered: false + Dock powered: false + Max charging current: 0 + Max charging voltage: 0 + Charge counter: 10000 + status: 4 + health: 2 + present: true + level: 80 + scale: 100 + voltage: 5000 + temperature: 250 + technology: Li-ion""" + d.shell = lambda cmd: _DUMPSYS_BATTERY_ + + bat = d.battery() + assert bat.ac_powered == False + assert bat.wireless_powered == False + assert bat.usb_powered == True + assert bat.dock_powered == False + assert bat.max_charging_current == 0 + assert bat.max_charging_voltage == 0 + assert bat.charge_counter == 10000 + assert bat.status == 4 + assert bat.health == 2 + assert bat.present == True + assert bat.level == 80 + assert bat.scale == 100 + assert bat.voltage == 5000 + assert bat.temperature == 25.0 + assert bat.technology == "Li-ion" \ No newline at end of file