diff --git a/.gitignore b/.gitignore index 19afbc2..4f7abfc 100755 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ debug* bin include lib -__pycache__ \ No newline at end of file +__pycache__ +*.egg-info +dist +build \ No newline at end of file diff --git a/README.md b/README.md index ec6f437..fff8011 100755 --- a/README.md +++ b/README.md @@ -28,9 +28,6 @@ You can set multiple accessories (of the same type) in a single command: **Looking for maintainers: If you are interested in maintaining this project, feel free to reach out.** -## Dependencies - - Python HomeScript API - ## Setup Important: HomeBridge **must** be run in insecure mode for this script to work! @@ -91,10 +88,10 @@ hs.py -s all \ value ## API As of v5.1+ HomeScript is now a fully importable API ```python -import HomeScript +import homescript # Initialize with hostname, port and auth code. Debug and sys.argv are optional -hs = HomeScript(hostname, port, auth, [debug], [sys.argv]) +hs = homescript.HomeScript(hostname, port, auth, [debug], [sys.argv]) # Select an accessory or group of accessories. Any get/set/print operation requires accessories to be selected first. hs.selectAccessory('mainlight') @@ -144,6 +141,9 @@ PRs and commits that you make to this repo must include the following:
## Changelog +### v5.1.1 + - Fixed import bug + ### v5.1 - Rewritten from the ground up to be object oriented - Now features an importable API diff --git a/homescript/__init__.py b/homescript/__init__.py index d72eff1..2a4dcb1 100755 --- a/homescript/__init__.py +++ b/homescript/__init__.py @@ -1,3 +1,208 @@ -# __init__.py +# HomeScript - Python script to control homebridge devices using the command line +# v5.1 +# Created by Menahi Shayan. 2019. +# https://github.com/menahishayan/HomeScript -__version__ = "5.1.0" \ No newline at end of file +import requests +import json +import logging +from datetime import date +# to add webcolors + +__version__ = "5.1.1" + +class HomeScript: + def __init__(self,hostname,port, auth, debug=None, argv=None): + self.__version__ = '5.1.1' + self.url = 'http://' + hostname + ':' + str(port) + '/' + self.headers = {'Content-Type': 'Application/json', 'authorization': auth} + + self.accessories={} + self.selectedAccessories=[] + self.selectedAccessoryNames={} + + self.getAccessories() + + self.debug = debug + self.exceptionFile='homescript_exception_' + date.today().strftime("%Y.%m.%d") + '.log' + if self.debug: + self.debugHandler('init',argv) + + def getAccessories(self): + global getAcc + try: + getAcc = requests.get(self.url + 'accessories', headers=self.headers) + if getAcc.status_code == 200: + getAcc = getAcc.json() + # load sample data for debugging + # with open('acc.json') as f: + # data = json.load(f) + for item in getAcc['accessories']: + if getAcc['accessories'].index(item) != 0: + interfaces = [] + for i in item['services'][1]['characteristics'][1:]: + if i['format'] not in ['bool','string','tlv8','uint8','float']: + interfaces.append({'iid':i['iid'],'description': i['description'],'maxValue': i['maxValue'],'minValue': i['minValue'],'minStep': i['minStep'], 'value': i['value']}) + else: + interfaces.append({'iid':i['iid'],'description': i['description'],'value': i['value']}) + self.accessories.update({str(item['services'][0]['characteristics'][3]['value']).replace(' ','_') : {'aid':item['aid'],'iid':10,'type':item['services'][0]['characteristics'][2]['value'],'value':interfaces}}) + + except: + if self.debug: + logging.error(Exception, exc_info=True) + print('Exception logged: ' + self.exceptionFile) + return self.accessories + + def selectAccessory(self,inputName): + try: + for key in self.accessories: + if inputName in key.lower(): + self.selectedAccessoryNames.update({self.accessories[key]['aid']:{'name':key}}) + self.selectedAccessories.append({'aid':self.accessories[key]['aid'], 'value':self.accessories[key]['value']}) + return self.selectedAccessories + except: + if self.debug: + logging.error(Exception, exc_info=True) + print('Exception logged: ' + self.exceptionFile) + + def selectGroup(self,inputName): + try: + for key in self.accessories: + if self.accessories[key]['type'].lower().startswith(inputName[:len(inputName)-2]): + self.selectedAccessoryNames.update({self.accessories[key]['aid']:{'name':key}}) + self.selectedAccessories.append({'aid':self.accessories[key]['aid'], 'iid':self.accessories[key]['iid'], 'value':self.accessories[key]['value']}) + return self.selectedAccessories + except: + if self.debug: + logging.error(Exception, exc_info=True) + print('Exception logged: ' + self.exceptionFile) + + def printAccessories(self,param=''): + try: + if param == 'json': + print(json.dumps(self.accessories)) + return + for key in self.accessories: + if param == 'aid': + print(str(self.accessories[key]['aid']) + ' ', end='') + print(key, end=' ') + if param == 'type' or param == 'all': + print(str(self.accessories[key]['type']) + ' ', end='') + if param in ['value','iid','all']: + # print(str(self.accessories[key]['value']), end='') + for i in self.accessories[key]['value']: + print('\n ', end='') + if param == 'iid' or param == 'all': + print(str(i['iid']) + ' ' + str(i['value']), end=' ') + if param == 'value' or param == 'all': + print(str(i['description']) + ' ' + str(i['value']), end='') + print('') + except: + if self.debug: + # debugHandler(json.dumps(sys.exc_info())) + logging.error(Exception, exc_info=True) + print('Exception logged: ' + self.exceptionFile) + + def getVersion(self): + return self.__version__ + + def getSelectedItems(self): + return self.selectedAccessories + + def printSelectedItems(self): + for item in self.selectedAccessories: + print(self.selectedAccessoryNames[item['aid']]['name'] + ' ' + str(item['value'])) + + def setStates(self,value): + setData = [] + valueIndex = 0 + + for item in self.selectedAccessories: + if value.isdigit(): + item['value'][0]['value'] = int(value) + elif item['value'][0]['value'] == 0 or item['value'][0]['value'] == False: + item['value'][0]['value'] = 1 + else: + item['value'][0]['value'] = 0 + + self.selectedAccessoryNames[item['aid']].update({'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) + setData.append({'aid': item['aid'],'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) + setReq = requests.put(self.url + 'characteristics', headers=self.headers, data='{"characteristics":' + str(setData).replace('\'','\"') + '}') + + if self.debug: + self.debugHandler('Set characteristics response:\n'+setReq.text) + self.debugHandler('End set characteristics response') + + try: + if setReq.status_code != 204: + print(setReq) + for item in setReq.json()['characteristics']: + print(self.selectedAccessoryNames[item['aid']]['name'] + ' Error: ' + str(item['status'])) + return item['status'] + except: + if self.debug: + logging.error(Exception, exc_info=True) + print('Exception logged: ' + self.exceptionFile) + + def setValues(self, attribute, value): + setData = [] + valueIndex = 0 + + for item in self.selectedAccessories: + valueIndex = next((item['value'].index(v) for v in item['value'] if v['description'] == attribute), 0) + + if value.isdigit(): + if (int(value) <= item['value'][valueIndex]['maxValue']) and (int(value) >= item['value'][valueIndex]['minValue']) and (int(value)%item['value'][valueIndex]['minStep'] == 0): + item['value'][valueIndex]['value'] = int(value) + else: + print('Error:\n Max Value: ' + str(item['value'][valueIndex]['maxValue']) + '\n Min Value: ' + str(item['value'][valueIndex]['minValue']) + '\n Min Step: ' + str(item['value'][valueIndex]['minStep'])) + elif item['value'][valueIndex]['value'] >= ((item['value'][valueIndex]['maxValue']-item['value'][valueIndex]['minValue'])/2) - (((item['value'][valueIndex]['maxValue']-item['value'][valueIndex]['minValue'])/2)%item['value'][valueIndex]['minStep']): + item['value'][valueIndex]['value'] = item['value'][valueIndex]['maxValue'] + else: + item['value'][valueIndex]['value'] = item['value'][valueIndex]['minValue'] + + self.selectedAccessoryNames[item['aid']].update({'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) + setData.append({'aid': item['aid'],'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) + setReq = requests.put(self.url + 'characteristics', headers=self.headers, data='{"characteristics":' + str(setData).replace('\'','\"') + '}') + + if self.debug: + self.debugHandler('Set characteristics response:\n'+setReq.text) + self.debugHandler('End set characteristics response') + + try: + if setReq.status_code != 204: + print(setReq) + for item in setReq.json()['characteristics']: + print(self.selectedAccessoryNames[item['aid']]['name'] + ' Error: ' + str(item['status'])) + return item['status'] + except: + if self.debug: + logging.error(Exception, exc_info=True) + print('Exception logged: ' + self.exceptionFile) + + def debugHandler(self,content='init',argv=''): + if content=='init': + logging.basicConfig(filename=self.exceptionFile,filemode = 'a',encoding='utf-8', level=logging.DEBUG) + debugFile=open('homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log', "w") + debugFile.write('HSDB: homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log\nHSDB: HomeScript version: ' + self.__version__) + debugFile.write('\nHSDB: URL: ' + self.url + '\nHSDB: Headers: ' + json.dumps(self.headers)) + debugFile.write('\nHSDB: Accessories: ' + json.dumps(self.accessories)) + debugFile.write('\nHSDB: Selected accessories: ' + json.dumps(self.selectedAccessories)) + debugFile.write('\nHSDB: Selected accessory names: ' + json.dumps(self.selectedAccessoryNames)) + debugFile.write('\nHSDB: Arguments: ' + json.dumps(argv)) + debugFile.write('\nHSDB: Get accessories response:\n' + str(getAcc) + '\nHSDB: End get accessories response\n') + elif content=='end': + debugFile=open('homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log', "a") + debugFile.write('HSDB: End homeScript debug file') + print('Debug logged: homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log') + else: + debugFile=open('homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log', "a") + debugFile.write('HSDB: ' + str(content) + '\n') + debugFile.close() + + def __del__(self): + try: + if self.debug: + self.debugHandler('end') + except: + None diff --git a/homescript/__main__.py b/homescript/__main__.py deleted file mode 100755 index f96c75d..0000000 --- a/homescript/__main__.py +++ /dev/null @@ -1,205 +0,0 @@ -# HomeScript - Python script to control homebridge devices using the command line -# v5.1 -# Created by Menahi Shayan. 2019. -# https://github.com/menahishayan/HomeScript - -import requests -import json -import logging -from datetime import date -# to add webcolors - -class HomeScript: - def __init__(self,hostname,port, auth, debug=None, argv=None): - self.__version__ = '5.1.0' - self.url = 'http://' + hostname + ':' + str(port) + '/' - self.headers = {'Content-Type': 'Application/json', 'authorization': auth} - - self.accessories={} - self.selectedAccessories=[] - self.selectedAccessoryNames={} - - self.getAccessories() - - self.debug = debug - self.exceptionFile='homescript_exception_' + date.today().strftime("%Y.%m.%d") + '.log' - if self.debug: - self.debugHandler('init',argv) - - def getAccessories(self): - global getAcc - try: - getAcc = requests.get(self.url + 'accessories', headers=self.headers) - if getAcc.status_code == 200: - getAcc = getAcc.json() - # load sample data for debugging - # with open('acc.json') as f: - # data = json.load(f) - for item in getAcc['accessories']: - if getAcc['accessories'].index(item) != 0: - interfaces = [] - for i in item['services'][1]['characteristics'][1:]: - if i['format'] not in ['bool','string','tlv8','uint8','float']: - interfaces.append({'iid':i['iid'],'description': i['description'],'maxValue': i['maxValue'],'minValue': i['minValue'],'minStep': i['minStep'], 'value': i['value']}) - else: - interfaces.append({'iid':i['iid'],'description': i['description'],'value': i['value']}) - self.accessories.update({str(item['services'][0]['characteristics'][3]['value']).replace(' ','_') : {'aid':item['aid'],'iid':10,'type':item['services'][0]['characteristics'][2]['value'],'value':interfaces}}) - - except: - if self.debug: - logging.error(Exception, exc_info=True) - print('Exception logged: ' + self.exceptionFile) - return self.accessories - - def selectAccessory(self,inputName): - try: - for key in self.accessories: - if inputName in key.lower(): - self.selectedAccessoryNames.update({self.accessories[key]['aid']:{'name':key}}) - self.selectedAccessories.append({'aid':self.accessories[key]['aid'], 'value':self.accessories[key]['value']}) - return self.selectedAccessories - except: - if self.debug: - logging.error(Exception, exc_info=True) - print('Exception logged: ' + self.exceptionFile) - - def selectGroup(self,inputName): - try: - for key in self.accessories: - if self.accessories[key]['type'].lower().startswith(inputName[:len(inputName)-2]): - self.selectedAccessoryNames.update({self.accessories[key]['aid']:{'name':key}}) - self.selectedAccessories.append({'aid':self.accessories[key]['aid'], 'iid':self.accessories[key]['iid'], 'value':self.accessories[key]['value']}) - return self.selectedAccessories - except: - if self.debug: - logging.error(Exception, exc_info=True) - print('Exception logged: ' + self.exceptionFile) - - def printAccessories(self,param=''): - try: - if param == 'json': - print(json.dumps(self.accessories)) - return - for key in self.accessories: - if param == 'aid': - print(str(self.accessories[key]['aid']) + ' ', end='') - print(key, end=' ') - if param == 'type' or param == 'all': - print(str(self.accessories[key]['type']) + ' ', end='') - if param in ['value','iid','all']: - # print(str(self.accessories[key]['value']), end='') - for i in self.accessories[key]['value']: - print('\n ', end='') - if param == 'iid' or param == 'all': - print(str(i['iid']) + ' ' + str(i['value']), end=' ') - if param == 'value' or param == 'all': - print(str(i['description']) + ' ' + str(i['value']), end='') - print('') - except: - if self.debug: - # debugHandler(json.dumps(sys.exc_info())) - logging.error(Exception, exc_info=True) - print('Exception logged: ' + self.exceptionFile) - - def getVersion(self): - return self.__version__ - - def getSelectedItems(self): - return self.selectedAccessories - - def printSelectedItems(self): - for item in self.selectedAccessories: - print(self.selectedAccessoryNames[item['aid']]['name'] + ' ' + str(item['value'])) - - def setStates(self,value): - setData = [] - valueIndex = 0 - - for item in self.selectedAccessories: - if value.isdigit(): - item['value'][0]['value'] = int(value) - elif item['value'][0]['value'] == 0 or item['value'][0]['value'] == False: - item['value'][0]['value'] = 1 - else: - item['value'][0]['value'] = 0 - - self.selectedAccessoryNames[item['aid']].update({'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) - setData.append({'aid': item['aid'],'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) - setReq = requests.put(self.url + 'characteristics', headers=self.headers, data='{"characteristics":' + str(setData).replace('\'','\"') + '}') - - if self.debug: - self.debugHandler('Set characteristics response:\n'+setReq.text) - self.debugHandler('End set characteristics response') - - try: - if setReq.status_code != 204: - print(setReq) - for item in setReq.json()['characteristics']: - print(self.selectedAccessoryNames[item['aid']]['name'] + ' Error: ' + str(item['status'])) - return item['status'] - except: - if self.debug: - logging.error(Exception, exc_info=True) - print('Exception logged: ' + self.exceptionFile) - - def setValues(self, attribute, value): - setData = [] - valueIndex = 0 - - for item in self.selectedAccessories: - valueIndex = next((item['value'].index(v) for v in item['value'] if v['description'] == attribute), 0) - - if value.isdigit(): - if (int(value) <= item['value'][valueIndex]['maxValue']) and (int(value) >= item['value'][valueIndex]['minValue']) and (int(value)%item['value'][valueIndex]['minStep'] == 0): - item['value'][valueIndex]['value'] = int(value) - else: - print('Error:\n Max Value: ' + str(item['value'][valueIndex]['maxValue']) + '\n Min Value: ' + str(item['value'][valueIndex]['minValue']) + '\n Min Step: ' + str(item['value'][valueIndex]['minStep'])) - elif item['value'][valueIndex]['value'] >= ((item['value'][valueIndex]['maxValue']-item['value'][valueIndex]['minValue'])/2) - (((item['value'][valueIndex]['maxValue']-item['value'][valueIndex]['minValue'])/2)%item['value'][valueIndex]['minStep']): - item['value'][valueIndex]['value'] = item['value'][valueIndex]['maxValue'] - else: - item['value'][valueIndex]['value'] = item['value'][valueIndex]['minValue'] - - self.selectedAccessoryNames[item['aid']].update({'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) - setData.append({'aid': item['aid'],'iid': item['value'][valueIndex]['iid'], 'value': item['value'][valueIndex]['value']}) - setReq = requests.put(self.url + 'characteristics', headers=self.headers, data='{"characteristics":' + str(setData).replace('\'','\"') + '}') - - if self.debug: - self.debugHandler('Set characteristics response:\n'+setReq.text) - self.debugHandler('End set characteristics response') - - try: - if setReq.status_code != 204: - print(setReq) - for item in setReq.json()['characteristics']: - print(self.selectedAccessoryNames[item['aid']]['name'] + ' Error: ' + str(item['status'])) - return item['status'] - except: - if self.debug: - logging.error(Exception, exc_info=True) - print('Exception logged: ' + self.exceptionFile) - - def debugHandler(self,content='init',argv=''): - if content=='init': - logging.basicConfig(filename=self.exceptionFile,filemode = 'a',encoding='utf-8', level=logging.DEBUG) - debugFile=open('homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log', "w") - debugFile.write('HSDB: homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log\nHSDB: HomeScript version: ' + self.__version__) - debugFile.write('\nHSDB: URL: ' + self.url + '\nHSDB: Headers: ' + json.dumps(self.headers)) - debugFile.write('\nHSDB: Accessories: ' + json.dumps(self.accessories)) - debugFile.write('\nHSDB: Selected accessories: ' + json.dumps(self.selectedAccessories)) - debugFile.write('\nHSDB: Selected accessory names: ' + json.dumps(self.selectedAccessoryNames)) - debugFile.write('\nHSDB: Arguments: ' + json.dumps(argv)) - debugFile.write('\nHSDB: Get accessories response:\n' + str(getAcc) + '\nHSDB: End get accessories response\n') - elif content=='end': - debugFile=open('homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log', "a") - debugFile.write('HSDB: End homeScript debug file') - print('Debug logged: homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log') - else: - debugFile=open('homescript_debug_' + date.today().strftime("%Y.%m.%d") + '.log', "a") - debugFile.write('HSDB: ' + str(content) + '\n') - debugFile.close() - - def __del__(self): - try: - self.debugHandler('end') - except: - None diff --git a/hs.py b/hs.py index 8558431..3772ec3 100644 --- a/hs.py +++ b/hs.py @@ -1,6 +1,6 @@ import argparse import sys -from homescript import HomeScript +import homescript __HOSTNAME__='192.168.0.106' __PORT__='51826' @@ -51,7 +51,7 @@ def printHelp(): parser.add_argument('-v', '--version', action='store_true') args = parser.parse_args() -hs = HomeScript(__HOSTNAME__,__PORT__,__AUTH__, args.debug, sys.argv) +hs = homescript.HomeScript(__HOSTNAME__,__PORT__,__AUTH__, args.debug, sys.argv) if argumentLength==1 or args.help: printHelp() @@ -62,7 +62,8 @@ def printHelp(): if args.list and len(args.list)>=0: hs.printAccessories(args.list[0] if len(args.list)>0 else '') - hs.debugHandler('end') + if args.debug: + hs.debugHandler('end') sys.exit() elif args.get and len(args.get)>=0: if args.get[0] == 'all': @@ -79,7 +80,8 @@ def printHelp(): print('Accessory/Group not found.\nHere are a list of accessories:\n') hs.printAccessories('type') print('\nFor usage info type \'hs.py -h\'') - hs.debugHandler('end') + if args.debug: + hs.debugHandler('end') sys.exit(-1) else: if args.set: @@ -101,4 +103,5 @@ def printHelp(): else: printHelp() -hs.debugHandler('end') \ No newline at end of file +if args.debug: + hs.debugHandler('end') \ No newline at end of file diff --git a/setup.py b/setup.py index 3f0e0da..d28740e 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="homescript", - version="5.1.0", + version="5.1.1", author="Menahi Shayan", author_email="menahi.shayan@gmail.com", description="HomeScript CLI: Command Line Control of HomeBridge", @@ -19,4 +19,5 @@ "Operating System :: OS Independent", ], python_requires='>=3.7', + install_requires=["requests"] ) \ No newline at end of file