From dda86c460ad3e613bb0615102034c9826fb0921e Mon Sep 17 00:00:00 2001 From: Colin Swelin Date: Tue, 18 Oct 2016 16:44:44 -0400 Subject: [PATCH] Initial commit --- .gitignore | 1 + LICENSE | 21 +++++ instructions.txt | 1 + kevoPy/__init__.py | 1 + kevoPy/kevopy.py | 173 +++++++++++++++++++++++++++++++++++ polykevo.py | 58 ++++++++++++ polykevo_types.py | 122 ++++++++++++++++++++++++ polykevo_types0.py | 99 ++++++++++++++++++++ profile.zip | Bin 0 -> 1688 bytes profile/editor/editors.xml | 7 ++ profile/nls/en_us.txt | 19 ++++ profile/nodedef/nodedefs.xml | 34 +++++++ profile/version.txt | 1 + server.json | 8 ++ zipprofile | 6 ++ 15 files changed, 551 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 instructions.txt create mode 100644 kevoPy/__init__.py create mode 100644 kevoPy/kevopy.py create mode 100644 polykevo.py create mode 100644 polykevo_types.py create mode 100644 polykevo_types0.py create mode 100644 profile.zip create mode 100755 profile/editor/editors.xml create mode 100755 profile/nls/en_us.txt create mode 100755 profile/nodedef/nodedefs.xml create mode 100755 profile/version.txt create mode 100644 server.json create mode 100755 zipprofile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35f8453 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +login.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3f3d4c3 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/instructions.txt b/instructions.txt new file mode 100644 index 0000000..80fd635 --- /dev/null +++ b/instructions.txt @@ -0,0 +1 @@ +https://github.com/cswelin/kevo-polyglot diff --git a/kevoPy/__init__.py b/kevoPy/__init__.py new file mode 100644 index 0000000..100ceca --- /dev/null +++ b/kevoPy/__init__.py @@ -0,0 +1 @@ +from .kevopy import KevoPy diff --git a/kevoPy/kevopy.py b/kevoPy/kevopy.py new file mode 100644 index 0000000..ed9275f --- /dev/null +++ b/kevoPy/kevopy.py @@ -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'
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() diff --git a/polykevo.py b/polykevo.py new file mode 100644 index 0000000..62b769e --- /dev/null +++ b/polykevo.py @@ -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() diff --git a/polykevo_types.py b/polykevo_types.py new file mode 100644 index 0000000..afa2a5c --- /dev/null +++ b/polykevo_types.py @@ -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' diff --git a/polykevo_types0.py b/polykevo_types0.py new file mode 100644 index 0000000..0a62851 --- /dev/null +++ b/polykevo_types0.py @@ -0,0 +1,99 @@ +from polyglot.nodeserver_api import Node +from kevoPy import KevoPy +from login import USERNAME, PASSWORD + +class KevoDiscovery(Node): + + def __init__(self, *args, **kwargs): + super(KevoDiscovery, self).__init__(*args, **kwargs) + self.kevo = KevoPy(USERNAME, PASSWORD) + self.kevo.logger = self.parent.poly.logger + + + 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.logger.info("Found %d Locks", len(self.kevo.locks())) + + if len(self.kevo.locks()) > 0: + for lock in self.kevo.locks(): + name = lock.name() + identifier = lock.identifier()[0:13] + + lnode = self.parent.get_node(identifier) + if not lnode: + self.logger.info('Adding new Kevo Lock: %s(%s)', name, lock.lock_id) + self.parent.locks.append(KevoLock(self.parent, identifier, self.parent.get_node('KEVODISCO'), lock, manifest)) + 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, address, primary, lock, manifest=None): + + self.parent = parent + self.logger = self.parent.poly.logger + self.name = lock.name() + self.lock = lock + + 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): + self.lock.lock() + + def _setOff(self, **kwargs): + self.lock.unlock() + + def _stateToUOM(self, state): + return {'Unlocked' : 0, + 'Locked' : 100, + 'Unknown' : 101, + 'Bolt Jam' : 102}[state] + + def query(self, **kwargs): + self.update_info() + self.report_driver() + return True + + def update_info(self): + try: + self.lock.refresh() + 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, + 'QUERY': query} + + node_def_id = 'KEVO' diff --git a/profile.zip b/profile.zip new file mode 100644 index 0000000000000000000000000000000000000000..76a6ebf7a9e25cf86713dbae2ce53462120a888d GIT binary patch literal 1688 zcmWIWW@h1H0D&9x9(jNnP=cL7h9Na2vn0PrKQx4sf!QOZB7D;v@rcq2ZU#n{uZ#=~ zEFwUq0YC#lGzY`!ig?dU&#p1%0(sMbSO{SN#GGQiirgHqS-R82Bg}yq&8*Fyz9)4~ z>T7L#9K^sR?HU`)`u@tMn$`!Cj!e3g#2OpBg2iQ~Q(D*BN!zn$)E9IJxiINOPMI-% z-l~9E^Rri{WUN>c5Hl-ePE^wBlm*Ed42Rkra+WNaFg=*(q746)wF~%7Bjl4d$%>2j zaF%d`9gf}SEI^0ny=cLfPK7mws=G+ zX6ThpaO7?>5NUY-TeKS3k?iu*WTmWomq|OT^N`Z+2$qfsXv^Jq24ig`_LiNy(^uH1oYy=4>wHHA8GE z)0J*b+ao&^UU5ltJxy^iy2z8P^|j;nRD`j^vF-G45bd2Z$Y z+2`%z=7(=IEPnDi>cQpO$<_b<9lEix|JIe8Ik_1uz6+(kw`MH(bG=z*V`l2Mxi^469Y1{^O57wyDlx>K``LnA56T zv4;(qhC!Gc817}MMa7x + + + + + + diff --git a/profile/nls/en_us.txt b/profile/nls/en_us.txt new file mode 100755 index 0000000..0dd3d65 --- /dev/null +++ b/profile/nls/en_us.txt @@ -0,0 +1,19 @@ +# KevoControl +ND-KEVOCONTROL-NAME = Kevo Disovery + +CMD-NETDISCO-NAME = Discover Locks +CMD-DISCOVER-NAME = Discover Locks + +# KevoLock +ND-KEVO-NAME = KEVO +ND-KEVO-ICON = DoorLock +ST-KEVO-ST-NAME = Status +ST-KEVO-0 = Unlocked +ST-KEVO-100 = Locked +ST-KEVO-101 = Unknown +ST-KEVO-102 = Jammed +ST-KEVO-303 = Processing +ST-KEVO-304 = Confirming + +CMD-KEVO-DON-NAME = Lock +CMD-KEVO-DOF-NAME = Unlock diff --git a/profile/nodedef/nodedefs.xml b/profile/nodedef/nodedefs.xml new file mode 100755 index 0000000..131d9e9 --- /dev/null +++ b/profile/nodedef/nodedefs.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profile/version.txt b/profile/version.txt new file mode 100755 index 0000000..d917d3e --- /dev/null +++ b/profile/version.txt @@ -0,0 +1 @@ +0.1.2 diff --git a/server.json b/server.json new file mode 100644 index 0000000..fdc27f1 --- /dev/null +++ b/server.json @@ -0,0 +1,8 @@ +{ + "name": "Kevo", + "docs": "https://www.universal-devices.com/", + "type": "python", + "executable": "polykevo.py", + "description": "Connect Kwikset kevo gateway system to the ISY994.", + "notice": "\"Kevo smart lock\" is a trademark owned by kwikset., see www.kwikset.com/kevo/smart-lock for more information. This Node Server is neither developed by nor endorsed by the kwikset organization." +} diff --git a/zipprofile b/zipprofile new file mode 100755 index 0000000..79eda2b --- /dev/null +++ b/zipprofile @@ -0,0 +1,6 @@ +#!/bin/bash +rm profile.zip +cd profile +zip -r profile.zip * +mv profile.zip .. +cd ..