Skip to content

Commit

Permalink
Add support for signal repeaters (#251)
Browse files Browse the repository at this point in the history
* Move constant to a more logical place

* Separate blind control into separate class

* Experimental support for stopping the blind

* Rename method

* Add signal repeater class

* Add repeater example

* Typo

* Fix typo

* Minor fixes

* Lint
  • Loading branch information
ggravlingen authored Oct 23, 2019
1 parent 9efc331 commit c3650cf
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 8 deletions.
137 changes: 137 additions & 0 deletions examples/example_cover_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""
This is an example of how the pytradfri-library can be used async.
To run the script, do the following:
$ pip3 install pytradfri
$ Download this file (example_async.py)
$ python3 example_async.py <IP>
Where <IP> is the address to your IKEA gateway. The first time
running you will be asked to input the 'Security Code' found on
the back of your IKEA gateway.
"""

# Hack to allow relative import above top level package
import sys
import os

folder = os.path.dirname(os.path.abspath(__file__)) # noqa
sys.path.insert(0, os.path.normpath("%s/.." % folder)) # noqa

from pytradfri import Gateway
from pytradfri.api.aiocoap_api import APIFactory
from pytradfri.error import PytradfriError
from pytradfri.util import load_json, save_json

import asyncio
import uuid
import argparse

CONFIG_FILE = 'tradfri_standalone_psk.conf'

parser = argparse.ArgumentParser()
parser.add_argument('host', metavar='IP', type=str,
help='IP Address of your Tradfri gateway')
parser.add_argument('-K', '--key', dest='key', required=False,
help='Key found on your Tradfri gateway')
args = parser.parse_args()

if args.host not in load_json(CONFIG_FILE) and args.key is None:
print("Please provide the 'Security Code' on the back of your "
"Tradfri gateway:", end=" ")
key = input().strip()
if len(key) != 16:
raise PytradfriError("Invalid 'Security Code' provided.")
else:
args.key = key


async def run():
# Assign configuration variables.
# The configuration check takes care they are present.
conf = load_json(CONFIG_FILE)

try:
identity = conf[args.host].get('identity')
psk = conf[args.host].get('key')
api_factory = APIFactory(host=args.host, psk_id=identity, psk=psk)
except KeyError:
identity = uuid.uuid4().hex
api_factory = APIFactory(host=args.host, psk_id=identity)

try:
psk = await api_factory.generate_psk(args.key)
print('Generated PSK: ', psk)

conf[args.host] = {'identity': identity,
'key': psk}
save_json(CONFIG_FILE, conf)
except AttributeError:
raise PytradfriError("Please provide the 'Security Code' on the "
"back of your Tradfri gateway using the "
"-K flag.")

api = api_factory.request

gateway = Gateway()

devices_command = gateway.get_devices()
devices_commands = await api(devices_command)
devices = await api(devices_commands)

blinds = [dev for dev in devices if dev.has_blind_control]
repeaters = [dev for dev in devices if dev.has_signal_repeater_control]

# Print all sockets
print("All blinds")
print(blinds)

print("All repeatersK")
print(repeaters)

# Sockets can be accessed by its index, so sockets[1] is the second blind
if blinds:
blind = blinds[0]
else:
print("No sockets found!")
blind = None

def observe_callback(updated_device):
blind = updated_device.blind_control.blinds[0]
print("Received message for: %s" % blind)

def observe_err_callback(err):
print('observe error:', err)

for blind in blinds:
observe_command = blind.observe(observe_callback,
observe_err_callback,
duration=120)
# Start observation as a second task on the loop.
asyncio.ensure_future(api(observe_command))
# Yield to allow observing to start.
await asyncio.sleep(0)

if blind:
# Example 1: What is the name of the blind
print("Name:", blind.name)

# Example 2: checks current battery level of blind
print("Battery (%):", blind.device_info.battery_level)

# Current level of the blind
print("Battery (%):", blinds[0].blind_control.blinds[0].
current_cover_position)

# Example 3: Set blind to 50% open
state_command = blinds[0].blind_control.set_state(50)
await api(state_command)

print("Waiting for observation to end (30 secs)")
await asyncio.sleep(30)

await api_factory.shutdown()


asyncio.get_event_loop().run_until_complete(run())
2 changes: 1 addition & 1 deletion pytradfri/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
ROOT_SIGNAL_REPEATER = "15014"
ROOT_SMART_TASKS = "15010"
ROOT_START_ACTION = "15013" # found under ATTR_START_ACTION
ATTR_START_BLINDS = "15015"

ATTR_ALEXA_PAIR_STATUS = "9093"
ATTR_AUTH = "9063"
Expand Down Expand Up @@ -96,7 +97,6 @@
ATTR_SENSOR_UNIT = "5701"
ATTR_SENSOR_VALUE = "5700"
ATTR_START_ACTION = "9042" # array
ATTR_START_BLINDS = "15015"
ATTR_SMART_TASK_TYPE = "9040" # 4 = transition | 1 = not home | 2 = on/off
ATTR_SMART_TASK_NOT_AT_HOME = 1
ATTR_SMART_TASK_LIGHTS_OFF = 2
Expand Down
14 changes: 13 additions & 1 deletion pytradfri/device/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

from pytradfri.const import ATTR_APPLICATION_TYPE, ROOT_DEVICES, \
ATTR_LAST_SEEN, ATTR_REACHABLE_STATE, \
ATTR_LIGHT_CONTROL, ATTR_SWITCH_PLUG, ATTR_DEVICE_INFO, ATTR_START_BLINDS
ATTR_LIGHT_CONTROL, ATTR_SWITCH_PLUG, ATTR_DEVICE_INFO,\
ATTR_START_BLINDS, ROOT_SIGNAL_REPEATER
from pytradfri.device.blind_control import BlindControl
from pytradfri.device.light_control import LightControl
from pytradfri.device.signal_repeater_control import SignalRepeaterControl
from pytradfri.device.socket_control import SocketControl
from pytradfri.resource import ApiResource

Expand Down Expand Up @@ -64,6 +66,16 @@ def blind_control(self):
if self.has_blind_control:
return BlindControl(self)

@property
def has_signal_repeater_control(self):
return (self.raw is not None and
len(self.raw.get(ROOT_SIGNAL_REPEATER, "")) > 0)

@property
def signal_repeater_control(self):
if self.has_signal_repeater_control:
return SignalRepeaterControl(self)

def __repr__(self):
return "<{} - {} ({})>".format(self.id, self.name,
self.device_info.model_number)
Expand Down
18 changes: 13 additions & 5 deletions pytradfri/device/blind_control.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Class to control the blinds."""
from pytradfri.command import Command
from pytradfri.const import RANGE_BLIND, \
ATTR_BLIND_CURRENT_POSITION, ATTR_START_BLINDS
ATTR_BLIND_CURRENT_POSITION, ATTR_START_BLINDS, ATTR_BLIND_TRIGGER
from pytradfri.device.blind import Blind
from pytradfri.device.controller import Controller

Expand All @@ -22,12 +22,22 @@ def blinds(self):
"""Return blind objects of the blind control."""
return [Blind(self._device, i) for i in range(len(self.raw))]

def trigger_blind(self):
"""Trigger the blind's movement."""
return self.set_value(
{
ATTR_BLIND_TRIGGER: True
}
)

def set_state(self, state):
"""Set state of a blind."""
self._value_validate(state, RANGE_BLIND, "Blind position")

return self.set_value(
state
{
ATTR_BLIND_CURRENT_POSITION: state
}
)

def set_value(self, value):
Expand All @@ -38,8 +48,6 @@ def set_value(self, value):
return Command('put', self._device.path, {
ATTR_START_BLINDS:
[
{
ATTR_BLIND_CURRENT_POSITION: value
}
value
]
})
15 changes: 15 additions & 0 deletions pytradfri/device/signal_repeater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Represent a signal repeater."""
from pytradfri.const import ROOT_SIGNAL_REPEATER


class SignalRepeater:
"""Represent a signal repeater."""

def __init__(self, device, index):
self.device = device
self.index = index

@property
def raw(self):
"""Return raw data that it represents."""
return self.device.raw[ROOT_SIGNAL_REPEATER][self.index]
26 changes: 26 additions & 0 deletions pytradfri/device/signal_repeater_control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Class to control the signal repeater."""
from pytradfri.const import ROOT_SIGNAL_REPEATER
from pytradfri.device.signal_repeater import SignalRepeater


class SignalRepeaterControl:
"""Class to control the signal repeaters."""

def __init__(self, device):
self._device = device

@property
def raw(self):
"""Return raw data that it represents."""
return self._device.raw[ROOT_SIGNAL_REPEATER]

@property
def signal_repeaters(self):
"""Return signal repeater objects of the signal repeater control."""
return [SignalRepeater(self._device, i) for i in range(len(self.raw))]

def __repr__(self):
return '<SignalRepeaterControl for {} ({} signal repeaters)>'.format(
self._device.name,
len(self.raw)
)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
long_description = f.read()

VERSION = "6.3.1"
VERSION = "6.4.0"
DOWNLOAD_URL = \
'https://github.com/ggravlingen/pytradfri/archive/{}.zip'.format(VERSION)

Expand Down

0 comments on commit c3650cf

Please sign in to comment.