Skip to content

Commit dffe576

Browse files
author
Fabian Affolter
committed
Merge branch 'master' of https://github.com/retoo/python-dingz into retoo-master
2 parents 67273a3 + 33e85f0 commit dffe576

File tree

7 files changed

+302
-36
lines changed

7 files changed

+302
-36
lines changed

.github/workflows/python.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Python lint and packages
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
build:
11+
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
python-version: [3.6, 3.7, 3.8]
16+
17+
steps:
18+
- uses: actions/checkout@v2
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v2
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
python setup.py install
27+
- name: Black Code Formatter
28+
uses: lgeiger/[email protected]

.github/workflows/pythonapp.yml

Lines changed: 0 additions & 33 deletions
This file was deleted.

CHANGELOG.rst

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

4+
0.3.0 (2020-09-28)
5+
6+
- Support for Shades/Blinds
7+
- Access to more parts of the Dingz API
8+
(Blind Config, System Config and others)
9+
- Automatic discovery for Dingz units in the local network
10+
411
0.2.0 (2020-05-20)
512
------------------
613

dingz/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,23 @@
2121
LIGHT = "light"
2222
SCHEDULE = "schedule"
2323
TIMER = "timer"
24+
SHADE = "shade"
25+
STATE = "state"
2426

2527
# Special endpoints
2628
LOG = "/log"
2729
FIRMWARE = "/load"
2830

2931
SETTINGS = "settings"
32+
SYSTEM_CONFIG = "system_config"
3033
FRONT_LED_GET = "led/get"
3134
FRONT_LED_SET = "led/set"
3235
BUTTON_ACTIONS = "action"
3336
WIFI_SCAN = "scan"
3437

3538
# Configuration endpoints
3639
PIR_CONFIGURATION = "pir_config"
40+
BLIND_CONFIGURATION = "blind_config"
3741
THERMOSTAT_CONFIGURATION = "thermostat_config"
3842
INPUT_CONFIGURATION = "input_config"
3943

dingz/dingz.py

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
WIFI_SCAN,
2222
TIMER,
2323
SCHEDULE,
24+
SHADE,
25+
INFO,
26+
STATE,
27+
SYSTEM_CONFIG,
28+
BLIND_CONFIGURATION,
2429
)
2530

2631
_LOGGER = logging.getLogger(__name__)
@@ -35,6 +40,7 @@ def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> N
3540
self._host = host
3641
self._session = session
3742
self._device_details = None
43+
self._info = None
3844
self._wifi_networks = None
3945
self._settings = None
4046
self._catch_all = {}
@@ -47,15 +53,26 @@ def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> N
4753
self._motion = None
4854
self._schedule = None
4955
self._timer = None
56+
self._state = {}
57+
self._blind_config = None
58+
self._system_config = None
59+
5060
self.uri = URL.build(scheme="http", host=self._host).join(URL(API))
5161

5262
async def get_device_info(self) -> None:
5363
"""Get the details from the dingz."""
5464
url = URL(self.uri).join(URL(DEVICE_INFO))
5565
response = await make_call(self, uri=url)
56-
self._device_details = response
66+
# response is: "mac" => { device_details }
67+
self._device_details = next(iter(response.values()))
5768

5869
async def get_info(self) -> None:
70+
"""Get general information fro the dingz."""
71+
url = URL(self.uri).join(URL(INFO))
72+
response = await make_call(self, uri=url)
73+
self._info = response
74+
75+
async def get_all_info(self) -> None:
5976
"""Get everything from the dingz unit."""
6077
for endpoint in [
6178
PUCK,
@@ -118,6 +135,21 @@ async def get_light(self) -> None:
118135
self._intensity = response["intensity"]
119136
self._hour_of_day = response["state"]
120137

138+
async def get_state(self) -> None:
139+
url = URL(self.uri).join(URL(STATE))
140+
response = await make_call(self, uri=url)
141+
self._state = response
142+
143+
async def get_blind_config(self) -> None:
144+
url = URL(self.uri).join(URL(BLIND_CONFIGURATION))
145+
response = await make_call(self, uri=url)
146+
self._blind_config = response
147+
148+
async def get_system_config(self) -> None:
149+
url = URL(self.uri).join(URL(SYSTEM_CONFIG))
150+
response = await make_call(self, uri=url)
151+
self._system_config = response
152+
121153
async def enabled(self) -> bool:
122154
"""Return true if front LED is on."""
123155
url = URL(self.uri).join(URL(FRONT_LED_GET))
@@ -136,6 +168,59 @@ async def turn_off(self) -> None:
136168
url = URL(self.uri).join(URL(FRONT_LED_SET))
137169
await make_call(self, uri=url, method="POST", data=data)
138170

171+
async def operate_shade(self, shade_no, blind=None, lamella=None) -> None:
172+
"""operate the lamella and blind
173+
blind: 0 fully closed, 100 fully open
174+
lamella: 0 lamellas closed, 100 lamellas open
175+
"""
176+
177+
# With newer versions of dingz, we can just leave
178+
# either lamella or blind None (i.e. do not chang)
179+
# but currently we need to lookup the current state
180+
# of the shade first.
181+
if blind is None or lamella is None:
182+
await self.get_state()
183+
184+
if blind is None:
185+
blind = self.current_blind_level(shade_no)
186+
187+
if lamella is None:
188+
if self.is_shade_opened(shade_no):
189+
# if the shade is currently completely opened (i.e. up), the lamella
190+
# value is not really relevant (has no effect). We assume the
191+
# lamella value to be 0, ie. closed.
192+
# i.e. we set lamella to 45, raise blind to the top, and then back down again
193+
# => de we expect the lamella to be set to 45 again, or does it get resetted to 0?
194+
lamella = 0
195+
else:
196+
lamella = self.current_lamella_level(shade_no)
197+
198+
url = URL(self.uri).join(URL("%s/%s" % (SHADE, shade_no)))
199+
params = {"blind": str(blind), "lamella": str(lamella)}
200+
await make_call(self, uri=url, method="POST", parameters=params)
201+
202+
async def shade_up(self, shade_no) -> None:
203+
await self.shade_command(shade_no, "up")
204+
205+
async def shade_down(self, shade_no) -> None:
206+
await self.shade_command(shade_no, "down")
207+
208+
async def shade_stop(self, shade_no) -> None:
209+
await self.shade_command(shade_no, "stop")
210+
211+
async def lamella_open(self, shade_no) -> None:
212+
await self.operate_shade(shade_no, lamella=100)
213+
214+
async def lamella_close(self, shade_no) -> None:
215+
await self.operate_shade(shade_no, lamella=0)
216+
217+
async def lamella_stop(self, shade_no) -> None:
218+
await self.shade_stop(shade_no)
219+
220+
async def shade_command(self, shade_no, verb):
221+
url = URL(self.uri).join(URL("%s/%s/%s" % (SHADE, shade_no, verb)))
222+
await make_call(self, uri=url, method="POST")
223+
139224
async def set_timer(self, data) -> None:
140225
"""Set a timer."""
141226
print(data)
@@ -147,6 +232,10 @@ async def stop_timer(self, data) -> None:
147232
url = URL(self.uri).join(URL(TIMER))
148233
await make_call(self, uri=url, method="POST", data=data)
149234

235+
@property
236+
def dingz_name(self) -> str:
237+
return self._system_config["dingz_name"]
238+
150239
@property
151240
def device_details(self) -> str:
152241
"""Return the current device details."""
@@ -178,7 +267,7 @@ def wifi_networks(self) -> str:
178267
return self._wifi_networks
179268

180269
@property
181-
def everything(self) -> str:
270+
def everything(self) -> dict:
182271
"""Return the all available device details."""
183272
return self._catch_all
184273

@@ -212,6 +301,76 @@ def intensity(self) -> float:
212301
"""Return the current light intensity in lux."""
213302
return round(self._intensity, 1)
214303

304+
@property
305+
def version(self) -> str:
306+
return self._info["version"]
307+
308+
@property
309+
def type(self) -> str:
310+
return self._info["type"]
311+
312+
@property
313+
def mac(self) -> str:
314+
return self._info["mac"]
315+
316+
@property
317+
def front_hw_model(self) -> str:
318+
return self._device_details["front_hw_model"]
319+
320+
@property
321+
def puck_hw_model(self) -> str:
322+
return self._device_details["puck_hw_model"]
323+
324+
@property
325+
def front_sn(self) -> str:
326+
return self._device_details["front_sn"]
327+
328+
@property
329+
def puck_sn(self) -> str:
330+
return self._device_details["puck_sn"]
331+
332+
@property
333+
def hw_version(self) -> str:
334+
return self._device_details["hw_version"]
335+
336+
@property
337+
def fw_version(self) -> str:
338+
return self._device_details["fw_version"]
339+
340+
def _shade_current_state(self, shade_no: int):
341+
return self._state["blinds"][shade_no]
342+
343+
def current_blind_level(self, shade_no):
344+
return self._shade_current_state(shade_no)["position"]
345+
346+
def current_lamella_level(self, shade_no):
347+
return self._shade_current_state(shade_no)["lamella"]
348+
349+
def is_shade_closed(self, shade_no):
350+
# when closed, we care if the lamellas are opened or not
351+
return (
352+
self.current_blind_level(shade_no) == 0
353+
and self.current_lamella_level(shade_no) == 0
354+
)
355+
356+
def is_shade_opened(self, shade_no):
357+
return self.current_blind_level(shade_no) == 100
358+
359+
def is_lamella_closed(self, shade_no):
360+
return self.current_lamella_level(shade_no) == 0
361+
362+
def is_lamella_opened(self, shade_no):
363+
return self.current_lamella_level(shade_no) == 100
364+
365+
def is_blind_opening(self, shade_no):
366+
return self._shade_current_state(shade_no)["moving"] == "up"
367+
368+
def is_blind_closing(self, shade_no):
369+
return self._shade_current_state(shade_no)["moving"] == "down"
370+
371+
def blind_name(self, shade_no):
372+
return self._blind_config["blinds"][shade_no]["name"]
373+
215374
# See "Using Asyncio in Python" by Caleb Hattingh for implementation
216375
# details.
217376
async def close(self) -> None:

0 commit comments

Comments
 (0)