Skip to content

Commit 0b9d2ab

Browse files
committed
Update to 0.0.2
1 parent 049ee89 commit 0b9d2ab

File tree

4 files changed

+276
-1
lines changed

4 files changed

+276
-1
lines changed

README.md

+54-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,54 @@
1-
# goslide-api
1+
2+
# GoSlide Open Cloud API
3+
4+
Python API to utilise the GoSlide Open Cloud JSON API
5+
6+
## Requirements
7+
8+
- Python >= 3.5.2
9+
10+
## Usage
11+
```python
12+
13+
import asyncio
14+
from goslideapi import GoSlideCloud
15+
16+
loop = asyncio.get_event_loop()
17+
goslide = GoSlideCloud('email', 'password')
18+
19+
login = loop.run_until_complete(goslide.login())
20+
if login:
21+
22+
# Get the slide list
23+
slides = loop.run_until_complete(goslide.slidesoverview())
24+
if slides:
25+
for slidedev in slides:
26+
print(slidedev['device_id'], slidedev['device_name'])
27+
print(' ', slidedev['device_info']['pos'])
28+
else:
29+
print('Something went wrong while retrieving the slide information')
30+
31+
# Open slide with id 1
32+
result = loop.run_until_complete(goslide.slideopen(1))
33+
if result:
34+
print('Succesfully opened slide 1')
35+
else:
36+
print('Failed opened slide 1')
37+
38+
# Close slide with id 1
39+
result = loop.run_until_complete(goslide.slideclose(1))
40+
41+
loop.run_until_complete(goslide.logout())
42+
else:
43+
print('login failed')
44+
```
45+
46+
## TODO:
47+
48+
- Test with a real slide (awaiting delivery ;-))
49+
- Expose more API functions
50+
51+
## License
52+
53+
Apache License 2.0
54+

goslideapi/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .goslideapi import *
2+
name = 'goslideapi'

goslideapi/goslideapi.py

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
2+
import json
3+
import logging
4+
import asyncio
5+
import aiohttp
6+
7+
_LOGGER = logging.getLogger(__name__)
8+
9+
BASEURL = 'https://api.goslide.io/api/{}'
10+
11+
class GoSlideCloud:
12+
"""API Wrapper for the Go Slide devices"""
13+
14+
def __init__(self, username, password):
15+
"""Create the object with required parameters."""
16+
self._username = username
17+
self._password = password
18+
self._authenticated = False
19+
self._accesstoken = ''
20+
self._expiretoken = None
21+
self._authfailed = 0
22+
23+
async def _dorequest(self, type, urlsuffix, data=None):
24+
"""Internal request handler."""
25+
headers = {'Content-Type': 'application/json'}
26+
27+
if self._authenticated:
28+
headers['Authorization'] = 'Bearer {}'.format(self._accesstoken)
29+
30+
_LOGGER.debug('%s API: %s, data=%s', type, BASEURL.format(urlsuffix), json.dumps(data))
31+
32+
async with aiohttp.request(type, BASEURL.format(urlsuffix), headers=headers, json=data) as resp:
33+
if resp.status == 200:
34+
textdata = await resp.text()
35+
_LOGGER.debug('REQUEST=%s, HTTPCode=200, Data=%s', BASEURL.format(urlsuffix), textdata)
36+
try:
37+
jsondata = json.loads(textdata)
38+
except:
39+
_LOGGER.error('Invalid JSON response "%s"', textdata)
40+
jsondata = None
41+
pass
42+
43+
return jsondata
44+
else:
45+
textdata = await resp.text()
46+
_LOGGER.error('REQUEST=%s, HTTPCode=%s, Data=%s', BASEURL.format(urlsuffix), resp.status, textdata)
47+
48+
if resp.status == 401:
49+
self._authfailed += 1
50+
51+
return None
52+
53+
54+
async def _request(self, *args):
55+
"""Wrapper around dorequest to do at least 1 retry if we got a HTTP=401."""
56+
resp = await self._dorequest(*args)
57+
58+
if self._authfailed > 0:
59+
_LOGGER.warning('Retrying request')
60+
resp = await self._dorequest(*args)
61+
if self._authfailed > 0:
62+
_LOGGER.error('Request failed')
63+
64+
return resp
65+
66+
67+
async def _checkauth(self):
68+
"""Check if we are authenticated and if we should refresh our token if it less valid then 7 days."""
69+
if self._authenticated:
70+
import datetime
71+
72+
if self._expiretoken != None:
73+
if (self._expiretoken - datetime.datetime.now(datetime.timezone.utc)).days <= 7:
74+
result = await self.login()
75+
return result
76+
else:
77+
return True
78+
else:
79+
return await self.login()
80+
81+
82+
async def login(self):
83+
"""Login to the Cloud API and retrieve a token."""
84+
import datetime
85+
86+
self._authenticated = False
87+
self._accesstoken = ''
88+
89+
result = await self._request('POST', 'auth/login', {'email': self._username, 'password': self._password})
90+
if result:
91+
if 'access_token' in result:
92+
self._authfailed = 0
93+
self._authenticated = True
94+
self._accesstoken = result['access_token']
95+
if 'expires_at' in result:
96+
self._expiretoken = datetime.datetime.strptime(result['expires_at'] + ' +0000', '%Y-%m-%d %H:%M:%S %z')
97+
_LOGGER.debug('Auth login token expiry: %s', result['expires_at'])
98+
else:
99+
self._expiretoken = None
100+
_LOGGER.error('Auth login JSON is missing the "expires_at" field in %s', result)
101+
102+
return self._authenticated
103+
104+
105+
async def logout(self):
106+
"""Logout of the Cloud API."""
107+
if self._authenticated:
108+
result = await self._request('POST', 'auth/logout')
109+
return True
110+
else:
111+
return False
112+
113+
114+
async def slidesoverview(self):
115+
"""Retrieve the slides overview list. The format is:
116+
[{"device_name": "", "device_id": 1, "id": 1, "slide_setup": "", "curtain_type": "", "device_info": {"pos": 0.0}, "zone_id": "", "touch_go": ""}, {...}]
117+
"""
118+
if self._checkauth:
119+
result = await self._request('GET', 'slides/overview')
120+
if result and 'slides' in result:
121+
return result['slides']
122+
else:
123+
_LOGGER.error('Missing key "slides" in JSON response "%s"', json.dumps(result))
124+
return None
125+
else:
126+
return None
127+
128+
129+
async def slidegetposition(self, slideid):
130+
"""Retrieve the slide position. The format is:
131+
{"device_info": {"pos": 0.0}, "touch_go": ""}
132+
"""
133+
if self._checkauth:
134+
result = await self._request('GET', 'slide/{}/info'.format(slideid))
135+
if result and 'device_info' in result and 'pos' in result['device_info']:
136+
return result['device_info']['pos']
137+
else:
138+
#_LOGGER.error('Missing key "device_info" and "pos" in JSON response "%s"', json.dumps(result))
139+
return None
140+
else:
141+
return None
142+
143+
144+
async def slidesetposition(self, slideid, posin):
145+
"""Set the slide position, only 0.0 - 1.0 is allowed."""
146+
try:
147+
pos = float(posin)
148+
except ValueError:
149+
_LOGGER.error('SlideSetPosition called, but "%s" is not numeric', posin)
150+
return None
151+
152+
if pos < 0 or pos > 1:
153+
_LOGGER.error('SlideSetPosition called, but "%s" is not between 0.0 - 1.0', pos)
154+
return None
155+
156+
if self._checkauth:
157+
result = await self._request('POST', 'slide/{}/position'.format(slideid), {'pos': pos})
158+
else:
159+
return None
160+
161+
162+
async def slideopen(self, slideid):
163+
"""Open a slide."""
164+
if self._checkauth:
165+
result = await self._request('POST', 'slide/{}/position'.format(slideid), {'pos': 0.0})
166+
else:
167+
return None
168+
169+
170+
async def slideclose(self, slideid):
171+
"""Close a slide."""
172+
if self._checkauth:
173+
result = await self._request('POST', 'slide/{}/position'.format(slideid), {'pos': 1.0})
174+
else:
175+
return None
176+
177+
178+
async def slidestop(self, slideid):
179+
"""Stop a slide."""
180+
if self._checkauth:
181+
result = await self._request('POST', 'slide/{}/stop'.format(slideid))
182+
else:
183+
return None
184+
185+
186+
async def slidecalibrate(self, slideid):
187+
"""Calibrate a slide."""
188+
if self._checkauth:
189+
result = await self._request('POST', 'slide/{}/calibrate'.format(slideid))
190+
else:
191+
return None
192+
193+

setup.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'''
2+
Install goslide.io Open Cloud API
3+
'''
4+
5+
import setuptools
6+
7+
with open('README.md') as f:
8+
LONG_DESCRIPTION = f.read()
9+
10+
setuptools.setup(
11+
name='goslide-api',
12+
version='0.0.2',
13+
url='https://github.com/ualex73/goslide-api',
14+
license='Apache License 2.0',
15+
author='Alexander Kuiper',
16+
author_email='[email protected]',
17+
description='Python API to utilise the goslide.io Open Cloud API',
18+
long_description=LONG_DESCRIPTION,
19+
long_description_content_type='text/markdown',
20+
packages=setuptools.find_packages(),
21+
install_requires=['aiohttp', 'asyncio'],
22+
classifiers=[
23+
'Programming Language :: Python :: 3',
24+
'License :: OSI Approved :: Apache Software License',
25+
'Operating System :: OS Independent',
26+
],
27+
)

0 commit comments

Comments
 (0)