Skip to content

Commit

Permalink
General cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ldotlopez committed Apr 20, 2022
1 parent b50cfaf commit 6ae2b67
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 99 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ routers, water detectors and more.
* https://github.com/waffelheld/dlink-device-tracker
* https://github.com/bikerp/dsp-w215-hnap
* https://github.com/postlund/dlink_hnap
* API for smart plugs: https://github.com/bikerp/dsp-w215-hnap/blob/master/js/soapclient.js
15 changes: 13 additions & 2 deletions hnap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,28 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

from .devices import Camera, DeviceFactory, Motion, Router, Siren, SirenSound, Water
from .soapclient import AuthenticationError, MethodCallError
from .devices import (
Camera,
Device,
DeviceFactory,
Motion,
Router,
Siren,
SirenSound,
Water,
)
from .soapclient import AuthenticationError, MethodCallError, SoapClient

__all__ = [
"AuthenticationError",
"MethodCallError",
"Device",
"DeviceFactory",
"Camera",
"Motion",
"Router",
"Siren",
"SirenSound",
"SoapClient",
"Water",
]
11 changes: 4 additions & 7 deletions hnap/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from .soapclient import SoapClient


OUTPUT_TMPL = """
Device info
===========
Expand All @@ -38,9 +39,9 @@
==============
{device_actions}
SOAP actions
Module actions
==============
{soap_actions}
{module_actions}
"""


Expand Down Expand Up @@ -86,10 +87,6 @@ def main():
)
args = parser.parse_args()

if len(args.call) > 1:
print("Error: Only on call is allowed", file=sys.stderr)
return 1

client = SoapClient(
hostname=args.hostname,
username=args.username,
Expand Down Expand Up @@ -127,8 +124,8 @@ def main():
print(
OUTPUT_TMPL.format(
info=pprint.pformat(client.device_info()),
soap_actions=pprint.pformat(client.soap_actions()),
device_actions=pprint.pformat(client.device_actions()),
module_actions=pprint.pformat(client.module_actions()),
).strip()
)

Expand Down
25 changes: 25 additions & 0 deletions hnap/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 Luis López <luis@cuarentaydos.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.


DEFAULT_MODULE_ID = "1"
DEFAULT_PORT = 80
DEFAULT_REQUEST_TIMEOUT = 10
DEFAULT_SESSION_LIFETIME = 3600
DEFAULT_USERNAME = "admin"
86 changes: 33 additions & 53 deletions hnap/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,25 @@
# USA.


import functools
import logging
from datetime import datetime
from enum import Enum

from .const import DEFAULT_USERNAME, DEFAULT_MODULE_ID, DEFAULT_PORT
from .soapclient import MethodCallError, SoapClient

_LOGGER = logging.getLogger(__name__)
from .helpers import auth_required


def auth_required(fn):
@functools.wraps(fn)
def _wrap(device, *args, **kwargs):
if not device.client.authenticated:
device.client.authenticate()
_LOGGER.debug("Device authenticated")
return fn(device, *args, **kwargs)

return _wrap
_LOGGER = logging.getLogger(__name__)


def DeviceFactory(
*, client=None, hostname=None, password=None, username="Admin", port=80
*,
client=None,
hostname=None,
password=None,
username=DEFAULT_USERNAME,
port=DEFAULT_PORT,
):
client = client or SoapClient(
hostname=hostname, password=password, username=username, port=port
Expand All @@ -53,11 +49,15 @@ def DeviceFactory(

if "Audio Renderer" in module_types:
cls = Siren
# 'Optical Recognition', 'Environmental Sensor', 'Camera']

elif "Camera" in module_types:
# Other posible values for camera (needs testing):
# 'Optical Recognition', 'Environmental Sensor', 'Camera'
cls = Camera

elif "Motion Sensor" in module_types:
cls = Motion

else:
raise TypeError(module_types)

Expand All @@ -73,15 +73,16 @@ def __init__(
client=None,
hostname=None,
password=None,
username="Admin",
port=80,
username=DEFAULT_USERNAME,
port=DEFAULT_PORT,
module_id=DEFAULT_MODULE_ID,
):
self.client = client or SoapClient(
hostname=hostname, password=password, username=username, port=port
)
self.module_id = module_id

self._info = None
self._module_id = None
self._controller = None

@property
def info(self):
Expand All @@ -90,59 +91,38 @@ def info(self):

return self._info

@property
def module_id(self):
if not self._module_id:
self._module_id = self.info["ModuleTypes"].find(self.MODULE_TYPE) + 1

return self._module_id

@property
def controller(self):
# NOTE: not sure about this
return self.module_id

def call(self, *args, **kwargs):
kwargs["ModuleID"] = kwargs.get("ModuleID") or self.module_id
kwargs["Controller"] = kwargs.get("Controller") or self.controller

kwargs["ModuleID"] = self.module_id
return self.client.call(*args, **kwargs)

# def get_info(self):
# info = self.client.device_info()

# if isinstance(info["ModuleTypes"], str):
# info["ModuleTypes"] = [info["ModuleTypes"]]

# dev = set(info["ModuleTypes"])
# req = set(self.REQUIRED_MODULE_TYPES)
# if not req.issubset(dev):
# raise TypeError(
# f"device '{self.client.hostname}' is not a "
# f"{self.__class__.__name__}",
# )
def is_authenticated(self):
return self.client.is_authenticated()

# return info
def authenticate(self):
return self.client.authenticate()


class Camera(Device):
MODULE_TYPE = "Camera"
DEFAULT_SCHEMA = "http://"
DEFAULT_STREAM_PATH = "/play1.sdp"
DEFAULT_PICTURE_PATH = "/image/jpeg.cgi"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._base_url = (
"http://"
{self.DEFAULT_SCHEMA}
+ f"{self.client.username.lower()}:{self.client.password}@"
+ f"{self.client.hostname}:{self.client.port}"
)

@property
def stream_url(self):
return f"{self._base_url}/play1.sdp"
return f"{self._base_url}{self.DEFAULT_STREAM_PATH}"

@property
def picture_url(self):
return f"{self._base_url}/image/jpeg.cgi"
return f"{self._base_url}{self.DEFAULT_PICTURE_PATH}"


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

# @backoff.setter
# def backoff(self, seconds):
# self.client.call(
# "SetMotionDetectorSettings", ModuleID=1, Backoff=self._backoff
# self.call(
# "SetMotionDetectorSettings", Backoff=self._backoff
# )
# _LOGGER.warning("set backoff property has no effect")

# def authenticate(self):
# super().authenticate()

# res = self.client.call("GetMotionDetectorSettings", ModuleID=1)
# res = self.call("GetMotionDetectorSettings")
# try:
# self._backoff = int(res["Backoff"])
# except (ValueError, TypeError, KeyError):
Expand Down
31 changes: 31 additions & 0 deletions hnap/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2021 Luis López <luis@cuarentaydos.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

import functools


def auth_required(fn):
@functools.wraps(fn)
def _wrap(access, *args, **kwargs):
if not access.is_authenticated():
access.authenticate()

return fn(access, *args, **kwargs)

return _wrap
Loading

0 comments on commit 6ae2b67

Please sign in to comment.