Skip to content

Commit 2c3b56b

Browse files
authored
Merge pull request #8 from home-assistant-ecosystem/dev
0.4.0
2 parents 251ce5b + 69f7c4a commit 2c3b56b

File tree

12 files changed

+531
-130
lines changed

12 files changed

+531
-130
lines changed

.github/workflows/python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: [3.6, 3.7, 3.8]
15+
python-version: [3.6, 3.7, 3.8, 3.9]
1616

1717
steps:
1818
- uses: actions/checkout@v2

CHANGELOG.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
Changelog
22
=========
33

4+
0.4.0 (2021-12-02)
5+
------------------
6+
7+
- Support for dimmers
8+
- Allos later async_timeout
9+
- Refactored shades
10+
11+
- With version 0.3.0
12+
::
13+
14+
dingz.shade_down(shade_id)
15+
16+
- Starting from 0.4.0:
17+
::
18+
19+
shade = dingz.shades.get(shade_id)
20+
shade.shade_down()
21+
22+
423
0.3.0 (2020-11-08)
524
------------------
625

README.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,43 @@ The package is available in the `Python Package Index <https://pypi.python.org/>
4646
4747
$ pip install dingz
4848
49+
On a Fedora-based system or on a CentOS/RHEL machine which has EPEL enabled.
50+
51+
.. code:: bash
52+
53+
$ sudo dnf -y install python3-dingz
54+
4955
Module usage
5056
------------
5157

5258
Every unit has its own web interface: `http://IP_ADDRESS <http://IP_ADDRESS>`_ .
5359

5460
See `example.py` for detail about module.
5561

62+
63+
How to operate shades / dimmers
64+
-------------------------------
65+
66+
.. code:: python
67+
68+
d = Dingz("ip_address_or_host")
69+
# Fetch config, this has to be done once to fetch all details about the shades/dimmers
70+
await d.get_devices_config()
71+
72+
# Fetch the current state of the lights/vers
73+
await d.get_state()
74+
75+
# Get details about shade
76+
shade_0 = d.shades.get(0)
77+
print("Blinds: %s Lamella: %s" % (shade_0.current_blind_level(), shade_0.current_lamella_level()))
78+
79+
# Operate shade
80+
shade_0.shade_down()
81+
82+
# Turn on light
83+
d.dimmers.get(2).turn_on(brightness_pct=70)
84+
85+
5686
CLI usage
5787
---------
5888

dingz/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
"""Base details for the dingz Python bindings."""
22
import asyncio
3-
import json
43
import socket
54
from typing import Any, Mapping, Optional
65

76
import aiohttp
87
import async_timeout
98

109
from .constants import TIMEOUT, USER_AGENT, CONTENT_TYPE_JSON, CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN
11-
from .exceptions import DingzConnectionError, DingzError
10+
from .exceptions import DingzConnectionError
1211

1312

1413
async def make_call(

dingz/cli.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""Command-line interface to interact with dingz devices."""
2-
import click
3-
from .discovery import discover_dingz_devices
42
import asyncio
3+
from functools import wraps
54

5+
import click
66

7-
import asyncio
8-
from functools import wraps
7+
from dingz.dingz import Dingz
8+
9+
from .discovery import discover_dingz_devices
910

1011

1112
def coro(f):
@@ -39,5 +40,61 @@ async def discover():
3940
)
4041

4142

43+
@main.group("info")
44+
def info():
45+
"""Get the information of a dingz device."""
46+
47+
48+
@info.command("config")
49+
@coro
50+
@click.option(
51+
"--ip", prompt="IP address of the device", help="IP address of the device."
52+
)
53+
async def get_config(ip):
54+
"""Read the current configuration of a myStrom device."""
55+
click.echo("Read configuration from %s" % ip)
56+
async with Dingz(ip) as dingz:
57+
await dingz.get_device_info()
58+
click.echo(dingz.device_details)
59+
60+
61+
@main.group("front_led")
62+
def front_led():
63+
"""Handle the front LED of dingz."""
64+
65+
66+
@front_led.command("on")
67+
@coro
68+
@click.option(
69+
"--ip", prompt="IP address of the device", help="IP address of the device."
70+
)
71+
async def set_on(ip):
72+
"""Turn the front LED on."""
73+
async with Dingz(ip) as dingz:
74+
await dingz.turn_on()
75+
76+
77+
@front_led.command("off")
78+
@coro
79+
@click.option(
80+
"--ip", prompt="IP address of the device", help="IP address of the device."
81+
)
82+
async def set_off(ip):
83+
"""Turn the front LED off."""
84+
async with Dingz(ip) as dingz:
85+
await dingz.turn_off()
86+
87+
88+
@front_led.command("status")
89+
@coro
90+
@click.option(
91+
"--ip", prompt="IP address of the device", help="IP address of the device."
92+
)
93+
async def get_status(ip):
94+
"""Get the status of the front LED off."""
95+
async with Dingz(ip) as dingz:
96+
click.echo(await dingz.enabled())
97+
98+
4299
if __name__ == "__main__":
43100
main()

dingz/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
# Configuration endpoints
3939
PIR_CONFIGURATION = "pir_config"
4040
BLIND_CONFIGURATION = "blind_config"
41+
DIMMER_CONFIGURATION = "dimmer_config"
4142
THERMOSTAT_CONFIGURATION = "thermostat_config"
4243
INPUT_CONFIGURATION = "input_config"
4344

dingz/dimmer.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from yarl import URL
2+
3+
from dingz import make_call
4+
from dingz.constants import DIMMER
5+
from dingz.registry import BaseRegistry, organize_by_absolute_index
6+
7+
8+
class Dimmer(object):
9+
def __init__(self, absolute_index, dingz):
10+
self.dingz = dingz
11+
self.absolute_index = absolute_index
12+
self.on = None
13+
self.brightness_pct = None
14+
self.enabled = None
15+
self.dimmable = None
16+
self.output = None
17+
self.name = None
18+
self.seen_state = False
19+
20+
async def toggle(self, brightness_pct=100):
21+
"""
22+
Toggle light (based on internal state).
23+
:param brightness_pct: brightness in percent, or None
24+
:return:
25+
"""
26+
action = "off" if self.on else "on"
27+
await self.operate_light(action, brightness_pct)
28+
29+
async def turn_on(self, brightness_pct=100):
30+
""" Turn light on.
31+
:param brightness_pct: brightness in percent, or None.
32+
"""
33+
await self.operate_light("on", brightness_pct)
34+
35+
async def turn_off(self):
36+
""" Rurn light off."""
37+
await self.operate_light("off")
38+
39+
VALID_OPERATIONS = ('on', 'off')
40+
41+
async def operate_light(self, action, brightness_pct=None):
42+
"""
43+
Operate the light (turn it on or off).
44+
:param action: 'on' or 'off'
45+
:param brightness_pct: brightness in percent or None if not defined
46+
:return:
47+
"""
48+
if action not in Dimmer.VALID_OPERATIONS:
49+
raise ValueError("invalid action %s, expected one of %s" %
50+
(repr(action), repr(Dimmer.VALID_OPERATIONS)))
51+
52+
if brightness_pct is not None and (brightness_pct > 100 or brightness_pct < 0):
53+
raise ValueError("invalid brightness_pct %s, expected value between 0 and 100" %
54+
(repr(brightness_pct)))
55+
56+
url = URL(self.dingz.uri).join(URL("%s/%s/%s" % (DIMMER, self.index_relative, action)))
57+
params = {}
58+
if brightness_pct is not None:
59+
params["value"] = str(brightness_pct)
60+
61+
await make_call(self.dingz, uri=url, method="POST", parameters=params)
62+
self.on = action
63+
if brightness_pct is not None:
64+
self.brightness_pct = brightness_pct
65+
66+
def _consume_state(self, state_details):
67+
"""
68+
Example for state_details:
69+
{
70+
"on": true, "output": 100, "ramp": 0, "readonly": false,
71+
"index": { "relative": 0, "absolute": 2 }
72+
}
73+
:param state_details:
74+
:return:
75+
"""
76+
assert self.absolute_index == state_details['index']['absolute']
77+
self.seen_state = True
78+
self.index_relative = state_details['index']['relative']
79+
self.on = state_details['on']
80+
self.brightness_pct = state_details['output']
81+
82+
def _consume_config(self, config):
83+
# "output": "halogen", "name": "Dimmable 3", "feedback": null, "feedback_intensity": 10
84+
self.output = config['output']
85+
self.enabled = config['output'] != 'not_connected'
86+
self.dimmable = config['output'] != 'non_dimmable'
87+
self.name = config['name']
88+
89+
90+
class DimmerRegistry(BaseRegistry[Dimmer]):
91+
def __init__(self, dingz):
92+
super().__init__(factory=lambda absolute_index: Dimmer(absolute_index, dingz))
93+
94+
def _consume_config(self, dimmer_configs):
95+
for absolute_index, dimmer_config in enumerate(dimmer_configs):
96+
dimmer = self._get_or_create(absolute_index)
97+
dimmer._consume_config(dimmer_config)
98+
99+
def _consume_dimmer_state(self, dimmer_states):
100+
for absolute_index, dimmer_state in organize_by_absolute_index(dimmer_states):
101+
dimmer = self._get_or_create(absolute_index)
102+
dimmer._consume_state(dimmer_state)

0 commit comments

Comments
 (0)