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
|
python3 "$LWS/src/battery/battery/main.py" "$@"
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|||||||
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