Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// For format details, see https://aka.ms/devcontainer.json.
{
"name": "Python 3",
"image": "mcr.microsoft.com/devcontainers/python:1-3-bookworm",
"runArgs": [
// Use host network for discovery
"--network=host"
],
"features": {
"ghcr.io/va-h/devcontainers-features/uv:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"charliermarsh.ruff",
"tamasfe.even-better-toml",
]
}
}
}
40 changes: 28 additions & 12 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
name: Python lint and packages
name: Python lint, format, test

on:
push:
branches: [ master ]
branches: [ dev ]
pull_request:
branches: [ master ]
branches: [ dev ]

jobs:
build:

check:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ 3.9, "3.10", "3.11" ]
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
- name: Lint code
if: ${{ !cancelled() }}
uses: astral-sh/ruff-action@v3

- name: Check formatting
if: ${{ !cancelled() }}
uses: astral-sh/ruff-action@v3
with:
args: "format --check --diff"

- name: Run unit tests
if: ${{ !cancelled() }}
run: |
python -m pip install --upgrade pip
python setup.py install
github_report_args=(--md=report.md --emoji)
uv run pytest "${github_report_args[@]}" || exit

- name: Black Code Formatter
uses: jpetrucciani/black-check@22.12.0
{
echo "# Python ${{ matrix.python-version }} Test Report"
echo "<details><summary>Click to expand!</summary>"
tail -n+2 report.md
echo "</details>"
} >>"$GITHUB_STEP_SUMMARY"
1 change: 0 additions & 1 deletion MANIFEST.in

This file was deleted.

16 changes: 8 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ python-dingz

Python API for interacting with `Dingz <https://dingz.ch>`_ devices.

This module is not official, developed, supported or endorsed by iolo AG or
myStrom AG. For questions and other inquiries, use the issue tracker in this
repository please.
This module is not official, developed, supported or endorsed by iolo AG.
For questions and other inquiries, please use the issue tracker in this
repository.

Without the support of iolo AG and myStrom AG it would have taken much longer
to create this module which is the base for the integration into
to create this module, which is the base for the integration into
`Home Assistant <https://home-assistant.io>`_. Both companies have provided
and are still providing hardware, valuable feedback and advice. Their
continuous support make further development of this module possible.
continuous support makes further development of this module possible.

See `api.dingz.ch <https://api.dingz.ch/>`_ for the API details.

Expand All @@ -20,7 +20,7 @@ Limitations

This module is at the moment limited to consuming sensor data, device details,
device configurations and states.
The front LED can be controlled but buttons requires you to programm them by
The front LED can be controlled but buttons require you to program them by
yourself.

No support for setting timers and schedules.
Expand All @@ -30,7 +30,7 @@ Requirements

You need to have `Python 3 <https://www.python.org>`_ installed.

- `dingz <https://dingz.ch>`_ device
- `dingz <https://dingz.ch>`_ device with firmware version 2.4.3 or newer.
- Network connection
- Devices connected to your network

Expand All @@ -52,7 +52,7 @@ On a Fedora-based system or on a CentOS/RHEL machine which has EPEL enabled.

$ sudo dnf -y install python3-dingz

For Nix or NixOS users is a package available. Keep in mind that the lastest releases might only
For Nix or NixOS users is a package available. Keep in mind that the latest releases might only
be present in the ``unstable`` channel.

.. code:: bash
Expand Down
46 changes: 32 additions & 14 deletions dingz/__init__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
"""Base details for the dingz Python bindings."""

from __future__ import annotations

import asyncio
import socket
from typing import Any, Mapping, Optional
import sys
from typing import TYPE_CHECKING, Any

import aiohttp
import sys

if sys.version_info >= (3, 11):
import asyncio as async_timeout
else:
import async_timeout

from .constants import TIMEOUT, USER_AGENT, CONTENT_TYPE_JSON, CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN
from .constants import (
CONTENT_TYPE,
CONTENT_TYPE_JSON,
CONTENT_TYPE_TEXT_PLAIN,
TIMEOUT,
USER_AGENT,
)
from .exceptions import DingzConnectionError

if TYPE_CHECKING:
from collections.abc import Mapping


async def make_call(
self,
uri: str,
method: str = "GET",
data: Optional[Any] = None,
json_data: Optional[dict] = None,
parameters: Optional[Mapping[str, str]] = None,
token: str = None,
data: Any | None = None,
json_data: dict | None = None,
parameters: Mapping[str, str] | None = None,
token: str | None = None,
) -> Any:
"""Handle the requests to the dingz unit."""

headers = {
"User-Agent": USER_AGENT,
"Accept": f"{CONTENT_TYPE_JSON}, {CONTENT_TYPE_TEXT_PLAIN}, */*",
Expand All @@ -38,17 +50,23 @@ async def make_call(
self._close_session = True

try:
with async_timeout.timeout(TIMEOUT):
async with async_timeout.timeout(TIMEOUT):
response = await self._session.request(
method, uri, data=data, json=json_data, params=parameters, headers=headers,
method,
uri,
data=data,
json=json_data,
params=parameters,
headers=headers,
)
except asyncio.TimeoutError as exception:
raise DingzConnectionError("Timeout occurred while connecting to dingz unit") from exception
msg = "Timeout occurred while connecting to dingz unit"
raise DingzConnectionError(msg) from exception
except (aiohttp.ClientError, socket.gaierror) as exception:
raise DingzConnectionError("Error occurred while communicating with dingz") from exception
msg = "Error occurred while communicating with dingz"
raise DingzConnectionError(msg) from exception

if CONTENT_TYPE_JSON in response.headers.get(CONTENT_TYPE, ""):
response_json = await response.json()
return response_json
return await response.json()

return response.text
39 changes: 15 additions & 24 deletions dingz/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Command-line interface to interact with dingz devices."""

import asyncio
from functools import wraps

Expand All @@ -22,75 +23,65 @@ def wrapper(*args, **kwargs):

@click.group()
@click.version_option()
def main():
def main() -> None:
"""Simple command-line tool to interact with dingz devices."""


@main.command("discover")
@coro
async def discover():
async def discover() -> None:
"""Read the current configuration of a myStrom device."""
click.echo("Waiting for UDP broadcast packages...")
devices = await discover_dingz_devices()

print(f"Found {len(devices)} devices")
for device in devices:
print(
f" MAC address: {device.mac}, IP address: {device.host}, HW: {device.hardware}"
)
print(f" MAC address: {device.mac}, IP address: {device.host}, HW: {device.hardware}")


@main.group("info")
def info():
def info() -> None:
"""Get the information of a dingz device."""


@info.command("config")
@coro
@click.option(
"--ip", prompt="IP address of the device", help="IP address of the device."
)
async def get_config(ip):
@click.option("--ip", prompt="IP address of the device", help="IP address of the device.")
async def get_config(ip) -> None:
"""Read the current configuration of a myStrom device."""
click.echo("Read configuration from %s" % ip)
click.echo(f"Read configuration from {ip}")
async with Dingz(ip) as dingz:
await dingz.get_device_info()
click.echo(dingz.device_details)


@main.group("front_led")
def front_led():
def front_led() -> None:
"""Handle the front LED of dingz."""


@front_led.command("on")
@coro
@click.option(
"--ip", prompt="IP address of the device", help="IP address of the device."
)
async def set_on(ip):
@click.option("--ip", prompt="IP address of the device", help="IP address of the device.")
async def set_on(ip) -> None:
"""Turn the front LED on."""
async with Dingz(ip) as dingz:
await dingz.turn_on()


@front_led.command("off")
@coro
@click.option(
"--ip", prompt="IP address of the device", help="IP address of the device."
)
async def set_off(ip):
@click.option("--ip", prompt="IP address of the device", help="IP address of the device.")
async def set_off(ip) -> None:
"""Turn the front LED off."""
async with Dingz(ip) as dingz:
await dingz.turn_off()


@front_led.command("status")
@coro
@click.option(
"--ip", prompt="IP address of the device", help="IP address of the device."
)
async def get_status(ip):
@click.option("--ip", prompt="IP address of the device", help="IP address of the device.")
async def get_status(ip) -> None:
"""Get the status of the front LED off."""
async with Dingz(ip) as dingz:
click.echo(await dingz.enabled())
Expand Down
5 changes: 3 additions & 2 deletions dingz/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Constants used by the Python API for interacting with dingz units."""
import pkg_resources

import importlib.metadata

try:
__version__ = pkg_resources.get_distribution("setuptools").version
__version__ = importlib.metadata.version("dingz")
except Exception:
__version__ = "unknown"

Expand Down
Loading