From e224438cf343b0c0ace69474ffbac7d5eccc9714 Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Tue, 12 Nov 2024 10:13:08 -0500 Subject: [PATCH] Initial commit --- .gitignore | 11 +++++ README.md | 0 ipowerswitch.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 24 +++++++++++ 4 files changed, 141 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ipowerswitch.py create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2962b1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +.idea +*.log +tmp/ +__pycache__/ + +*.py[cod] +*.egg +build +htmlcov +poetry.lock diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ipowerswitch.py b/ipowerswitch.py new file mode 100644 index 0000000..2e3b8e0 --- /dev/null +++ b/ipowerswitch.py @@ -0,0 +1,106 @@ +import dataclasses +import enum +import logging +import typing + +import requests +import requests.auth +import typer +import yarl + + +class SwitchStatus(str, enum.Enum): + ON = "ON" + OFF = "OFF" + + +@dataclasses.dataclass +class Switch: + address: str + user: str + password: str + + def switch( + self, + switch_id: int, + outlet: int, + from_state: SwitchStatus | None = None, + ) -> None: + url = yarl.URL(f"http://{self.address}").joinpath( + "cgi-bin", + f"iswitch{switch_id:02d}", + "irswitch.exe", + ) + + params = { + "CURRENT": f"{switch_id:02d}", + f"SW{outlet}.x": 1, + f"SW{outlet}.y": 1, + } + if from_state: + params[f"STATUS{outlet}"] = f"{from_state.value:3s}" + + logging.info("Switching outlet: %s", params) + response = requests.post( + str(url), + auth=requests.auth.HTTPBasicAuth(self.user, self.password), + data=params, + allow_redirects=False, + ) + print(response) + + def switch_on(self, switch_id: int, outlet: int) -> None: + return self.switch(switch_id, outlet, from_state=SwitchStatus.OFF) + + def switch_off(self, switch_id: int, outlet: int) -> None: + return self.switch(switch_id, outlet, from_state=SwitchStatus.ON) + + +app = typer.Typer() + +SwitchIdArgument = typing.Annotated[int, typer.Argument(min=1, max=16)] +OutletArgument = typing.Annotated[int, typer.Argument(min=1, max=8)] + + +class SwitchAction(str, enum.Enum): + on = "on" + off = "off" + toggle = "toggle" + + +@app.command() +def switch( + context: typer.Context, + switch_id: SwitchIdArgument, + outlet: OutletArgument, + action: typing.Annotated[ + SwitchAction, typer.Argument(case_sensitive=False) + ] = SwitchAction.toggle, +) -> None: + switch: Switch = context.obj + match action: + case SwitchAction.on: + switch.switch_on(switch_id, outlet) + case SwitchAction.off: + switch.switch_off(switch_id, outlet) + case SwitchAction.toggle: + switch.switch(switch_id, outlet) + + +@app.callback() +def main( + context: typer.Context, + address: str, + user: str = "admin", + password: str = "admin", +) -> None: + logging.basicConfig(level=logging.INFO) + context.obj = Switch( + address=address, + user=user, + password=password, + ) + + +if __name__ == "__main__": + app() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9b39cdf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "ipowerswitch" +version = "0.1.0" +description = "" +authors = ["Correl Roush "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +requests = "^2.32.3" +bs4 = "^0.0.2" +typer = "^0.13.0" +yarl = "^1.17.1" + +[tool.poetry.group.dev.dependencies] +black = "^24.10.0" +types-requests = "^2.32.0.20241016" + +[tool.poetry.scripts] +ips = "ipowerswitch:app" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api"