Skip to content

Commit 6ae2b67

Browse files
committed
General cleanup
1 parent b50cfaf commit 6ae2b67

7 files changed

Lines changed: 164 additions & 99 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ routers, water detectors and more.
1414
* https://github.com/waffelheld/dlink-device-tracker
1515
* https://github.com/bikerp/dsp-w215-hnap
1616
* https://github.com/postlund/dlink_hnap
17+
* API for smart plugs: https://github.com/bikerp/dsp-w215-hnap/blob/master/js/soapclient.js

hnap/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,28 @@
1717
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
1818
# USA.
1919

20-
from .devices import Camera, DeviceFactory, Motion, Router, Siren, SirenSound, Water
21-
from .soapclient import AuthenticationError, MethodCallError
20+
from .devices import (
21+
Camera,
22+
Device,
23+
DeviceFactory,
24+
Motion,
25+
Router,
26+
Siren,
27+
SirenSound,
28+
Water,
29+
)
30+
from .soapclient import AuthenticationError, MethodCallError, SoapClient
2231

2332
__all__ = [
2433
"AuthenticationError",
2534
"MethodCallError",
35+
"Device",
2636
"DeviceFactory",
2737
"Camera",
2838
"Motion",
2939
"Router",
3040
"Siren",
3141
"SirenSound",
42+
"SoapClient",
3243
"Water",
3344
]

hnap/cli.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from .soapclient import SoapClient
3131

32+
3233
OUTPUT_TMPL = """
3334
Device info
3435
===========
@@ -38,9 +39,9 @@
3839
==============
3940
{device_actions}
4041
41-
SOAP actions
42+
Module actions
4243
==============
43-
{soap_actions}
44+
{module_actions}
4445
"""
4546

4647

@@ -86,10 +87,6 @@ def main():
8687
)
8788
args = parser.parse_args()
8889

89-
if len(args.call) > 1:
90-
print("Error: Only on call is allowed", file=sys.stderr)
91-
return 1
92-
9390
client = SoapClient(
9491
hostname=args.hostname,
9592
username=args.username,
@@ -127,8 +124,8 @@ def main():
127124
print(
128125
OUTPUT_TMPL.format(
129126
info=pprint.pformat(client.device_info()),
130-
soap_actions=pprint.pformat(client.soap_actions()),
131127
device_actions=pprint.pformat(client.device_actions()),
128+
module_actions=pprint.pformat(client.module_actions()),
132129
).strip()
133130
)
134131

hnap/const.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2021 Luis López <luis@cuarentaydos.com>
4+
#
5+
# This program is free software; you can redistribute it and/or
6+
# modify it under the terms of the GNU General Public License
7+
# as published by the Free Software Foundation; either version 2
8+
# of the License, or (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18+
# USA.
19+
20+
21+
DEFAULT_MODULE_ID = "1"
22+
DEFAULT_PORT = 80
23+
DEFAULT_REQUEST_TIMEOUT = 10
24+
DEFAULT_SESSION_LIFETIME = 3600
25+
DEFAULT_USERNAME = "admin"

hnap/devices.py

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,25 @@
1818
# USA.
1919

2020

21-
import functools
2221
import logging
2322
from datetime import datetime
2423
from enum import Enum
2524

25+
from .const import DEFAULT_USERNAME, DEFAULT_MODULE_ID, DEFAULT_PORT
2626
from .soapclient import MethodCallError, SoapClient
27-
28-
_LOGGER = logging.getLogger(__name__)
27+
from .helpers import auth_required
2928

3029

31-
def auth_required(fn):
32-
@functools.wraps(fn)
33-
def _wrap(device, *args, **kwargs):
34-
if not device.client.authenticated:
35-
device.client.authenticate()
36-
_LOGGER.debug("Device authenticated")
37-
return fn(device, *args, **kwargs)
38-
39-
return _wrap
30+
_LOGGER = logging.getLogger(__name__)
4031

4132

4233
def DeviceFactory(
43-
*, client=None, hostname=None, password=None, username="Admin", port=80
34+
*,
35+
client=None,
36+
hostname=None,
37+
password=None,
38+
username=DEFAULT_USERNAME,
39+
port=DEFAULT_PORT,
4440
):
4541
client = client or SoapClient(
4642
hostname=hostname, password=password, username=username, port=port
@@ -53,11 +49,15 @@ def DeviceFactory(
5349

5450
if "Audio Renderer" in module_types:
5551
cls = Siren
56-
# 'Optical Recognition', 'Environmental Sensor', 'Camera']
52+
5753
elif "Camera" in module_types:
54+
# Other posible values for camera (needs testing):
55+
# 'Optical Recognition', 'Environmental Sensor', 'Camera'
5856
cls = Camera
57+
5958
elif "Motion Sensor" in module_types:
6059
cls = Motion
60+
6161
else:
6262
raise TypeError(module_types)
6363

@@ -73,15 +73,16 @@ def __init__(
7373
client=None,
7474
hostname=None,
7575
password=None,
76-
username="Admin",
77-
port=80,
76+
username=DEFAULT_USERNAME,
77+
port=DEFAULT_PORT,
78+
module_id=DEFAULT_MODULE_ID,
7879
):
7980
self.client = client or SoapClient(
8081
hostname=hostname, password=password, username=username, port=port
8182
)
83+
self.module_id = module_id
84+
8285
self._info = None
83-
self._module_id = None
84-
self._controller = None
8586

8687
@property
8788
def info(self):
@@ -90,59 +91,38 @@ def info(self):
9091

9192
return self._info
9293

93-
@property
94-
def module_id(self):
95-
if not self._module_id:
96-
self._module_id = self.info["ModuleTypes"].find(self.MODULE_TYPE) + 1
97-
98-
return self._module_id
99-
100-
@property
101-
def controller(self):
102-
# NOTE: not sure about this
103-
return self.module_id
104-
10594
def call(self, *args, **kwargs):
106-
kwargs["ModuleID"] = kwargs.get("ModuleID") or self.module_id
107-
kwargs["Controller"] = kwargs.get("Controller") or self.controller
108-
95+
kwargs["ModuleID"] = self.module_id
10996
return self.client.call(*args, **kwargs)
11097

111-
# def get_info(self):
112-
# info = self.client.device_info()
113-
114-
# if isinstance(info["ModuleTypes"], str):
115-
# info["ModuleTypes"] = [info["ModuleTypes"]]
116-
117-
# dev = set(info["ModuleTypes"])
118-
# req = set(self.REQUIRED_MODULE_TYPES)
119-
# if not req.issubset(dev):
120-
# raise TypeError(
121-
# f"device '{self.client.hostname}' is not a "
122-
# f"{self.__class__.__name__}",
123-
# )
98+
def is_authenticated(self):
99+
return self.client.is_authenticated()
124100

125-
# return info
101+
def authenticate(self):
102+
return self.client.authenticate()
126103

127104

128105
class Camera(Device):
129106
MODULE_TYPE = "Camera"
107+
DEFAULT_SCHEMA = "http://"
108+
DEFAULT_STREAM_PATH = "/play1.sdp"
109+
DEFAULT_PICTURE_PATH = "/image/jpeg.cgi"
130110

131111
def __init__(self, *args, **kwargs):
132112
super().__init__(*args, **kwargs)
133113
self._base_url = (
134-
"http://"
114+
{self.DEFAULT_SCHEMA}
135115
+ f"{self.client.username.lower()}:{self.client.password}@"
136116
+ f"{self.client.hostname}:{self.client.port}"
137117
)
138118

139119
@property
140120
def stream_url(self):
141-
return f"{self._base_url}/play1.sdp"
121+
return f"{self._base_url}{self.DEFAULT_STREAM_PATH}"
142122

143123
@property
144124
def picture_url(self):
145-
return f"{self._base_url}/image/jpeg.cgi"
125+
return f"{self._base_url}{self.DEFAULT_PICTURE_PATH}"
146126

147127

148128
class Motion(Device):
@@ -167,15 +147,15 @@ def backoff(self):
167147

168148
# @backoff.setter
169149
# def backoff(self, seconds):
170-
# self.client.call(
171-
# "SetMotionDetectorSettings", ModuleID=1, Backoff=self._backoff
150+
# self.call(
151+
# "SetMotionDetectorSettings", Backoff=self._backoff
172152
# )
173153
# _LOGGER.warning("set backoff property has no effect")
174154

175155
# def authenticate(self):
176156
# super().authenticate()
177157

178-
# res = self.client.call("GetMotionDetectorSettings", ModuleID=1)
158+
# res = self.call("GetMotionDetectorSettings")
179159
# try:
180160
# self._backoff = int(res["Backoff"])
181161
# except (ValueError, TypeError, KeyError):

hnap/helpers.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2021 Luis López <luis@cuarentaydos.com>
4+
#
5+
# This program is free software; you can redistribute it and/or
6+
# modify it under the terms of the GNU General Public License
7+
# as published by the Free Software Foundation; either version 2
8+
# of the License, or (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18+
# USA.
19+
20+
import functools
21+
22+
23+
def auth_required(fn):
24+
@functools.wraps(fn)
25+
def _wrap(access, *args, **kwargs):
26+
if not access.is_authenticated():
27+
access.authenticate()
28+
29+
return fn(access, *args, **kwargs)
30+
31+
return _wrap

0 commit comments

Comments
 (0)