-
Notifications
You must be signed in to change notification settings - Fork 0
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
Colin Swelin
committed
Oct 31, 2016
0 parents
commit dda86c4
Showing
15 changed files
with
551 additions
and
0 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 |
---|---|---|
@@ -0,0 +1 @@ | ||
login.py |
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,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2016 Colin Swelin | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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 @@ | ||
https://github.com/cswelin/kevo-polyglot |
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 @@ | ||
from .kevopy import KevoPy |
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,173 @@ | ||
# -*- coding: utf-8 -*- | ||
import requests | ||
import re | ||
import json | ||
import time | ||
|
||
from requests.packages.urllib3.exceptions import InsecureRequestWarning | ||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning) | ||
|
||
class KevoPy(object): | ||
|
||
_username = '' | ||
_password = '' | ||
_host = '' | ||
|
||
_loginPath = 'login' | ||
_signinPath = 'signin' | ||
|
||
_locks = {} | ||
|
||
def __init__(self, username, password, host='https://www.mykevo.com/'): | ||
|
||
self._username = username | ||
self._password = password | ||
|
||
self._host = host | ||
self._session = requests.Session() | ||
|
||
def _reteiveToken(self): | ||
|
||
response = self._session.get(self._host + self._loginPath, verify=False) | ||
# response = self._session.get(self._host + self._loginPath, verify=False) | ||
# print response | ||
responseText = response.text | ||
|
||
if response.url.endswith("user/locks"): | ||
return | ||
|
||
# print response.url | ||
p = re.compile(r'input name="authenticity_token".*?value=\"(.*?)\"', re.MULTILINE | re.DOTALL) | ||
values = re.findall(p, responseText) | ||
|
||
self._token = values[0].strip() | ||
|
||
def _praseLockIdentifiers(self,html): | ||
|
||
p = re.compile(r'<div class=\'lock_unlock_container\' data-bolt-state=\'.*?\' data-lock-id=\'(.*?)\' id', re.MULTILINE | re.DOTALL) | ||
# print html | ||
values = re.findall(p, html) | ||
|
||
# print html | ||
for value in values: | ||
kevo = KevoLock(value.strip(), self._session, self._host) | ||
kevo.refresh() | ||
self._locks[kevo.lock_id] = kevo | ||
|
||
def connect(self): | ||
|
||
self._reteiveToken() | ||
|
||
r = self._session.post(self._host + self._signinPath, | ||
|
||
data={"user[username]" : self._username, | ||
"user[password]" : self._password, | ||
"authenticity_token" : self._token, | ||
"commit" : "Sign In", | ||
"utf8" : "✓"}) | ||
|
||
self._praseLockIdentifiers(r.text) | ||
|
||
def locks(self): | ||
return self._locks.values() | ||
|
||
def refreshAll(self): | ||
for lock in self.locks(): | ||
lock.refresh() | ||
|
||
|
||
class KevoLock(object): | ||
|
||
_lock_data = {} | ||
_unlock = 'user/remote_locks/command/remote_unlock.json?arguments=' | ||
_lock = 'user/remote_locks/command/remote_lock.json?arguments=' | ||
_info = 'user/remote_locks/command/lock.json?arguments=' | ||
_session = '' | ||
_host = '' | ||
|
||
def __init__(self, lock_id, session, host): | ||
|
||
self.lock_id = lock_id | ||
self._session = session | ||
self._host = host | ||
|
||
def refresh(self): | ||
|
||
r = self._session.get(self._host + self._info + self.lock_id) | ||
self._lock_data = json.loads(r.text) | ||
|
||
def name(self): | ||
|
||
return self._lock_data["name"] | ||
|
||
def identifier(self): | ||
|
||
return self._lock_data["id"] | ||
|
||
def query(self): | ||
|
||
return self._lock_data["bolt_state"] | ||
|
||
def unlock(self): | ||
|
||
try: | ||
|
||
self._expected_state = "Unlocked" | ||
self._previous_lock_state = self._lock_data["bolt_state"] | ||
|
||
r = self._session.get(self._host + self._unlock + self.lock_id) | ||
return self.verifyConfirmation() | ||
|
||
except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError, TypeError) as e: | ||
|
||
return False | ||
|
||
def lock(self): | ||
|
||
try: | ||
|
||
self._expected_state = "Locked" | ||
self._previous_lock_state = self._lock_data["bolt_state"] | ||
|
||
r = self._session.get(self._host + self._lock + self.lock_id) | ||
return self.verifyConfirmation() | ||
|
||
except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError, TypeError) as e: | ||
|
||
return False | ||
|
||
def toggle(self): | ||
|
||
if self.query() == "Unlocked": | ||
self.lockLock() | ||
else: | ||
self.unlockLock() | ||
|
||
|
||
def verifyConfirmation(self): | ||
|
||
timeout = time.time() + 60*2 | ||
while self._expected_state != self._lock_data["bolt_state"]: | ||
|
||
if time.time() > timeout: | ||
return False | ||
|
||
time.sleep(15) | ||
|
||
self.refresh() | ||
|
||
self.parent.update_driver() | ||
return True | ||
|
||
|
||
def main(): | ||
|
||
kevo = KevoPy('user', 'pass') | ||
kevo.connect() | ||
|
||
for lock in kevo.locks(): | ||
print lock.query() | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
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,58 @@ | ||
from polyglot.nodeserver_api import SimpleNodeServer, PolyglotConnector | ||
from polykevo_types import KevoDiscovery, KevoLock | ||
|
||
VERSION = "0.1.0" | ||
|
||
class KevoNodeServer(SimpleNodeServer): | ||
|
||
controller = [] | ||
locks = [] | ||
|
||
def setup(self): | ||
super(KevoNodeServer, self).setup() | ||
manifest = self.config.get('manifest',{}) | ||
|
||
self.controller = KevoDiscovery(self,'disco','Kevo Discovery', True, manifest) | ||
# self.poly.logger.info("FROM Poly ISYVER: " + self.poly.isyver) | ||
self.controller.discover() | ||
self.update_config() | ||
|
||
# self.nodes['kevocontrol'].discover() | ||
|
||
def poll(self): | ||
self.poly.logger.info("poll") | ||
if len(self.locks) >= 1: | ||
self.controller.refreshAll() | ||
|
||
|
||
def long_poll(self): | ||
self.poly.logger.info("long_poll") | ||
if len(self.locks) >= 1: | ||
self.controller.refreshAll() | ||
|
||
|
||
def report_drivers(self): | ||
self.poly.logger.info("report_drivers") | ||
# if len(self.controller.locks()) >= 1: | ||
# for i in self.locks: | ||
# i.report_driver() | ||
|
||
|
||
def main(): | ||
|
||
poly = PolyglotConnector() | ||
|
||
nserver = KevoNodeServer(poly, 20, 40) | ||
poly.connect() | ||
poly.wait_for_config() | ||
|
||
poly.logger.info("Kevo Interface version " + VERSION + "created") | ||
nserver.setup() | ||
|
||
poly.logger.info("Setup completed. Running Server.") | ||
|
||
nserver.run() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
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,122 @@ | ||
from polyglot.nodeserver_api import Node | ||
from kevoPy import KevoPy | ||
from login import USERNAME, PASSWORD | ||
import hashlib | ||
|
||
class KevoDiscovery(Node): | ||
|
||
def __init__(self, *args, **kwargs): | ||
super(KevoDiscovery, self).__init__(*args, **kwargs) | ||
self.kevo = KevoPy(USERNAME, PASSWORD) | ||
|
||
def id_2_addr(self, udn): | ||
''' convert udn id to isy address ''' | ||
hasher = hashlib.md5() | ||
hasher.update(udn) | ||
return hasher.hexdigest()[0:14] | ||
|
||
def discover(self, **kwargs): | ||
|
||
manifest = self.parent.config.get('manifest', {}) | ||
self.parent.poly.logger.info("Discovering Kevo Locks...") | ||
self.parent.poly.logger.info("User: %s", USERNAME) | ||
self.kevo.connect() | ||
|
||
self.parent.poly.logger.info("Found %d Locks", len(self.kevo.locks())) | ||
|
||
if len(self.kevo.locks()) > 0: | ||
for lock in self.kevo.locks(): | ||
|
||
name = lock.name() | ||
address = self.id_2_addr(lock.identifier()) | ||
|
||
lnode = self.parent.get_node(address) | ||
if not lnode: | ||
self.logger.info('Adding new Kevo Lock: %s(%s)', name, lock.lock_id) | ||
lock = KevoLock(self.parent, self.parent.get_node('disco'), address, lock, manifest) | ||
|
||
self.parent.locks.append(lock) | ||
else: | ||
self.logger.info('Kevo Lock: %s(%s) already added', name, lock.lock_id) | ||
else: | ||
self.logger.info("No Locks found") | ||
|
||
return True | ||
|
||
def query(self, **kwargs): | ||
self.parent.report_drivers() | ||
return True | ||
|
||
def refreshAll(self): | ||
self.kevo.refreshAll() | ||
|
||
_drivers = {} | ||
|
||
_commands = {'NETDISCO': discover} | ||
|
||
node_def_id = 'KEVODISCO' | ||
|
||
class KevoLock(Node): | ||
|
||
def __init__(self, parent, primary, address, lock, manifest=None): | ||
|
||
lock.parent = self | ||
self.logger = parent.poly.logger | ||
self.parent = parent | ||
self.name = lock.name() | ||
self.lock = lock | ||
self.logger.info('Address: %s', address) | ||
# super(KevoLock, self).__init__(parent, address, "Kevo " + lock.name(), primary, manifest) | ||
super(KevoLock, self).__init__(parent, address, "Kevo " + lock.name(), primary, manifest) | ||
self.logger.info('Initializing New Kevo Lock') | ||
self.update_info() | ||
|
||
def _setOn(self, **kwargs): | ||
|
||
result = self.lock.lock() | ||
if result == True: | ||
self.report_driver() | ||
|
||
return result | ||
|
||
def _setOff(self, **kwargs): | ||
|
||
result = self.lock.unlock() | ||
if result == True: | ||
self.report_driver() | ||
|
||
return result | ||
|
||
|
||
def _stateToUOM(self, state): | ||
return {'Unlocked' : 0, | ||
'Locked' : 100, | ||
'Unknown' : 101, | ||
'Bolt Jam' : 102, | ||
'Processing' : 303, | ||
'Confirming' : 304}[state] | ||
|
||
def query(self, **kwargs): | ||
self.update_info() | ||
self.report_driver() | ||
return True | ||
|
||
def update_info(self): | ||
self.lock.refresh() | ||
self.update_driver() | ||
|
||
def update_driver(self): | ||
try: | ||
self.logger.info("Setting ST to %d", self._stateToUOM(self.lock.query())) | ||
self.set_driver('ST', self._stateToUOM(self.lock.query())) | ||
except requests.exceptions.ConnectionError as e: | ||
self.logger.error('Connection error to ISY: %s', e) | ||
|
||
|
||
_drivers = {'ST': [0, 11, int]} | ||
|
||
_commands = {'DON': _setOn, | ||
'DOF': _setOff, | ||
'ST' : query} | ||
|
||
node_def_id = 'KEVO' |
Oops, something went wrong.