Skip to content

Commit

Permalink
add typing hints to make it easier for 3rd party developers to use th…
Browse files Browse the repository at this point in the history
…e library (#90)

* add typing hints to make it easier for 3rd party developers to use the library

* remove unused devicetype enum to support python3.3

* add python 3.3 to travis and tox, install typing module in setup.py
  • Loading branch information
rytilahti authored Sep 18, 2017
1 parent 3ddd31f commit af90a36
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 123 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
sudo: false
language: python
python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
Expand Down
8 changes: 6 additions & 2 deletions pyHS100/discover.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import socket
import logging
import json
from typing import Dict

from pyHS100 import TPLinkSmartHomeProtocol, SmartPlug, SmartBulb
from pyHS100 import TPLinkSmartHomeProtocol, SmartDevice, SmartPlug, SmartBulb

_LOGGER = logging.getLogger(__name__)


class Discover:
@staticmethod
def discover(protocol=None, port=9999, timeout=3):
def discover(protocol: TPLinkSmartHomeProtocol = None,
port: int = 9999,
timeout: int = 3) -> Dict[str, SmartDevice]:
"""
Sends discovery message to 255.255.255.255:9999 in order
to detect available supported devices in the local network,
and waits for given timeout for answers from devices.
:param protocol: Protocol implementation to use
:param timeout: How long to wait for responses, defaults to 5
:param port: port to send broadcast messages, defaults to 9999.
:rtype: dict
Expand Down
13 changes: 8 additions & 5 deletions pyHS100/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import socket
import struct
import logging
from typing import Any, Dict, Union

_LOGGER = logging.getLogger(__name__)

Expand All @@ -24,7 +25,9 @@ class TPLinkSmartHomeProtocol:
DEFAULT_TIMEOUT = 5

@staticmethod
def query(host, request, port=DEFAULT_PORT):
def query(host: str,
request: Union[str, Dict],
port: int = DEFAULT_PORT) -> Any:
"""
Request information from a TP-Link SmartHome Device and return the
response.
Expand Down Expand Up @@ -76,7 +79,7 @@ def query(host, request, port=DEFAULT_PORT):
return json.loads(response)

@staticmethod
def encrypt(request):
def encrypt(request: str) -> bytearray:
"""
Encrypt a request for a TP-Link Smart Home Device.
Expand All @@ -94,7 +97,7 @@ def encrypt(request):
return buffer

@staticmethod
def decrypt(ciphertext):
def decrypt(ciphertext: bytes) -> str:
"""
Decrypt a response of a TP-Link Smart Home Device.
Expand All @@ -104,9 +107,9 @@ def decrypt(ciphertext):
key = TPLinkSmartHomeProtocol.INITIALIZATION_VECTOR
buffer = []

ciphertext = ciphertext.decode('latin-1')
ciphertext_str = ciphertext.decode('latin-1')

for char in ciphertext:
for char in ciphertext_str:
plain = key ^ ord(char)
key = ord(char)
buffer.append(chr(plain))
Expand Down
99 changes: 51 additions & 48 deletions pyHS100/smartbulb.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pyHS100 import SmartDevice
from typing import Any, Dict, Optional, Tuple


class SmartBulb(SmartDevice):
Expand Down Expand Up @@ -40,13 +41,15 @@ class SmartBulb(SmartDevice):
BULB_STATE_ON = 'ON'
BULB_STATE_OFF = 'OFF'

def __init__(self, ip_address, protocol=None):
def __init__(self,
ip_address: str,
protocol: 'TPLinkSmartHomeProtocol' = None) -> None:
SmartDevice.__init__(self, ip_address, protocol)
self.emeter_type = "smartlife.iot.common.emeter"
self.emeter_units = True

@property
def is_color(self):
def is_color(self) -> bool:
"""
Whether the bulb supports color changes
Expand All @@ -56,7 +59,7 @@ def is_color(self):
return bool(self.sys_info['is_color'])

@property
def is_dimmable(self):
def is_dimmable(self) -> bool:
"""
Whether the bulb supports brightness changes
Expand All @@ -66,7 +69,7 @@ def is_dimmable(self):
return bool(self.sys_info['is_dimmable'])

@property
def is_variable_color_temp(self):
def is_variable_color_temp(self) -> bool:
"""
Whether the bulb supports color temperature changes
Expand All @@ -76,16 +79,16 @@ def is_variable_color_temp(self):
"""
return bool(self.sys_info['is_variable_color_temp'])

def get_light_state(self):
def get_light_state(self) -> Dict:
return self._query_helper("smartlife.iot.smartbulb.lightingservice",
"get_light_state")

def set_light_state(self, state):
def set_light_state(self, state: Dict) -> Dict:
return self._query_helper("smartlife.iot.smartbulb.lightingservice",
"transition_light_state", state)

@property
def hsv(self):
def hsv(self) -> Optional[Tuple[int, int, int]]:
"""
Returns the current HSV state of the bulb, if supported
Expand All @@ -109,7 +112,7 @@ def hsv(self):
return hue, saturation, value

@hsv.setter
def hsv(self, state):
def hsv(self, state: Tuple[int, int, int]):
"""
Sets new HSV, if supported
Expand All @@ -124,10 +127,10 @@ def hsv(self, state):
"brightness": int(state[2] * 100 / 255),
"color_temp": 0
}
return self.set_light_state(light_state)
self.set_light_state(light_state)

@property
def color_temp(self):
def color_temp(self) -> Optional[int]:
"""
Color temperature of the device, if supported
Expand All @@ -139,12 +142,12 @@ def color_temp(self):

light_state = self.get_light_state()
if not self.is_on:
return light_state['dft_on_state']['color_temp']
return int(light_state['dft_on_state']['color_temp'])
else:
return light_state['color_temp']
return int(light_state['color_temp'])

@color_temp.setter
def color_temp(self, temp):
def color_temp(self, temp: int) -> None:
"""
Set the color temperature of the device, if supported
Expand All @@ -156,10 +159,10 @@ def color_temp(self, temp):
light_state = {
"color_temp": temp,
}
return self.set_light_state(light_state)
self.set_light_state(light_state)

@property
def brightness(self):
def brightness(self) -> Optional[int]:
"""
Current brightness of the device, if supported
Expand All @@ -171,12 +174,12 @@ def brightness(self):

light_state = self.get_light_state()
if not self.is_on:
return light_state['dft_on_state']['brightness']
return int(light_state['dft_on_state']['brightness'])
else:
return light_state['brightness']
return int(light_state['brightness'])

@brightness.setter
def brightness(self, brightness):
def brightness(self, brightness: int) -> None:
"""
Set the current brightness of the device, if supported
Expand All @@ -188,10 +191,10 @@ def brightness(self, brightness):
light_state = {
"brightness": brightness,
}
return self.set_light_state(light_state)
self.set_light_state(light_state)

@property
def state(self):
def state(self) -> str:
"""
Retrieve the bulb state
Expand All @@ -205,8 +208,29 @@ def state(self):
return self.BULB_STATE_ON
return self.BULB_STATE_OFF

@state.setter
def state(self, bulb_state: str) -> None:
"""
Set the new bulb state
:param bulb_state: one of
BULB_STATE_ON
BULB_STATE_OFF
"""
if bulb_state == self.BULB_STATE_ON:
new_state = 1
elif bulb_state == self.BULB_STATE_OFF:
new_state = 0
else:
raise ValueError

light_state = {
"on_off": new_state,
}
self.set_light_state(light_state)

@property
def state_information(self):
def state_information(self) -> Dict[str, Any]:
"""
Return bulb-specific state information.
:return: Bulb information dict, keys in user-presentable form.
Expand All @@ -215,7 +239,7 @@ def state_information(self):
info = {
'Brightness': self.brightness,
'Is dimmable': self.is_dimmable,
}
} # type: Dict[str, Any]
if self.is_variable_color_temp:
info["Color temperature"] = self.color_temp
if self.is_color:
Expand All @@ -224,42 +248,21 @@ def state_information(self):
return info

@property
def is_on(self):
return self.state == self.BULB_STATE_ON
def is_on(self) -> bool:
return bool(self.state == self.BULB_STATE_ON)

def turn_off(self):
def turn_off(self) -> None:
"""
Turn the bulb off.
"""
self.state = self.BULB_STATE_OFF

def turn_on(self):
def turn_on(self) -> None:
"""
Turn the bulb on.
"""
self.state = self.BULB_STATE_ON

@state.setter
def state(self, bulb_state):
"""
Set the new bulb state
:param bulb_state: one of
BULB_STATE_ON
BULB_STATE_OFF
"""
if bulb_state == self.BULB_STATE_ON:
bulb_state = 1
elif bulb_state == self.BULB_STATE_OFF:
bulb_state = 0
else:
raise ValueError

light_state = {
"on_off": bulb_state,
}
return self.set_light_state(light_state)

@property
def has_emeter(self):
def has_emeter(self) -> bool:
return True
Loading

0 comments on commit af90a36

Please sign in to comment.