-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
206 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,5 @@ venv.bak/ | |
|
||
# mypy | ||
.mypy_cache/ | ||
.vscode | ||
Pipfile* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,60 @@ | ||
# somfy-mylink-api | ||
API to simplify interactions with a Somfy MyLink device | ||
[![Build Status](https://travis-ci.org/bendews/somfy-mylink-synergy.svg?branch=master)](https://travis-ci.org/bendews/somfy-mylink-synergy) | ||
|
||
# Somfy MyLink Synergy API | ||
|
||
Python API to utilise the Somfy Synergy API utilising JsonRPC. | ||
|
||
## Requirements | ||
|
||
- Python >= 3.4 | ||
|
||
## Usage | ||
```python | ||
|
||
import asyncio | ||
from somfy_mylink_synergy import SomfyMyLinkSynergy | ||
|
||
loop = asyncio.get_event_loop() | ||
mylink = SomfyMyLinkSynergy('YourSystemID', '10.1.1.50') | ||
|
||
|
||
mylink_status = loop.run_until_complete(mylink.status_info()) | ||
for device in mylink_status['result']: | ||
print(device['targetID'], device['name']) | ||
|
||
> ('CC0000A.1', 'Bedroom Cover') | ||
> ('CC0000A.2', 'Kitchen Cover') | ||
|
||
mylink_status = loop.run_until_complete(mylink.scene_list()) | ||
for scene in mylink_status['result']: | ||
print(device['targetID'], device['name']) | ||
|
||
> ('123456789', 'Morning') | ||
> ('987654321', 'Evening') | ||
|
||
mylink_ping= loop.run_until_complete(mylink.status_ping()) | ||
for device in mylink_status['result']: | ||
print(device) | ||
|
||
> ('CC0000A.1') | ||
> ('CC0000A.2') | ||
|
||
open_cover = loop.run_until_complete(mylink.move_up('CC0000A.1')) | ||
close_cover = loop.run_until_complete(mylink.move_down('CC0000A.1')) | ||
stop_cover = loop.run_until_complete(mylink.move_stop('CC0000A.1')) | ||
activate_scene = loop.run_until_complete(mylink.scene_run('123456789')) | ||
|
||
``` | ||
|
||
|
||
## TODO: | ||
|
||
- None | ||
|
||
## License | ||
|
||
MIT | ||
|
||
## Author Information | ||
|
||
Created in 2018 by [Ben Dews](https://bendews.com) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
""" | ||
Install Somfy MyLink Synergy API | ||
""" | ||
|
||
from setuptools import setup, find_packages | ||
|
||
with open('README.md') as f: | ||
long_description = f.read() | ||
|
||
setup( | ||
name='somfy_mylink_synergy', | ||
version='1.0.1', | ||
url='http://github.com/bendews/somfy-mylink-synergy', | ||
license='MIT', | ||
author='Ben Dews', | ||
author_email='[email protected]', | ||
description='Python API to utilise the Somfy Synergy JsonRPC API', | ||
long_description=long_description, | ||
long_description_content_type='text/markdown', | ||
packages=find_packages(), | ||
keywords='somfy mylink synergy covers sensors api jsonrpc', | ||
platforms='any' | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import json | ||
import logging | ||
import asyncio | ||
from random import randint | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class SomfyMyLinkSynergy: | ||
"""API Wrapper for the Somfy MyLink device.""" | ||
|
||
def __init__(self, system_id, host, port=44100, timeout=3): | ||
"""Create the object with required parameters.""" | ||
self.host = host | ||
self.port = port | ||
self.system_id = system_id | ||
self._timeout = timeout | ||
self._stream_reader = None | ||
self._stream_writer = None | ||
|
||
async def scene_list(self): | ||
"""List all Somfy scenes.""" | ||
return await self.command("mylink.scene.list") | ||
|
||
async def scene_run(self, scene_id): | ||
"""Run specified Somfy scene.""" | ||
return await self.command("mylink.scene.run", sceneID=scene_id) | ||
|
||
async def status_info(self, target_id="*.*"): | ||
"""Retrieve info on all Somfy devices.""" | ||
return await self.command("mylink.status.info", targetID=target_id) | ||
|
||
async def status_ping(self, target_id="*.*"): | ||
"""Send a Ping message to all Somfy devices.""" | ||
return await self.command("mylink.status.ping", targetID=target_id) | ||
|
||
async def move_up(self, target_id="*.*"): | ||
"""Format a Move up message and send it.""" | ||
return await self.command("mylink.move.up", targetID=target_id) | ||
|
||
async def move_down(self, target_id="*.*"): | ||
"""Format a Move Down message and send it.""" | ||
return await self.command("mylink.move.down", targetID=target_id) | ||
|
||
async def move_stop(self, target_id="*.*"): | ||
"""Format a Stop message and send it.""" | ||
return await self.command("mylink.move.stop", targetID=target_id) | ||
|
||
async def command(self, method, **kwargs): | ||
"""Format a Somfy JSON API message.""" | ||
params = dict(**kwargs) | ||
params.setdefault('auth', self.system_id) | ||
# Set a random message ID | ||
message_id = randint(0, 1000) | ||
message = dict(method=method, params=params, id=message_id) | ||
return await self.send_message(message) | ||
|
||
async def send_message(self, message): | ||
"""Send a Somfy JSON API message and gather response.""" | ||
# Substring to search in response string to signify end of the message | ||
# MyLink always returns message 'id' as last key so we search for that | ||
# print(read_until_string) | ||
# > b'"id":3}' | ||
message_id_bytes = str(message['id']).encode('utf-8') | ||
read_until_string = b'"id":'+message_id_bytes+b'}' | ||
try: | ||
await self._send_data(message) | ||
return await self._recieve_data(read_until_string) | ||
except UnicodeDecodeError as unicode_error: | ||
_LOGGER.info('Message collision, trying again: %s', unicode_error) | ||
return await self.send_message(message) | ||
|
||
async def _make_connection(self): | ||
"""Open asyncio socket connection with MyLink device.""" | ||
if self._stream_writer: | ||
_LOGGER.debug('Reusing existing socket connection to %s on %s', | ||
self.host, self.port) | ||
return | ||
_LOGGER.debug('Opening new socket connection to %s on %s', | ||
self.host, self.port) | ||
conn = asyncio.open_connection(self.host, self.port) | ||
conn_wait = asyncio.wait_for(conn, timeout=self._timeout) | ||
try: | ||
self._stream_reader, self._stream_writer = await conn_wait | ||
except (asyncio.TimeoutError, ConnectionRefusedError, OSError) as timeout_err: | ||
_LOGGER.error('Connection failed for %s on %s. ' | ||
'Please ensure device is reachable.', | ||
self.host, self.port) | ||
raise timeout_err | ||
|
||
async def _send_data(self, data): | ||
"""Send data to MyLink using JsonRPC via Socket.""" | ||
await self._make_connection() | ||
try: | ||
data_as_bytes = str.encode(json.dumps(data)) | ||
self._stream_writer.write(data_as_bytes) | ||
except TypeError as data_error: | ||
_LOGGER.error('Invalid data sent to device') | ||
raise data_error | ||
|
||
async def _recieve_data(self, read_until=None): | ||
"""Recieve Data from MyLink using JsonRPC via Socket.""" | ||
await self._make_connection() | ||
try: | ||
if read_until: | ||
reader = self._stream_reader.readuntil(read_until) | ||
else: | ||
reader = self._stream_reader.read(1024) | ||
data_bytes = await asyncio.wait_for(reader, timeout=self._timeout) | ||
data_dict = json.loads(data_bytes.decode('utf-8')) | ||
return data_dict | ||
except asyncio.TimeoutError as timeout_err: | ||
_LOGGER.error('Recieved timeout whilst waiting for' | ||
' response from MyLink device.') | ||
raise timeout_err | ||
except UnicodeDecodeError as unicode_error: | ||
_LOGGER.error('Could not decode Unicode: %s', data_bytes) | ||
raise unicode_error | ||
except json.decoder.JSONDecodeError as json_error: | ||
_LOGGER.error('Could not decode JSON: %s', data_bytes) | ||
raise json_error |