Move battery script to its own project
Start new Python project for battery script
This commit is contained in:
194
bin/battery
194
bin/battery
@@ -1,193 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/bin/bash
|
||||
|
||||
import sys
|
||||
import re
|
||||
from sys import stdout
|
||||
from os import path, environ
|
||||
from os.path import join, isdir
|
||||
from collections import namedtuple, OrderedDict
|
||||
|
||||
|
||||
POWER_CLASS = '/sys/class/power_supply'
|
||||
BATTERIES = (
|
||||
environ.get('BATTERY', 'BAT0'),
|
||||
'cw2015-battery'
|
||||
)
|
||||
DISCHARNGING = 'Discharging'
|
||||
|
||||
RESET = '0'
|
||||
RED = '0;31'
|
||||
GREEN = '0;32'
|
||||
YELLOW = '0;33'
|
||||
BLUE = '0;34'
|
||||
MAGENTA = '0;35'
|
||||
CYAN = '0;36'
|
||||
|
||||
|
||||
BatteryInfo = namedtuple(
|
||||
'BatteryInfo',
|
||||
[
|
||||
'name',
|
||||
'model',
|
||||
'manufacturer',
|
||||
'technology',
|
||||
'capacity',
|
||||
'status',
|
||||
'currrent_percent',
|
||||
'time_to_empty',
|
||||
'charging_current'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def print_help():
|
||||
text = """
|
||||
battery [-h] [-d] [-i]
|
||||
-h ... help
|
||||
-d ... dump all values
|
||||
-i ... short info
|
||||
""".strip()
|
||||
pattern = re.compile(r'^\s+', re.MULTILINE)
|
||||
text = pattern.sub('', text)
|
||||
print(text)
|
||||
|
||||
|
||||
def find_battery_uevent(batteries):
|
||||
for battery in batteries:
|
||||
path = join(POWER_CLASS, battery)
|
||||
if isdir(path):
|
||||
return join(path, 'uevent')
|
||||
return None
|
||||
|
||||
|
||||
def start_color(color):
|
||||
return '\033[' + color + 'm'
|
||||
|
||||
|
||||
def colored(text, color):
|
||||
return start_color(color) + text + start_color(RESET)
|
||||
|
||||
|
||||
def read_battery(filename):
|
||||
with open(filename) as file:
|
||||
lines = file.readlines()
|
||||
items = map(lambda line: line.strip().split('=', 2), lines)
|
||||
return OrderedDict(items)
|
||||
|
||||
|
||||
def load_battery_info(info):
|
||||
return BatteryInfo(
|
||||
name = info['POWER_SUPPLY_NAME'],
|
||||
model = info.get('POWER_SUPPLY_MODEL_NAME', ''),
|
||||
manufacturer = info.get('POWER_SUPPLY_MANUFACTURER', ''),
|
||||
technology = info['POWER_SUPPLY_TECHNOLOGY'],
|
||||
capacity = float(info['POWER_SUPPLY_CHARGE_FULL']) / 1e6,
|
||||
status = info['POWER_SUPPLY_STATUS'],
|
||||
currrent_percent = int(info['POWER_SUPPLY_CAPACITY']),
|
||||
time_to_empty = int(info.get('POWER_SUPPLY_TIME_TO_EMPTY_NOW', 0)),
|
||||
charging_current = int(float(info['POWER_SUPPLY_CURRENT_NOW']) / 1e3)
|
||||
)
|
||||
|
||||
|
||||
def terminal_colored(text, color):
|
||||
if sys.stdout.isatty():
|
||||
return colored(text, color)
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def format_status(info):
|
||||
color = RED if info.status == DISCHARNGING else GREEN
|
||||
return terminal_colored(info.status, color)
|
||||
|
||||
|
||||
def format_percent(info):
|
||||
color = GREEN if info.currrent_percent > 30 else RED
|
||||
return terminal_colored(f'{info.currrent_percent}%', color)
|
||||
|
||||
|
||||
def format_time_to_empty(info):
|
||||
if info.time_to_empty == 0:
|
||||
return '??:??'
|
||||
else:
|
||||
hours = info.time_to_empty // 60
|
||||
minutes = info.time_to_empty % 60
|
||||
return terminal_colored(f"{hours}:{minutes:02d}", CYAN)
|
||||
|
||||
|
||||
def battery_model(info):
|
||||
model = ''
|
||||
if info.model:
|
||||
model += info.model
|
||||
if info.manufacturer:
|
||||
if model:
|
||||
model += ' - '
|
||||
model += info.manufacturer
|
||||
if model:
|
||||
model += ': '
|
||||
return model
|
||||
|
||||
|
||||
def print_battery_detail(info):
|
||||
model = battery_model(info)
|
||||
capacity = terminal_colored(str(info.capacity), YELLOW)
|
||||
print(f"Battery: {model}{info.technology} {capacity} Ah")
|
||||
print("Battery level: " + format_percent(info))
|
||||
print("Battery status: " + format_status(info))
|
||||
if info.status == DISCHARNGING:
|
||||
time_to_empty = format_time_to_empty(info)
|
||||
print(f"Time to empty: {time_to_empty} [hour:minutes]")
|
||||
print(f"{info.status} current: {info.charging_current} mA")
|
||||
|
||||
|
||||
def print_battery_info(info):
|
||||
percent = format_percent(info)
|
||||
status = format_status(info)
|
||||
state = f"level={percent} status={status}"
|
||||
if info.status == DISCHARNGING:
|
||||
time_to_empty = format_time_to_empty(info)
|
||||
state += f" duration={time_to_empty}"
|
||||
print(state)
|
||||
|
||||
|
||||
def dump_battery_info(uevent, battery):
|
||||
print(f"# {uevent}")
|
||||
for key, value in battery.items():
|
||||
print(f"{key} = {value}")
|
||||
|
||||
|
||||
def main(args):
|
||||
uevent = find_battery_uevent(BATTERIES)
|
||||
if not uevent:
|
||||
print("No battery found")
|
||||
return
|
||||
battery = read_battery(uevent)
|
||||
info = load_battery_info(battery)
|
||||
if '-h' in args:
|
||||
print_help()
|
||||
elif '-d' in args:
|
||||
dump_battery_info(uevent, battery)
|
||||
elif '-i' in args:
|
||||
print_battery_info(info)
|
||||
else:
|
||||
print_battery_detail(info)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
|
||||
|
||||
"""
|
||||
cat /sys/class/power_supply/cw2015-battery/uevent
|
||||
POWER_SUPPLY_NAME=cw2015-battery
|
||||
POWER_SUPPLY_TYPE=Battery
|
||||
POWER_SUPPLY_CAPACITY=100
|
||||
POWER_SUPPLY_STATUS=Full
|
||||
POWER_SUPPLY_PRESENT=1
|
||||
POWER_SUPPLY_VOLTAGE_NOW=4314000
|
||||
POWER_SUPPLY_TIME_TO_EMPTY_NOW=0
|
||||
POWER_SUPPLY_TECHNOLOGY=Li-ion
|
||||
POWER_SUPPLY_CHARGE_FULL=9800000
|
||||
POWER_SUPPLY_CHARGE_FULL_DESIGN=9800000
|
||||
POWER_SUPPLY_CURRENT_NOW=0
|
||||
"""
|
||||
python3 "$LWS/src/battery/battery/main.py" "$@"
|
||||
|
||||
32
src/battery/.editorconfig
Normal file
32
src/battery/.editorconfig
Normal file
@@ -0,0 +1,32 @@
|
||||
# defaults
|
||||
[*]
|
||||
charset=utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
end_of_line=lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.xml]
|
||||
continuation_indent_size = 8
|
||||
|
||||
# YAML
|
||||
# http://yaml.org/spec/1.2/2009-07-21/spec.html#id2576668
|
||||
[*.{yaml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
# Shell
|
||||
# https://google.github.io/styleguide/shell.xml#Indentation
|
||||
[*.{bash,sh,zsh}]
|
||||
indent_size = 2
|
||||
|
||||
# HTML
|
||||
# https://google.github.io/styleguide/htmlcssguide.xml#General_Formatting_Rules
|
||||
[*.{htm,html}]
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_size=2
|
||||
71
src/battery/.gitignore
vendored
Normal file
71
src/battery/.gitignore
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
venv/
|
||||
.venv/
|
||||
.python-version
|
||||
.pytest_cache
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
docs/_autosummary/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
#Ipython Notebook
|
||||
.ipynb_checkpoints
|
||||
65
src/battery/Makefile
Normal file
65
src/battery/Makefile
Normal file
@@ -0,0 +1,65 @@
|
||||
VENV_NAME=.venv
|
||||
VENV_BIN=$(VENV_NAME)/bin
|
||||
PYTHON=$(VENV_BIN)/python3
|
||||
PIP=$(VENV_BIN)/pip3
|
||||
BLACK=$(VENV_BIN)/black
|
||||
ISORT=$(VENV_BIN)/isort
|
||||
|
||||
.PHONY: install-dev venv lint mypy test
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
define PRINT_HELP_PYSCRIPT
|
||||
import re, sys
|
||||
|
||||
for line in sys.stdin:
|
||||
match = re.match(r'^(?P<target>[a-zA-Z0-9_//-]+):(.*?## (?P<help>.*))?', line)
|
||||
if match:
|
||||
target = match.group('target')
|
||||
help = match.group('help')
|
||||
if not help:
|
||||
help = ''
|
||||
print(f"{target:20s} {help}")
|
||||
endef
|
||||
export PRINT_HELP_PYSCRIPT
|
||||
|
||||
help: ## list all commands
|
||||
@python3 -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
|
||||
|
||||
install-dev: ## prepare virtual environment and install all dependencies
|
||||
python3 -m venv $(VENV_NAME) --upgrade-deps
|
||||
$(PIP) install -r requirements-dev.txt
|
||||
|
||||
uninstall-dev:
|
||||
rm -rf $(VENV_NAME)
|
||||
find -iname "*.pyc" -delete
|
||||
|
||||
lint: venv lint/flake8 lint/black lint/isort mypy ## lint source codes
|
||||
|
||||
lint/flake8: venv
|
||||
$(VENV_BIN)/flake8 battery/ tests/
|
||||
|
||||
lint/black: venv
|
||||
$(BLACK) --check .
|
||||
|
||||
lint/isort:
|
||||
$(ISORT) --check .
|
||||
|
||||
format-diff: venv ## show invalid source code formatting
|
||||
$(BLACK) --diff .
|
||||
$(ISORT) --diff .
|
||||
|
||||
format-fix: venv ## fix source code formatting
|
||||
$(BLACK) .
|
||||
$(ISORT) .
|
||||
|
||||
mypy: venv ## run mypy
|
||||
$(PYTHON) -m mypy battery/ tests/
|
||||
|
||||
test: venv ## run tests
|
||||
$(PYTHON) -m unittest discover -v -s tests/
|
||||
|
||||
pytest: venv ## run pytests
|
||||
$(VENV_BIN)/pytest -v
|
||||
|
||||
venv:
|
||||
. $(VENV_NAME)/bin/activate
|
||||
10
src/battery/README.md
Normal file
10
src/battery/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Notebook battery info
|
||||
Read and print battery details
|
||||
|
||||
## Links
|
||||
|
||||
### Project structure tips
|
||||
* [Cookiecutter](https://cookiecutter.readthedocs.io/)
|
||||
* [Python Application Layouts: A Reference](https://realpython.com/python-application-layouts/)
|
||||
* [Structuring Your Project](https://docs.python-guide.org/writing/structure/)
|
||||
* [The Good way to structure a Python Project](https://towardsdatascience.com/the-good-way-to-structure-a-python-project-d914f27dfcc9)
|
||||
0
src/battery/battery/__init__.py
Normal file
0
src/battery/battery/__init__.py
Normal file
24
src/battery/battery/colorterm.py
Normal file
24
src/battery/battery/colorterm.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import sys
|
||||
|
||||
RESET = "0"
|
||||
RED = "0;31"
|
||||
GREEN = "0;32"
|
||||
YELLOW = "0;33"
|
||||
BLUE = "0;34"
|
||||
MAGENTA = "0;35"
|
||||
CYAN = "0;36"
|
||||
|
||||
|
||||
def start_color(color: str) -> str:
|
||||
return "\033[" + color + "m"
|
||||
|
||||
|
||||
def colored(text: str, color: str) -> str:
|
||||
return start_color(color) + text + start_color(RESET)
|
||||
|
||||
|
||||
def terminal_colored(text: str, color: str) -> str:
|
||||
if sys.stdout.isatty():
|
||||
return colored(text, color)
|
||||
else:
|
||||
return text
|
||||
110
src/battery/battery/main.py
Normal file
110
src/battery/battery/main.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import re
|
||||
import sys
|
||||
from os import environ
|
||||
from typing import List
|
||||
|
||||
from colorterm import CYAN, GREEN, RED, YELLOW, terminal_colored
|
||||
from power import Battery, battery_info, find_uevent, read_uevent
|
||||
|
||||
BATTERIES = (environ.get("BATTERY", "BAT0"), "cw2015-battery")
|
||||
DISCHARGING = "Discharging"
|
||||
|
||||
|
||||
def print_help(app: str) -> None:
|
||||
text = f"""
|
||||
{app} [-h] [-d] [-i]
|
||||
-h ... help
|
||||
-d ... dump all values
|
||||
-i ... short info
|
||||
""".strip()
|
||||
pattern = re.compile(r"^\s+", re.MULTILINE)
|
||||
text = pattern.sub("", text)
|
||||
print(text)
|
||||
|
||||
|
||||
def format_status(battery: Battery) -> str:
|
||||
color = RED if battery.status == DISCHARGING else GREEN
|
||||
return terminal_colored(battery.status, color)
|
||||
|
||||
|
||||
def format_percent(battery: Battery) -> str:
|
||||
color = GREEN if battery.percent > 30 else RED
|
||||
return terminal_colored(f"{battery.percent}%", color)
|
||||
|
||||
|
||||
def format_time_to_empty(battery: Battery) -> str:
|
||||
if battery.time_to_empty == 0:
|
||||
return "??:??"
|
||||
else:
|
||||
hours = battery.time_to_empty // 60
|
||||
minutes = battery.time_to_empty % 60
|
||||
return terminal_colored(f"{hours}:{minutes:02d}", CYAN)
|
||||
|
||||
|
||||
def battery_model(battery: Battery) -> str:
|
||||
model = ""
|
||||
if battery.model:
|
||||
model += battery.model
|
||||
if battery.manufacturer:
|
||||
if model:
|
||||
model += " - "
|
||||
model += battery.manufacturer
|
||||
if model:
|
||||
model += ": "
|
||||
return model
|
||||
|
||||
|
||||
def print_battery_detail(battery: Battery) -> None:
|
||||
model = battery_model(battery)
|
||||
capacity = terminal_colored(str(battery.capacity), YELLOW)
|
||||
print(f"Battery: {model}{battery.technology} {capacity} Ah")
|
||||
print("Battery level: " + format_percent(battery))
|
||||
print("Battery status: " + format_status(battery))
|
||||
if battery.status == DISCHARGING:
|
||||
time_to_empty = format_time_to_empty(battery)
|
||||
print(f"Time to empty: {time_to_empty} [hour:minutes]")
|
||||
print(f"{battery.status} current: {battery.current} mA")
|
||||
|
||||
|
||||
def print_battery_info(battery: Battery) -> None:
|
||||
percent = format_percent(battery)
|
||||
status = format_status(battery)
|
||||
state = f"level={percent} status={status}"
|
||||
if battery.status == DISCHARGING:
|
||||
time_to_empty = format_time_to_empty(battery)
|
||||
state += f" duration={time_to_empty}"
|
||||
print(state)
|
||||
|
||||
|
||||
def dump_battery_info(uevent: str, battery: Battery) -> None:
|
||||
print(f"# {uevent}")
|
||||
for key, value in battery.items():
|
||||
print(f"{key} = {value}")
|
||||
|
||||
|
||||
def main(args: List[str]) -> None:
|
||||
if "-h" in args:
|
||||
print_help(args[0])
|
||||
return
|
||||
|
||||
if len(args) >= 3 and args[1] == "-b":
|
||||
uevent = args[2]
|
||||
else:
|
||||
uevent = find_uevent(BATTERIES)
|
||||
if not uevent:
|
||||
print("No battery found")
|
||||
return
|
||||
|
||||
battery = read_uevent(uevent)
|
||||
info = battery_info(battery)
|
||||
|
||||
if "-d" in args:
|
||||
dump_battery_info(uevent, battery)
|
||||
elif "-i" in args:
|
||||
print_battery_info(info)
|
||||
else:
|
||||
print_battery_detail(info)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
48
src/battery/battery/power.py
Normal file
48
src/battery/battery/power.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from collections import OrderedDict, namedtuple
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
POWER_CLASS = Path("/sys/class/power_supply")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Battery:
|
||||
name: str
|
||||
model: str
|
||||
manufacturer: str
|
||||
technology: str
|
||||
capacity: float
|
||||
percent: int
|
||||
status: str
|
||||
time_to_empty: int
|
||||
current: int
|
||||
|
||||
|
||||
def find_uevent(batteries: List[str]) -> Union[Path, None]:
|
||||
for battery in batteries:
|
||||
uevent_path = POWER_CLASS.joinpath(battery, "uevent")
|
||||
if uevent_path.is_fifo():
|
||||
return uevent_path
|
||||
return None
|
||||
|
||||
|
||||
def read_uevent(filename: Path) -> Dict[str, str]:
|
||||
with open(filename) as file:
|
||||
lines = file.readlines()
|
||||
items = map(lambda line: line.strip().split("=", 2), lines)
|
||||
return OrderedDict(items)
|
||||
|
||||
|
||||
def battery_info(uevent: Dict[str, str]) -> Battery:
|
||||
return Battery(
|
||||
name=uevent.get("POWER_SUPPLY_NAME", "??"),
|
||||
model=uevent.get("POWER_SUPPLY_MODEL_NAME", ""),
|
||||
manufacturer=uevent.get("POWER_SUPPLY_MANUFACTURER", ""),
|
||||
technology=uevent.get("POWER_SUPPLY_TECHNOLOGY", ""),
|
||||
capacity=float(uevent.get("POWER_SUPPLY_CHARGE_FULL", 0)) / 1e6,
|
||||
percent=int(uevent.get("POWER_SUPPLY_CAPACITY", 0)),
|
||||
status=uevent.get("POWER_SUPPLY_STATUS", "??"),
|
||||
time_to_empty=int(uevent.get("POWER_SUPPLY_TIME_TO_EMPTY_NOW", 0)),
|
||||
current=int(float(uevent.get("POWER_SUPPLY_CURRENT_NOW", 0)) / 1e3),
|
||||
)
|
||||
6
src/battery/mypy.ini
Normal file
6
src/battery/mypy.ini
Normal file
@@ -0,0 +1,6 @@
|
||||
[mypy]
|
||||
plugins = pydantic.mypy
|
||||
disallow_untyped_defs = True
|
||||
warn_redundant_casts = True
|
||||
strict_equality = True
|
||||
ignore_missing_imports = True
|
||||
7
src/battery/requirements-dev.txt
Normal file
7
src/battery/requirements-dev.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
isort
|
||||
pylint
|
||||
mypy
|
||||
pydantic
|
||||
flake8
|
||||
black
|
||||
pytest
|
||||
0
src/battery/tests/__init__.py
Normal file
0
src/battery/tests/__init__.py
Normal file
17
src/battery/tests/fixtures/dellbook.txt
vendored
Normal file
17
src/battery/tests/fixtures/dellbook.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
POWER_SUPPLY_NAME=BAT0
|
||||
POWER_SUPPLY_TYPE=Battery
|
||||
POWER_SUPPLY_STATUS=Discharging
|
||||
POWER_SUPPLY_PRESENT=1
|
||||
POWER_SUPPLY_TECHNOLOGY=Li-ion
|
||||
POWER_SUPPLY_CYCLE_COUNT=0
|
||||
POWER_SUPPLY_VOLTAGE_MIN_DESIGN=11100000
|
||||
POWER_SUPPLY_VOLTAGE_NOW=11766000
|
||||
POWER_SUPPLY_CURRENT_NOW=2991000
|
||||
POWER_SUPPLY_CHARGE_FULL_DESIGN=4400000
|
||||
POWER_SUPPLY_CHARGE_FULL=4400000
|
||||
POWER_SUPPLY_CHARGE_NOW=4292000
|
||||
POWER_SUPPLY_CAPACITY=97
|
||||
POWER_SUPPLY_CAPACITY_LEVEL=Normal
|
||||
POWER_SUPPLY_MODEL_NAME=DELL CP2848C
|
||||
POWER_SUPPLY_MANUFACTURER=Samsung SDI
|
||||
POWER_SUPPLY_SERIAL_NUMBER=20797
|
||||
0
src/battery/tests/fixtures/empty.txt
vendored
Normal file
0
src/battery/tests/fixtures/empty.txt
vendored
Normal file
12
src/battery/tests/fixtures/pinebook.txt
vendored
Normal file
12
src/battery/tests/fixtures/pinebook.txt
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
POWER_SUPPLY_NAME=cw2015-battery
|
||||
POWER_SUPPLY_TYPE=Battery
|
||||
POWER_SUPPLY_CAPACITY=100
|
||||
POWER_SUPPLY_STATUS=Discharging
|
||||
POWER_SUPPLY_PRESENT=1
|
||||
POWER_SUPPLY_VOLTAGE_NOW=4200000
|
||||
POWER_SUPPLY_TIME_TO_EMPTY_NOW=606
|
||||
POWER_SUPPLY_TECHNOLOGY=Li-ion
|
||||
POWER_SUPPLY_CHARGE_COUNTER=0
|
||||
POWER_SUPPLY_CHARGE_FULL=9800000
|
||||
POWER_SUPPLY_CHARGE_FULL_DESIGN=9800000
|
||||
POWER_SUPPLY_CURRENT_NOW=970297
|
||||
17
src/battery/tests/fixtures/thinkpad.txt
vendored
Normal file
17
src/battery/tests/fixtures/thinkpad.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
POWER_SUPPLY_NAME=BAT0
|
||||
POWER_SUPPLY_TYPE=Battery
|
||||
POWER_SUPPLY_STATUS=Discharging
|
||||
POWER_SUPPLY_PRESENT=1
|
||||
POWER_SUPPLY_TECHNOLOGY=Li-poly
|
||||
POWER_SUPPLY_CYCLE_COUNT=70
|
||||
POWER_SUPPLY_VOLTAGE_MIN_DESIGN=11550000
|
||||
POWER_SUPPLY_VOLTAGE_NOW=12495000
|
||||
POWER_SUPPLY_POWER_NOW=10008000
|
||||
POWER_SUPPLY_ENERGY_FULL_DESIGN=50500000
|
||||
POWER_SUPPLY_ENERGY_FULL=51480000
|
||||
POWER_SUPPLY_ENERGY_NOW=51110000
|
||||
POWER_SUPPLY_CAPACITY=99
|
||||
POWER_SUPPLY_CAPACITY_LEVEL=Normal
|
||||
POWER_SUPPLY_MODEL_NAME=02DL007
|
||||
POWER_SUPPLY_MANUFACTURER=LGC
|
||||
POWER_SUPPLY_SERIAL_NUMBER= 487
|
||||
8
src/battery/tests/test_colorterm.py
Normal file
8
src/battery/tests/test_colorterm.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import unittest
|
||||
|
||||
from battery.colorterm import RED, colored
|
||||
|
||||
|
||||
class TestColorTerm(unittest.TestCase):
|
||||
def test_colored_text(self) -> None:
|
||||
self.assertEqual(colored("TEXT", RED), "\033[0;31mTEXT\033[0m")
|
||||
83
src/battery/tests/test_power.py
Normal file
83
src/battery/tests/test_power.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from battery.power import Battery, battery_info, read_uevent
|
||||
|
||||
|
||||
def load_fixture(filename: str) -> dict[str, str]:
|
||||
directory = Path(__file__).parent.resolve()
|
||||
fixture = directory.joinpath("fixtures", filename)
|
||||
return read_uevent(fixture)
|
||||
|
||||
|
||||
batteries = (
|
||||
(
|
||||
"empty",
|
||||
"empty.txt",
|
||||
Battery(
|
||||
name="??",
|
||||
model="",
|
||||
manufacturer="",
|
||||
technology="",
|
||||
capacity=0.0,
|
||||
percent=0,
|
||||
status="??",
|
||||
time_to_empty=0,
|
||||
current=0.0,
|
||||
),
|
||||
),
|
||||
(
|
||||
"dell",
|
||||
"dellbook.txt",
|
||||
Battery(
|
||||
name="BAT0",
|
||||
model="DELL CP2848C",
|
||||
manufacturer="Samsung SDI",
|
||||
technology="Li-ion",
|
||||
capacity=4.4,
|
||||
percent=97,
|
||||
status="Discharging",
|
||||
time_to_empty=0,
|
||||
current=2991,
|
||||
),
|
||||
),
|
||||
(
|
||||
"pine",
|
||||
"pinebook.txt",
|
||||
Battery(
|
||||
name="cw2015-battery",
|
||||
model="",
|
||||
manufacturer="",
|
||||
technology="Li-ion",
|
||||
capacity=9.8,
|
||||
percent=100,
|
||||
status="Discharging",
|
||||
time_to_empty=606,
|
||||
current=970,
|
||||
),
|
||||
),
|
||||
(
|
||||
"lenovi",
|
||||
"thinkpad.txt",
|
||||
Battery(
|
||||
name="BAT0",
|
||||
model="02DL007",
|
||||
manufacturer="LGC",
|
||||
technology="Li-poly",
|
||||
capacity=0.0,
|
||||
percent=99,
|
||||
status="Discharging",
|
||||
time_to_empty=0,
|
||||
current=0,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestPower(unittest.TestCase):
|
||||
def test_battery_uevent(self) -> None:
|
||||
for name, filename, expected_info in batteries:
|
||||
with self.subTest(name):
|
||||
uevent = load_fixture(filename)
|
||||
info = battery_info(uevent)
|
||||
self.assertEqual(info, expected_info)
|
||||
Reference in New Issue
Block a user