├── .gitignore ├── CHANGES.txt ├── ISY ├── IsyClass.py ├── IsyDebug.py ├── IsyDiscover.py ├── IsyEvent.py ├── IsyEventData.py ├── IsyExceptionClass.py ├── IsyNodeClass.py ├── IsyProgramClass.py ├── IsyUtilClass.py ├── IsyVarClass.py ├── __init__.py ├── _isy_printevent.py ├── _isyclimate.py ├── _isynet_resources.py ├── _isynode.py ├── _isyprog.py ├── _isyvar.py └── _isyzb.py ├── LICENSE.txt ├── MANIFEST ├── MANIFEST.in ├── Makefile ├── README.md ├── README.txt ├── bin ├── isy_audit_pgm_var.py ├── isy_audit_x10_use.py ├── isy_backup.py ├── isy_fauxmo.py ├── isy_find.py ├── isy_log.py ├── isy_manage_node.py ├── isy_nestset.py ├── isy_net_res.py ├── isy_net_wol.py ├── isy_nodes.py ├── isy_progs.py ├── isy_showevents.py ├── isy_var.py └── isy_web.py ├── docs ├── Using_IsyNode_Class.txt ├── Using_IsyProg_Class ├── Using_IsyVar_Class.txt └── Using_Isy_Class.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | EGG-INFO 4 | suds 5 | .exrc 6 | tt.py 7 | test.* 8 | Sav/* 9 | Bak/* 10 | *bak 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Packages 16 | *.egg 17 | *.egg-info 18 | dist 19 | build 20 | eggs 21 | parts 22 | var 23 | sdist 24 | develop-eggs 25 | .installed.cfg 26 | lib 27 | lib64 28 | 29 | # Installer logs 30 | pip-log.txt 31 | 32 | # Unit test / coverage reports 33 | .coverage 34 | .tox 35 | nosetests.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 2 | 0.1.4 added Calls to Soap interface to supplement rest interface 3 | added enable/disable support for nodes 4 | 5 | 0.1.3 added event stream processing ( keep library state cache updated in near real time ) 6 | 7 | -- Initial release. 8 | -------------------------------------------------------------------------------- /ISY/IsyDebug.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Debug Flags 4 | # 5 | _debug_loads_ = 0x0001 6 | _debug_urls_ = 0x0002 7 | _debug_func_call_ = 0x0004 8 | _debug_dump_data_ = 0x0008 9 | # 10 | _debug_node_chng_ = 0x0010 11 | _debug_soap_web_ = 0x0020 12 | _debug_events_ = 0x0040 13 | _debug_del_ = 0x0040 14 | # 15 | # _debug_ = 0x0100 = 16 | _debug_resp_dat_ = 0x0200 17 | # _debug_ = 0x0400 18 | # _debug_ = 0x0800 19 | # 20 | # _debug_ = 0x1000 21 | # _debug_ = 0x2000 22 | # _debug_ = 0x4000 23 | _debug_test = 0x8000 24 | # 25 | 26 | __all__ = ['_debug_loads_', '_debug_urls_', '_debug_func_call_', '_debug_dump_data_', '_debug_node_chng_', '_debug_soap_web_', '_debug_events_', '_debug_del_', '_debug_resp_dat_', '_debug_test' ] 27 | -------------------------------------------------------------------------------- /ISY/IsyDiscover.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | """Simple Python lib discovering ISY devices with Upnp """ 4 | __author__ = 'Peter Shipley ' 5 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 6 | __license__ = "BSD" 7 | 8 | 9 | 10 | # 11 | # THIS IS BAD CODE 12 | # DOES NOT PROPERLY HANDLE XML namespace FOR Upnp 13 | # 14 | 15 | import socket 16 | import struct 17 | import sys 18 | import xml.etree.ElementTree as ET 19 | # import base64 20 | if sys.hexversion < 0x3000000: 21 | import urllib2 as URL 22 | else: 23 | import urllib.request as URL 24 | 25 | import re 26 | # from pprint import pprint 27 | 28 | 29 | import signal 30 | 31 | __all__ = ['isy_discover'] 32 | 33 | class UpnpLimitExpired(Exception): 34 | pass 35 | 36 | 37 | def isy_discover(**kwargs): 38 | """ discover a device's IP 39 | 40 | named args: 41 | node : node name of id 42 | timeout : how long to wait for replies 43 | count : number of devices to wait for 44 | passively : passivly wait for broadcast 45 | debug : print debug info 46 | 47 | return: a list of dict obj containing: 48 | - friendlyName: the device name 49 | - URLBase: base URL for device 50 | - UDN: uuid 51 | ( optional : eventSubURL controlURL SCPDURL ) 52 | 53 | 54 | """ 55 | class _IsyDiscoveryData: 56 | debug = 0 57 | timeout = 20 58 | passive = 0 59 | count = 2 60 | upnp_urls = [] 61 | 62 | ddata = _IsyDiscoveryData() 63 | 64 | ddata.debug = kwargs.get("debug", 0) 65 | ddata.timeout = kwargs.get("timeout", 30) 66 | ddata.passive = kwargs.get("passive", 0) 67 | ddata.count = kwargs.get("count", 2) 68 | 69 | if ddata.debug: 70 | print("isy_discover :debug=%s\ttimeout=%s\tpassive=%s\tcount=%s\n" % \ 71 | (ddata.debug, ddata.timeout, ddata.passive, ddata.count)) 72 | 73 | def isy_discover_timeout(signum, frame): 74 | print("isy_discover_timeout CALL") 75 | raise UpnpLimitExpired("Timed Out") 76 | 77 | # def isy_timeout(signum, frame): 78 | # print("isy_timeout CALL") 79 | # print('Signal handler called with signal', signum) 80 | 81 | def isy_upnp(ddata): 82 | 83 | if ddata.debug: 84 | print("isy_upnp CalL") 85 | 86 | print("isy_upnp debug=%s\ttimeout=%s\tpassive=%s\tcount=%s\n" % \ 87 | (ddata.debug, ddata.timeout, ddata.passive, ddata.count)) 88 | 89 | multicast_group = '239.255.255.250' 90 | multicast_port = 1900 91 | server_address = ('', multicast_port) 92 | 93 | 94 | # Create the socket 95 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 96 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 97 | sock.bind(server_address) 98 | group = socket.inet_aton(multicast_group) 99 | mreq = struct.pack('4sL', group, socket.INADDR_ANY) 100 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 101 | 102 | if not ddata.passive: 103 | probe = "M-SEARCH * HTTP/1.1\r\nHOST:239.255.255.250:1900\r\n" \ 104 | "MAN:\"ssdp.discover\"\r\nMX:1\r\n" \ 105 | "ST:urn:udi-com:device:X_Insteon_Lighting_Device:1\r\n\r\n" 106 | 107 | #print "sending : ", probe 108 | sock.sendto(probe.encode('utf-8'), (multicast_group, multicast_port)) 109 | 110 | 111 | while len(ddata.upnp_urls) < ddata.count: 112 | 113 | if ddata.debug: 114 | print("while IN") 115 | 116 | data, address = sock.recvfrom(1024) 117 | 118 | #.decode('UTF-8') 119 | if sys.hexversion >= 0x3000000: 120 | data = str( data, encoding='utf8') 121 | 122 | if ddata.debug: 123 | print('received %s bytes from %s' % (len(data), address)) 124 | print(data) 125 | print("ddata.upnp_urls = ", ddata.upnp_urls) 126 | 127 | # only ISY devices 128 | # if should I look for 129 | # SERVER:UCoS, UPnP/1.0, UDI/1.0 130 | if not "X_Insteon_Lighting_" in data: 131 | continue 132 | 133 | upnp_packet = data.splitlines() 134 | 135 | if "M-SEARCH " in upnp_packet[0]: 136 | continue 137 | 138 | # extract LOCATION 139 | for l in upnp_packet: 140 | a = l.split(':', 1) 141 | if len(a) == 2: 142 | if str(a[0]).upper() == "LOCATION": 143 | ddata.upnp_urls.append(str(a[1]).strip()) 144 | # uniq the list 145 | ddata.upnp_urls = list(set(ddata.upnp_urls)) 146 | 147 | #print "returning ", ddata.upnp_urls 148 | 149 | 150 | old_handler = signal.signal(signal.SIGALRM, isy_discover_timeout) 151 | 152 | #isy_upnp(ddata) 153 | try: 154 | signal.alarm(ddata.timeout) 155 | isy_upnp(ddata) 156 | signal.alarm(0) 157 | signal.signal(signal.SIGALRM, old_handler) 158 | except UpnpLimitExpired: 159 | pass 160 | # except Exception: 161 | # print("Unexpected error:", sys.exc_info()[0]) 162 | finally: 163 | signal.alarm(0) 164 | signal.signal(signal.SIGALRM, old_handler) 165 | if ddata.debug: 166 | print("return data.upnp_urls = ", ddata.upnp_urls) 167 | 168 | result = {} 169 | # result_tags = ["UDN", "URLBase", "SCPDURL", 170 | # "controlURL", "eventSubURL"] 171 | 172 | 173 | for s in ddata.upnp_urls: 174 | req = URL.Request(s) 175 | resp = URL.urlopen(req) 176 | 177 | pagedata = resp.read().decode('utf-8') 178 | resp.close() 179 | 180 | # does this even work ?? 181 | # ET.register_namespace("isy", 'urn:schemas-upnp-org:device-1-0') 182 | #print "_namespace_map = {0}".format(ET._namespace_map) 183 | 184 | # this is a hack to deal with namespace: 185 | pa = re.sub(r" xmlns=\"urn:schemas-upnp-org:device-1-0\"", "", pagedata) 186 | # grok the XML from the Upnp discovered server 187 | xmlres = ET.fromstring(pa) 188 | 189 | # print "_namespace_map : ", 190 | # pprint(ET._namespace_map) 191 | 192 | #if hasattr(xmlres, 'tag'): 193 | # xmlns = re.search('\{(.*)\}', xmlres.tag).group(1) 194 | #else: 195 | # continue 196 | 197 | #print "xmlns ", xmlns 198 | 199 | isy_res = dict() 200 | 201 | xelm = xmlres.find("URLBase") 202 | if hasattr(xelm, 'text'): 203 | isy_res["URLBase"] = xelm.text 204 | 205 | xelm = xmlres.find("device/friendlyName") 206 | if hasattr(xelm, 'text'): 207 | isy_res["friendlyName"] = xelm.text 208 | 209 | xelm = xmlres.find("device/UDN") 210 | if hasattr(xelm, 'text'): 211 | isy_res["UDN"] = xelm.text 212 | 213 | for elm in xmlres.iter("service"): 214 | serv = xelm.find('serviceType') 215 | if hasattr(serv, 'text') and serv.text == "urn:udi-com:service:X_Insteon_Lighting_Service:1": 216 | 217 | serv = elm.find('SCPDURL') 218 | if hasattr(serv, 'text'): 219 | isy_res["SCPDURL"] = serv.text 220 | 221 | serv = elm.find('controlURL') 222 | if hasattr(serv, 'text'): 223 | isy_res["controlURL"] = serv.text 224 | 225 | serv = elm.find('eventSubURL') 226 | if hasattr(serv, 'text'): 227 | isy_res["eventSubURL"] = serv.text 228 | 229 | result[isy_res["UDN"]] = isy_res 230 | 231 | return result 232 | 233 | 234 | 235 | 236 | if __name__ == "__main__": 237 | import __main__ 238 | print(__main__.__file__) 239 | print("syntax ok") 240 | 241 | # res = isy_discover(count=1, timeout=10, passive=0) 242 | # for h in res: 243 | # print "res : ", h 244 | exit(0) 245 | -------------------------------------------------------------------------------- /ISY/IsyEvent.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Ugly... 4 | 5 | work in progress, ment as proof of concept 6 | 7 | needs rewrite or cleanup 8 | """ 9 | __author__ = 'Peter Shipley ' 10 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 11 | __license__ = "BSD" 12 | 13 | 14 | import time 15 | 16 | import sys 17 | # import os 18 | # import traceback 19 | import warnings 20 | # import re 21 | import base64 22 | import socket 23 | import select 24 | 25 | 26 | import xml.etree.ElementTree as ET 27 | 28 | from ISY.IsyEventData import EVENT_CTRL 29 | from ISY._isy_printevent import _print_event 30 | import collections 31 | 32 | try: 33 | import fcntl 34 | except ImportError: 35 | fcntl = None 36 | 37 | __all__ = ['ISYEvent'] 38 | 39 | 40 | class ISYEvent(object): 41 | 42 | 43 | def __init__(self, addr=None, **kwargs): 44 | # print "ISYEvent ", self.__class__.__name__ 45 | 46 | self.debug = kwargs.get("debug", 0) 47 | self.connect_list = [] 48 | self._shut_down = 0 49 | self.connected = False 50 | self.isy = kwargs.get("isy", None) 51 | self.level = kwargs.get("level", "1") 52 | 53 | self.process_func = kwargs.get("process_func", _print_event) 54 | self.process_func_arg = kwargs.get("process_func_arg", None) 55 | 56 | print "_print_event", _print_event 57 | if self.process_func: 58 | assert isinstance(self.process_func, collections.Callable), \ 59 | "process_func Arg must me callable" 60 | 61 | addr = kwargs.get("addr", None) 62 | 63 | if addr: 64 | userl = kwargs.get("userl", "admin") 65 | userp = kwargs.get("userp", "admin") 66 | authtuple = ( addr, userl, userp) 67 | self.connect_list.append(ISYEventConnection(self, authtuple)) 68 | 69 | 70 | def set_process_func(self, func, arg): 71 | 72 | if func: 73 | # if self.debug & 0x01: 74 | print "set_process_func", func 75 | self.process_func = func 76 | assert isinstance(self.process_func, collections.Callable), \ 77 | "process_func Arg must me callable" 78 | else: 79 | self.process_func = _print_event 80 | 81 | if arg: 82 | self.process_func_arg = arg 83 | 84 | 85 | 86 | def _finish(self): 87 | # print "Finishing... ", self.__class__.__name__ 88 | for s in self.connect_list: 89 | s.disconnect() 90 | 91 | del self.connect_list[:] 92 | if self.isy: 93 | self.isy._isy_event = None 94 | # print "Finished... ", self.__class__.__name__ 95 | 96 | # def __del__(self): 97 | # print "\n\n\n>>>>>>>>>__del__ ", \ 98 | # self.__class__.__name__, "<<<<<<<<<<<<<\n\n\n" 99 | 100 | def _stop_event_loop(self): 101 | # print self.__class__.__name__ 102 | self._shut_down = 1 103 | 104 | # 105 | # 106 | # 107 | def subscribe(self, **kwargs): 108 | """ subscribe to Isy device event stream 109 | 110 | this function adds an ISY device to the list of devices to 111 | receive events from 112 | 113 | named args: 114 | addr = IP address or hostname of isydevice 115 | userl = isy admin login 116 | userp = isy admin password 117 | level = debug level 118 | """ 119 | 120 | addr = kwargs.get("addr", None) 121 | 122 | if self.debug & 0x01: 123 | print("subscribe ", addr) 124 | 125 | if addr in self.connect_list: 126 | # print "addr :", addr 127 | print "connect_list :", self.connect_list 128 | warnstr = str("Duplicate addr : {0}").format(addr) 129 | warnings.warn(warnstr, RuntimeWarning) 130 | return 131 | 132 | userl = kwargs.get("userl", "admin") 133 | userp = kwargs.get("userp", "admin") 134 | 135 | authtuple = ( addr, userl, userp) 136 | 137 | new_conn = ISYEventConnection(self, authtuple) 138 | 139 | # see if the other connections are connected 140 | # if so connect to avoid an error in select() 141 | if self.connected: 142 | new_conn.connect() 143 | 144 | self.connect_list.append(new_conn) 145 | 146 | def unsubscribe(self, addr): 147 | """ unsubscribe to Isy device event stream 148 | 149 | this function removes an ISY device to the list of devices to 150 | receive events from 151 | 152 | arg: IP address or hostname of isydevice 153 | """ 154 | remote_ip = socket.gethostbyname(addr) 155 | if not addr in self.connect_list: 156 | warnings.warn( 157 | "address {0}/{1} not subscribed".format(addr, remote_ip), 158 | RuntimeWarning) 159 | return 160 | isyconn = self.connect_list[self.connect_list.index(addr)] 161 | isyconn.disconnect() 162 | self.connect_list.remove(isyconn) 163 | del(isyconn) 164 | 165 | def _process_event(self, conn_obj): 166 | """ 167 | 168 | _process_event : takes XML from the events stream 169 | coverts to a dict and passed to process_func provided 170 | """ 171 | #print "-" 172 | 173 | l = conn_obj.event_rf.readline() 174 | if len(l) == 0: 175 | raise IOError("bad read form socket") 176 | # conn_obj._opensock(self.authtuple[0]) 177 | # conn_obj._subscribe() 178 | # print "_process_event = ", l 179 | if (l[:5] != 'POST '): 180 | print("Stream Sync Error") 181 | for x in range(10): 182 | print(x, " ") 183 | l = conn_obj.event_rf.readline() 184 | if (l[:5] == 'POST '): 185 | break 186 | else: 187 | raise IOError("can not resync event stream") 188 | 189 | while 1: 190 | l = conn_obj.event_rf.readline() 191 | if len(l) == 2: 192 | break 193 | # print "HEADER : ", l 194 | if l[:15].upper() == "CONTENT-LENGTH:": 195 | l.rstrip('\r\n') 196 | data_len = int(l.split(':')[1]) 197 | 198 | # print "HEADER data_len ", data_len 199 | 200 | # data = conn_obj.event_rf.readread(data_len) 201 | data_remaining = data_len 202 | L = [] 203 | while data_remaining: 204 | chunk = conn_obj.event_rf.read(data_remaining) 205 | if not chunk: 206 | break 207 | L.append(chunk) 208 | data_remaining -= len(chunk) 209 | data = ''.join(L) 210 | 211 | ddat = dict() 212 | 213 | # work around for encoding bugs 214 | # in eventinfo field 215 | if data.find('-->') >= 0: 216 | data = data.replace('-->', '-->') 217 | 218 | if data.find('<=') >= 0: 219 | data = data.replace('<=', '<=') 220 | 221 | if data.find('< ') >= 0: 222 | # print "< HACK" 223 | data = data.replace('< ', '< ') 224 | 225 | if data.find(''): 226 | data = data.replace('', '<NULL>') 227 | 228 | # ev = ET.fromstring(data) 229 | try: 230 | ev = ET.fromstring(data) 231 | except ET.ParseError as e: 232 | print "Etree ParseError " 233 | print "data = ", data, 234 | print "e.message = ", e.message 235 | raise 236 | 237 | #print "_process_event ", data, "\n\n" 238 | 239 | 240 | ddat = self.et2d(ev) 241 | 242 | # print ddat 243 | #if ddat[control][0] == "_": 244 | # return 245 | # print ddat 246 | return(ddat, data) 247 | #return(ddat) 248 | 249 | def et2d(self, et): 250 | """ Etree to Dict 251 | 252 | converts an ETree to a Dict Tree 253 | lists are created for duplicate tag 254 | 255 | if there are multiple XML of the same name 256 | an list array is used 257 | attrib tags are converted to "tag_name" + "attrib_name" 258 | 259 | if an invalid arg is passed a empty dict is retrurned 260 | 261 | 262 | arg: ETree Element obj 263 | 264 | returns: a dict obj 265 | """ 266 | d = dict() 267 | if not isinstance(et, ET.Element): 268 | return d 269 | children = list(et) 270 | if et.attrib: 271 | for k, v in list(et.items()): 272 | d[et.tag + "-" + k] = v 273 | if et.text is not None: 274 | #d[et.tag + "_val"] = et.text 275 | d["#val"] = et.text 276 | if children: 277 | for child in children: 278 | if child.tag in d: 279 | if type(d[child.tag]) != list: 280 | t = d[child.tag] 281 | d[child.tag] = [t] 282 | if list(child) or child.attrib: 283 | if child.tag in d: 284 | d[child.tag].append(self.et2d(child)) 285 | else: 286 | d[child.tag] = self.et2d(child) 287 | else: 288 | if child.tag in d: 289 | d[child.tag].append(child.text) 290 | else: 291 | d[child.tag] = child.text 292 | return d 293 | 294 | def _et2d(self, et): 295 | """ Etree to Dict 296 | 297 | converts an ETree to a Dict Tree 298 | lists are created for duplicate tag 299 | 300 | arg: a ETree obj 301 | returns: a dict() obj 302 | 303 | """ 304 | d = dict() 305 | children = list(et) 306 | if et.attrib: 307 | for k, v in list(et.items()): 308 | d[et.tag + "-" + k] = v 309 | if children: 310 | for child in children: 311 | if child.tag in d: 312 | if type(d[child.tag]) != list: 313 | t = d[child.tag] 314 | d[child.tag] = [t] 315 | if list(child) or child.attrib: 316 | if child.tag in d: 317 | d[child.tag].append(self.et2d(child)) 318 | else: 319 | d[child.tag] = self.et2d(child) 320 | else: 321 | if child.tag in d: 322 | d[child.tag].append(child.text) 323 | else: 324 | d[child.tag] = child.text 325 | return d 326 | 327 | 328 | 329 | 330 | 331 | def event_iter(self, ignorelist=None, poll_interval=0.5): 332 | """Loop thought events 333 | 334 | reads events packets and passes them to processor 335 | 336 | """ 337 | 338 | self.connected = True 339 | for s in self.connect_list: 340 | s.connect() 341 | 342 | while not self._shut_down: 343 | if len(self.connect_list) == 0: 344 | break 345 | try: 346 | r, _, e = select.select(self.connect_list, [], [], poll_interval) 347 | for rl in r: 348 | d, _ = self._process_event(rl) 349 | if ignorelist: 350 | if d["control"] in ignorelist: 351 | continue 352 | yield d 353 | except socket.error: 354 | print("socket error({0}): {1}".format(e.errno, e.strerror)) 355 | self.reconnect() 356 | except IOError as e: 357 | print("I/O error({0}): {1}".format(e.errno, e.strerror)) 358 | self.reconnect() 359 | except KeyboardInterrupt: 360 | return 361 | #except Exception: 362 | #print("Unexpected Error:", sys.exc_info()[0]) 363 | #traceback.print_stack() 364 | #print repr(traceback.extract_stack()) 365 | #print repr(traceback.format_stack()) 366 | finally: 367 | pass 368 | 369 | if self._shut_down: 370 | self._finish() 371 | 372 | 373 | def reconnect(self): 374 | self.connected = True 375 | for isy_conn in self.connect_list: 376 | isy_conn.reconnect() 377 | 378 | def events_loop(self, **kargs): 379 | """Loop thought events 380 | 381 | reads events packets and passes them to processor 382 | 383 | """ 384 | ignorelist = kargs.get("ignorelist", None) 385 | poll_interval = kargs.get("poll_interval", 0.5) 386 | 387 | if self.debug & 0x01: 388 | print("events_loop ", self.__class__.__name__) 389 | 390 | self.connected = True 391 | for isystream in self.connect_list: 392 | isystream.connect() 393 | 394 | 395 | while not self._shut_down: 396 | try: 397 | r, _, e = select.select(self.connect_list, [], [], poll_interval) 398 | for rs in r: 399 | d, x = self._process_event(rs) 400 | # print "d :", type(d) 401 | if self.debug & 0x0400: 402 | print "---------" 403 | print "event_loop= ", x 404 | print "event_loop= ", d 405 | sys.stdout.flush() 406 | if ignorelist: 407 | if d["control"] in ignorelist: 408 | continue 409 | self.process_func(d, self.process_func_arg, x) 410 | # self.process_func(d, x) 411 | except socket.error as e: 412 | print("socket error({0}): {1}".format(e.errno, e.strerror)) 413 | self.reconnect() 414 | except IOError as e: 415 | print("I/O error({0}): {1}".format(e.errno, e.strerror)) 416 | self.reconnect() 417 | # except Exception: 418 | # print "Unexpected error:", sys.exc_info()[0] 419 | finally: 420 | pass 421 | 422 | if self._shut_down: 423 | self._finish() 424 | 425 | class ISYEventConnection(object): 426 | 427 | def __init__(self, isyevent, authtuple): 428 | self.event_rf = None 429 | self.event_wf = None 430 | self.event_sock = None 431 | self.parent = isyevent 432 | self.error = 0 433 | self.debug = isyevent.debug 434 | 435 | # print "authtuple : ", type(authtuple), authtuple 436 | self.authtuple = authtuple 437 | 438 | def __hash__(self): 439 | return str.__hash__(self.authtuple[0]) 440 | 441 | def __repr__(self): 442 | return "" % (self.authtuple[0], id(self)) 443 | 444 | def __str__(self): 445 | return self.authtuple[0] 446 | 447 | def __del__(self): 448 | self.disconnect() 449 | #print "\n\n\n>>>>>>>>>__del__ ", self.__class__.__name__, "<<<<<<<<<<<<<\n\n\n" 450 | 451 | def __eq__(self, other): 452 | if isinstance(other, str): 453 | return self.authtuple[0] == other 454 | if not hasattr(other, "authtuple"): 455 | return False 456 | return self.authtuple == other.authtuple 457 | 458 | def fileno(self): 459 | """ Interface required by select(). """ 460 | return self.event_sock.fileno() 461 | 462 | def reconnect(self): 463 | # print "--reconnect to self.authtuple[0]--" 464 | self.error += 1 465 | 466 | retry = True 467 | while retry: 468 | try: 469 | self.disconnect() 470 | self.connect() 471 | retry = False 472 | except socket.error as e: 473 | print("socket error - reconnecting({0}): {1}".format(e.errno, e.strerror)) 474 | time.sleep(1) 475 | retry = True 476 | 477 | def disconnect(self): 478 | try: 479 | if self.event_rf: 480 | self.event_rf.close() 481 | self.event_rf = False 482 | except IOError: 483 | pass 484 | 485 | try: 486 | if self.event_wf: 487 | self.event_wf.close() 488 | self.event_wf = False 489 | except IOError: 490 | pass 491 | 492 | try: 493 | if self.event_sock: 494 | self.event_sock.close() 495 | self.event_sock = False 496 | except IOError: 497 | pass 498 | 499 | def connect(self): 500 | if self.debug & 0x01: 501 | print("connect ", self.__class__.__name__) 502 | self._opensock() 503 | self._subscribe() 504 | 505 | def _opensock(self): 506 | 507 | if self.debug & 0x01: 508 | print("_opensock ", self.authtuple[0]) 509 | 510 | # self.event_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 511 | 512 | server_address = (self.authtuple[0], 80) 513 | self.event_sock = socket.create_connection(server_address, 10) 514 | self.event_sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 515 | 516 | if hasattr(socket, 'TCP_KEEPINTVL'): 517 | self.event_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1) 518 | if hasattr(socket, 'TCP_KEEPCNT'): 519 | self.event_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) 520 | if hasattr(socket, 'TCP_KEEPIDLE'): 521 | self.event_sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 10) 522 | 523 | #sn = sock.getsockname() 524 | #self.myip = sn[0] 525 | #print "P ", self.myip 526 | 527 | #self.myurl = "http://{0}:{1}/".format(sn[0], self.server_address[1]) 528 | #print "myurl ", self.myurl 529 | 530 | if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): 531 | flags = fcntl.fcntl(self.event_sock.fileno(), fcntl.F_GETFD) 532 | flags |= fcntl.FD_CLOEXEC 533 | fcntl.fcntl(self.event_sock.fileno(), fcntl.F_SETFD, flags) 534 | 535 | self.event_rf = self.event_sock.makefile("rb") 536 | self.event_wf = self.event_sock.makefile("wb") 537 | 538 | return self.event_sock 539 | 540 | def _subscribe(self): 541 | 542 | if self.debug & 0x01: 543 | print("_subscribe : ", self.__class__.__name__) 544 | 545 | # if ( not isinstance(self.event_wf, socket) 546 | # or not isinstance(self.event_rf, socket)): 547 | # raise RuntimeError( 548 | # "{!s} called with invalid socket".format(self.__class__.__name__)) 549 | 550 | # uuid:168 551 | post_body = "" \ 552 | "" \ 553 | + "REUSE_SOCKET" \ 554 | + "infinite" \ 555 | "" 556 | # "\r\n\r\n" 557 | 558 | base64pass = base64.encodestring('%s:%s' % (self.authtuple[1], self.authtuple[2]))[:-1] 559 | post_head = "POST /services HTTP/1.1\r\n" \ 560 | + "Host: {0}:80\r\n".format(self.authtuple[0]) \ 561 | + "Authorization: Basic {0}\r\n".format(base64pass) \ 562 | + "Content-Length: {0}\r\n".format(len(post_body)) \ 563 | + "Content-Type: text/xml; charset=\"utf-8\"\r\n" \ 564 | + "\r\n\r\n" 565 | 566 | 567 | post = post_head + post_body 568 | 569 | self.event_wf.write(post) 570 | self.event_wf.flush() 571 | 572 | l = self.event_rf.readline() 573 | if (l[:5] != 'HTTP/'): 574 | raise ValueError(l) 575 | 576 | if (l.split(' ')[1] != "200"): 577 | raise ValueError(l) 578 | 579 | while 1: 580 | l = self.event_rf.readline() 581 | if len(l) == 2: 582 | break 583 | if l[:15] == "Content-Length:": 584 | l.rstrip('\r\n') 585 | data_len = int(l.split(':')[1]) 586 | 587 | 588 | reply = self.event_rf.read(data_len) 589 | if self.debug & 0x01: 590 | print("_subscribe reply = '", reply, "'") 591 | 592 | 593 | # 594 | # Do nothing 595 | # (syntax check) 596 | # 597 | if __name__ == "__main__": 598 | import __main__ 599 | print(__main__.__file__) 600 | 601 | print("syntax ok") 602 | exit(0) 603 | -------------------------------------------------------------------------------- /ISY/IsyEventData.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a data file for IsyEvent.py 3 | 4 | """ 5 | # author : Peter Shipley 6 | # copyrigh : Copyright (C) 2015 Peter Shipley 7 | # license : BSD 8 | 9 | 10 | __all__ = [] # EVENT_CTRL, LOG_USERID 11 | 12 | ## EVENT_CTRL ## 13 | 14 | EVENT_CTRL = { 15 | "_0" : "Heartbeat", 16 | "_1" : "Trigger", 17 | "_2" : "Protocol Specific", 18 | "_3" : "Nodes Updated", 19 | "_4" : "System Config Updated", 20 | "_5" : "System Status", 21 | "_6" : "Internet Access", 22 | "_7" : "System Progress", 23 | "_8" : "Security System", 24 | "_9" : "System Alert", 25 | "_10" : "OpenADR", #"Electricity", 26 | "_11" : "Climate", 27 | "_12" : "AMI/SEP", 28 | "_13" : "Ext Energy Mon", 29 | "_14" : "UPB Linker", 30 | "_15" : "UPB Dev State", 31 | "_16" : "UPB Dev Status", 32 | "_17" : "Gas", 33 | "_18" : "ZigBee", 34 | "_19" : "Elk", 35 | "_20" : "Device Link", 36 | "_21" : "Z-Wave", 37 | "_22" : "Billing", 38 | "_23" : "Portal", 39 | "DON" : "Device On", 40 | "DFON" : "Device Fast On", 41 | "DOF" : "Device Off", 42 | "DFOF" : "Device Fast Off", 43 | "ST" : "Status", 44 | "OL" : "On Level", 45 | "RR" : "Ramp Rate", 46 | "BMAN" : "Start Manual Change", 47 | "SMAN" : "Stop Manual Change", 48 | "CLISP" : "Setpoint", 49 | "CLISPH" : "Heat Setpoint", 50 | "CLISPC" : "Cool Setpoint", 51 | "CLIFS" : "Fan State", 52 | "CLIMD" : "Thermostat Mode", 53 | "CLIHUM" : "Humidity", 54 | "CLIHCS" : "Heat/Cool State", 55 | "BRT" : "Brighten", 56 | "DIM" : "Dim", 57 | "X10" : "Direct X10 Commands", 58 | "BEEP" : "Beep", 59 | } 60 | 61 | EVENT_CTRL_ACTION = { 62 | "_0" : { }, 63 | 64 | "_1" : { 65 | '0': "Event Status", 66 | '1': "Get Status", 67 | '2': "Key Change", 68 | '3': "Info String", 69 | '4': "IR Learn Mode", 70 | '5': "Schedule", 71 | '6': "Var Stat", 72 | '7': "Var Init", 73 | '8': "Key", 74 | }, 75 | 76 | # Driver Specific Events 77 | "_2" : {}, 78 | 79 | # Node Changed/Updated 80 | '_3': { 81 | 'NN': 'Node Renamed', 82 | 'NR': 'Node Removed', 83 | 'ND': 'Node Added', 84 | 'MV': 'Node Moved (into a scene)', 85 | 'CL': 'Link Changed (in a scene)', 86 | 'RG': 'Removed From Group (scene)', 87 | 'EN': 'Enabled', 88 | 'PC': 'Parent Changed', 89 | 'PI': 'Power Info Changed', 90 | 'DI': 'Device ID Changed', 91 | 'DP': 'Device Property Changed', 92 | 'GN': 'Group Renamed', 93 | 'GR': 'Group Removed', 94 | 'GD': 'Group Added', 95 | 'FN': 'Folder Renamed', 96 | 'FR': 'Folder Removed', 97 | 'FD': 'Folder Added', 98 | 'NE': 'Node Error (Comm. Errors)', 99 | 'CE': 'Clear Node Error (Comm. Errors Cleared)', 100 | 'SN': 'Discovering Nodes (Linking)', 101 | 'SC': 'Node Discovery Complete', 102 | 'WR': 'Network Renamed', 103 | 'WH': 'Pending Device Operation', 104 | 'WD': 'Programming Device', 105 | 'RV': 'Node Revised (UPB)', 106 | }, 107 | 108 | # System Configuration Updated 109 | '_4': { 110 | '0': 'Time Changed', 111 | '1': 'Time Configuration Changed', 112 | '2': 'NTP Settings Updated', 113 | '3': 'Notifications Settings Updated', 114 | '4': 'NTP Communications Error', 115 | '5': 'Batch Mode Updated', 116 | '6': 'Battery Mode Programming Updated', 117 | }, 118 | 119 | # System Status Updated 120 | '_5': { 121 | '0': 'Not Busy', 122 | '1': 'Busy', 123 | '2': 'Idle', 124 | '3': 'Safe Mode', 125 | }, 126 | 127 | # Internet Access Status 128 | '_6': { 129 | '0': 'Disabled', 130 | '1': 'Enabled', 131 | '2': 'Failed', 132 | }, 133 | 134 | # Progress Report 135 | '_7': { 136 | '1': 'Update', 137 | '2.1': 'Device Adder Info (UPB Only)', 138 | '2.2': 'Device Adder Warn (UPB Only)', 139 | '2.3': 'Device Adder Error (UPB Only)', 140 | }, 141 | 142 | # Security System Event 143 | '_8': { 144 | '0': 'Disconnected', 145 | '1': 'Connected', 146 | 'DA': 'Disarmed', 147 | 'AW': 'Armed Away', 148 | 'AS': 'Armed Stay', 149 | 'ASI': 'Armed Stay Instant', 150 | 'AN': 'Armed Night', 151 | 'ANI': 'Armed Night Instant', 152 | 'AV': 'Armed Vacation', 153 | }, 154 | 155 | # System Alert Event 156 | "_9" : {}, 157 | 158 | # Electricity / OpenADR and Flex Your Power Events 159 | '_10': { 160 | '1': 'Open ADR Error', 161 | '2': 'Open ADR Status Update', 162 | '5': 'Flex Your Power Error', 163 | '6': 'Flex Your Power Status Updated', 164 | '8': 'OpenADR Registration Status', 165 | '9': 'OpenADR Report Status', 166 | '10': 'OpenADR Opt Status', 167 | }, 168 | 169 | 170 | # Climate Events 171 | '_11': { 172 | '0': 'Error', 173 | '1': 'Temperature', 174 | '2': 'Temperature High', 175 | '3': 'Temperature Low', 176 | '4': 'Feels Like', 177 | '5': 'Temperature Rate', 178 | '6': 'Humidity', 179 | '7': 'Humidity Rate', 180 | '8': 'Pressure', 181 | '9': 'Pressure Rate', 182 | '10': 'Dew Point', 183 | '11': 'Wind Speed', 184 | '12': 'Average Wind Speed', 185 | '13': 'Wind Direction', 186 | '14': 'Average Wind Direction', 187 | '15': 'Gust Wind Speed', 188 | '16': 'Gust Wind Direction', 189 | '17': 'Rain Today', 190 | '18': 'Ambient Light', 191 | '19': 'Light Rate', 192 | '20': 'Rain Rate', 193 | '21': 'Rain Rate Max', 194 | '22': 'Evapotranspiration', 195 | '23': 'Irrigation Requirement', 196 | '24': 'Water Deficit Yesterday', 197 | '25': 'Elevation', 198 | '26': 'Coverage', 199 | '27': 'Intensity', 200 | '28': 'Weather Condition', 201 | '29': 'Cloud Condition', 202 | '30': "Tomorrow's Forecast: Average Temperature", 203 | '31': "Tomorrow's Forecast: High Temperature", 204 | '32': "Tomorrow's Forecast: Low Temperature", 205 | '33': "Tomorrow's Forecast: Humidity", 206 | '34': "Tomorrow's Forecast: Wind Speed", 207 | '35': "Tomorrow's Forecast: Gust Speed", 208 | '36': "Tomorrow's Forecast: Rain", 209 | '37': "Tomorrow's Forecast: Snow", 210 | '38': "Tomorrow's Forecast: Coverage", 211 | '39': "Tomorrow's Forecast: Intensity", 212 | '40': "Tomorrow's Forecast: Weather Condition", 213 | '41': "Tomorrow's Forecast: Cloud Condition", 214 | '42': '24 Hour Forecast: Average Temperature', 215 | '43': '24 Hour Forecast: High Temperature', 216 | '44': '24 Hour Forecast: Low Temperature', 217 | '45': '24 Hour Forecast: Humidity', 218 | '46': '24 Hour Forecast: Rain', 219 | '47': '24 Hour Forecast: Snow', 220 | '48': '24 Hour Forecast: Coverage', 221 | '49': '24 Hour Forecast: Intensity', 222 | '50': '24 Hour Forecast: Weather Condition', 223 | '51': '24 Hour Forecast: Cloud Condition', 224 | '100': 'Last Updated Timestamp', 225 | }, 226 | 227 | # ISY SEP EVENTS 228 | '_12': { 229 | '1': 'Network Status Changed', 230 | '2': 'Time Status Changed', 231 | '3': 'New Message', 232 | '4': 'Message Stopped', 233 | '5': 'New Price', 234 | '6': 'Price Stopped', 235 | '7': 'New DR Event', 236 | '8': 'DR Event Stopped', 237 | '9': 'Metering Event', 238 | '10': 'Metering Format Event', 239 | '31': 'Scheduled Message', 240 | '51': 'Scheduled Price', 241 | '71': 'Scheduled DR Event', 242 | '110': 'Fast Poll Mode', 243 | '111': 'Normal Poll Mode', 244 | }, 245 | 246 | # External Energy Monitoring Events 247 | '_13': { 248 | '1': 'Number of Channels', 249 | '2': 'Channel Report', 250 | '3': 'Zigbee Status', 251 | '7': 'Raw Packet', 252 | }, 253 | 254 | # UPB Linker Events 255 | '_14': { 256 | '1': 'Device Status', 257 | '2': 'Pending Stop Find', 258 | '3': 'Pending Cancel Device Adder', 259 | }, 260 | 261 | # UPB Dev State 262 | '_15': {}, 263 | 264 | # UPB Device Status Events 265 | '_16': { 266 | '1': 'Device Signal Report', 267 | '2': 'Device Signal Report Removed', 268 | }, 269 | 270 | # Gas Meter Events 271 | '_17': { 272 | '1': 'Status', 273 | '2': 'Error', 274 | }, 275 | 276 | # Zigbee Events 277 | '_18': { 278 | '1': 'Status', 279 | }, 280 | 281 | # ELK 282 | "_19" : {}, 283 | 284 | # Device Linker Events 285 | '_20': { 286 | '1': 'Status', 287 | '2': 'Cleared', 288 | }, 289 | 290 | # Z-Wave 291 | "_21" : {}, 292 | 293 | # Billing Events 294 | '_22': { 295 | '1': 'Cost/Usage Changed Event', 296 | }, 297 | 298 | # Portal Events 299 | '_23': { 300 | '1': 'Status', 301 | }, 302 | 303 | } 304 | 305 | LOG_USERID = [ "SYSTEM_USER", "SYSTEM_DRIVER_USER", "WEB_USER", 306 | "SCHEDULER_USER", "D2D_USER", " ELK_USER", 307 | "SEP_DEVICE_UMETER_USER", "SEP_DEVICE_UPRICE_USER", 308 | "SEP_DEVICE_UMSG_USER", "SEP_DEVICE_UDR_USER", 309 | "GAS_METER_USER" ] 310 | 311 | 312 | LOG_TYPES = { 313 | "1": "SYSTEM_STARTUP", 314 | "2": "SYSTEM_SHUTDOWN", 315 | "3": "WARNING", 316 | "4": "INFO", 317 | "5": "LOG", 318 | "6": "UD_SEP_SUBSYS_STARTUP", 319 | "-1": "REQUEST_FAILED_ERROR", 320 | "-2": "DEVICE_COMMUNICATION_ERROR", 321 | "-3": "DEVICE_RETURNED_INVALID_NODE", 322 | "-4": "DEVICE_RETURNED_INVALID_ADDRESS", 323 | "-5": "ERROR_LOGGER_STARTUP", 324 | "-10": "MAIN_HAML_DRIVER_NOT_FOUND", 325 | "-20": "MAIN_LOCAL_DEVICE_BLANK", 326 | "-100": "SYSTEM_NO_NETWORK_CONNECTION", 327 | "-101": "SYSTEM_WEBSERVER_SELECT_FAILED", 328 | "-500": "HAML_DRIVER_LISTENER_NOT_REGISTERED", 329 | "-1000": "HAML_PARSER_UNDEFINED_ELEMENT", 330 | "-1001": "HAML_PARSER_ONDATA", 331 | "-5001": "UPNP_DRIVER_NO_DEVICES_CONFIGURED", 332 | "-5002": "UPNP_DRIVER_SERIAL_READER_FAILED", 333 | "-5003": "UPNP_DRIVER_MAX_DEVICES", 334 | "-5004": "UPNP_SERVICE_TYPE_SEARCH_NS", 335 | "-5005": "UPNP_SUBSCRIPTION_NOT_FOUND_FOR_RENEWAL", 336 | "-5006": "UPNP_SUBSCRIPTION_NOT_FOUND_FOR_CANCELATION", 337 | "-5007": "UPNP_INVALID_SUBSCRIPTION_URL", 338 | "-5008": "UPNP_INVALID_SUBSCRIPTION_CALLBACK", 339 | "-5009": "UPNP_MAX_SUBSCRIBERS", 340 | "-5010": "UPNP_SUBSCRIBER_TCP_CONNECT_FAILURE", 341 | "-5011": "PROCESS_DEVICE_STATE_CHANGE_SID_NOT_FOUND", 342 | "-5012": "UPNP_SUBSCRIBER_NOREPLY_TO_EVENT_1", 343 | "-5013": "UPNP_SUBSCRIBER_NOREPLY_TO_EVENT_2", 344 | "-5014": "UPNP_SUBSCRIBER_NOREPLY_TO_EVENT_3", 345 | "-5015": "UPNP_CONTROL_MALFORMED_SOAP_REQUEST_1", 346 | "-5016": "UPNP_CONTROL_MALFORMED_SOAP_REQUEST_2", 347 | "-6000": "OS_DUPLICATE_TASK_PRIORITY", 348 | "-6001": "OS_OPEN_SERIAL_FAILED", 349 | "-7020": "D2D_PARSER_ERROR", 350 | "-7029": "NOTIFICATIONS_MAIL_TO_ADDRESS_REQUIRED", 351 | "-7030": "NOTIFICATIONS_SEND_MAIL_FAILED", 352 | "-7050": "D2D_EXPECTED_D2D_TAG", 353 | "-7051": "D2D_UNEXPECTED_TAG_IN_SENSE", 354 | "-7052": "D2D_UNEXPECTED_TAG_IN_CONDITION", 355 | "-7501": "DIAG_PARSER_ERROR", 356 | "-7601": "LINK_PARSER_ERROR", 357 | "-10100": "PNP_SECURITY_NOT_VERIFIED", 358 | "-10001": "SSL_DECODING_LENGTHS_FAILED", 359 | "-10002": "SSL_DECODING_PMOD_FAILED", 360 | "-10003": "SSL_DECODING_PEXP_FAILED", 361 | "-10004": "SSL_DECODING_PRI_EXP_FAILED", 362 | "-10005": "SSL_DECODING_PRI_P_FAILED", 363 | "-10006": "SSL_DECODING_PRI_Q_FAILED", 364 | "-10007": "SSL_DECODING_PRI_X1_FAILED", 365 | "-10008": "SSL_DECODING_PRI_X2_FAILED", 366 | "-10009": "SSL_DECODING_COEFF_FAILED", 367 | "-10010": "SSL_DECODING_CERT_FAILED", 368 | "-10011": "SSL_REQUEST_NOT_AUTHENTICATED", 369 | "-10026": "SECURE_SESSION_DOES_NOT_EXIST", 370 | "-10027": "SECURE_SESSIONS_EXHAUSTED", 371 | "-10101": "AUTHENTICATION_UNSUPPORTED_UID_LEN", 372 | "-10102": "AUTHENTICATION_UNSUPPORTED_PWD_LEN", 373 | "-10103": "AUTHENTICATION_USER_ID_DOES_NOT_EXIST", 374 | "-10104": "AUTHENTICATION_USER_ID_PWD_NOT_PRESENT", 375 | "-10105": "AUTHENTICATION_WRONG_PASSWORD", 376 | "-10106": "AUTHENTICATION_FAILED", 377 | "-10107": "HTTP_AUTH_DECODING_FAILED", 378 | "-11000": "SECURITY_INITIALIZATION_FAILED", 379 | "-12000": "TIMED_OUT_WAITING_FOR_CRITICAL_SECION", 380 | "-12001": "ERROR_LEAVING_CRITICAL_SECTION_NOT_OWNED", 381 | "-13000": "CONTENT_LEN_NOT_EQUAL_TO_HEADER_CONTENT_LEN", 382 | "-14001 ": "XML_MALFORMED_TAG", 383 | "-14002": "XML_MALFORMED_END_TAG", 384 | "-14003 ": "XML_NO_START_TAG", 385 | "-14004 ": "XML_NO_TAG_NAME", 386 | "-14005 ": "XML_START_END_NAME_MISMATCH", 387 | "-20000": "MALFORMED_UPNP_HEADERS", 388 | "-50000": "MAIL_SERVER_CONNECT_ERROR", 389 | "-50001": "SMTP_SERVER_FAILURE", 390 | "-50010": "MAIL_SERVER_DNS_ERROR", 391 | "-50011": "MAIL_MAX_FROM_LEN", 392 | "-50012": "MAIL_MAX_SUBJECT_LEN", 393 | "-50013": "MAIL_MAX_TO_LEN", 394 | "-60000": "NTP_CONFIG_SERVER_NO_HOST_PARAM", 395 | "-60001": "NTP_CONFIG_SERVER_ADDRESS_RESOLUTION_FAILED", 396 | "-60002": "NTP_CONFIG_SERVER_NO_INTERVAL_PARAM", 397 | "-60006": "NTP_SERVER_NOT_RESPONDING", 398 | "-60007": "NTP_SERVER_CONNECT_ERROR", 399 | "-70000": "OUT_OF_MEMORY", 400 | "-80000": "IGD_FAILED_PARSING_DESCRIPTION_URL", 401 | "-80001": "IGD_FAILED_RETRIEVING_DESCRIPTION_FILE", 402 | "-80002": "IGD_FAILED_RETRIEVING_URL_BASE", 403 | "-80003": "IGD_FAILED_PARSING_URL_BASE", 404 | "-80004": "IGD_FAILED_RETRIEVING_WAN_CONNECTION_DEVICE", 405 | "-80005": "IGD_FAILED_RETRIEVING_CONTROL_URL", 406 | "-80006": "IGD_FAILED_PARSING_CONTROL_URL", 407 | "-80007": "IGD_FAILED_RETRIEVING_EXTERNAL_IP", 408 | "-80008": "IGD_NO_RESPONSE_FROM_GATEWAY", 409 | "-80009": "IGD_FAILED_STRIPPING_HTTP_HEADERS", 410 | "-80010": "IGD_FAILED_DELETING_PORT_FORWARD_MAP", 411 | "-80011": "IGD_FAILED_ADDING_PORT_FORWARD_MAP", 412 | "-80012": "IGD_FAILED_GETTING_SPECIFIC_ENTRY", 413 | "-90001": "CRC_INVALID_ORDER", 414 | "-90002": "CRC_INVALID_POLYNOM", 415 | "-90003": "CRC_INVALID_CRC_INIT", 416 | "-90004": "CRC_INVALID_CRC_XOR", 417 | "-100000": "LOGGER_DIRECTORY_CREATION_FAILED", 418 | "-100001": "LOGGER_SD_IS_NOT_INSTALLED", 419 | "-100002": "LOGGER_LOG_FILE_OPEN_FAILED", 420 | "-110000": "FILE_TO_STRING_OPEN_FAILED", 421 | "-110001": "FILE_TO_STRING_MEM_ALLOC_FAILED", 422 | "-110002": "SD_DRIVE_FORMAT_FAILED_1", 423 | "-110003": "SD_DRIVE_FORMAT_FAILED_2", 424 | "-110004": "SD_DRIVE_MOUNT_FAILED_1", 425 | "-110005": "SD_DRIVE_MOUNT_FAILED_2", 426 | "-110006": "SEND_FILE_OPEN_FAILED", 427 | "-110007": "SEND_FILE_READ_FAILED", 428 | "-110008": "RECEIVE_FILE_WRITE_FAILED", 429 | "-110009": "RECEIVE_FILE_OPEN_FAILED", 430 | "-110010": "SD_DRIVE_DIRECTORY_CREATION_FAILED", 431 | "-110011": "SD_DRIVE_CONFIG_FILE_OPEN_WRITE_FAILED", 432 | "-110012": "SD_DRIVE_CONFIG_FILE_OPEN_READ_FAILED", 433 | "-110013": "SD_DRIVE_CONFIG_WRITE_FAILED", 434 | "-110014": "SD_DRIVE_CONFIG_READ_FAILED", 435 | "-110015": "STRING_TO_FILE_OPEN_FAILED", 436 | "-110016": "STRING_TO_FILE_WRITE_FAILED", 437 | "-110017": "FILE_TO_STRING_READ_FAILED", 438 | "-110018": "REMOVE_FILE_FAILED", 439 | "-110019": "REMOVE_DIR_FAILED", 440 | "-110020": "FLUSH_FILE_FAILED", 441 | "-110021": "CLOSE_FILE_FAILED", 442 | "-110022": "OPEN_FILE_FAILED", 443 | "-110023": "FLUSH_FILE_SYSTEM_FAILED", 444 | "-110024": "FILESYSTEM_INIT_FAILED", 445 | "-110025": "FILESYSTEM_CRIT_FAILED", 446 | "-120000": "FIRMWARE_UPDATE_OPEN_FILE_FAILED", 447 | "-120001": "FIRMWARE_UPDATE_HEADER_READ_FAILED", 448 | "-120002": "FIRMWARE_UPDATE_CHECKSUM_FAILED", 449 | "-120003": "FIRMWARE_UPDATE_MALLOC_FAILED", 450 | "-120004": "FIRMWARE_UPDATE_DATA_READ_FAILED", 451 | "-130000": "ELK_CONFIG_PARSER_ERROR", 452 | "-140000": "HTTP_CLIENT_DNS_ERROR", 453 | "-140001": "HTTP_CLIENT_BASE64_ENCRYPTION_FAILED", 454 | "-140002": "HTTP_CLIENT_CONNECTION_TIMED_OUT", 455 | "-140003": "HTTP_CLIENT_WRITE_HEADER_FAILED", 456 | "-140004": "HTTP_CLIENT_WRITE_BODY_FAILED", 457 | "-140005": "HTTP_CLIENT_READ_RESPONSE_FAILED", 458 | "-140006": "HTTP_CLIENT_HEADER_NO_STATUS", 459 | "-140007": "HTTP_CLIENT_RESOURCE_MOVED", 460 | "-140008": "HTTP_CLIENT_REQUEST_FAILED", 461 | "-140009": "HTTP_CLIENT_NO_NETWORK", 462 | "-150000": "TCP_CLIENT_WRITE_FAILED", 463 | "-150100": "UDP_CLIENT_DNS_ERROR", 464 | "-160000": "PROTOCOL_READER_READ_ERROR", 465 | "-160001": "PROTOCOL_READER_BUFFER_OVERFLOW", 466 | "-160002": "PROTOCOL_READER_REOPEN_ERROR", 467 | "-170000": "WEB_MODULE_NO_FREE_SPACE", 468 | "-170001": "SYSTEM_ACCESS_LOG", 469 | "-180000": "SEP_NETWORK_SCAN_ERROR", 470 | "-180001": "SEP_NETWORK_KEY_EST_ERROR", 471 | "-180002": "SEP_NETWORK_DISCOVERY_ERROR", 472 | "-180003": "SEP_NETWORK_SYNCH_ERROR", 473 | "-180004": "SEP_MODULE_RESET_ERROR", 474 | "-180005": "SEP_MODULE_INVALID_CALL_ERROR", 475 | "-180006": "SEP_MODULE_UNKNOWN_ERROR", 476 | "-190001": "UDERR_ISY_API_NO_SPACE", 477 | "-190002": "UDERR_ISY_API_INVALID_8_3_FILENAME", 478 | "-190003": "UDERR_ISY_API_INVALID_PGM_FILENAME", 479 | "-190004": "UDERR_ISY_API_INCORRECT_PGM_KEY", 480 | "-190005": "UDERR_ISY_API_INVALID_PGM_URL_SEARCH_STRING", 481 | "-200000": "DEVICE_DRIVER_ERROR_MSG", 482 | "-210001": "CALL_HOME_PORTAL_NO_FD", 483 | } 484 | 485 | 486 | 487 | # 488 | # Do nothing 489 | # (syntax check) 490 | # 491 | if __name__ == "__main__": 492 | import __main__ 493 | print(__main__.__file__) 494 | 495 | print("syntax ok") 496 | exit(0) 497 | -------------------------------------------------------------------------------- /ISY/IsyExceptionClass.py: -------------------------------------------------------------------------------- 1 | from urllib2 import URLError 2 | 3 | __author__ = 'Peter Shipley ' 4 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 5 | __license__ = "BSD" 6 | 7 | __all__ = [ 'IsyError', 'IsyNodeError', 8 | 'IsyResponseError', 'IsyPropertyError', 'IsyValueError', 9 | 'IsyInvalidCmdError', 10 | 'IsySoapError', 'IsyTypeError', 11 | 'IsyCommunicationError', 12 | 'IsyRuntimeWarning', 'IsyWarning' 13 | ] 14 | 15 | from urllib2 import URLError, HTTPError 16 | 17 | # 18 | # The Following was lifted from other modules used as examples 19 | # 20 | class IsyError(Exception): 21 | """ISY Base exception 22 | 23 | SubClasses: 24 | - IsyResponseError(IsyError): 25 | - IsyPropertyError(IsyError): 26 | - IsyValueError(IsyError): 27 | - IsyInvalidCmdError(IsyError): 28 | - IsyPropertyValueError(IsyError): 29 | - IsyAttributeError(IsyError): 30 | 31 | """ 32 | def __init__(self, msg, exception=None, httperr=None): 33 | """Creates an exception. The message is required, but the exception 34 | is optional.""" 35 | self._msg = msg 36 | self._exception = exception 37 | self.httperr = httperr 38 | if self.httperr is not None: 39 | self.httperrbody = httperr.read() 40 | else: 41 | self.httperrbody = None 42 | Exception.__init__(self, msg) 43 | 44 | def code(self): 45 | if self.httperr is not None: 46 | return self.httperr.code 47 | return None 48 | 49 | def getMessage(self): 50 | "Return a message for this exception." 51 | return self._msg 52 | 53 | def getException(self): 54 | "Return the embedded exception, or None if there was none." 55 | return self._exception 56 | 57 | def __str__(self): 58 | "Create a string representation of the exception." 59 | return self._msg 60 | 61 | def __getitem__(self, ix): 62 | """Avoids weird error messages if someone does exception[ix] by 63 | mistake, since Exception has __getitem__ defined.""" 64 | raise AttributeError("__getitem__({0})".format(ix)) 65 | 66 | 67 | class IsyCommunicationError(IsyError, URLError): 68 | """Failed Server connection of responce .""" 69 | pass 70 | 71 | # class IsyCommandError(IsyError): 72 | # """General exception for command errors.""" 73 | # pass 74 | 75 | class IsySoapError(IsyError, HTTPError): 76 | """General exception for SOAP errors.""" 77 | # def __init__(self, message, Errors): 78 | # 79 | # # Call the base class constructor with the parameters it needs 80 | # HTTPError.__init__(self, message) 81 | # 82 | # # Now for your custom code... 83 | # self.Errors = Errors 84 | 85 | 86 | pass 87 | 88 | class IsyTypeError(IsyError, TypeError): 89 | """General exception for Type errors.""" 90 | pass 91 | 92 | class IsyNodeError(IsyError): 93 | """General exception for Node errors.""" 94 | pass 95 | 96 | class IsyResponseError(IsyError, RuntimeError): 97 | """General exception for Isy responce errors.""" 98 | pass 99 | 100 | class IsyLookupError(IsyError, LookupError): 101 | """General exception for property errors.""" 102 | pass 103 | 104 | class IsyPropertyError(IsyError, LookupError): 105 | """General exception for property errors.""" 106 | pass 107 | 108 | class IsyInternalError(IsyError): 109 | """General exception for Internal errors.""" 110 | pass 111 | 112 | class IsyValueError(IsyError, ValueError): 113 | """General exception for arg value errors.""" 114 | pass 115 | 116 | # IsyInvalidValueError ?? 117 | class IsyInvalidArgError(IsyError): 118 | """General exception for command errors.""" 119 | pass 120 | 121 | class IsyInvalidCmdError(IsyError): 122 | """General exception for command errors.""" 123 | pass 124 | 125 | #class IsyPropertyValueError(IsyError): 126 | # """General exception for Isy errors.""" 127 | # pass 128 | 129 | class IsyAttributeError(IsyError, AttributeError): 130 | """General exception for Isy errors.""" 131 | pass 132 | 133 | 134 | class IsyWarning(Warning): 135 | pass 136 | 137 | class IsyRuntimeWarning(IsyWarning, RuntimeWarning): 138 | pass 139 | 140 | 141 | # 142 | # Do nothing 143 | # (syntax check) 144 | # 145 | if __name__ == "__main__": 146 | import __main__ 147 | print(__main__.__file__) 148 | print("syntax ok") 149 | exit(0) 150 | -------------------------------------------------------------------------------- /ISY/IsyProgramClass.py: -------------------------------------------------------------------------------- 1 | """ Obj Class Isy Program entries """ 2 | 3 | __author__ = 'Peter Shipley ' 4 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 5 | __license__ = "BSD" 6 | 7 | from ISY.IsyUtilClass import IsySubClass, val2bool 8 | from ISY.IsyClass import * 9 | #fromm ISY.IsyNodeClass import * 10 | # fromm ISY.IsyVarClass import * 11 | 12 | __all__ = ['IsyProgram'] 13 | 14 | class IsyProgram(IsySubClass): 15 | """ Program Class for ISY 16 | 17 | attributes/properties: 18 | enabled : 'true', 19 | folder : 'false', 20 | id : '0002', 21 | lastFinishTime : '2013/03/16 13:35:26', 22 | lastRunTime : '2013/03/16 13:35:26', 23 | name : 'QueryAll', 24 | nextScheduledRunTime : '2013/03/17 03:00:00', 25 | parentId : '0001', 26 | runAtStartup : 'false', 27 | running : 'idle', 28 | status: 'false'}, 29 | 30 | 31 | funtions: 32 | get_prog_enable(): 33 | set_prog_enable(): 34 | get_prog_runatstart(): 35 | set_prog_runatstart(): 36 | send_command(): 37 | 38 | get_prog_ts() : get timestamp 39 | get_prog_type() : get Prog type 40 | get_prog_init() : get inital value for Var 41 | set_prog_init(new_value) : set inital value for Var 42 | get_prog_value() : get current value 43 | set_prog_value(new_value) : set new value for Var 44 | get_prog_id() : get unique for Var used by ISY 45 | get_prog_name() : get name of var 46 | 47 | """ 48 | _getlist = ['enabled', 'folder', 'id', 'lastFinishTime', 49 | 'lastRunTime', 'name', 'nextScheduledRunTime', 'parentId', 50 | 'runAtStartup', 'running', 'status'] 51 | _setlist = [ 'enabled' ] 52 | _propalias = { 'val': 'status', 'value': 'status', 53 | 'addr': 'id', 'address': 'id' } 54 | _boollist = [ "enabled", "folder", "status", "runAtStartup"] 55 | 56 | # _objtype = (-1, "program") 57 | _objtype = "program" 58 | 59 | def _get_prop(self, prop): 60 | if prop == 'src': 61 | return self.get_src() 62 | return super(self.__class__, self)._get_prop(prop) 63 | 64 | def get_prog_enable(self): 65 | """ check if prog is enabled (bool) """ 66 | #en = self._get_prop("enabled") 67 | #return bool( en == "true" ) 68 | if "enabled" in self._mydict: 69 | return bool(self._mydict["enabled"] == "true") 70 | return True 71 | def set_prog_enable(self, en): 72 | rval = val2bool(en) 73 | #print "set_prog_enable ", rval 74 | if "enabled" in self._mydict: 75 | if rval: 76 | self.isy.prog_comm(self._mydict['id'], "enable") 77 | else: 78 | self.isy.prog_comm(self._mydict['id'], "disable") 79 | self._mydict["enabled"] = rval 80 | return rval 81 | enabled = property(get_prog_enable, set_prog_enable) 82 | 83 | def get_prog_runatstart(self): 84 | """ check property runAtStartup (bool) """ 85 | #en = self._get_prop("runAtStartup") 86 | #return bool( en == "true" ) 87 | return bool(self._mydict['runAtStartup'] == "true") 88 | def set_prog_runatstart(self, en): 89 | rval = val2bool(en) 90 | #print "set_prog_enable ", rval 91 | if rval: 92 | self.isy.prog_comm(self._mydict['id'], "runAtStartup") 93 | else: 94 | self.isy.prog_comm(self._mydict['id'], "disableRunAtStartup") 95 | self._mydict["runAtStartup"] = rval 96 | return rval 97 | runatstart = property(get_prog_runatstart, set_prog_runatstart) 98 | 99 | def get_path(self): 100 | return self.isy._prog_get_path(self._mydict['id']) 101 | path = property(get_path) 102 | 103 | def get_src(self): 104 | """ get D2D source for program """ 105 | return self.isy.prog_get_src(self._mydict['id']) 106 | src = property(get_src) 107 | 108 | # def get_prog_folder(self): 109 | # return self._get_prop("folder") 110 | # folder = property(get_prog_folder) 111 | 112 | # def get_prog_id(self): 113 | # return self._get_prop("id") 114 | # ptype = property(get_prog_id) 115 | 116 | # def get_prog_lastFinishTime(self): 117 | # return self._get_prop("lastFinishTime") 118 | # lastFinishTime = property(get_prog_lastFinishTime) 119 | 120 | # def get_prog_lastRunTime(self): 121 | # return self._get_prop("lastRunTime") 122 | # lastRunTime = property(get_prog_lastRunTime) 123 | 124 | # def get_prog_name(self): 125 | # return self._get_prop("name") 126 | # name = property(get_prog_name) 127 | 128 | # def get_prog_nextScheduledRunTime(self): 129 | # return self._get_prop("nextScheduledRunTime") 130 | # nextScheduledRunTime = property(get_prog_nextScheduledRunTime) 131 | 132 | # def get_prog_parentId(self): 133 | # return self._get_prop("parentId") 134 | # parentId = property(get_prog_parentId) 135 | 136 | # def get_prog_runAtStartup(self): 137 | # return self._get_prop("runAtStartup") 138 | # runAtStartup = property(get_prog_runAtStartup) 139 | 140 | # def get_prog_running(self): 141 | # return self._get_prop("running") 142 | # running = property(get_prog_running) 143 | 144 | # def get_prog_status(self): 145 | # return self._get_prop("status") 146 | # status = property(get_prog_status) 147 | 148 | def send_command(self, cmd): 149 | self.isy.prog_comm(self._mydict['id'], cmd) 150 | 151 | # 152 | # Do nothing 153 | # (syntax check) 154 | # 155 | if __name__ == "__main__": 156 | import __main__ 157 | print(__main__.__file__) 158 | print("syntax ok") 159 | exit(0) 160 | -------------------------------------------------------------------------------- /ISY/IsyUtilClass.py: -------------------------------------------------------------------------------- 1 | 2 | __author__ = 'Peter Shipley ' 3 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 4 | __license__ = "BSD" 5 | 6 | # from xml.dom.minidom import parse, parseString 7 | #from StringIO import StringIO 8 | import xml.etree.ElementTree as ET 9 | from xml.etree.ElementTree import iselement 10 | from ISY.IsyExceptionClass import IsyPropertyError, IsyValueError, IsySoapError 11 | # import base64 12 | import sys 13 | if sys.hexversion < 0x3000000: 14 | import urllib2 as URL 15 | from urllib2 import URLError, HTTPError 16 | else: 17 | import urllib as URL 18 | # import re 19 | from pprint import pprint 20 | 21 | #__all__ = ['IsyUtil', 'IsySubClass' ] 22 | __all__ = ['format_node_addr'] 23 | 24 | 25 | 26 | def val2bool(en): 27 | if isinstance(en, str): 28 | rval = (str(en).strip().lower() in ("yes", "y", "true", "t", "1")) 29 | else: # if isinstance(en, (long, int, float)): 30 | # Punt 31 | rval = bool(en) 32 | return(rval) 33 | 34 | 35 | 36 | def et2d(et): 37 | """ Etree to Dict 38 | 39 | converts an ETree to a Dict Tree 40 | lists are created for duplicate tag 41 | 42 | if there are multiple XML of the same name 43 | an list array is used 44 | attrib tags are converted to "tag_name" + "attrib_name" 45 | 46 | if an invalid arg is passed a empty dict is retrurned 47 | 48 | 49 | arg: ETree Element obj 50 | 51 | returns: a dict obj 52 | """ 53 | d = dict() 54 | if not isinstance(et, ET.Element): 55 | return d 56 | children = list(et) 57 | if et.attrib: 58 | for k, v in list(et.items()): 59 | d[et.tag + "-" + k] = v 60 | if et.text is not None: 61 | #d[et.tag + "_val"] = et.text 62 | d["#val"] = et.text 63 | if children: 64 | for child in children: 65 | if child.tag in d: 66 | if type(d[child.tag]) != list: 67 | t = d[child.tag] 68 | d[child.tag] = [t] 69 | if list(child) or child.attrib: 70 | if child.tag in d: 71 | d[child.tag].append(et2d(child)) 72 | else: 73 | d[child.tag] = et2d(child) 74 | else: 75 | if child.tag in d: 76 | d[child.tag].append(child.text) 77 | else: 78 | d[child.tag] = child.text 79 | return d 80 | 81 | 82 | def format_node_addr(naddr): 83 | if not isinstance(naddr, str): 84 | raise IsyValueError("{0} arg not string".format(__name__)) 85 | addr_el = naddr.upper().split() 86 | a = "{0:0>2}' '{1:0>2}' '{2:0>2}' ".format( *addr_el ) 87 | return a 88 | 89 | 90 | 91 | 92 | # 93 | # Simple Base class for ISY Class 94 | # 95 | class IsyUtil(object): 96 | def __init__(self): 97 | self.debug = 0 98 | self.baseurl = "" # never used 99 | # self.pp = pprint.PrettyPrinter(indent=4) 100 | 101 | 102 | def _printXML(self, xml): 103 | """ Pretty Print XML, for internal debug""" 104 | print("_printXML start") 105 | ET.dump(xml) 106 | 107 | def _set_prop(self, *arg): 108 | pass 109 | 110 | def _getXMLetree(self, xmlpath, noquote=0, timeout=10): 111 | """ take a URL path, download XLM and return parsed Etree """ 112 | if ( noquote): 113 | xurl = self.baseurl + xmlpath 114 | else: 115 | xurl = self.baseurl + URL.quote(xmlpath) 116 | if self.debug & 0x02: 117 | print("_getXMLetree: " + xurl) 118 | # print("_getXMLetree : URL.Request") 119 | req = URL.Request(xurl) 120 | # print("_getXMLetree : self._opener.open ") 121 | # HTTPError 122 | try: 123 | res = self._opener.open(req, None, timeout) 124 | data = res.read() 125 | # print("res.getcode() ", res.getcode(), len(data)) 126 | res.close() 127 | except URL.HTTPError, e: 128 | self.error_str = str("Reponse Code : {0} : {1}" ).format(e.code, xurl) 129 | return None 130 | 131 | if len(self.error_str) : self.error_str = "" 132 | if self.debug & 0x200: 133 | print res.info() 134 | print data 135 | et = None 136 | if len(data): 137 | try: 138 | et = ET.fromstring(data) 139 | except ET.ParseError as e: 140 | print "Etree ParseError " 141 | print "data = ", data, 142 | print "e.message = ", e.message 143 | # raise 144 | finally: 145 | return et 146 | 147 | else: 148 | return None 149 | 150 | def _gensoap(self, cmd, **kwargs): 151 | 152 | if self.debug & 0x200: 153 | print "len kwargs = ", len(kwargs), kwargs 154 | if len(kwargs) == 0: 155 | cmdsoap = "" \ 156 | + "" \ 157 | + "" \ 159 | + "" 160 | else: 161 | cmdsoap = "" \ 162 | + "" \ 163 | + "" 165 | 166 | for k, v in kwargs.items(): 167 | cmdsoap += "<{0}>{1!s}".format(k, v) 168 | 169 | cmdsoap += "".format(cmd) \ 170 | + "" 171 | 172 | # print "cmdsoap = \n", cmdsoap 173 | return cmdsoap 174 | 175 | # http://wiki.universal-devices.com/index.php?title=ISY-99i/ISY-26_INSTEON:Errors_And_Error_Messages 176 | def soapcomm(self, cmd, **kwargs): 177 | """ 178 | takes a command name and a list of keyword arguments. 179 | each keyword is converted into a xml element 180 | """ 181 | 182 | if not isinstance(cmd, str) or not len(cmd): 183 | raise IsyValueError("SOAP Method name missing") 184 | 185 | if self.debug & 0x02: 186 | print "sendcomm : ", cmd 187 | 188 | soap_cmd = self._gensoap(cmd, **kwargs) 189 | 190 | xurl = self.baseurl + "/services" 191 | if self.debug & 0x02: 192 | print "xurl = ", xurl 193 | print "soap_cmd = ", soap_cmd 194 | 195 | req = URL.Request(xurl, soap_cmd, {'Content-Type': 'application/xml; charset="utf-8"'}) 196 | 197 | data = "" 198 | try: 199 | res = self._opener.open(req, None) 200 | data = res.read() 201 | if self.debug & 0x200: 202 | print("res.getcode() ", res.getcode(), len(data)) 203 | print("data ", data) 204 | res.close() 205 | except URL.HTTPError, e: 206 | 207 | self.error_str = str("Reponse Code : {0} : {1} {2}" ).format(e.code, xurl, cmd) 208 | if (( cmd == "DiscoverNodes" and e.code == 803 ) 209 | or ( cmd == "CancelNodesDiscovery" and e.code == 501 ) 210 | # or ( cmd == "RemoveNode" and e.code == 501 ) 211 | ): 212 | 213 | 214 | if self.debug & 0x02: 215 | print "spacial case : {0} : {1}".format(cmd, e.code) 216 | print "e.code = ", e.code 217 | print "e.msg = ", e.msg 218 | print "e.hdrs = ", e.hdrs 219 | print "e.filename = ", e.filename 220 | print "e.code = ", type(e.code), e.code 221 | print "\n" 222 | 223 | return e.read() 224 | 225 | if self.debug & 0x202: 226 | print "e.code = ", type(e.code), e.code 227 | # print "e.read = ", e.read() 228 | print "e = ", e 229 | print "data = ", data 230 | 231 | mess = "{!s} : {!s} : {!s}".format(cmd, kwargs, e.code) 232 | # This a messy and should change 233 | raise IsySoapError(mess, httperr=e) 234 | else: 235 | if len(self.error_str) : self.error_str = "" 236 | if self.debug & 0x200: 237 | print data 238 | return data 239 | 240 | 241 | 242 | def sendDeviceSpecific(command, s1, s2, s3, s4, stringbuffer): 243 | pass 244 | # 245 | # public Object sendDeviceSpecific(String s, String s1, String s2, String s3, String s4, StringBuffer stringbuffer) 246 | # { 247 | # UDHTTPResponse udhttpresponse = null; 248 | # StringBuffer stringbuffer1 = new StringBuffer(); 249 | # stringbuffer1.append("").append(s != null ? s : "").append(""); 250 | # stringbuffer1.append("").append(s1 != null ? s1 : "").append(""); 251 | # stringbuffer1.append("").append(s2 != null ? s2 : "").append(""); 252 | # stringbuffer1.append("").append(s3 != null ? s3 : "").append(""); 253 | # stringbuffer1.append("").append(s4 != null ? s4 : "").append(""); 254 | # stringbuffer1.append("0"); 255 | # stringbuffer1.append("").append(((CharSequence) (stringbuffer != null ? ((CharSequence) (stringbuffer)) : ""))).append(""); 256 | # udhttpresponse = submitSOAPRequest("DeviceSpecific", stringbuffer1, (short)2, false, false); 257 | # if(udhttpresponse == null) 258 | # return null; 259 | # if(!udhttpresponse.opStat) 260 | # return null; 261 | # else 262 | # return udhttpresponse.body; 263 | # } 264 | # 265 | 266 | def sendfile(self, src=None, filename="", data=None): 267 | """ 268 | upload file 269 | 270 | args: 271 | data content for fine to upload 272 | src file to load upload content ( if data is None ) 273 | filename name for remote file 274 | """ 275 | 276 | if self.debug & 0x01: 277 | print "sendfile : ", self.__class__.__name__ 278 | 279 | if filename[0] != '/': 280 | filename = "/USER/WEB/" + filename 281 | elif not str(filename).upper().startswith("/USER/WEB/"): 282 | raise IsyValueError("sendfile: invalid dst filename : {!s}".format(filename)) 283 | 284 | if not len(data): 285 | if not src: 286 | src = filename 287 | 288 | if self.debug & 0x20: 289 | print "using file {!s} as data src".format(src) 290 | 291 | with open(src, 'r') as content_file: 292 | data = content_file.read() 293 | else: 294 | if self.debug & 0x20: 295 | print "using provided data as data src" 296 | 297 | self._sendfile(filename=filename, data=data, load="n") 298 | 299 | 300 | def _sendfile(self, filename="", data="", load="n"): 301 | 302 | if ( filename.startswith('/')): 303 | xurl = self.baseurl + "/file/upload" + filename + "?load=" + load 304 | else: 305 | xurl = self.baseurl + "/file/upload/" + filename + "?load=" + load 306 | 307 | if self.debug & 0x02: 308 | print("{0} xurl : {1}".format(__name__, xurl)) 309 | req = URL.Request(xurl, data, {'Content-Type': 'application/xml; charset="utf-8"'}) 310 | 311 | try: 312 | res = self._opener.open(req, None) 313 | responce = res.read() 314 | # print("res.getcode() ", res.getcode(), len(responce)) 315 | res.close() 316 | except URL.HTTPError, e: 317 | # print "e.read : ", e.read() 318 | mess = "{!s} : {!s} : {!s}".format("/file/upload", filename, e.code) 319 | raise IsySoapError(mess, httperr=e) 320 | else: 321 | return responce 322 | 323 | 324 | 325 | def _printdict(self, d): 326 | """ Pretty Print dictionary, for internal debug""" 327 | print("===START===") 328 | pprint(d) 329 | print("===END===") 330 | 331 | def _printinfo(self, uobj, ulabel="\t"): 332 | """ Debug util """ 333 | print("%s: tag=%s text=%s attr=%s : atype=%s : type=%s" % (ulabel, uobj.tag, uobj.text, uobj.attrib, type(uobj.attrib), type(uobj))) 334 | 335 | # Var : '1:1': { 'id': '1:1', 'init': '0', 'name': 'enter_light', 336 | # 'ts': '20130114 14:33:35', 'type': '1', 'val': '0'} 337 | # 338 | # Node: '15 4B 53 1': { 'ELK_ID': 'A01', 'address': '15 4B 53 1', 'flag': '128', 339 | # 'enabled': 'true', 'name': 'Front Light 2', 'pnode': '15 4B 53 1', 340 | # 'property': { 'ST': { 'formatted': 'Off', 'id': 'ST', 341 | # 'uom': '%/on/off', 'value': '0'}} 342 | # 343 | # Scene: '20326': { 'ELK_ID': 'C11', 'address': '20326', 'deviceGroup': '25', 344 | # 'flag': '132', 'name': 'Garage Lights' 345 | # 'members': { '16 3F E5 1': '32', '16 D3 73 1': '32' }, }, 346 | # 347 | # NodeFolder: '12743': { 'address': '12743', 'name': 'Garage Scenes'}, 348 | # 349 | # Prog '0003': { 'enabled': 'true', 'folder': 'false', 'id': '0003', 350 | # 'name': 'AM Off', 'parentId': '0001', 'runAtStartup': 'false', 351 | # 'running': 'idle', 'status': 'false', 352 | # 'lastFinishTime': '2013/03/27 09:41:28', 353 | # 'lastRunTime': '2013/03/27 09:41:28', 354 | # 'nextScheduledRunTime': '2013/03/27 10:05:00', }, 355 | # 356 | # Prog '000A': { 'folder': 'true', 'id': '000A', 'lastFinishTime': None, 357 | # 'lastRunTime': None, 'name': 'garage', 358 | # 'parentId': '0001', 'status': 'true'}, 359 | 360 | 361 | 362 | class IsySubClass(IsyUtil): 363 | """ Sub Class for ISY 364 | This is a Sub Class for Node, Scene, Folder, Var, and Program Objects 365 | 366 | This Class is not intended for direct use 367 | 368 | attributes/properties: 369 | type : object dependent flag 370 | value : current value 371 | id/address : unique for object used by ISY 372 | name : name of object 373 | 374 | funtions: 375 | no public funtions 376 | 377 | """ 378 | 379 | _getlist = [ "name", "id", "value", "address", "type", "enabled" ] 380 | _setlist = [ ] 381 | _propalias = { } 382 | _boollist = [ "enabled" ] 383 | 384 | def __init__(self, isy, objdict): 385 | """ INIT """ 386 | 387 | if isinstance(objdict, dict): 388 | self._mydict = objdict 389 | else: 390 | raise IsyValueError("{!s}: called without objdict".format(self.__class__.__name__)) 391 | 392 | if isinstance(isy, IsyUtil): 393 | self.isy = isy 394 | self.debug = isy.debug 395 | else: 396 | # print("error : class " + self.__class__.__name__ + " called without Isy") 397 | raise TypeError("IsySubClass: isy arg is not a ISY family class") 398 | 399 | if self.debug & 0x04: 400 | print("IsySubClass: ",) 401 | self._printdict(self._mydict) 402 | 403 | #_objtype = (0, "unknown") 404 | _objtype = "unknown" 405 | 406 | def objType(self): 407 | return self._objtype 408 | #return self._objtype[0] 409 | 410 | def _get_prop(self, prop): 411 | """ Internal funtion call """ 412 | # print("U _get_prop =", prop) 413 | if prop in self._propalias: 414 | prop = self._propalias[prop] 415 | 416 | if prop in self._getlist: 417 | if prop in self._mydict: 418 | if prop in self._boollist: 419 | return(val2bool(self._mydict[prop])) 420 | else: 421 | return(self._mydict[prop]) 422 | return(None) 423 | 424 | # def _set_prop(self, prop, val): 425 | # """ Internal funtion call """ 426 | # if prop in self._propalias: 427 | # prop = self._propalias[prop] 428 | # 429 | # if not prop in self._setlist: 430 | # raise IsyPropertyError("_set_prop : " 431 | # "no property Attribute " + prop) 432 | # pass 433 | 434 | 435 | # def get_prop_list(self, l): 436 | # """ Get a list of properties 437 | # 438 | # args: 439 | # prop_list : a list of property names 440 | # 441 | # returns 442 | # a list of property values 443 | # 444 | # if a property does not exist a value of None is used 445 | # ( instead of raising a Attribute error) 446 | # 447 | # """ 448 | # pass 449 | # 450 | 451 | def _getaddr(self): 452 | """ Address or ID of Node (readonly) """ 453 | return self._get_prop("address") 454 | address = property(_getaddr) 455 | 456 | def _getname(self): 457 | """ Name of Node (readonly) """ 458 | return self._get_prop("name") 459 | name = property(_getname) 460 | 461 | def _gettype(self): 462 | """ Type of Node (readonly) """ 463 | # self._get_prop("type") 464 | # return self._objtype[1] 465 | return self._objtype 466 | objtype = property(_gettype) 467 | 468 | def __getitem__(self, prop): 469 | """ Internal method 470 | 471 | allows Objects properties to be accessed in a dict style 472 | 473 | """ 474 | return self._get_prop(prop) 475 | 476 | def __setitem__(self, prop, val): 477 | """ Internal method 478 | 479 | allows Objects properties to be accessed/set in a dict style 480 | 481 | """ 482 | return self._set_prop(prop, val) 483 | 484 | def __delitem__(self, prop): 485 | raise IsyPropertyError("__delitem__ : can't delete propery : " + str(prop) ) 486 | 487 | 488 | def __del__(self): 489 | if self.debug & 0x80: 490 | print "__del__ ", self.__repr__() 491 | if hasattr(self, "_mydict"): 492 | self._mydict.clear() 493 | 494 | def __iter__(self): 495 | """ Internal method 496 | 497 | allows Objects properties to be access through iteration 498 | 499 | """ 500 | if self.debug & 0x80: 501 | print("IsyUtil __iter__") 502 | for p in self._getlist: 503 | if p in self._mydict: 504 | yield (p , self._get_prop(p)) 505 | else: 506 | yield (p , None) 507 | 508 | 509 | def __repr__(self): 510 | return "<%s %s @ %s at 0x%x>" % ( self.__class__.__name__, 511 | self._get_prop("id"), self.isy.addr, id(self)) 512 | 513 | 514 | 515 | # def __hash__(self): 516 | # #print("_hash__ called") 517 | # return str.__hash__(self._get_prop("id]").myval) 518 | 519 | # def __compare__(self, other): 520 | # print("__compare__ called") 521 | # if isinstance(other, str): 522 | # if not hasattr(other, "myval"): 523 | # return -1 524 | # return ( str.__cmp__(self.myval, other.myval) ) 525 | 526 | def __getattr__(self, attr): 527 | # print("U attr =", attr) 528 | attr_v = self._get_prop(attr) 529 | if attr_v != None: 530 | return attr_v 531 | else: 532 | # print("attr =", attr, self.address) 533 | # print("self type = ", type(self)) 534 | # pprint(self._mydict) 535 | raise(AttributeError, attr) 536 | 537 | 538 | # This allows for 539 | def __eq__(self, other): 540 | """ smarter test for compairing Obj value """ 541 | #print("IsyUtil __eq__") 542 | #print("self", self) 543 | #print("other", other) 544 | if isinstance(other, str): 545 | return self._get_prop("id") == other 546 | if type(self) != type(other): 547 | return(False) 548 | # NotImplemented 549 | if hasattr(other._mydict, "id"): 550 | return(self._get_prop("id") == other._get_prop("id")) 551 | return(False) 552 | 553 | 554 | 555 | # 556 | # Do nothing 557 | # (syntax check) 558 | # 559 | if __name__ == "__main__": 560 | import __main__ 561 | print(__main__.__file__) 562 | print("syntax ok") 563 | exit(0) 564 | -------------------------------------------------------------------------------- /ISY/IsyVarClass.py: -------------------------------------------------------------------------------- 1 | """ Obj Class Isy veriable entries """ 2 | 3 | __author__ = 'Peter Shipley ' 4 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 5 | __license__ = "BSD" 6 | 7 | 8 | from ISY.IsyExceptionClass import * 9 | from ISY.IsyUtilClass import IsySubClass 10 | 11 | __all__ = ['IsyVar'] 12 | 13 | class IsyVar(IsySubClass): 14 | """ VAR Class for ISY 15 | 16 | attributes/properties: 17 | ts : timetamp 18 | type : Var Type 19 | init : inital value for Var ( at ISY boot ) 20 | value : current value 21 | id : unique for Var used by ISY 22 | name : name of var 23 | 24 | funtions: 25 | get_var_init() : get inital value for Var 26 | set_var_init(new_value) : set inital value for Var 27 | get_var_value() : get current value 28 | set_var_value(new_value) : set new value for Var 29 | 30 | """ 31 | 32 | _getlist = ['id', 'type', 'init', 'val', 'ts', 'name'] 33 | _setlist = ['init', 'val'] 34 | _propalias = {'value': 'val', 'status': 'val', 'addr': 'id', 'address': 'id'} 35 | # _objtype = (-1, "var") 36 | _objtype = "var" 37 | 38 | # Var : { '1:1': { 'id': '1:1', 'init': '0', 'name': 'enter_light', 39 | # 'ts': '20130114 14:33:35', 'type': '1', 'val': '0'} 40 | 41 | # def get_var_ts(self): 42 | # """ returns var timestamp 43 | # this is also avalible via the property : ts 44 | # """ 45 | # return self._get_prop("ts") 46 | # ts = property(get_var_ts) 47 | 48 | # def get_var_type(self): 49 | # """ returns var timestamp 50 | # this is also avalible via the property : type 51 | # """ 52 | # return self._get_prop("type") 53 | # type = property(get_var_type) 54 | 55 | def get_var_init(self): 56 | """ returns var init value 57 | this is also avalible via the property : init 58 | """ 59 | return self._mydict["init"] 60 | 61 | def set_var_init(self, new_value): 62 | """ sets var init value 63 | this can also be set via the property : init 64 | """ 65 | # if new_value == self._mydict["init"]: 66 | # return 67 | self.isy._var_set_value(self._mydict['id'], new_value, "init") 68 | 69 | init = property(get_var_init, set_var_init) 70 | 71 | 72 | def refresh(self): 73 | "reload val from isy" 74 | return self.isy.var_refresh_value(self._mydict["id"]) 75 | 76 | def get_var_value(self): 77 | """ returns var value 78 | this is also avalible via the property : value 79 | """ 80 | return self._mydict["val"] 81 | def set_var_value(self, new_value): 82 | """ sets var value 83 | this can also be set via the property : value 84 | """ 85 | # if new_value == self._mydict["val"]: 86 | # return 87 | self.isy._var_set_value(self._mydict['id'], new_value) 88 | value = property(get_var_value, set_var_value) 89 | 90 | def get_callback(self): 91 | return self.isy.callback_get(self._mydict["id"]) 92 | def set_callback(self, func, *args): 93 | if func is None: 94 | return self.isy.callback_del(self._mydict["id"]) 95 | else: 96 | return self.isy.callback_set(self._mydict["id"], func, args) 97 | callback = property(get_callback, set_callback) 98 | 99 | # def get_var_id(self): 100 | # return self._get_prop("id") 101 | # id = property(get_var_id) 102 | 103 | # def get_var_name(self): 104 | # return self._get_prop("name") 105 | # name = property(get_var_name) 106 | 107 | 108 | # def rename(self, newname): 109 | # self.isy.call_soap_method("RenameNode", 110 | # self._mydict["address"], newwname) 111 | 112 | # Not fully Implemented 113 | def __cast(self, other): 114 | if isinstance(other, self.__class__): return other._mydict["val"] 115 | if isinstance(other, str) and other.isdigit() : return int( other ) 116 | else: return other 117 | 118 | def bit_length(self): return int(self._mydict["val"]).bit_length() 119 | 120 | # 121 | # Type conversion 122 | def __str__(self): return str(self._mydict["val"]) 123 | # Type conversion 124 | def __long__(self): return long(self._mydict["val"]) 125 | # Type conversion 126 | def __float__(self): return float(self._mydict["val"]) 127 | # Type conversion 128 | def __int__(self): return int(self._mydict["val"]) 129 | # Type conversion 130 | def __bool__(self) : return bool( self._mydict["val"]) != 0 131 | 132 | # mathematical operator 133 | def __abs__(self): return abs(self._mydict["val"]) 134 | 135 | # comparison functions 136 | def __lt__(self, n): return self._mydict["val"] < self.__cast(n) 137 | # comparison functions 138 | def __le__(self, n): return self._mydict["val"] <= self.__cast(n) 139 | # comparison functions 140 | def __eq__(self, n): return self._mydict["val"] == self.__cast(n) 141 | # comparison functions 142 | def __ne__(self, n): return self._mydict["val"] != self.__cast(n) 143 | # comparison functions 144 | def __gt__(self, n): return self._mydict["val"] > self.__cast(n) 145 | # comparison functions 146 | def __ge__(self, n): return self._mydict["val"] >= self.__cast(n) 147 | # comparison functions 148 | 149 | # comparison functions 150 | def __cmp__(self, n): return cmp(self._mydict["val"], self.__cast(n)) 151 | 152 | 153 | # mathematical operator 154 | def __add__(self, n): 155 | #print "__add__" 156 | if isinstance(n, self.__class__): 157 | return (self._mydict["val"] + n._mydict["val"]) 158 | elif isinstance(n, type(self._mydict["val"])): 159 | return (self._mydict["val"] + n) 160 | else: 161 | return (self._mydict["val"] + n) 162 | 163 | __radd__ = __add__ 164 | 165 | # mathematical operator 166 | def __iadd__(self, n): 167 | if isinstance(n, self.__class__): self._mydict["val"] += n._mydict["val"] 168 | else: self._mydict["val"] += int(n) 169 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 170 | return self 171 | 172 | # mathematical operator 173 | def __sub__(self, n): 174 | if isinstance(n, self.__class__): 175 | return (self._mydict["val"] - n._mydict["val"]) 176 | elif isinstance(n, type(self._mydict["val"])): 177 | return (self._mydict["val"] - int(n)) 178 | 179 | # mathematical operator 180 | def __isub__(self, n): 181 | if isinstance(n, self.__class__): self._mydict["val"] -= n._mydict["val"] 182 | else: self._mydict["val"] -= int(n) 183 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 184 | return self 185 | 186 | # Mult & div 187 | 188 | # mathematical operator 189 | def __mul__(self, n): return (self._mydict["val"]*n) 190 | __rmul__ = __mul__ 191 | 192 | def __imul__(self, n): 193 | 194 | # mathematical operator 195 | self._mydict["val"] *= n 196 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 197 | return self 198 | 199 | def __floordiv__(self, n): return self._mydict["val"] // self.__cast(n) 200 | 201 | # mathematical operator 202 | 203 | def __ifloordiv__(self, n): 204 | 205 | # mathematical operator 206 | self._mydict["val"] = self._mydict["val"] // n 207 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 208 | return self 209 | 210 | def __truediv__(self, n): return (self._mydict["val"] / self.__cast(n)) 211 | 212 | # mathematical operator 213 | __div__ = __truediv__ 214 | 215 | def __itruediv__(self, n): 216 | 217 | # mathematical operator 218 | self._mydict["val"] /= self.__cast(n) 219 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 220 | return self 221 | __idiv__ = __itruediv__ 222 | 223 | def __imod__(self, n): 224 | 225 | # mathematical operator 226 | self._mydict["val"] %= self.__cast(n) 227 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 228 | return self 229 | 230 | # def __ipow__(self, n): 231 | # mathematical operator 232 | # self._mydict["val"] **= self.__cast(n) 233 | # self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 234 | # return self 235 | 236 | def __neg__(self): return - self._mydict["val"] 237 | 238 | # mathematical operator 239 | 240 | # logic opts 241 | def __and__(self, n): return self._mydict["val"] & self.__cast(n) 242 | 243 | def __iand__(self, n): 244 | self._mydict["val"] &= self.__cast(n) 245 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 246 | return self 247 | 248 | def __or__(self, n): return self._mydict["val"] | self.__cast(n) 249 | __ror__ = __or__ 250 | 251 | def __ior__(self, n): 252 | self._mydict["val"] |= self.__cast(n) 253 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 254 | return self 255 | 256 | def __ixor__(self, n): 257 | self._mydict["val"] ^= self.__cast(n) 258 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 259 | return self 260 | 261 | def __xor__(self, n): return self._mydict["val"] ^ self.__cast(n) 262 | 263 | def __invert__(self): return ~ self._mydict["val"] 264 | 265 | def __irshift__(self, n): 266 | self._mydict["val"] >>= self.__cast(n) 267 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 268 | return self 269 | 270 | def __ilshift__(self, n): 271 | self._mydict["val"] >>= self.__cast(n) 272 | self.isy._var_set_value(self._mydict['id'], self._mydict["val"]) 273 | return self 274 | 275 | 276 | 277 | #def __format__(self, spec): return int(self._mydict["val"]).__format__(spec) 278 | 279 | def __repr__(self): 280 | return "<%s %s at 0x%x>" % (self.__class__.__name__, self.isy.addr, id(self)) 281 | 282 | # 283 | # Do nothing 284 | # (syntax check) 285 | # 286 | if __name__ == "__main__": 287 | import __main__ 288 | print(__main__.__file__) 289 | print("syntax ok") 290 | exit(0) 291 | -------------------------------------------------------------------------------- /ISY/__init__.py: -------------------------------------------------------------------------------- 1 | """API for the Universal Device's ISY 2 | 3 | This is a Work in progress 4 | 5 | Supporting a Simple and OO interface for ISY home automation netapp 6 | 7 | 8 | see also : http://www.universal-devices.com/residential/ 9 | http://wiki.universal-devices.com/index.php?title=Main_Page 10 | 11 | NOTE: This Libaray is not written my or supported by universal devices 12 | 13 | 14 | ----- 15 | 16 | to use set the following env vars 17 | 18 | ISY_ADDR the IP address of your ISY device 19 | ISY_AUTH your loging and password 20 | 21 | eg: 22 | 23 | export ISY_AUTH=admin:mypasswd 24 | export ISY_ADDR=192.168.1.2 25 | 26 | Files: 27 | 28 | ISY/* - ISY Python lib 29 | bin/isy_find.py - Upnp probe for devices on your network 30 | bin/isy_nodespy - List registered devices 31 | bin/isy_log.py - Get event or error logs 32 | bin/isy_showevents.py - print live stream of events from ISY 33 | bin/isy_var.py - Set, Get or display system vars 34 | bin/isy_progs.py - List/Run registered programs 35 | bin/isy_nestset.py - sync values from a Nest thermostat with an ISY 36 | bin/isy_net_res.py - call registered net resorces on ISY 37 | bin/isy_net_wol.py - send WOL to registered devices 38 | 39 | The example code included it ment to demonstrate API use with minimal 40 | code for clarity. 41 | 42 | 43 | 44 | This package provides the following classes: 45 | 46 | 47 | 48 | - Isy - primary class for interacting with a ISY network appliance 49 | from this class most operations can be made though a simple call interface 50 | 51 | - IsyNode - Node Object 52 | Represent lights, switches, motion sensors 53 | - IsyScene - Scene Object 54 | Represents Scenes contains Nodes that comprise a "Scene" 55 | - IsyNodeFolder - Can hold Scene's or Nodes 56 | a organizational obj for Scene's and Nodes 57 | - IsyVar - ISY device Variable 58 | Represents a variables that are avalible in the ISY device 59 | - IsyProgram - ISY device Programs 60 | Represents a variables that are avalible in the ISY device 61 | 62 | 63 | Additional support functions 64 | - isy_discover - use Upnp to discover IP addr or ISY device 65 | 66 | Internal classes 67 | - IsyUtil - base class for most ISY classes 68 | - IsySubClass - base class for sub Objects ( eg: Nodes, Scenes, Vars, Programs ) 69 | 70 | Exception Classes: 71 | IsyError 72 | IsyCommandError 73 | IsyNodeError 74 | IsyResponseError 75 | IsyPropertyError 76 | IsyValueError 77 | IsyInvalidCmdError 78 | IsyAttributeError 79 | 80 | UpnpLimitExpired 81 | 82 | """ 83 | 84 | import sys 85 | if sys.hexversion < 0x2070100: 86 | sys.stderr.write("You need python 2.7.1 or later to run this script (ver={:0X})\n".format(sys.hexversion)) 87 | 88 | __revision__ = "$Id$" 89 | __version__ = "0.1.20160710" 90 | __author__ = 'Peter Shipley ' 91 | __copyright__ = "Copyright (C) 2016 Peter Shipley" 92 | __license__ = "BSD" 93 | 94 | # 95 | # from ISY.IsyUtilClass import IsyUtil 96 | # 97 | from ISY.IsyClass import Isy, IsyGetArg 98 | from ISY.IsyDiscover import isy_discover 99 | from ISY.IsyNodeClass import IsyNode, IsyScene, IsyNodeFolder 100 | from ISY.IsyVarClass import IsyVar 101 | from ISY.IsyProgramClass import IsyProgram 102 | from ISY.IsyExceptionClass import IsyError 103 | from ISY.IsyDebug import * 104 | # 105 | 106 | 107 | 108 | #__all__ = ['IsyUtil', 'Isy', 'IsyNode', 'IsyProgram', 'IsyVar'] 109 | __all__ = ['Isy', 'IsyUtil', 'IsyUtilClass', 'IsyClass', 'IsyNode', 'IsyVar', 110 | 'isy_discover', 'IsyGetArg'] 111 | #__all__ = ['IsyUtil', 'Isy'] 112 | 113 | 114 | 115 | 116 | # 117 | # Do nothing 118 | # (syntax check) 119 | # 120 | if __name__ == "__main__": 121 | import __main__ 122 | #print(__main__.__file___) 123 | print("ISY.__init__") 124 | print("syntax ok") 125 | exit(0) 126 | 127 | -------------------------------------------------------------------------------- /ISY/_isy_printevent.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a subfile for IsyEvent.py 3 | 4 | """ 5 | 6 | __author__ = 'Peter Shipley ' 7 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 8 | __license__ = "BSD" 9 | 10 | import time 11 | import sys 12 | from ISY.IsyEventData import EVENT_CTRL, EVENT_CTRL_ACTION 13 | 14 | 15 | # @staticmethod 16 | 17 | def _action_val(a): 18 | if isinstance(a, str): 19 | return a 20 | elif isinstance(a, dict): 21 | if "#val" in a: 22 | return a["#val"] 23 | else: 24 | return None 25 | 26 | def _print_event(*arg): 27 | 28 | ddat = arg[0] 29 | # mydat = arg[1] 30 | exml = arg[2] 31 | 32 | # Event Dat: 33 | # {'control': 'DOF', 'node': '16 6C D2 7', 'eventInfo': None, 'Event-seqnum': '141', 'action': '0', 'Event-sid': 'uuid:40'} 34 | # DOF016 6C D2 7 35 | # 36 | ti = time.strftime('%X') 37 | try: 38 | if "control" not in ddat or ddat["control"] is None: 39 | return 40 | 41 | control_val = ddat["control"] 42 | 43 | if "action" in ddat and ddat["action"] is not None: 44 | action_val = _action_val(ddat["action"]) 45 | else : 46 | action_val = None 47 | 48 | if control_val in EVENT_CTRL_ACTION and action_val: 49 | action_str = EVENT_CTRL_ACTION[control_val].get(action_val, action_val) 50 | else : 51 | action_str = "" 52 | 53 | node = ddat.get("node", "") 54 | if node is None: 55 | node = "" 56 | 57 | control_str = EVENT_CTRL.get(control_val, control_val) 58 | 59 | evi = "" 60 | 61 | if ddat["control"] in ["_0"]: 62 | pass 63 | 64 | elif ddat["control"] == "ERR": 65 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s:<12}\t{!s}".format( 66 | ti, ddat['Event-seqnum'], 67 | "ERR", 68 | ddat['node'], action_str) 69 | 70 | return 71 | 72 | 73 | #elif ddat["control"] in ["DOF", "DON", "BMAN", "SMAN", "FDUP", "FDSTOP", "FDDOWN" ]: 74 | # print "{!s:<7} {!s:<4}\t{!s:<12}{!s}\t{!s}".format( 75 | # ti, ddat['Event-seqnum'], node, control_str, action_str) 76 | # return 77 | 78 | elif ddat["control"] in ["ST", "RR", "OL", "DOF", "DON", "DFOF", "DFON", 79 | "BMAN", "SMAN", "FDUP", "FDSTOP", "FDDOWN" ]: 80 | 81 | if ddat["eventInfo"] is not None: 82 | evi = ddat["eventInfo"] 83 | else : 84 | evi = "" 85 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s:<12}\t{!s:<12}\t{!s}".format( 86 | ti, ddat["Event-seqnum"], node, control_str, action_val, evi) 87 | return 88 | 89 | elif ddat["control"] == "_1": 90 | # 'on': None, 'f': '140630 20:55:55', 's': '31', 'r': '140630 20:55:55', 'nr': None, 'id': '1E'} 91 | 92 | #action = EVENT_CTRL_ACTION[ddat["control"]].get(ddat['action'], ddat['action']) 93 | status = "" 94 | 95 | if action_val == '0': 96 | st=[ ] 97 | 98 | if 'id' in ddat["eventInfo"]: 99 | st.append("id={}".format(ddat["eventInfo"]['id'])) 100 | 101 | if 's' in ddat["eventInfo"]: 102 | st.append("status={}".format(ddat["eventInfo"]['s'])) 103 | 104 | if 'on' in ddat["eventInfo"]: 105 | st.append( "enabled=true") 106 | if 'off' in ddat["eventInfo"]: 107 | st.append( "enabled=false") 108 | 109 | if 'rr' in ddat["eventInfo"]: 110 | st.append( "runAtStartup=true") 111 | if 'nr' in ddat["eventInfo"]: 112 | st.append( "runAtStartup=false") 113 | 114 | if 'r' in ddat["eventInfo"]: 115 | st.append("lastRunTime={}".format(ddat["eventInfo"]['r'])) 116 | 117 | if 'f' in ddat["eventInfo"]: 118 | st.append("lastFinishTime={}".format(ddat["eventInfo"]['f'])) 119 | 120 | if 'nsr' in ddat["eventInfo"]: 121 | st.append("nextScheduledRunTime={}".format( 122 | ddat["eventInfo"]['nsr'])) 123 | 124 | status = " ".join(st) 125 | 126 | elif action_val == '6': 127 | status = "{!s:<12} {!s}:{!s} {!s} {!s}".format( 128 | ddat['node'], 129 | ddat['eventInfo']['var']['var-type'], 130 | ddat['eventInfo']['var']['var-id'], 131 | ddat['eventInfo']['var']['val'], 132 | ddat['eventInfo']['var']['ts']) 133 | 134 | elif action_val == '7': 135 | status = "{!s:<12} {!s}:{!s} {!s}".format( 136 | ddat['node'], 137 | ddat['eventInfo']['var']['var-type'], 138 | ddat['eventInfo']['var']['var-id'], 139 | ddat['eventInfo']['var']['init']) 140 | 141 | else: 142 | if isinstance( ddat['eventInfo'], dict): 143 | status = " ".join(["{}={}".format(a,b) for a, b in ddat['eventInfo'].items()] ) 144 | elif ddat['eventInfo'] is None: 145 | status="" 146 | else: 147 | status = ddat['eventInfo'] 148 | 149 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s}\t{!s}\t{!s}".format( 150 | ti, ddat["Event-seqnum"], node, control_str, action_str, status) 151 | 152 | return 153 | 154 | elif ddat["control"] in [ "_3", "_4", "_5", "_6", "_7", "_8", "_9", 155 | "_10", "_11", "_12", "_13", "_14", "_15", "_16", "_19", 156 | "_20", "_21", "_22" ] : 157 | d = ddat['eventInfo'] 158 | if isinstance( d, dict): 159 | status = " ".join(["{}={}".format(a,b) for a, b in d.items()] ) 160 | elif d is None: 161 | status="" 162 | else: 163 | status = eventInfo 164 | 165 | #status = ddat['eventInfo'] 166 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s:<12}\t{!s:<12}\t{!s}".format( 167 | ti, ddat["Event-seqnum"], node, control_str, action_str, status) 168 | 169 | return 170 | 171 | 172 | # elif ddat["control"] == "_11": 173 | # status = ddat['eventInfo'] 174 | # status="value={} unit={}".format( 175 | # ddat['eventInfo'].get('value', ""), 176 | # ddat['eventInfo'].get('unit', "")) 177 | # 178 | # print "{!s:<7} {!s:<4}\t{!s:<12}\t{!s}\t{!s}\t{!s}".format( 179 | # ti, ddat["Event-seqnum"], node, control_str, action_str, status) 180 | # return 181 | 182 | 183 | elif ddat["control"] == "_17": 184 | if action_val == '1': 185 | status = "total={!s:<12} lastReadTS={!s}".format( 186 | ddat['eventInfo'].get('total', ""), 187 | ddat['eventInfo'].get('lastReadTS', "") 188 | ) 189 | else: 190 | status = ddat.get('eventInfo', "") 191 | 192 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s:<12}\t{!s:<12}\t{!s}".format( 193 | ti, ddat["Event-seqnum"], node, control_str, action_str, status) 194 | return 195 | 196 | elif ddat["control"] == "_18": 197 | if 'ZBNetwork' in ddat['eventInfo']: 198 | d = ddat['eventInfo']['ZBNetwork'] 199 | status = " ".join(["{}={}".format(a,b) for a, b in d.items()] ) 200 | else: 201 | status = ddat['eventInfo'] 202 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s:<12}\t{!s:<12}\t{!s}".format( 203 | ti, ddat["Event-seqnum"], node, control_str, action_str, status) 204 | return 205 | 206 | elif ddat["control"] == "_23": 207 | if 'PortalStatus' in ddat['eventInfo']: 208 | d = ddat['eventInfo']['PortalStatus'] 209 | status = " ".join(["{}={}".format(a,b) for a, b in d.items()] ) 210 | else: 211 | status = ddat['eventInfo'] 212 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s:<12}\t{!s:<12}\t{!s}".format( 213 | ti, ddat["Event-seqnum"], node, control_str, action_str, status) 214 | return 215 | 216 | else: 217 | status = ddat.get('eventInfo', "") 218 | print "{!s:<7} {!s:<4}\t{!s:<12}{!s:<12}\t{!s:<12}\t{!s}".format( 219 | ti, ddat["Event-seqnum"], node, control_str, action_str, status) 220 | # return 221 | 222 | # if node is None: 223 | # node = "" 224 | # if action is None: 225 | # action = ddat.get('action', "") 226 | # print "{!s:<7} {!s:<4}\t{} : {} : {}\t{!s:<12}".format( 227 | # ti, ddat['Event-seqnum'], 228 | # node, 229 | # control_str, 230 | # action, 231 | # ddat.get('eventInfo', "-") ) 232 | 233 | print "Event Dat : \n\t", ddat, "\n\t", exml 234 | 235 | 236 | sys.stdout.flush() 237 | #print ddat 238 | # print data 239 | except Exception as e: 240 | print("Unexpected error:", sys.exc_info()[0]) 241 | print(e) 242 | print(ddat) 243 | raise 244 | # print data 245 | finally: 246 | pass 247 | 248 | -------------------------------------------------------------------------------- /ISY/_isyclimate.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | This is a subfile for IsyClass.py 4 | 5 | These funtions are accessable via the Isy class opj 6 | """ 7 | 8 | __author__ = 'Peter Shipley ' 9 | __copyright__ = "Copyright (C) 2015 Peter Shipley" 10 | __license__ = "BSD" 11 | 12 | import time 13 | 14 | ## 15 | ## Climate funtions 16 | ## 17 | def load_clim(self): 18 | """ Load climate data from ISY device 19 | 20 | args : none 21 | 22 | internal function call 23 | 24 | """ 25 | if self.debug & 0x01: 26 | print("load_clim") 27 | clim_tree = self._getXMLetree("/rest/climate") 28 | self.climateinfo = dict() 29 | if clim_tree is None: 30 | return 31 | # Isy._printXML(self.climateinfo) 32 | 33 | for cl in clim_tree.iter("climate"): 34 | for k, v in cl.items(): 35 | self.climateinfo[k] = v 36 | for ce in list(cl): 37 | self.climateinfo[ce.tag] = ce.text 38 | 39 | self.climateinfo["time"] = time.gmtime() 40 | 41 | def clim_get_val(self, prop): 42 | pass 43 | 44 | def clim_query(self): 45 | """ returns dictionary of climate info """ 46 | if not self.climateinfo: 47 | self.load_clim() 48 | 49 | # 50 | # ADD CODE to check self.cachetime 51 | # 52 | return self.climateinfo 53 | 54 | def clim_iter(self): 55 | """ Iterate though climate values 56 | 57 | args: 58 | None 59 | 60 | returns: 61 | Return an iterator over the climate values 62 | """ 63 | if not self.climateinfo: 64 | self.load_clim() 65 | k = self.climateinfo.keys() 66 | for p in k: 67 | yield self.climateinfo[p] 68 | 69 | # Do nothing 70 | # (syntax check) 71 | # 72 | if __name__ == "__main__": 73 | import __main__ 74 | print(__main__.__file__) 75 | 76 | print("syntax ok") 77 | exit(0) 78 | -------------------------------------------------------------------------------- /ISY/_isynet_resources.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | This is a subfile for IsyClass.py 4 | 5 | These funtions are accessable via the Isy class opj 6 | """ 7 | 8 | # author : Peter Shipley 9 | # copyrigh : Copyright (C) 2015 Peter Shipley 10 | # license : BSD 11 | 12 | from ISY.IsyUtilClass import et2d 13 | from ISY.IsyExceptionClass import IsyResponseError, IsyValueError 14 | 15 | ## 16 | ## networking resources 17 | ## 18 | def _load_networking(self, resource_id): 19 | if self.debug & 0x01: 20 | print("_load_networking {!s} called".format(resource_id)) 21 | 22 | if not hasattr(self, '_name2id') or not isinstance(self._name2id, dict): 23 | self._name2id = dict() 24 | 25 | xurl = "/rest/networking/{!s}".format(resource_id) 26 | if self.debug & 0x02: 27 | print("_load_networking : xurl = " + xurl) 28 | net_res_tree = self._getXMLetree(xurl) 29 | if net_res_tree is None: 30 | if ( len(self.error_str)): 31 | raise IsyResponseError (self.error_str) 32 | else: 33 | raise IsyResponseError (xurl) 34 | net_dict = dict() 35 | name2rid = dict() 36 | for netr in net_res_tree.iter('NetRule'): 37 | netrule = et2d(netr) 38 | if 'id' in netrule: 39 | net_dict[netrule['id']] = netrule 40 | if 'name' in netrule: 41 | n = netrule['name'] 42 | name2rid[n] = netrule['id'] 43 | 44 | # name2id to replace name2var as a global lookup table 45 | if n in self._name2id: 46 | print("Dup name2id : \"" + n + "\" : " + netrule['id']) 47 | print("\tname2id ", self._name2id[n]) 48 | else: 49 | self._name2id[n] = (resource_id, netrule['id']) 50 | 51 | return(net_dict, name2rid) 52 | 53 | def load_net_resource(self): 54 | """ Load node WOL and Net Resource config infomation 55 | 56 | args: none 57 | """ 58 | (self._net_resource, self.name2net_res) = self._load_networking("resources") 59 | #self._printdict(self._net_resource) 60 | #self._printdict(self.name2net_res) 61 | 62 | 63 | def _net_resource_get_id(self, name): 64 | if not self._net_resource: 65 | self.load_net_resource() 66 | 67 | if name in self._net_resource: 68 | return name 69 | if name in self.name2net_res: 70 | return self.name2net_res[name] 71 | 72 | return None 73 | 74 | def net_resource_run(self, rrid): 75 | """ Calls and executes net resource 76 | 77 | args: 78 | rrid : network resource ID 79 | calls : /rest/networking/resources/ 80 | """ 81 | 82 | rid = self._net_resource_get_id(rrid) 83 | 84 | if rid is None: 85 | raise IsyValueError("net_resource_run : bad network resources ID : " + rrid) 86 | 87 | xurl = "/rest/networking/resources/{!s}".format(rid) 88 | 89 | if self.debug & 0x02: 90 | print("wol : xurl = " + xurl) 91 | resp = self._getXMLetree(xurl) 92 | # self._printXML(resp) 93 | if resp is None or resp.attrib["succeeded"] != 'true': 94 | raise IsyResponseError("ISY network resources error : rid=" + str(rid)) 95 | 96 | 97 | def net_resource_get_src(self, rrid): 98 | 99 | rid = self._net_resource_get_id(rrid) 100 | 101 | if rid is None: 102 | raise IsyValueError("net_resource_get_src: bad network resources ID : " + rrid) 103 | 104 | r = self.soapcomm("GetSysConf", name="/CONF/NET/" + rrid + ".RES") 105 | 106 | return r 107 | 108 | def net_resource_ids(self): 109 | """ 110 | method to retrieve a list of networking resource ids 111 | 112 | args: none 113 | 114 | returns List of ids or None 115 | """ 116 | if not self._net_resource: 117 | self.load_net_resource() 118 | 119 | return self._net_resource.keys() 120 | 121 | 122 | def net_resource_names(self): 123 | """ 124 | method to retrieve a list of networking resource names 125 | 126 | args: none 127 | 128 | returns List of names or None 129 | """ 130 | if not self._net_resource: 131 | self.load_net_resource() 132 | 133 | return self.name2net_res.keys() 134 | 135 | 136 | def net_resource_iter(self): 137 | """ iterate net_resource data 138 | 139 | args: none 140 | """ 141 | if not self._net_resource: 142 | self.load_net_resource() 143 | for k, v in self._net_resource.items(): 144 | yield v 145 | 146 | 147 | 148 | ## 149 | ## WOL (Wake on LAN) funtions 150 | ## 151 | def load_net_wol(self): 152 | """ Load Wake On LAN networking resources 153 | 154 | internal function call 155 | """ 156 | (self._wolinfo, self.name2wol) = self._load_networking("wol") 157 | 158 | 159 | 160 | def net_wol(self, wid): 161 | """ Send Wake On LAN to registared wol ID 162 | 163 | args: 164 | wid : WOL resource ID 165 | calls : /rest/networking/wol/ 166 | """ 167 | 168 | wol_id = self._net_wol_get_id(wid) 169 | 170 | # wol_id = str(wid).upper() 171 | 172 | if wol_id is None: 173 | raise IsyValueError("bad wol ID : " + wid) 174 | 175 | xurl = "/rest/networking/wol/" + wol_id 176 | 177 | if self.debug & 0x02: 178 | print("wol : xurl = " + xurl) 179 | resp = self._getXMLetree(xurl) 180 | # self._printXML(resp) 181 | if resp.attrib["succeeded"] != 'true': 182 | raise IsyResponseError("ISY command error : cmd=wol wol_id=" \ 183 | + str(wol_id)) 184 | 185 | 186 | def _net_wol_get_id(self, name): 187 | """ wol name to wol ID """ 188 | if not self._wolinfo: 189 | self.load_net_wol() 190 | 191 | # name = str(name).upper() 192 | if name in self._wolinfo: 193 | return name 194 | 195 | if name in self.name2wol: 196 | return self.name2wol[name] 197 | 198 | return None 199 | 200 | 201 | def net_wol_names(self): 202 | """ method to retrieve a list of WOL names 203 | 204 | args: none 205 | 206 | returns List of WOL names or None 207 | """ 208 | if not self._wolinfo: 209 | self.load_net_wol() 210 | return self.name2wol.keys() 211 | 212 | def net_wol_ids(self): 213 | """ method to retrieve a list of WOL ids 214 | 215 | args: none 216 | 217 | returns List of WOL names or None 218 | """ 219 | if not self._wolinfo: 220 | self.load_net_wol() 221 | return self._wolinfo.keys() 222 | 223 | 224 | def net_wol_iter(self): 225 | """ Iterate though Wol Ids values 226 | 227 | args: none 228 | """ 229 | if not self._wolinfo: 230 | self.load_net_wol() 231 | 232 | for v in self._wolinfo.values(): 233 | yield v 234 | 235 | 236 | 237 | # Do nothing 238 | # (syntax check) 239 | # 240 | if __name__ == "__main__": 241 | import __main__ 242 | print(__main__.__file__) 243 | 244 | print("syntax ok") 245 | exit(0) 246 | -------------------------------------------------------------------------------- /ISY/_isyprog.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a subfile for IsyClass.py 3 | 4 | These funtions are accessable via the Isy class opj 5 | """ 6 | 7 | # author : Peter Shipley 8 | # copyrigh : Copyright (C) 2015 Peter Shipley 9 | # license : BSD 10 | 11 | from ISY.IsyExceptionClass import IsyInvalidCmdError, IsyResponseError 12 | from ISY.IsyProgramClass import IsyProgram 13 | import xml.etree.ElementTree as ET 14 | from warnings import warn 15 | 16 | 17 | ## 18 | ## ISY Programs Code 19 | ## 20 | def load_prog(self, progid=None): 21 | """ Load Program status and Info 22 | 23 | args : none 24 | 25 | internal function call 26 | 27 | """ 28 | if self.debug & 0x01: 29 | print("load_prog") 30 | 31 | if not hasattr(self, '_progdict') or not isinstance(self._progdict, dict): 32 | self._progdict = dict() 33 | 34 | if not hasattr(self, '_name2id') or not isinstance(self._name2id, dict): 35 | self._name2id = dict() 36 | 37 | if progid: 38 | xurl = "/rest/programs/" + progid 39 | else: 40 | xurl = "/rest/programs?subfolders=true" 41 | self.name2prog = dict() 42 | 43 | prog_tree = self._getXMLetree(xurl, noquote=1) 44 | 45 | for pg in prog_tree.iter("program"): 46 | pdict = dict() 47 | for k, v in pg.items(): 48 | pdict[k] = v 49 | for pe in list(pg): 50 | pdict[pe.tag] = pe.text 51 | 52 | # spacial case for root program node folder 53 | if not "parentId" in pdict: 54 | pdict["parentId"] = pdict["id"] 55 | 56 | if "id" in pdict: 57 | if str(pdict["id"]) in self._progdict: 58 | self._progdict[str(pdict["id"])].update(pdict) 59 | else: 60 | self._progdict[str(pdict["id"])] = pdict 61 | n = pdict["name"].upper() 62 | 63 | # # name2id to replace name2prog as a global lookup table 64 | # # but not sure if consolidating namespace between prog & nodes 65 | # # is it a good idea 66 | # if n in self._name2id: 67 | # # print("Dup name2id : \"" + n + "\" ", pdict["id"]) 68 | # # print("name2id ", self._name2id[n]) 69 | # pass 70 | # else: 71 | # self._name2id[n] = ("program", pdict["id"]) 72 | 73 | if n in self.name2prog: 74 | print("Dup name : \"" + n + "\" ", pdict["id"]) 75 | print("name2prog ", self.name2prog[n]) 76 | else: 77 | self.name2prog[n] = pdict["id"] 78 | 79 | #self._printdict(self._progdict) 80 | #self._printdict(self.name2prog) 81 | 82 | 83 | 84 | def get_prog(self, pname): 85 | """ Get a Program object for given program name or ID 86 | 87 | args: 88 | pname : program name of id 89 | 90 | return: 91 | An IsyProgram class object representing the requested program 92 | 93 | """ 94 | if self.debug & 0x01: 95 | print("get_prog :" + pname) 96 | if not self._progdict: 97 | self.load_prog() 98 | 99 | progid = self._prog_get_id(pname) 100 | # print("\tprogid : " + progid) 101 | if progid in self._progdict: 102 | if not progid in self.progCdict: 103 | # print("not progid in self.progCdict:") 104 | # self._printdict(self._progdict[progid]) 105 | self.progCdict[progid] = IsyProgram(self, self._progdict[progid]) 106 | #self._printdict(self._progdict) 107 | # print("return : ",) 108 | #self._printdict(self.progCdict[progid]) 109 | return self.progCdict[progid] 110 | else: 111 | if self.debug & 0x01: 112 | print("Isy get_prog no prog : \"%s\"" % progid) 113 | raise LookupError("no prog : " + str(progid) ) 114 | 115 | def _prog_get_id(self, pname): 116 | """ Lookup prog value by name or ID 117 | returns ISY Id or None 118 | """ 119 | if isinstance(pname, IsyProgram): 120 | return pname["id"] 121 | if isinstance(pname, (int, long)): 122 | p = "{0:04X}".format(pname) 123 | else: 124 | p = str(pname).strip() 125 | 126 | if p.upper() in self._progdict: 127 | # print("_prog_get_id : " + p + " progdict " + p.upper()) 128 | return p.upper() 129 | if p in self.name2prog: 130 | # print("_prog_get_id : " + p + " name2prog " + self.name2prog[p]) 131 | return self.name2prog[p] 132 | 133 | # print("_prog_get_id : " + n + " None") 134 | return None 135 | 136 | def prog_get_path(self, pname): 137 | " get path of parent names " 138 | if not self._progdict: 139 | self.load_prog() 140 | prog_id = self._prog_get_id(pname) 141 | if prog_id is None: 142 | raise IsyValueError("prog_get_path: unknown program id : " + str(pname) ) 143 | return self._prog_get_path(prog_id) 144 | 145 | def _prog_get_path(self, prog_id): 146 | fpath = self._progdict[prog_id]['name'] 147 | pgm = self._progdict[ self._progdict[prog_id]['parentId'] ] 148 | 149 | while pgm['id'] != '0001': 150 | fpath = pgm['name'] + "/" + fpath 151 | pgm = self._progdict[ pgm['parentId'] ] 152 | 153 | fpath = "/" + fpath 154 | return fpath 155 | 156 | def prog_addrs(self): 157 | """ 158 | access method for prog address list 159 | """ 160 | if not self._progdict: 161 | self.load_prog() 162 | return self._progdict.viewkeys() 163 | 164 | def prog_get_src(self, pname): 165 | 166 | if not self._progdict: 167 | self.load_prog() 168 | 169 | prog_id = self._prog_get_id(pname) 170 | 171 | if prog_id is None: 172 | raise IsyValueError("prog_get_src: unknown program : " + str(prog_id) ) 173 | 174 | r = self.soapcomm("GetSysConf", name="/CONF/D2D/" + prog_id + ".PGM") 175 | 176 | return r 177 | 178 | 179 | def prog_iter(self): 180 | """ Iterate though program objects 181 | 182 | Returns an iterator over Program Objects types 183 | """ 184 | if not self._progdict: 185 | self.load_prog() 186 | 187 | k = sorted(self._progdict.keys()) 188 | for v in k: 189 | yield self.get_prog(v) 190 | 191 | prog_valid_comm = ['run', 'runThen', 'runElse', 192 | 'stop', 'enable', 'disable', 193 | 'enableRunAtStartup', 'disableRunAtStartup'] 194 | 195 | def prog_cmd_list(self): 196 | """ get list of valid commands for prog_comm() """ 197 | return prog_valid_comm[:] 198 | 199 | def prog_comm(self, paddr, cmd): 200 | """ Send program command 201 | 202 | args: 203 | paddr = program name, address or program obj 204 | cmd = name of command 205 | 206 | raise: 207 | LookupError : if node name or Id is invalid 208 | IsyPropertyError : if property invalid 209 | TypeError : if property valid 210 | 211 | Valid Commands : 'run', 'runThen', 'runElse', 'stop', 'enable', 'disable', 'enableRunAtStartup', 'disableRunAtStartup' 212 | 213 | calls /rest/programs// 214 | """ 215 | 216 | prog_id = self._prog_get_id(paddr) 217 | 218 | #print("self.controls :", self.controls) 219 | #print("self.name2control :", self.name2control) 220 | 221 | if not prog_id: 222 | raise IsyValueError("prog_comm: unknown program id : " + 223 | str(paddr) ) 224 | 225 | if not cmd in prog_valid_comm: 226 | raise IsyInvalidCmdError("prog_comm: unknown command : " + 227 | str(cmd) ) 228 | 229 | self._prog_comm(prog_id, cmd) 230 | 231 | def _prog_comm(self, prog_id, cmd): 232 | """ called by prog_comm() after argument validation """ 233 | # /rest/programs// 234 | xurl = "/rest/programs/" + prog_id + "/" + cmd 235 | 236 | if self.debug & 0x02: 237 | print("xurl = " + xurl) 238 | 239 | resp = self._getXMLetree(xurl) 240 | #self._printXML(resp) 241 | if resp.attrib["succeeded"] != 'true': 242 | raise IsyResponseError("ISY command error : prog_id=" + 243 | str(prog_id) + " cmd=" + str(cmd)) 244 | 245 | 246 | def prog_rename(self, prog=None, progname=None): 247 | """ 248 | Named args: 249 | prog a prog id 250 | progname New prog name 251 | """ 252 | 253 | if prog is None: 254 | raise IsyValueError("prog_rename: program id is None") 255 | 256 | prog_id = self._prog_get_id(paddr) 257 | 258 | if prog_id is None: 259 | raise IsyValueError("prog_rename: unknown program id : " + str(prog) ) 260 | 261 | if not isinstance(progname, str): 262 | raise IsyValueError("new program name should be string") 263 | 264 | r = self._prog_rename(progid=prog_id, progname=progname ) 265 | 266 | if self._progdict is not None and progid in self._progdict: 267 | self._progdict[progid]['name'] = progname 268 | self.name2prog[progname] = progid 269 | 270 | return r 271 | 272 | def _prog_rename(self, progid=None, progname=None): 273 | """ 274 | Named args: 275 | progid a prog id 276 | progname New prog name 277 | """ 278 | 279 | if not isinstance(progid, str): 280 | raise IsyValueError("program Id should be string") 281 | 282 | prog_path="/CONF/D2D/{0}.PGM".format(progid) 283 | 284 | result = self.soapcomm("GetSysConf", name=prog_path) 285 | 286 | # with open("prog-orig.xml", 'w') as fi: 287 | # fi.write(result) 288 | 289 | if result is None: 290 | raise IsyResponseError("Error loading Sys Conf file {0}".format(prog_path)) 291 | 292 | var_et = ET.fromstring(result) 293 | 294 | p = var_et.find("trigger/name") 295 | if not p is None: 296 | p.text = progname 297 | else: 298 | errorstr = "Internal Error, \"name\" element missing from D2D code :\n{0}\n".format(result) 299 | raise IsyRuntimeWarning(errorstr) 300 | 301 | # use method='html' to generate expanded empty elements 302 | new_prog_data = ET.tostring(var_et, method='html') 303 | 304 | # with open("prog-changed.xml", 'w') as fi: 305 | # fi.write(new_prog_data) 306 | 307 | 308 | r = self._sendfile(data=new_prog_data, filename=prog_path, load="y") 309 | 310 | return r 311 | 312 | 313 | # Do nothing 314 | # (syntax check) 315 | # 316 | if __name__ == "__main__": 317 | import __main__ 318 | print(__main__.__file__) 319 | 320 | print("syntax ok") 321 | exit(0) 322 | -------------------------------------------------------------------------------- /ISY/_isyzb.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a subfile for IsyClass.py 3 | 4 | ZigBee Support 5 | 6 | Only applicable to ISY994 Z Series. 7 | 8 | 9 | for now these are just empty hooks will I get my ZigBee Net working 10 | to test against 11 | 12 | """ 13 | 14 | # author : Peter Shipley 15 | # copyrigh : Copyright (C) 2015 Peter Shipley 16 | # license : BSD 17 | 18 | def load_zb(): 19 | # /rest/zb/nodes 20 | pass 21 | 22 | def zb_scannetwork(): 23 | # /rest/zb/scanNetwork 24 | pass 25 | 26 | def zb_ntable(): 27 | # /rest/zb/ntable 28 | pass 29 | 30 | def zb_ping_node(): 31 | # /rest/zb/nodes/[euid]/ping 32 | pass 33 | 34 | def get_zbnode(): 35 | pass 36 | 37 | def zbnode_addrs(): 38 | pass 39 | 40 | def zbnode_names(): 41 | pass 42 | 43 | def _zbnode_get_id(): 44 | pass 45 | 46 | def zbnode_comm(): 47 | pass 48 | 49 | def zbnode_iter(): 50 | pass 51 | 52 | 53 | # Do nothing 54 | # (syntax check) 55 | # 56 | if __name__ == "__main__": 57 | import __main__ 58 | print(__main__.__file__) 59 | 60 | print("syntax ok") 61 | exit(0) 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Peter Shipley 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or 5 | without modification, are permitted provided that the 6 | following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in 13 | the documentation and/or other materials provided with the 14 | distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | Makefile 3 | README.txt 4 | setup.py 5 | ISY/IsyClass.py 6 | ISY/IsyDiscover.py 7 | ISY/IsyEvent.py 8 | ISY/IsyEventData.py 9 | ISY/IsyExceptionClass.py 10 | ISY/IsyNodeClass.py 11 | ISY/IsyProgramClass.py 12 | ISY/IsyUtilClass.py 13 | ISY/IsyVarClass.py 14 | ISY/__init__.py 15 | ISY/_isyclimate.py 16 | ISY/_isynode.py 17 | ISY/_isyvar.py 18 | ISY/_isywol.py 19 | ISY/_isynet_resources.py 20 | ISY/_isyprog.py 21 | ISY/_isyzb.py 22 | bin/isy_find.py 23 | bin/isy_net_wol.py 24 | bin/isy_showevents.py 25 | bin/isy_log.py 26 | bin/isy_nodes.py 27 | bin/isy_var.py 28 | bin/isy_nestset.py 29 | bin/isy_progs.py 30 | bin/isy_net_res.py 31 | docs/Using_IsyNode_Class.txt 32 | docs/Using_IsyProg_Class 33 | docs/Using_IsyVar_Class.txt 34 | docs/Using_Isy_Class.txt 35 | 36 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include docs/* 2 | include bin/*.py 3 | include ISY/*.py 4 | prune Sav 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | PEP8=pep8 3 | #PEP8ARG=--ignore=E127,E265,E101,E128,E201,E202,E203,E211,E302,E303,W191,E501 4 | # E203 space before ":" 5 | # E201 whitespace after '(' 6 | # E202 whitespace before ')' 7 | PEP8ARG=--ignore=E203,E201,E202 8 | REPO=git@github.com:evilpete/ISYlib-python.git 9 | PROGS= 10 | PLIB=./ISY.py 11 | GIT=git 12 | 13 | all: 14 | echo "Be more exact" 15 | 16 | t: style 17 | 18 | FILES= ISY/IsyClass.py ISY/IsyExceptionClass.py ISY/IsyNodeClass.py ISY/IsyProgramClass.py ISY/IsyUtilClass.py ISY/IsyVarClass.py ISY/IsyDiscover.py ISY/IsyEventData.py ISY/IsyEvent.py ISY/_isyclimate.py ISY/_isynode.py ISY/_isyvar.py ISY/_isynet_resources.py ISY/__init__.py ISY/_isy_printevent.py 19 | 20 | BINFILES= bin/isy_find.py bin/isy_nodes.py bin/isy_log.py bin/isy_progs.py bin/isy_showevents.py bin/isy_var.py bin/isy_nestset.py bin/isy_net_wol.py bin/isy_net_res.py bin/isy_web.py 21 | 22 | #README.txt: ${FILES} 23 | # pydoc ISY > README.txt 24 | # git commit --message "file GENERATED by pydoc" README.txt 25 | 26 | syntax: 27 | for targ in ${FILES} ; do \ 28 | python $$targ ; \ 29 | done 30 | for targ in ${BINFILES} ; do \ 31 | echo $$targ ; \ 32 | python -m py_compile $$targ ; \ 33 | done 34 | 35 | style: 36 | ${PEP8} ${PEP8ARG} ${FILES} 37 | 38 | 39 | list: ${FILES} 40 | for targ in ${FILES} ; do \ 41 | echo $$targ ; \ 42 | egrep -h '^ *class |^ *def |^ ##|^def ' $$targ ;\ 43 | done 44 | 45 | doc: 46 | pydoc ${FILES} 47 | 48 | 49 | lint: 50 | pylint -d W0312,C0111,C0301,C0103 ${FILES} 51 | 52 | clean: 53 | rm bin/*.pyc ISY/*.pyc 54 | 55 | checkin: commit push 56 | 57 | commit: README.txt 58 | ${GIT} commit -a 59 | 60 | diff: 61 | ${GIT} diff 62 | 63 | status: 64 | ${GIT} status 65 | 66 | push: 67 | ${GIT} push 68 | 69 | pull: 70 | ${GIT} pull 71 | 72 | setup: setup.py README.txt 73 | python setup.py sdist 74 | git commit --message "file GENERATED by distutils" MANIFEST 75 | 76 | ${PLBS}: 77 | @echo ${GIT} pull 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **mostly inactive / will not be updated unless requested** 2 | 3 | ----- 4 | 5 | Branches 6 | 7 | public_dev : public commits 8 | dev : developer branch ( unstable ) 9 | master : Main branch ( mostly stable ) 10 | 11 | ISYlib-python 12 | ============= 13 | 14 | by: Peter Shipley 15 | 16 | Simple Python lib for the ISY home automation netapp Supporting a Simple and OO interface 17 | 18 | 19 | The Goal / design concept is to provide a fast and simple to use interface 20 | supporting both object oriented and procedural methods 21 | 22 | Also Supports real time cache updating by optionally running a sub-thread subscribing to event stream ) 23 | 24 | 25 | Note: 26 | This Lib has grown to the point it needs to be restructured / split up 27 | 28 | ---- 29 | 30 | This is a work in progress ( so expect new features ) 31 | 32 | see [/bin](/bin) for more examples 33 | 34 | nodes, programs and iay vars can be controlled via objects or call methods. 35 | 36 | Get and print the status for the node called "Garage Light" 37 | 38 | import ISY 39 | myisy = ISY.Isy(addr="admin", userp="admin, userl="isy") 40 | 41 | garage_light = myisy.get_node("Garage Light") 42 | 43 | print "Node {:} is {:}".format(garage_light.name, garage_light.formatted) 44 | 45 | 46 | -- 47 | 48 | Get an object that represents the node called "Garage Light" 49 | and turn it off if it is on 50 | 51 | import ISY 52 | myisy = ISY.Isy() 53 | 54 | garage_light = myisy.get_node("Garage Light") 55 | if garage_light : 56 | garage_light.off() 57 | 58 | 59 | -- 60 | 61 | Alternately you can obtain a Node's object by indexing 62 | a Isy obj my the node name or address 63 | 64 | import ISY 65 | myisy = ISY.Isy() 66 | myisy["16 3F E5 1"].off() 67 | or 68 | myisy["Garage Light"].off() 69 | 70 | 71 | on 50% : 72 | 73 | garage_light = myisy["Garage Light"] 74 | garage_light.on(128) 75 | 76 | or without node device objs 77 | 78 | myisy.node_comm("Garage Light", "on", 128) 79 | 80 | list all nodes and scenes and their status : 81 | 82 | pfmt = "{:<22} {:>12}\t{:<12}{!s:<12}" 83 | print(pfmt.format("Node Name", "Address", "Status", "Enabled")) 84 | print(pfmt.format("---------", "-------", "------", "------")) 85 | for nod in isy : 86 | if nod.objtype == "scene" : 87 | print(pfmt.format(nod.name, nod.address, "-", "-", )) 88 | else : 89 | print(pfmt.format(nod.name, nod.address, nod.formatted, nod.enabled, )) 90 | 91 | 92 | -- 93 | 94 | Callbacks can be set up as easy as 95 | 96 | def mycall(*args): 97 | print "mycall called: " 98 | for i in args : 99 | print "arg : ", type(i), i 100 | 101 | myisy = ISY.Isy(addr="10.1.1.3", eventupdates=1) 102 | myisy.callback_set("Garage Light", mycall, "my extra args") 103 | 104 | or 105 | 106 | garage_light = myisy["Garage Light"] 107 | garage_light.set_callback(mycall, "my extra args") 108 | 109 | or if your not passing extra arguments you can just : 110 | 111 | garage_light = myisy["Garage Light"] 112 | garage_light.set_callback = mycall 113 | 114 | Callback will be call for all events relating to the node it is registared to 115 | 116 | Callbacks are executed as a part of the event subthread 117 | 118 | -- 119 | 120 | see also : 121 | http://www.universal-devices.com/residential/ 122 | and/or 123 | http://wiki.universal-devices.com/index.php?title=Main_Page 124 | 125 | NOTE: This Libaray is not written by or supported by universal devices 126 | 127 | 128 | 129 | 130 | ISYlib-python Documentation 131 | --------------------------- 132 | [This needs to be updated] 133 | 134 | * [Using_Isy_Class](/docs/Using_Isy_Class.txt) This is the main class that used to represent the ISY device iitself 135 | 136 | * [Using_IsyNode_Class](/docs/Using_IsyNode_Class.txt) This class is used to represent and control individual Nodes ( aka: sensors and light switches ) 137 | 138 | * [Using_IsyVar_Class](/docs/Using_IsyVar_Class.txt) This class is used to represent varabibles internal to the ISY 139 | 140 | [![Analytics](https://ga-beacon.appspot.com/UA-63572697-1/evilpete/isylib)] 141 | ======= 142 | 143 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | ISY - API for the Universal Device's ISY 2 | 3 | 4 | A Simple and OO interface for ISY home automation netapp 5 | 6 | 7 | alowing for simple access to status control of Nodes controled by and ISY home automation controller 8 | 9 | 10 | see also : https://github.com/evilpete/ISYlib-python 11 | http://www.universal-devices.com/residential/ 12 | http://wiki.universal-devices.com/index.php?title=Main_Page 13 | 14 | -------------------------------------------------------------------------------- /bin/isy_audit_pgm_var.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Peter Shipley" 4 | # 5 | # quick hack to check what ISY vars are in use by which programs 6 | # 7 | # then report: 8 | # program name and var ids used 9 | # 10 | # summary list of used vars by ids 11 | # summary vars only referanced once 12 | # summary list of unused vars by ids 13 | # summary list of unused vars by name 14 | # 15 | # TODO : add options 16 | # 17 | 18 | # print program's full path include parent folder 19 | opt_fullpath = True 20 | # Skip program that don't use vars 21 | opt_skipnovars = True 22 | 23 | 24 | import xml.etree.ElementTree as ET 25 | 26 | import ISY 27 | 28 | def list_prog_vars(isy): 29 | 30 | # get a list of all var Ids and store them as a set 31 | var_known_set = set(isy.var_ids()) 32 | 33 | # a set for all referanced cars 34 | var_used_all = set() 35 | 36 | var_use_count = dict() 37 | 38 | if opt_fullpath: 39 | name_width = 45 40 | else: 41 | name_width = 24 42 | 43 | # iterate though all programs and program folders 44 | for p in isy.prog_iter(): 45 | 46 | var_used = [ ] 47 | 48 | # skip root folder. 49 | if p.id == '0001': 50 | continue 51 | 52 | # get D2D src for program 53 | src_xml = isy.prog_get_src(p.id) 54 | 55 | # parse the D2D code ( XML format ) 56 | src_info = ET.fromstring(src_xml) 57 | 58 | # find all the referances to program vars 59 | # and store them in an array 60 | for v in src_info.iter("var"): 61 | vid = v.attrib["type"] + ":" + v.attrib["id"] 62 | var_used.append(vid) 63 | var_use_count[vid] = var_use_count.get(vid, 0) + 1 64 | 65 | # convert the array into a set 66 | var_used_set = set(var_used) 67 | 68 | 69 | var_list = sorted(var_used_set) 70 | 71 | # add this set to total used set 72 | var_used_all.update(var_used_set) 73 | 74 | # referance program by name or full path 75 | if p.parentId == '0001' or opt_fullpath == False : 76 | pname = p.name 77 | else: 78 | pname = p.path 79 | 80 | 81 | # if program has vars, print name and list vars it contains. 82 | if len(var_list) > 0 or opt_skipnovars != True: 83 | print "{:<5}{:<{namew}} {!s}".format(p.id, pname, ", ".join(var_list), namew=name_width), 84 | 85 | # check it any of the referanced vars are missing from the system var list 86 | # if so report this 87 | missing_var_set = var_used_set - var_known_set 88 | if missing_var_set: 89 | print "( bad : ", str(", ").join(missing_var_set), " ) ", 90 | print 91 | 92 | 93 | # not that we have searched all the programs... 94 | 95 | # report vars that are referanced only once... 96 | var_used_once_set = set() 97 | for k, v in var_use_count.items(): 98 | if v == 1: 99 | var_used_once_set.add(k) 100 | 101 | 102 | # print all vars that are used. 103 | print "\nUsed var Ids (", len(var_used_all), "): ", 104 | print str(", ").join(sorted(var_used_all, None, varkey)) 105 | 106 | if var_used_once_set: 107 | print "\nUsed var once Ids (", len(var_used_once_set), "): ", 108 | print str(", ").join(sorted(var_used_once_set, None, varkey)) 109 | 110 | 111 | unused_var_set = var_known_set - var_used_all 112 | 113 | # print var Ids that exist that are not referanced 114 | unused_var_set_sorted = sorted(unused_var_set,None,varkey) 115 | print "\nUnused var Ids (", len(unused_var_set), "): ", 116 | print str(", ").join(unused_var_set_sorted) 117 | 118 | 119 | # also print the vars by name 120 | print "\nUnused var Names (", len(unused_var_set), "): ", 121 | print str(", ").join( [isy._vardict[el]["name"] for el in unused_var_set_sorted]) 122 | 123 | 124 | # funtion call for sorting var Ids ( in the form of 1:23 ) 125 | def varkey(vstr): 126 | vkeys = vstr.split(':') 127 | varkval = (int(vkeys[0]) * 100) + int(vkeys[1]) 128 | return str(varkval) 129 | 130 | 131 | 132 | if __name__ == '__main__': 133 | 134 | # open connection to ISY 135 | # don't preload node, dont subscribe to updates 136 | # 137 | # get login / pass from from Env. 138 | myisy = ISY.Isy(faststart=2,eventupdates=0,parsearg=1) 139 | 140 | # preload programs and vars 141 | myisy.load_vars() 142 | myisy.load_prog() 143 | 144 | list_prog_vars(myisy) 145 | 146 | exit(0) 147 | -------------------------------------------------------------------------------- /bin/isy_audit_x10_use.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Peter Shipley" 4 | 5 | # 6 | # quick hack to check x10 address usage 7 | # 8 | # Scan log history 9 | # Read Node configs 10 | # 11 | # report: 12 | # All active X10 addresses 13 | # All addresses from Registared Nodes 14 | # 15 | # Any X10 addresses used but not assocated with Registared Nodes 16 | # 17 | # TODO : add option 18 | 19 | 20 | # 21 | # 22 | from ISY.IsyClass import Isy, log_time_offset 23 | from ISY.IsyEventData import LOG_USERID, LOG_TYPES 24 | 25 | import sys 26 | 27 | 28 | time_const=2208988800; 29 | 30 | def main(isy): 31 | 32 | addr_received = set() 33 | addr_used = set() 34 | addr_known = set() 35 | addr_unknown = set() 36 | known_housecodes = set() 37 | 38 | header = [ "Node", "Control", "Action", "Time", "UID", "Log Type" ] 39 | 40 | for nod in isy: 41 | 42 | a = str(nod.address).split(' ') 43 | # or .type = 113.X.X.X 44 | if a[0] == 'FF': 45 | house = chr( 64 + int(a[1], 16) ) 46 | unit = str(int(a[2], 16)) 47 | node = house + unit 48 | addr_known.add(node) 49 | known_housecodes.add(house) 50 | 51 | print "addr_known : ", str(", ").join(sorted(addr_known)) 52 | 53 | 54 | for log_line in isy.log_iter(): 55 | col = str(log_line).split("\t") 56 | 57 | if col[0] == "X10": 58 | #print col[1] 59 | if int(col[4]) == 0: 60 | addr_received.add(col[1]) 61 | else: 62 | addr_used.add(col[1]) 63 | 64 | print "addr_received = ", str(", ").join(sorted(addr_received)) 65 | print "addr_used = ", str(", ").join(sorted(addr_used)) 66 | 67 | addr_unknown = addr_received.union(addr_used) - addr_known 68 | addr_unknown -= known_housecodes 69 | 70 | print "addr_unknown = ", str(", ").join(sorted(addr_unknown)) 71 | 72 | 73 | 74 | 75 | if __name__ == '__main__': 76 | myisy = Isy(parsearg=1) 77 | main(myisy) 78 | exit(0) 79 | 80 | -------------------------------------------------------------------------------- /bin/isy_backup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | generate backup for ISY 4 | """ 5 | 6 | __author__ = "Peter Shipley" 7 | 8 | 9 | import os 10 | import zipfile 11 | # import pprint 12 | import time 13 | import tempfile 14 | import argparse 15 | import xml.etree.ElementTree as ET 16 | import ISY 17 | from ISY.IsyUtilClass import et2d 18 | from ISY.IsyExceptionClass import IsySoapError 19 | 20 | dirset = set() 21 | fileset = set() 22 | #folder = None 23 | outfile = None 24 | verbose = 0 25 | restore = 0 26 | noop = False 27 | # noop = True 28 | 29 | local_time = None 30 | 31 | debug = 0 32 | 33 | backup_flags = 0b0000 34 | backup_sysconf = 0b0001 35 | backup_userweb = 0b0010 36 | backup_ui = 0b0100 37 | backup_logs = 0b1000 38 | # backup_system = 0b1000 39 | backup_all = (backup_sysconf | backup_userweb | backup_ui ) 40 | 41 | # this makes the backup not usable for restores 42 | # but is good for debuging since unzip will not 43 | # extract file with absolute paths from root 44 | zip_noroot = False 45 | 46 | reboot_after = False 47 | 48 | 49 | def date_time2str(dt): 50 | return "{0}-{1}-{2} {3}:{4}:{5}".format(*dt) 51 | 52 | #ISY-Backup.v4.1.2__Fri 2014.03.14 17.46.52.zip 53 | #uuid.00.21.b9.00.e7.08.zip* 54 | 55 | def parse_args(isy): 56 | # global folder 57 | global noop 58 | global outfile 59 | global restore 60 | global verbose 61 | global backup_flags 62 | global reboot_after 63 | global zip_noroot 64 | 65 | 66 | parser = isy.parser 67 | 68 | # if "parser" in isy: 69 | # parser = isy.parser 70 | # else: 71 | # if debug: 72 | # print "new parser" 73 | # parser = argparse.ArgumentParser() 74 | 75 | 76 | parser.add_help = True 77 | parser.epilog = "Default is to back up system, userweb, and web UI configs" 78 | 79 | # dest="outfile", 80 | 81 | parser.add_argument("-A", "--backup-all", action='store_true', 82 | help="backup all (sys config, User Web, UI config) ") 83 | 84 | parser.add_argument("-L", "--backup-logs", action='store_true', 85 | help="backup system log files") 86 | 87 | parser.add_argument("-W", "--backup-userweb", action='store_true', 88 | help="backup user web files") 89 | 90 | parser.add_argument("-C", "--backup-sysconf", action='store_true', 91 | help="backup system config") 92 | 93 | parser.add_argument("-U", "--backup-ui", action='store_true', 94 | help="backup web UI config") 95 | 96 | parser.add_argument("-n", "--noop", action='store_true', 97 | help="noop test arg") 98 | 99 | parser.add_argument("-R", "--Reboot", action='store_true', 100 | help="Reboot after restore") 101 | 102 | parser.add_argument("-r", "--restore", action='store_true', 103 | help="restore backup") 104 | 105 | parser.add_argument("-h", "--help", action='help', 106 | default=argparse.SUPPRESS) 107 | 108 | parser.add_argument("-f", "--file", dest="outfile", 109 | help="backup file") 110 | 111 | parser.add_argument("-N", "--noroot", action='store_true', 112 | help="backup without root origin") 113 | 114 | # parser.add_argument("-c", "--copy", dest="copy", 115 | # help="copy config tree to folder") 116 | 117 | parser.add_argument('-v', '--verbose', action='count') 118 | 119 | args = parser.parse_args() 120 | # args, unknown_args = parser.parse_known_args() 121 | 122 | # if args.outfile and args.folder: 123 | # print "you can't use both file and folder" 124 | # parser.print_help() 125 | # exit(0) 126 | 127 | if args.restore and not args.outfile: 128 | parser.error('--file is required when --restore is set.') 129 | 130 | if args.Reboot: reboot_after = True 131 | 132 | if args.backup_sysconf: backup_flags |= backup_sysconf 133 | if args.backup_ui: backup_flags |= backup_ui 134 | if args.backup_userweb: backup_flags |= backup_userweb 135 | if args.backup_all: backup_flags |= (backup_all | backup_logs) 136 | if args.backup_logs: backup_flags |= backup_logs 137 | if backup_flags == 0: backup_flags = backup_all 138 | 139 | if debug: 140 | print "backup_flags = {0:04b}".format(backup_flags) 141 | noop = args.noop 142 | outfile = args.outfile 143 | verbose = args.verbose 144 | restore = args.restore 145 | zip_noroot = args.noroot 146 | # folder = args.folder 147 | 148 | 149 | 150 | def restore_isy(isy): 151 | if outfile is None: 152 | raise argparse.ArgumentTypeError("no restore file given") 153 | 154 | # mybackupid = "uuid.{0}.zip".format(myisy.id.replace(':', '.')) 155 | 156 | zf = zipfile.ZipFile(outfile, "r") 157 | isybackup = zf.namelist()[0] 158 | # print isybackup 159 | 160 | # if verbose: 161 | # for f in zf.infolist(): 162 | # print "{0:<30} : {1:6} : {2:#010x} ({3:#04o}) ".format( 163 | # f.filename, 164 | # f.file_size, 165 | # f.external_attr, 166 | # ( (f.external_attr >> 16L) & 0x0FFF) 167 | # ) 168 | 169 | 170 | if not (isybackup.startswith("uuid") and isybackup.endswith(".zip")): 171 | raise SystemExit("Invalid backup\n") 172 | 173 | td = tempfile.mkdtemp() 174 | 175 | zf.extract(isybackup, td) 176 | zf.close() 177 | uuidfile = "{0}/{1}".format(td, isybackup) 178 | 179 | zff = zipfile.ZipFile(uuidfile, "r") 180 | zff_info = zff.infolist() 181 | 182 | if verbose: 183 | print "{0} files to be retored".format(len(zff_info)) 184 | 185 | restore_filter = None 186 | if backup_flags != backup_all: 187 | restore_filter_list = list() 188 | 189 | if (backup_flags & backup_sysconf): 190 | restore_filter_list.append("/CONF") 191 | 192 | if (backup_flags & backup_userweb): 193 | restore_filter_list.append("/USER/WEB/") 194 | 195 | if (backup_flags & backup_ui): 196 | restore_filter_list.append("/WEB/CONF/") 197 | 198 | if (backup_flags & backup_logs): 199 | restore_filter_list.append("/LOG/") 200 | 201 | restore_filter = tuple(restore_filter_list) 202 | 203 | for z in zff_info: 204 | if restore_filter and not z.filename.startswith(restore_filter): 205 | if vebose: 206 | print "skipping {0:<30} : Not in restore path".format(z.filename) 207 | continue 208 | 209 | if (z.external_attr & 0x0010) or z.filename.endswith("/"): 210 | if verbose: 211 | print "skipping {0:<30} : directory".format(z.filename) 212 | continue 213 | 214 | if verbose: 215 | print "{0:<30} : {1:6} : {2:#010x} ({3:04o}) {4}".format( 216 | z.filename, 217 | z.file_size, 218 | z.external_attr, 219 | ((z.external_attr >> 16L) & 0x0FFF), 220 | date_time2str(z.date_time) 221 | ) 222 | 223 | if (not z.filename.startswith("/")): 224 | if verbose: 225 | print "skipping {0:<30} : not full path".format(z.filename) 226 | continue 227 | 228 | if not noop: 229 | fdata = zff.read(z) 230 | try: 231 | r = isy._sendfile(data=fdata, filename=z.filename, load="y") 232 | except IsySoapError, se: 233 | if se.code() == 403: 234 | print "Error restoring {0} : Forbidden (code=403)".format(z.filename) 235 | else: 236 | raise 237 | 238 | zff.close() 239 | 240 | os.unlink(uuidfile) 241 | os.rmdir(td) 242 | 243 | def backup_isy(isy): 244 | global outfile 245 | # global verbose 246 | time_str = time.strftime("%a_%Y.%m.%d_%H.%M.%S" , time.localtime()) 247 | 248 | # if folder is None: 249 | if outfile is None: 250 | outfile = "ISY-Backup.v{0}_{1}.zip".format(isy.app_version, time_str) 251 | elif not outfile.endswith((".zip", ".ZIP")): 252 | outfile = outfile + ".zip" 253 | 254 | backupid = "uuid.{0}.zip".format(myisy.id.replace(':', '.')) 255 | 256 | 257 | if (backup_flags & backup_sysconf): 258 | zip_get_conf(isy) 259 | 260 | if (backup_flags & backup_userweb): 261 | zip_get_userweb(isy) 262 | 263 | if (backup_flags & backup_logs): 264 | zip_get_logfiles(isy) 265 | 266 | if (backup_flags & backup_ui): 267 | zip_get_ui_conf(isy) 268 | 269 | tf = tempfile.NamedTemporaryFile(delete=False) 270 | zf = zipfile.ZipFile(tf, "w") 271 | 272 | for d in sorted(dirset): 273 | add_dir(isy, zf, d) 274 | 275 | for f in sorted(fileset): 276 | add_file(isy, zf, f) 277 | 278 | zf.close() 279 | tf.close() 280 | 281 | zff = zipfile.ZipFile(outfile, "w") 282 | zff.create_system = os.uname()[0] 283 | 284 | zff.write(tf.name, backupid) 285 | zff.close() 286 | os.unlink(tf.name) 287 | if verbose: 288 | print "Backup completed" 289 | print "output file = ", outfile 290 | 291 | 292 | def zip_get_ui_conf(isy): 293 | dirset.add("/WEB/CONF") 294 | fileset.add("/WEB/CONF/CAMS.JSN") 295 | 296 | def zip_get_logfiles(isy): 297 | dirset.add("/LOG/") 298 | fileset.add("/LOG/A.LOG") 299 | fileset.add("/LOG/ERRORA.LOG") 300 | fileset.add("/LOG/UPRICEA.LOG") 301 | fileset.add("/LOG/UMSGA.LOG") 302 | fileset.add("/LOG/UDRLCA.LOG") 303 | fileset.add("/LOG/UMETERA.LOG") 304 | 305 | 306 | def zip_get_conf(isy): 307 | 308 | dat = isy.soapcomm("GetSysConfFiles") 309 | flist = et2d(ET.fromstring(dat)) 310 | # pprint.pprint(flist) 311 | 312 | if "dir-name" in flist: 313 | if isinstance(flist['dir-name'], list): 314 | for d in flist['dir-name']: 315 | if d.startswith("/"): 316 | dirset.add(d) 317 | else: 318 | dirset.add("/CONF/" + d) 319 | else: 320 | if flist['dir-name'].startswith("/"): 321 | dirset.add(flist['dir-name']) 322 | else: 323 | dirset.add("/CONF/" + flist['dir-name']) 324 | 325 | if "entry" in flist: 326 | if isinstance(flist['entry'], list): 327 | for f in flist['entry']: 328 | fileset.add("/CONF/" + f) 329 | else: 330 | fileset.add("/CONF" + flist["entry"]) 331 | 332 | 333 | def zip_get_userweb(isy): 334 | 335 | dat = isy.soapcomm("GetUserDirectory") 336 | flist = et2d(ET.fromstring(dat)) 337 | 338 | # pprint.pprint(flist) 339 | 340 | if "dir" in flist: 341 | if isinstance(flist['dir'], list): 342 | for d in flist['dir']: 343 | dirset.add(d["dir-name"]) 344 | else: 345 | dirset.add(flist['dir']["dir-name"]) 346 | 347 | if "dir" in flist: 348 | if isinstance(flist['dir'], list): 349 | for d in flist['dir']: 350 | dirset.add(d["dir-name"]) 351 | else: 352 | dirset.add(flist['dir']["dir-name"]) 353 | 354 | if "file" in flist: 355 | if isinstance(flist['file'], list): 356 | for f in flist['file']: 357 | fileset.add(f["file-name"]) 358 | else: 359 | fileset.add(flist['file']["file-name"]) 360 | 361 | 362 | def add_file(isy, zf, fpath): 363 | # global verbose 364 | global local_time 365 | 366 | if not fpath.startswith('/'): 367 | errstr = "Internal Error" 368 | # "Internal Error : filename not full path : {0}\n".format(fpath) 369 | raise RuntimeError(errstr) 370 | 371 | try: 372 | dat = isy.soapcomm("GetSysConf", name=fpath) 373 | except IsySoapError, se: 374 | if fpath.startswith('/WEB/CONF/'): 375 | return 376 | raise 377 | else: 378 | if verbose: 379 | print "{0:<5} : {1}".format(len(dat), fpath) 380 | 381 | if (zip_noroot): 382 | fpath=fpath[1:] 383 | 384 | 385 | if local_time is None: 386 | local_time = time.localtime() 387 | zfi = zipfile.ZipInfo(fpath) 388 | zfi.date_time = local_time[:6] 389 | zfi.compress_type = zipfile.ZIP_STORED 390 | zfi.external_attr = (0o0644 << 16L) 391 | zf.writestr(zfi, dat) 392 | 393 | 394 | def add_dir(isy, zf, fpath): 395 | # global verbose 396 | 397 | if not fpath.startswith('/'): 398 | raise RuntimeError( 399 | "Internal Error : dir name not full path : {0}".format(fpath)) 400 | if not fpath.endswith('/'): 401 | fpath = fpath + '/' 402 | if verbose: 403 | print "{0:<5} : {1}".format("dir", fpath) 404 | zfi = zipfile.ZipInfo(fpath) 405 | zfi.compress_type = zipfile.ZIP_STORED 406 | zfi.external_attr = (0o040755 < 16L) | 0x10 407 | zf.writestr(zfi, '') 408 | 409 | if __name__ == '__main__': 410 | myisy = ISY.Isy(parsearg=1, faststart=1) 411 | parse_args(myisy) 412 | if restore: 413 | restore_isy(myisy) 414 | if reboot_after: 415 | myisy.reboot() 416 | else: 417 | backup_isy(myisy) 418 | exit(0) 419 | -------------------------------------------------------------------------------- /bin/isy_fauxmo.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python2.7 2 | 3 | """ 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Maker Musings 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | """ 26 | 27 | # For a complete discussion, see http://www.makermusings.com 28 | 29 | import email.utils 30 | import select 31 | import socket 32 | import struct 33 | import sys 34 | import time 35 | import urllib 36 | import uuid 37 | 38 | import ISY 39 | 40 | 41 | base_port = 54900 42 | 43 | isypass = "admin" 44 | isyuser = "admin" 45 | isyaddr = "10.1.1.36" 46 | 47 | # DOF DON DIM BRT 48 | # /rest/nodes//cmd/>/ 49 | # /rest/nodes/16 3F E5 1/cmd/DOF 50 | isydevs = { 51 | "office light": "16 3F E5 1", 52 | "garage": "20326", 53 | "floor lamp": "32468", 54 | "bathroom light": "17 54 69 1", 55 | "bathroom Fan": "17 50 F4 1", 56 | "desk light": "16 D3 73 1", 57 | "backyard lights": "FF 03 0F 2", 58 | } 59 | 60 | # /rest/programs// 61 | # Valid Commands : 'run', 'runThen', 'runElse', 'stop', 'enable', 'disable', 'enableRunAtStartup', 'disableRunAtStartup' 62 | 63 | 64 | # This XML is the minimum needed to define one of our virtual switches 65 | # to the Amazon Echo 66 | 67 | SETUP_XML = """ 68 | 69 | 70 | urn:MakerMusings:device:controllee:1 71 | %(device_name)s 72 | Belkin International Inc. 73 | Emulated Socket 74 | 3.1415 75 | %(device_state) 76 | uuid:Socket-1_0-%(device_serial)s 77 | 78 | 79 | """ 80 | 81 | DEBUG = True 82 | 83 | 84 | def dbg(msg): 85 | global DEBUG 86 | if DEBUG: 87 | print msg 88 | sys.stdout.flush() 89 | 90 | 91 | # A simple utility class to wait for incoming data to be 92 | # ready on a socket. 93 | 94 | class poller: 95 | def __init__(self): 96 | if 'poll' in dir(select): 97 | self.use_poll = True 98 | self.poller = select.poll() 99 | else: 100 | self.use_poll = False 101 | self.targets = {} 102 | 103 | def add(self, target, fileno=None): 104 | if not fileno: 105 | fileno = target.fileno() 106 | if self.use_poll: 107 | self.poller.register(fileno, select.POLLIN) 108 | self.targets[fileno] = target 109 | 110 | def remove(self, target, fileno=None): 111 | if not fileno: 112 | fileno = target.fileno() 113 | if self.use_poll: 114 | self.poller.unregister(fileno) 115 | del(self.targets[fileno]) 116 | 117 | def poll(self, timeout=0): 118 | if self.use_poll: 119 | ready = self.poller.poll(timeout) 120 | else: 121 | ready = [] 122 | if len(self.targets) > 0: 123 | (rlist, wlist, xlist) = select.select(self.targets.keys(), [], [], timeout) 124 | ready = [(x, None) for x in rlist] 125 | for one_ready in ready: 126 | target = self.targets.get(one_ready[0], None) 127 | if target: 128 | target.do_read(one_ready[0]) 129 | 130 | 131 | # Base class for a generic UPnP device. This is far from complete 132 | # but it supports either specified or automatic IP address and port 133 | # selection. 134 | 135 | class upnp_device(object): 136 | this_host_ip = None 137 | 138 | @staticmethod 139 | def local_ip_address(): 140 | if not upnp_device.this_host_ip: 141 | temp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 142 | try: 143 | temp_socket.connect(('8.8.8.8', 53)) 144 | upnp_device.this_host_ip = temp_socket.getsockname()[0] 145 | except: 146 | upnp_device.this_host_ip = '127.0.0.1' 147 | del(temp_socket) 148 | dbg("got local address of %s" % upnp_device.this_host_ip) 149 | return upnp_device.this_host_ip 150 | 151 | def __init__(self, listener, poller, port, root_url, server_version, persistent_uuid, other_headers=None, ip_address=None): 152 | self.listener = listener 153 | self.poller = poller 154 | self.port = port 155 | self.root_url = root_url 156 | self.server_version = server_version 157 | self.persistent_uuid = persistent_uuid 158 | self.uuid = uuid.uuid4() 159 | self.other_headers = other_headers 160 | 161 | if ip_address: 162 | self.ip_address = ip_address 163 | else: 164 | self.ip_address = upnp_device.local_ip_address() 165 | 166 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 167 | self.socket.bind((self.ip_address, self.port)) 168 | self.socket.listen(5) 169 | if self.port == 0: 170 | self.port = self.socket.getsockname()[1] 171 | self.poller.add(self) 172 | self.client_sockets = {} 173 | self.listener.add_device(self) 174 | 175 | def fileno(self): 176 | return self.socket.fileno() 177 | 178 | def do_read(self, fileno): 179 | if fileno == self.socket.fileno(): 180 | (client_socket, client_address) = self.socket.accept() 181 | self.poller.add(self, client_socket.fileno()) 182 | self.client_sockets[client_socket.fileno()] = client_socket 183 | else: 184 | data, sender = self.client_sockets[fileno].recvfrom(4096) 185 | if not data: 186 | self.poller.remove(self, fileno) 187 | del(self.client_sockets[fileno]) 188 | else: 189 | self.handle_request(data, sender, self.client_sockets[fileno]) 190 | 191 | def handle_request(self, data, sender, socket): 192 | pass 193 | 194 | def get_name(self): 195 | return "unknown" 196 | 197 | def respond_to_search(self, destination, search_target): 198 | dbg("Responding to search for {!s:} @ {!s:}".format(self.get_name(), destination)) 199 | date_str = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) 200 | location_url = self.root_url % {'ip_address': self.ip_address, 'port': self.port} 201 | message = ("HTTP/1.1 200 OK\r\n" 202 | "CACHE-CONTROL: max-age=86400\r\n" 203 | "DATE: %s\r\n" 204 | "EXT:\r\n" 205 | "LOCATION: %s\r\n" 206 | "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" 207 | "01-NLS: %s\r\n" 208 | "SERVER: %s\r\n" 209 | "ST: %s\r\n" 210 | "USN: uuid:%s::%s\r\n" % (date_str, location_url, self.uuid, self.server_version, search_target, self.persistent_uuid, search_target)) 211 | if self.other_headers: 212 | for header in self.other_headers: 213 | message += "%s\r\n" % header 214 | message += "\r\n" 215 | temp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 216 | temp_socket.sendto(message, destination) 217 | 218 | 219 | # This subclass does the bulk of the work to mimic a WeMo switch on the network. 220 | 221 | class fauxmo(upnp_device): 222 | @staticmethod 223 | def make_uuid(name): 224 | return ''.join(["%x" % sum([ord(c) for c in name])] + ["%x" % ord(c) for c in "%sfauxmo!" % name])[:14] 225 | 226 | def __init__(self, name, listener, poller, ip_address, port, action_handler=None): 227 | self.serial = self.make_uuid(name) 228 | self.name = "test " + name 229 | self.ip_address = ip_address 230 | persistent_uuid = "Socket-1_0-" + self.serial 231 | other_headers = ['X-User-Agent: redsonic'] 232 | upnp_device.__init__(self, listener, poller, port, "http://%(ip_address)s:%(port)s/setup.xml", "Unspecified, UPnP/1.0, Unspecified", persistent_uuid, other_headers=other_headers, ip_address=ip_address) 233 | if action_handler: 234 | self.action_handler = action_handler 235 | else: 236 | self.action_handler = self 237 | dbg("FauxMo device '%s' ready on %s:%s state=%s" % (self.name, self.ip_address, self.port, self.action_handler.status)) 238 | 239 | def get_name(self): 240 | return self.name 241 | 242 | def handle_request(self, data, sender, socket): 243 | if data.find('GET /setup.xml HTTP/1.1') == 0: 244 | dbg("Responding to setup.xml for %s" % self.name) 245 | device_state = self.action_handler.status 246 | if device_state == "" or device_state == "0": 247 | device_state = "0" 248 | else: 249 | device_state = "1" 250 | xml = SETUP_XML % {'device_name': self.name, 251 | 'device_serial': self.serial, 252 | 'device_state': "device_state"} 253 | date_str = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) 254 | message = ("HTTP/1.1 200 OK\r\n" 255 | "CONTENT-LENGTH: %d\r\n" 256 | "CONTENT-TYPE: text/xml\r\n" 257 | "DATE: %s\r\n" 258 | "LAST-MODIFIED: Sat, 01 Jan 2000 00:01:15 GMT\r\n" 259 | "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" 260 | "X-User-Agent: redsonic\r\n" 261 | "CONNECTION: close\r\n" 262 | "\r\n" 263 | "%s" % (len(xml), date_str, xml)) 264 | socket.send(message) 265 | elif data.find('SOAPACTION: "urn:Belkin:service:basicevent:1#SetBinaryState"') != -1: 266 | success = False 267 | if data.find('1') != -1: 268 | # on 269 | dbg("Responding to ON for %s" % self.name) 270 | success = self.action_handler.on() 271 | elif data.find('0') != -1: 272 | # off 273 | dbg("Responding to OFF for %s" % self.name) 274 | success = self.action_handler.off() 275 | else: 276 | print("Unknown Binary State request:") 277 | print(data) 278 | if success: 279 | # The echo is happy with the 200 status code and doesn't 280 | # appear to care about the SOAP response body 281 | soap = "" 282 | date_str = email.utils.formatdate(timeval=None, localtime=False, usegmt=True) 283 | message = ("HTTP/1.1 200 OK\r\n" 284 | "CONTENT-LENGTH: %d\r\n" 285 | "CONTENT-TYPE: text/xml charset=\"utf-8\"\r\n" 286 | "DATE: %s\r\n" 287 | "EXT:\r\n" 288 | "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n" 289 | "X-User-Agent: redsonic\r\n" 290 | "CONNECTION: close\r\n" 291 | "\r\n" 292 | "%s" % (len(soap), date_str, soap)) 293 | socket.send(message) 294 | else: 295 | dbg("success={!s:}".format(success)) 296 | else: 297 | dbg(data) 298 | 299 | def on(self): 300 | return False 301 | 302 | def off(self): 303 | return True 304 | 305 | 306 | # Since we have a single process managing several virtual UPnP devices, 307 | # we only need a single listener for UPnP broadcasts. When a matching 308 | # search is received, it causes each device instance to respond. 309 | # 310 | # Note that this is currently hard-coded to recognize only the search 311 | # from the Amazon Echo for WeMo devices. In particular, it does not 312 | # support the more common root device general search. The Echo 313 | # doesn't search for root devices. 314 | 315 | class upnp_broadcast_responder(object): 316 | TIMEOUT = 0 317 | 318 | def __init__(self): 319 | self.devices = [] 320 | 321 | def init_socket(self): 322 | ok = True 323 | self.ip = '239.255.255.250' 324 | self.port = 1900 325 | try: 326 | # This is needed to join a multicast group 327 | self.mreq = struct.pack("4sl", socket.inet_aton(self.ip), socket.INADDR_ANY) 328 | 329 | # Set up server socket 330 | self.ssock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) 331 | self.ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 332 | 333 | try: 334 | self.ssock.bind(('', self.port)) 335 | except Exception, e: 336 | dbg("WARNING: Failed to bind %s:%d: %s", (self.ip, self.port, e)) 337 | ok = False 338 | 339 | try: 340 | self.ssock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, self.mreq) 341 | except Exception, e: 342 | dbg('WARNING: Failed to join multicast group:', e) 343 | ok = False 344 | 345 | except Exception, e: 346 | dbg("Failed to initialize UPnP sockets:", e) 347 | return False 348 | if ok: 349 | dbg("Listening for UPnP broadcasts") 350 | 351 | def fileno(self): 352 | return self.ssock.fileno() 353 | 354 | def do_read(self, fileno): 355 | data, sender = self.recvfrom(1024) 356 | if data: 357 | if data.find('M-SEARCH') == 0 and data.find('urn:Belkin:device:**') != -1: 358 | for device in self.devices: 359 | time.sleep(0.1) 360 | device.respond_to_search(sender, 'urn:Belkin:device:**') 361 | else: 362 | pass 363 | 364 | # Receive network data 365 | def recvfrom(self, size): 366 | if self.TIMEOUT: 367 | self.ssock.setblocking(0) 368 | ready = select.select([self.ssock], [], [], self.TIMEOUT)[0] 369 | else: 370 | self.ssock.setblocking(1) 371 | ready = True 372 | 373 | try: 374 | if ready: 375 | return self.ssock.recvfrom(size) 376 | else: 377 | return False, False 378 | except Exception, e: 379 | dbg("recvfrom") 380 | dbg(e) 381 | return False, False 382 | 383 | def add_device(self, device): 384 | self.devices.append(device) 385 | # dbg("UPnP broadcast listener: new device registered") 386 | 387 | # Each entry is a list with the following elements: 388 | # 389 | # name of the virtual switch 390 | # object with 'on' and 'off' methods 391 | # port # (optional; may be omitted) 392 | 393 | # NOTE: As of 2015-08-17, the Echo appears to have a hard-coded limit of 394 | # 16 switches it can control. Only the first 16 elements of the FAUXMOS 395 | # list will be used. 396 | 397 | def load_fauxmos(myisy=None, fport=None): 398 | 399 | conf = None 400 | r = None 401 | try: 402 | js = myisy.soapcomm("GetSysConf", name="/WEB/CONF/fauxmo.jsn") 403 | # print "r=",r 404 | except Exception, e: 405 | return(None) 406 | pass 407 | else: 408 | import json 409 | conf = json.loads(js) 410 | 411 | if conf is not None: 412 | 413 | ret_list = list() 414 | 415 | baseurl = "http://{:s}:{:s}@{:s}/rest".format(isyuser,isypass, isyaddr) 416 | 417 | # 418 | # isydevs = { 419 | # "office light" : "16 3F E5 1" , 420 | # "garage" : "20326", 421 | # } 422 | 423 | if "isydevs" in conf: 424 | isydevs = conf['isydevs'] 425 | for k in sorted(isydevs.keys()): 426 | try: 427 | nod = myisy[k] 428 | except Exception, e: 429 | pass 430 | else: 431 | l = [ k, nod ] 432 | 433 | if fport is not None: 434 | l.append(fport) 435 | fport = fport + 1 436 | ret_list.append(l) 437 | 438 | 439 | # isyprog { 440 | # bath fan" : { 441 | # "on" : ("006E", "runThen"), 442 | # "off" : ("0070", "runThen"), 443 | # }, 444 | # } 445 | 446 | # 447 | # the following is incomplete 448 | # 449 | if isyprog in conf: 450 | isyprog = conf['isyprog'] 451 | for k in sorted(isyprog.keys()): 452 | try: 453 | prg = myisy.get_prog(k) 454 | except Exception, e: 455 | pass 456 | else: 457 | l = [ k, prg ] 458 | if fport is not None: 459 | l.append(fport) 460 | fport = fport + 1 461 | ret_list.append( l ) 462 | 463 | return ret_list 464 | 465 | def build_fauxmos(myisy=None, fport=None): 466 | 467 | if myisy is None: 468 | print("myisy is None") 469 | exit(0) 470 | 471 | ret_list = list() 472 | 473 | for nod in myisy: 474 | if nod.objtype != "node": 475 | continue 476 | 477 | if nod.enabled is not True: 478 | continue 479 | 480 | if (nod.spoken): 481 | # print("Node :", nod.name, nod.address, nod.enabled, nod.spoken) 482 | 483 | l = [nod.spoken, nod] 484 | if fport is not None: 485 | l.append(fport) 486 | fport = fport + 1 487 | ret_list.append(l) 488 | 489 | return ret_list 490 | 491 | 492 | def main(myisy): 493 | global DEBUG 494 | 495 | if len(sys.argv) > 1 and sys.argv[1] == '-d': 496 | DEBUG = True 497 | 498 | bfm = load_fauxmos(myisy) 499 | if bfm is None: 500 | print "building device list from ISY" 501 | bfm = build_fauxmos(myisy, base_port) 502 | 503 | # Set up our singleton for polling the sockets for data ready 504 | p = poller() 505 | 506 | # Set up our singleton listener for UPnP broadcasts 507 | u = upnp_broadcast_responder() 508 | u.init_socket() 509 | 510 | # Add the UPnP broadcast listener to the poller so we can respond 511 | # when a broadcast is received. 512 | p.add(u) 513 | 514 | if DEBUG > 2: 515 | import pprint 516 | # 517 | print "\nbfm :", 518 | pprint.pprint(bfm) 519 | 520 | # Create our FauxMo virtual switch devices 521 | for one_faux in bfm: 522 | if len(one_faux) == 2: 523 | # a fixed port wasn't specified, use a dynamic one 524 | one_faux.append(0) 525 | switch = fauxmo(one_faux[0], u, p, None, one_faux[2], action_handler=one_faux[1]) 526 | 527 | dbg("Entering main loop\n") 528 | exit(0) 529 | 530 | while True: 531 | try: 532 | # Allow time for a ctrl-c to stop the process 533 | p.poll(100) 534 | time.sleep(0.1) 535 | except Exception, e: 536 | dbg(e) 537 | break 538 | 539 | if __name__ == '__main__': 540 | myisy = ISY.Isy(parsearg=1) # debug=0x80 541 | main(myisy) 542 | exit(0) 543 | -------------------------------------------------------------------------------- /bin/isy_find.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Peter Shipley" 4 | __license__ = "BSD" 5 | 6 | 7 | # Simple example 8 | # 9 | # This script listens for Upnp advertisements from any local ISY 10 | # unit and prints results 11 | # 12 | 13 | 14 | from ISY.IsyDiscover import isy_discover 15 | 16 | def list_units(): 17 | fmt = "%-25s %-25s %s" 18 | print(fmt % ("Device Name", "Device Number", "URL Address" )) 19 | print(fmt % ("-" * 20, "-" * 20, "-" * 20 )) 20 | 21 | # wait upto 5 seconds or after you have discovered two unit 22 | r = isy_discover(timeout=5, count=2) 23 | for key, unit in r.items(): 24 | print(fmt % ( unit['friendlyName'], unit['UDN'], unit['URLBase'] )) 25 | 26 | 27 | if __name__ == '__main__': 28 | list_units() 29 | exit(0) 30 | 31 | -------------------------------------------------------------------------------- /bin/isy_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Peter Shipley" 4 | 5 | 6 | from ISY.IsyClass import Isy, log_time_offset 7 | from ISY.IsyEventData import LOG_USERID, LOG_TYPES 8 | 9 | import sys 10 | import getopt 11 | import time 12 | 13 | opt_nosec = 0 14 | opt_tab = 0 15 | opt_debug = 0 16 | opt_error = 0 17 | opt_errorlog = 0 18 | opt_names = 0 19 | opt_addr = None 20 | 21 | time_const=2208988800; 22 | 23 | 24 | def main(isy): 25 | 26 | if opt_errorlog: 27 | log_err(isy) 28 | else: 29 | log_sys(isy) 30 | 31 | 32 | def log_err(isy): 33 | 34 | header = [ "Time", "UID", "Log Type", "Error Message" ] 35 | 36 | if opt_tab: 37 | fmt = "{0}\t{1}\t{2}\t{3}" 38 | else: 39 | fmt = "{:<15} {:<24} {:<38} {!s}" 40 | 41 | if opt_nosec: 42 | time_fmt = "%b %d %H:%M" 43 | else: 44 | time_fmt = "%b %d %H:%M:%S" 45 | 46 | time_offset = log_time_offset() 47 | 48 | # llimit = 200 49 | 50 | #print "{0} {1} {2} {3}".format(*header) 51 | print fmt.format(*header) 52 | for log_line in isy.log_iter(error = 1): 53 | col = str(log_line).split("\t") 54 | 55 | # print "log_line : ", len(col), " : ", "|".join(col) 56 | if ( len(col) < 4 ): 57 | print "BAD log_line : ", len(col), " : ", "|".join(col) 58 | continue 59 | 60 | newtime = int(col[0]) - time_const - time_offset 61 | ti = time.localtime(newtime) 62 | col[0] = time.strftime(time_fmt, ti) 63 | col[1] = int(col[1]) 64 | if col[1] < len(LOG_USERID) : col[1] = LOG_USERID[col[1]] 65 | if col[2] in LOG_TYPES : col[2] = LOG_TYPES[col[2]] 66 | 67 | print fmt.format( *col ) 68 | 69 | #if llimit == 0: 70 | # break 71 | 72 | 73 | def log_sys(isy): 74 | 75 | nodefmt="{:<12}" 76 | commfmt="{:<4}" 77 | 78 | header = [ "Node", "Control", "Action", "Time", "UID", "Log Type" ] 79 | 80 | if opt_names: 81 | nodefmt="{:<18}" 82 | commfmt="{:<10}" 83 | print "opt_names = ", opt_names 84 | 85 | if opt_tab: 86 | fmt = "{0}\t{1}\t{2}\t{3}\t{4}\t{5}" 87 | else: 88 | fmt = nodefmt + " " + commfmt + " {:<20} {:<15} {:<15} {:<15}" 89 | 90 | if opt_nosec: 91 | time_fmt = "%b %d %H:%M" 92 | else: 93 | time_fmt = "%b %d %H:%M:%S" 94 | 95 | # fmt = "{0} {1} {2} {3} {4} {5}" 96 | 97 | time_offset = log_time_offset() 98 | 99 | # llimit = 200 100 | 101 | # print "{0} {1} {2} {3} {4} {5}".format(*header) 102 | print fmt.format(*header) 103 | for log_line in isy.log_iter(error = opt_errorlog): 104 | col = str(log_line).split("\t") 105 | 106 | if opt_names: 107 | gn = isy._node_get_name(col[0]) 108 | # print "n / gn = ", col[0], " / ", gn 109 | if gn[1] is not None: 110 | col[0] = gn[1] 111 | 112 | newtime = int(col[3]) - time_const - time_offset 113 | ti = time.localtime(newtime) 114 | col[3] = time.strftime(time_fmt, ti) 115 | col[4] = int(col[4]) 116 | if col[4] < len(LOG_USERID) : col[4] = LOG_USERID[col[4]] 117 | if col[5] in LOG_TYPES : col[5] = LOG_TYPES[col[5]] 118 | 119 | print fmt.format( *col ) 120 | 121 | #if llimit == 0: 122 | # break 123 | #llimit = llimit - 1 124 | 125 | 126 | def parseargs(): 127 | global opt_names, opt_addr, opt_errorlog, opt_debug, opt_tab, opt_nosec 128 | try: 129 | opts, args = getopt.getopt( 130 | sys.argv[1:], "ahetnsd:", 131 | ['help', 'error', 'debug', 'addr', 'tab', 'nosec', 'names']) 132 | except getopt.error, e: 133 | usage(1, e) 134 | 135 | for opt, arg in opts: 136 | if opt in ('-h', '--help'): 137 | usage(0) 138 | elif opt in ('-n', '--names'): 139 | opt_names = 1 140 | elif opt in ('-a', '--addr'): 141 | opt_addr = arg 142 | elif opt in ('-e', '--error'): 143 | opt_errorlog = 1 144 | elif opt in ('-d', '--debug'): 145 | opt_debug = arg 146 | elif opt in ('-t', '--tab'): 147 | opt_tab = 1 148 | elif opt in ('-s', '--nosec'): 149 | opt_nosec = 1 150 | 151 | 152 | def usage(code, msg=''): 153 | print >> sys.stderr, __doc__ % globals() 154 | if msg: 155 | print >> sys.stderr, msg 156 | sys.exit(code) 157 | 158 | if __name__ == '__main__': 159 | parseargs() 160 | myisy = Isy( addr=opt_addr, debug=opt_debug ) 161 | main(myisy) 162 | exit(0) 163 | 164 | -------------------------------------------------------------------------------- /bin/isy_manage_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Node managment 5 | 6 | WORK IN PROGRESS 7 | 8 | Some command may not work yet 9 | 10 | """ 11 | 12 | __author__ = "Peter Shipley" 13 | 14 | import ISY 15 | # from ISY.IsyExceptionClass import IsyError 16 | 17 | class cmdException(Exception): 18 | def __init__(self, msg): 19 | Exception.__init__(self, msg) 20 | 21 | 22 | verbose=0 23 | 24 | commands_help = { 25 | "LINK, DISCOVER" : "place PLM into discover mode", 26 | "MV, RENAME" : "rename node", 27 | "RM, DEL, DELETE" : "delete node", 28 | "RESTORE" : "Restore node settings", 29 | "MD, NEWFOLDER, MKDIR" : "create new node folder", 30 | "NODE" : "send command to node", 31 | "RMDIR*" : "delete node folder", 32 | "ENABLE" : "enable node", 33 | "DISABLE" : "disable node", 34 | "LIST, LS" : "list nodes", 35 | "SCENE" : "add or delete node from scene", 36 | "FOLDER*" : "add or delete node from folder", 37 | "DEBUGLEVEL*" : "set/get ISY debug level", 38 | "REBOOT*" : "Reboot ISY", 39 | "HELP" : "print command list", 40 | "VERBOSE" : "set verbose", 41 | "ERROR" : "print last ISY error", 42 | "EXIT" : "exit program", 43 | } 44 | 45 | 46 | 47 | scene_commands_help = { 48 | "DEL, DELETE" : "Delete Scene", 49 | "RM, REMOVE" : "Remove Node from scene", 50 | "ADD" : "Add node to scene", 51 | "NEW" : "Create new scene", 52 | "LS, LIST" : "List Scenes" 53 | } 54 | 55 | folder_commands_help = { 56 | } 57 | 58 | 59 | prog_commands_help = { 60 | } 61 | 62 | def doit(isy): 63 | 64 | interactive = False 65 | 66 | argv = isy.unknown_args[:] 67 | 68 | if len(argv) == 0: 69 | print "Entering interactive mode" 70 | import shlex 71 | interactive = True 72 | 73 | while 1: 74 | try: 75 | 76 | if interactive is True: 77 | print "isylib> ", 78 | argv = shlex.split(raw_input()) 79 | if argv is None or len(argv) == 0: 80 | continue 81 | 82 | run_comm(isy, argv) 83 | except EOFError: 84 | interactive = False 85 | break 86 | except cmdException as e: 87 | print e 88 | except ISY.IsyError as e: 89 | print "IsyError :" 90 | print "\t", e 91 | finally: 92 | if interactive is not True: 93 | break 94 | 95 | 96 | def run_comm(isy, argv): 97 | global verbose 98 | 99 | cmd = argv.pop(0).upper() 100 | 101 | if cmd in ["LINK", "DISCOVER"]: 102 | link_nodes(isy, cmd, argv) 103 | 104 | elif cmd in [ "NODE" ]: 105 | do_node(isy, cmd, argv) 106 | 107 | elif cmd in ["SCENE"]: 108 | do_scene(isy, cmd, argv) 109 | 110 | elif cmd in [ "PROG" ]: 111 | do_prog(isy, cmd, argv) 112 | 113 | elif cmd in ["FOLDER", "DIR"]: 114 | do_folder(isy, cmd, argv) 115 | 116 | # the following are shortcuts 117 | elif cmd in [ "LS", "LIST" ]: 118 | do_list_node(isy, cmd, argv) 119 | 120 | elif cmd in [ "RM", "DEL", "DELETE"]: 121 | do_del_node(isy, cmd, argv) 122 | 123 | elif cmd in ["MV", "RENAME"]: 124 | do_rename_nodes(isy ,cmd, argv) 125 | 126 | elif cmd in ["RESTORE"]: 127 | do_restore(isy, cmd, argv) 128 | 129 | elif cmd in ["MD", "NEWFOLDER", "MKDIR"]: 130 | if ( len(argv) > 0 and argv[0] != "?" ): 131 | foldername = argv.pop(0) 132 | print "newfolder {!s}".format(foldername) 133 | else: 134 | raise cmdException("Syntax :\n\t{!s} ".format(cmd)) 135 | 136 | elif cmd in ["RMDIR"]: 137 | pass 138 | 139 | elif cmd in ["ENABLE", "EN" ]: 140 | do_node_enable(isy, cmd, argv) 141 | 142 | 143 | elif cmd in ["DISABLE","DIS"]: 144 | if ( len(argv) > 0 and argv[0] != "?" ): 145 | nodeid = argv.pop(0) 146 | isy.node_enable(nodeid, enable=0) 147 | else: 148 | raise cmdException("Syntax :\n\t{!s} ".format(cmd)) 149 | 150 | # The following are debug and maintance 151 | elif cmd in ["ERROR", "ERR"]: 152 | # print last ISY error 153 | pass 154 | 155 | elif cmd in ["VERBOSE"]: 156 | if ( len(argv) > 0 ): 157 | if (argv[0] == "?"): 158 | raise cmdException("Syntax :\n\t{!s} [level]".format(cmd)) 159 | else: 160 | verbose = int(argv[0]) 161 | print "verbose = ", verbose 162 | 163 | elif cmd in ["REBOOT"]: 164 | do_reboot(isy) 165 | 166 | elif cmd in ["DEBUGLEVEL", "DBG"]: 167 | do_debuglevel(isy) 168 | 169 | elif cmd in ["HELP", "?"]: 170 | print_cmds() 171 | 172 | elif cmd in ["EXIT"]: 173 | if ( len(argv) > 0 and argv[0] != "?"): 174 | raise cmdException("Syntax :\n\t{!s}".format(cmd)) 175 | else: 176 | exit(0) 177 | 178 | # DEBUG 179 | elif cmd in ["TEST"]: 180 | if ( len(argv) > 0 and argv[0] != "?"): 181 | do_test(isy, cmd, argv) 182 | else: 183 | raise cmdException("+\t{!s} ".format(cmd)) 184 | 185 | else: 186 | print "Unknown command : ", cmd # str(" ").join(argv) 187 | 188 | # 189 | # TODO deal with MV node Folder 190 | # 191 | def do_rename_nodes(isy, cmd, argv): 192 | """ 193 | rename node glue 194 | """ 195 | if ( len(argv) > 1 and argv[0] != "?" ): 196 | old = argv.pop(0) 197 | new = argv.pop(0) 198 | print cmd, old, new 199 | isy.rename(old, new) 200 | else: 201 | raise cmdException("Syntax :\n\t{!s} ".format(cmd)) 202 | 203 | # DEBUG 204 | def do_test(isy, cmd, argv): 205 | if len(argv) == 1: 206 | raise cmdException("Missing Arg:\n\t{!s} ".format(cmd)) 207 | else: 208 | print "TEST ", str(", ").join(argv) 209 | 210 | 211 | def link_nodes(isy, cmd, argv): 212 | """ 213 | Link mode glue 214 | """ 215 | if len(argv) == 0: 216 | do_interactive_link(isy) 217 | 218 | cmd = argv.pop(0).upper() 219 | 220 | if cmd in [ "START" ]: 221 | isy.node_discover_start() 222 | elif cmd in [ "STOP" ]: 223 | isy.node_discover_stop() 224 | elif cmd in [ "?" ]: 225 | raise cmdException("Syntax :\n\tLINK [START|STOP]\n" 226 | + "\tPlace PLM into discovery mode\n" ) 227 | exit(0) 228 | 229 | 230 | def do_interactive_link(isy): 231 | 232 | isy.load_nodes() 233 | old_node_set = set(isy.node_addrs()) 234 | 235 | print "Entering Linking Mode" 236 | isy.node_discover() 237 | raw_input("Press Enter to continue...") 238 | 239 | isy.node_discover_cancel() 240 | 241 | print "Exited Linking Mode" 242 | isy.load_nodes(reload=1) 243 | updated_node_set = set(isy.node_addrs() ) 244 | 245 | new_node_set = updated_node_set - old_node_set 246 | print "New Nodes : ", str(", ").join(new_node_set) 247 | 248 | exit(0) 249 | def do_del_node(isy, cmd, argv): 250 | """ 251 | Delete node glue 252 | """ 253 | if ( len(argv) == 0 or argv[0] == '?' or len(argv) > 1): 254 | raise cmdException("Syntax :\n\t{!s} ".format(cmd)) 255 | nodeid = argv.pop(0) 256 | print "isy.node_del(nodeid)" 257 | 258 | 259 | def do_restore(isy, cmd, argv): 260 | """ 261 | restore node glue 262 | """ 263 | if ( len(argv) > 0 and argv[0] != "?" ): 264 | nodeid = argv.pop(0) 265 | if nodeid.upper() == "ALL": 266 | print "isy.node_restore_all(nodeid)" 267 | else : 268 | print "isy.node_restore(nodeid)" 269 | else: 270 | raise cmdException("Syntax :\n\t{!s} \n\tto restore all nodes, use 'ALL' as node_id\n".format(cmd)) 271 | 272 | def do_node_enable(isy, cmd, argv): 273 | if ( len(argv) > 0 and argv[0] != "?" ): 274 | nodeid = argv.pop(0) 275 | isy.node_enable(nodeid, enable=1) 276 | else: 277 | raise cmdException("Syntax :\n\t{!s} ".format(cmd)) 278 | 279 | def do_node(isy, cmd, argv): 280 | 281 | node_cmd={ 282 | "ENABLE" : do_node_enable, 283 | "LS" : do_list_node, 284 | "LIST" : do_list_node, 285 | "MV" : do_rename_nodes, 286 | "RENAME" : do_rename_nodes, 287 | "DEL" : do_del_node, 288 | "RESTORE" : do_restore, 289 | "ON" : do_node_on, 290 | "OFF" : do_node_off, 291 | } 292 | 293 | if ( len(argv) == 0 or ( len(argv) > 0 and argv[0] == "?") ): 294 | cmdlist = ", ".join( node_cmd.keys() ) 295 | raise cmdException("Syntax :\n\t{!s} [node_id]\n\tAvalible commands :\n\t\t{!s}\n".format(cmd, cmdlist)) 296 | 297 | subcmd = argv.pop(0).upper() 298 | if subcmd in node_cmd: 299 | node_cmd[subcmd](isy, subcmd, argv) 300 | 301 | def do_nodes(isy, cmd, argv): 302 | if ( len(argv) == 0 or ( len(argv) > 0 and argv[0] == "?") ): 303 | raise cmdException("Syntax :\n\t{!s} \n".format(cmd)) 304 | 305 | subcmd = argv.pop(0).upper() 306 | 307 | if subcmd in [ "ENABLE" ]: 308 | do_node_enable(isy, subcmd, argv) 309 | elif subcmd in [ "LS", "LIST" ]: 310 | do_list_node(isy, subcmd, argv, nodetype=("node")) 311 | elif subcmd in ["MV", "RENAME"]: 312 | do_rename_nodes(isy, subcmd, argv) 313 | elif cmd in [ "RM", "DEL", "DELETE"]: 314 | do_del_node(isy, subcmd, argv) 315 | elif cmd in ["RESTORE"]: 316 | do_restore(isy, subcmd, argv) 317 | elif cmd in ["ON"]: 318 | nodeid = argv.pop(0) 319 | isy.node_comm( nodeid, "ON") 320 | elif cmd in ["OFF"]: 321 | nodeid = argv.pop(0) 322 | isy.node_comm( nodeid, "ON") 323 | else: 324 | raise cmdException("Syntax :\n\t{!s} cmd \n".format(cmd)) 325 | 326 | 327 | def do_node_on(isy, cmd, argv): 328 | nodeid = argv.pop(0) 329 | print "TURN", nodeid, "ON" 330 | isy.node_comm( nodeid, "ON") 331 | 332 | def do_node_off(isy, cmd, argv): 333 | nodeid = argv.pop(0) 334 | print "TURN", nodeid, "OFF" 335 | isy.node_comm( nodeid, "OFF") 336 | 337 | def do_prog(isy, cmd, argv): 338 | pass 339 | 340 | def do_folder(isy, cmd, argv): 341 | pass 342 | 343 | def do_scene(isy, cmd, argv): 344 | if ( len(argv) == 0 or ( len(argv) > 0 and argv[0] == "?") ): 345 | print_cmds(scene_commands_help) 346 | raise cmdException("Syntax :\n\t{!s} cmd \n".format(cmd)) 347 | 348 | subcmd = argv.pop(0).upper() 349 | 350 | if subcmd in ["ADD", "DELETE", "DEL", "RM" ]: 351 | do_scene_add(isy, subcmd, argv) 352 | elif subcmd in ["NEW"]: 353 | do_scene_new(isy, subcmd, argv) 354 | elif subcmd in ["LS", "LIST"]: 355 | do_list_node(isy, subcmd, argv, nodetype=("scene")) 356 | else: 357 | raise cmdException("Syntax :\n\t{!s} cmd \n".format(cmd)) 358 | 359 | def do_scene_new(isy, cmd, argv): 360 | if ( len(argv) == 0 or argv[0] == "?" or len(argv) > 1 ): 361 | raise cmdException("Syntax :\n\tSCENE NEW \n".format(cmd)) 362 | 363 | sceneid = argv.pop(0) 364 | 365 | r = isy.scene_new(sname=sceneid) 366 | 367 | def do_scene_add(isy, cmd, argv): 368 | """ 369 | add/del node to/from scene glue 370 | create new scene/group glue 371 | """ 372 | if ( len(argv) == 0 or argv[0] == "?" or len(argv) < 3 ): 373 | print "1" 374 | op = "ERR" 375 | else: 376 | op = "ADD" 377 | nflag=0x10 378 | op = argv.pop(0).upper() 379 | 380 | if op in ["ADD", "DELETE", "DEL", "RM" ]: 381 | nodeid = argv.pop(0) 382 | sceneid = argv.pop(0) 383 | else: 384 | op = "ERR" 385 | 386 | 387 | # print "a do_scene", op, nodeid, "sceneid", nflag, "argv=", str(",").join(argv) 388 | 389 | if len(argv) > 0: 390 | optflag = argv.pop(0).upper() 391 | if optflag in [ "CONTROLLER", "0X10", "16" ] or optflag.startswith("CON"): 392 | nflag=0x10 393 | elif optflag in [ "RESPONDER", "0X20", "32" ] or optflag.startswith("RES"): 394 | nflag=0x20 395 | else: 396 | op = "ERR" 397 | 398 | if op in [ "ADD" ]: 399 | # isy.scene_add_node( sceneid, nodeid, nflag) 400 | print "isy.scene_add_node", sceneid, nodeid, nflag 401 | if op in [ "DELETE", "DEL", "RM" ]: 402 | # isy.scene_del_node( sceneid, nodeid) 403 | print "isy.scene_del_node", sceneid, nodeid 404 | else: 405 | raise cmdException("Syntax :\n\t{!s} [ADD|DEL] [controller|responder]\n".format(cmd)) 406 | 407 | 408 | 409 | def do_list_node(isy, cmd, argv, nodetype=None): 410 | """ 411 | list node glue 412 | """ 413 | # "nodetype", ("node", "scene") 414 | if ( len(argv) > 0 and argv[0] == "?" ): 415 | raise cmdException("Syntax :\n\t{!s} [-l]".format(cmd)) 416 | 417 | if nodetype is None: 418 | nodetype = ("node", "scene") 419 | 420 | if len(argv) > 0 and argv[0] == "-l": 421 | pfmt = "{:<22} {:>12}\t{:<12}{!s:<12} {!s:}" 422 | else: 423 | pfmt = "{:<22} {:>12}\t{:<12}{!s:<12}" 424 | 425 | # see isy_nodes.py 426 | if len(argv) > 0: 427 | nodeid = argv.pop(0) 428 | node = isy.get_node(nodeid) 429 | print node.name, node.address, node.formatted, node.enabled, node.ramprate 430 | else: 431 | print(pfmt.format("Node Name", "Address", "Status", "Enabled", "Path")) 432 | print(pfmt.format("---------", "-------", "------", "------", "----")) 433 | for nod in isy.node_iter(nodetype=nodetype): 434 | if nod.objtype == "scene": 435 | print(pfmt.format(nod.name, nod.address, "-", "-", "-")) 436 | else: 437 | print(pfmt.format(nod.name, nod.address, 438 | nod.formatted, nod.enabled, nod.path)) 439 | 440 | 441 | def do_debuglevel(isy): 442 | pass 443 | 444 | def do_reboot(isy): 445 | pass 446 | # ask "are you sure ?" 447 | # isy.reboot(isy) 448 | 449 | def print_cmds(cmd_list=commands_help): 450 | for k, v in cmd_list.items(): 451 | print " {!s:<22} :\t{!s}".format(k, v) 452 | print "\nFor more detail on command run command with arg '?'" 453 | print "\n* == may not be implemented\n" 454 | 455 | 456 | if __name__ == '__main__': 457 | myisy = ISY.Isy(parsearg=1, faststart=1) # debug=0x80 458 | doit(myisy) 459 | exit(0) 460 | 461 | -------------------------------------------------------------------------------- /bin/isy_nestset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | a demo app that uses pynest to read values from the Nest 4 | and set vars in a ISY home automation device 5 | 6 | See also : https://github.com/smbaker/pynest 7 | https://github.com/evilpete/ISYlib-python 8 | 9 | """ 10 | __author__ = "Peter Shipley" 11 | 12 | 13 | try: 14 | import nest_thermostat as nest 15 | except ImportError as e: 16 | print "Package nest-thermostat required :", \ 17 | "https://pypi.python.org/pypi/nest-thermostat" 18 | exit(1) 19 | 20 | #import nest 21 | import sys 22 | import os 23 | from warnings import warn 24 | 25 | import pprint 26 | from optparse import OptionParser 27 | 28 | import ISY 29 | from ISY.IsyExceptionClass import IsyValueError, IsyResponseError, IsyPropertyError 30 | 31 | 32 | uuser=os.getenv('NEST_USER', None) 33 | upass=os.getenv('NEST_PASS', None) 34 | 35 | temperature_vars = ( "away_temperature_high", "away_temperature_low", 36 | "current_temperature", "target_temperature", 37 | "target_temperature_high", "target_temperature_low", 38 | "temperature_lock_high_temp", "temperature_lock_low_temp", 39 | "upper_safety_temp", "lower_safety_temp" ) 40 | 41 | 42 | # 43 | # Some of the following code was outright copy/pasted from pynest 44 | # 45 | def main(): 46 | 47 | parser = create_parser() 48 | (opts, args) = parser.parse_args() 49 | 50 | if (len(args)==0) or (args[0]=="help"): 51 | help_txt() 52 | sys.exit(-1) 53 | 54 | if (not opts.uuser) or (not opts.upass): 55 | print "a --user and --password are needed" 56 | sys.exit(-1) 57 | 58 | # get Nest Values 59 | n = nest.Nest(opts.uuser, opts.upass, None, 0, "F") 60 | n.login() 61 | n.get_status() 62 | 63 | if (args[0]=="show"): 64 | n.show_status() 65 | exit(0) 66 | 67 | # consolidate data into a single dict 68 | nest_values = dict ( ) 69 | nest_values.update(n.status["structure"][n.structure_id]) 70 | nest_values.update(n.status["shared"][n.serial] ) 71 | nest_values.update(n.status["device"][n.serial] ) 72 | 73 | if (args[0]=="dump"): 74 | pprint.pprint(nest_values) 75 | exit(0) 76 | 77 | # faststart=1 don't load node data ( we not going to use it ) 78 | myisy= ISY.Isy(debug=0, faststart=1) 79 | 80 | 81 | # not really needed but will speed up the first call 82 | #myisy.load_vars() 83 | 84 | auto_args = ( "nest_temp=current_temperature", 85 | "nest_humidity=current_humidity", 86 | "nest_away=away") 87 | 88 | if (args[0]=="auto"): 89 | args.pop(0) 90 | args.extend(auto_args) 91 | 92 | for var_arg in args: 93 | (isy_var, src_var) = var_arg.split('=') 94 | 95 | # check we got two value names 96 | if (not isy_var) or (not src_var): 97 | warn("Invalid arg : {0}".format(var_arg), RuntimeWarning) 98 | continue 99 | 100 | # check if net value name is valid 101 | if src_var not in nest_values: 102 | warn("Invalid Nest Value : {0}".format(isy_var), RuntimeWarning) 103 | continue 104 | 105 | # convert temperature to F 106 | # we can't convert in place since the value may be used twice 107 | if src_var in temperature_vars and not opts.celsius: 108 | set_value = nest_values[src_var] *1.8 + 32.0 109 | else: 110 | set_value = nest_values[src_var] 111 | 112 | 113 | try: 114 | # this will raise an error if there is a problem with name or set_value 115 | myisy.var_set_value(isy_var, int(set_value)) 116 | 117 | except IsyPropertyError: 118 | warn("Invalid Isy Var : {0}".format(isy_var), RuntimeWarning) 119 | continue 120 | except (IsyValueError , ValueError): 121 | print "invalid value :", nest_values[src_var] 122 | warn("Invalid value for ISY var: {0}".format(set_value), 123 | RuntimeWarning) 124 | continue 125 | except: 126 | print("Unexpected error:", sys.exc_info()[0]) 127 | warn("Unexpected error: {0}".format(sys.exc_info()[0]), 128 | RuntimeWarning) 129 | exit(0) 130 | else: 131 | if opts.verbose: 132 | print isy_var,"=", int(set_value) 133 | 134 | # end of main 135 | return 136 | 137 | 138 | 139 | # convert time stamp into someting we can pass along 140 | # if src_var == "$timestamp": 141 | # ti = nest_values["$timestamp"] // 1000 142 | # set_value = time.strftime("%m%d%H%M%S", time.localtime(ti)).lstrip('0') 143 | # print "shared timestamp", nest_values["$timestamp"], 144 | # time.ctime(ti), set_value 145 | # 146 | 147 | 148 | def create_parser(): 149 | parser = OptionParser(usage="isy_nest [options] isy_var=nest_var ", 150 | description="Commands: fan temp", 151 | version="unknown") 152 | 153 | parser.add_option("-u", "--user", dest="uuser", 154 | help="username for nest.com", metavar="USER", default=uuser) 155 | parser.add_option("-p", "--password", dest="upass", 156 | help="password for nest.com", metavar="PASSWORD", default=upass) 157 | parser.add_option("-c", "--celsius", dest="celsius", action="store_true", default=False, 158 | help="use celsius instead of farenheit") 159 | parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, 160 | help="Be verbose") 161 | 162 | parser.add_option("-s", "--serial", dest="serial", default=None, 163 | help="optional, specify serial number of nest thermostat to talk to") 164 | 165 | parser.add_option("-i", "--index", dest="index", default=0, type="int", 166 | help="optional, specify index number of nest to talk to") 167 | 168 | return parser 169 | 170 | def help_txt(): 171 | print "syntax: isy_nestset [options] isyvar=nestvar .... " 172 | print "options:" 173 | print " --user ... username on nest.com" 174 | print " --password ... password on nest.com" 175 | print " --celsius ... use celsius (the default is farenheit)" 176 | print " --serial ... optional, specify serial number of nest to use" 177 | print " --index ... optional, 0-based index of nest" 178 | print " (use --serial or --index, but not both)" 179 | print " -v ... verbose" 180 | print 181 | print "commands: isyvar=nestvar, show, help" 182 | print " show ... show available nest vars" 183 | print " help ... print this help" 184 | print " auto ... set vars nest_awaynest_humidity nest_temp in ISY" 185 | print 186 | print " home_temp=current_temperature" 187 | print " ... set the var on the isy named 'home_temp'" 188 | print " to the value of the nest current_temperature" 189 | print " Note: the varable has to preexist on the ISY device " 190 | print 191 | print "examples:" 192 | print " isy_nestset.py --user joe@user.com --password swordfish home_temp=current_temperature" 193 | print " isy_nestset.py --user joe@user.com --password swordfish show" 194 | 195 | # end of help 196 | return 197 | 198 | if __name__=="__main__": 199 | main() 200 | exit(0) 201 | -------------------------------------------------------------------------------- /bin/isy_net_res.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | 4 | Simple example trigger a registared network resource on the ISY 5 | 6 | if this script is call without any arg 7 | we print a list of network resources systems 8 | 9 | 10 | """ 11 | 12 | __author__ = "Peter Shipley" 13 | 14 | import sys 15 | import ISY 16 | from ISY.IsyExceptionClass import IsyResponseError, IsyValueError 17 | 18 | # import pprint 19 | 20 | 21 | 22 | 23 | def main(isy): 24 | 25 | isy.load_net_resource() 26 | 27 | if len(sys.argv[1:]) > 0: 28 | for a in sys.argv[1:]: 29 | try: 30 | isy.net_resource_run(a) 31 | except (IsyValueError, IsyResponseError), errormsg: 32 | print "problem calling ISY network resource to {!s} : {!s}".format(a, errormsg) 33 | continue 34 | else: 35 | print "Net resource sent to {!s}".format(a) 36 | else: 37 | pfmt = "{:<5}{:<16} {:<20}" 38 | print(pfmt.format("Id", "Name", "Addr")) 39 | print(pfmt.format("-" * 4, "-" * 20, "-" * 20)) 40 | for r in isy.net_resource_iter(): 41 | #pprint.pprint(r) 42 | if "id" in r: 43 | print(pfmt.format(r['id'], r['name'], r['ControlInfo']['host'])) 44 | 45 | 46 | 47 | if __name__=="__main__": 48 | myisy= ISY.Isy(parsearg=1) 49 | main(myisy) 50 | exit(0) 51 | -------------------------------------------------------------------------------- /bin/isy_net_wol.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 5 | Simple example to send a WoL to a registared system on the ISY 6 | 7 | if this script is call without any arg 8 | we print a list of registared WoL systems 9 | 10 | if we have any args we treat them as registared WoL Id's 11 | and attempt to send a WoL packet 12 | 13 | """ 14 | __author__ = "Peter Shipley" 15 | 16 | 17 | import sys 18 | import ISY 19 | from ISY.IsyExceptionClass import IsyResponseError, IsyValueError 20 | 21 | def main(isy): 22 | 23 | if len(sys.argv[1:]) > 0: 24 | for a in sys.argv[1:]: 25 | try: 26 | isy.net_wol(a) 27 | except (IsyValueError, IsyResponseError), errormsg: 28 | print "problem sending WOL to {!s} : {!s}".format(a, errormsg) 29 | continue 30 | else: 31 | print "WOL sent to {!s}".format(a) 32 | else: 33 | pfmt = "{:<5}{:<16} {:<20}" 34 | print(pfmt.format("Id", "Name", "Mac")) 35 | print(pfmt.format("-" * 4, "-" * 20, "-" * 20)) 36 | for w in isy.net_wol_iter(): 37 | if "id" in w: 38 | print(pfmt.format(w['id'], w['name'], w['mac'])) 39 | 40 | 41 | 42 | if __name__=="__main__": 43 | myisy= ISY.Isy(parsearg=1) 44 | main(myisy) 45 | exit(0) 46 | -------------------------------------------------------------------------------- /bin/isy_nodes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A simple example showing how 4 | to obtain and print status of every node 5 | """ 6 | 7 | __author__ = "Peter Shipley" 8 | 9 | import ISY 10 | 11 | 12 | def list_nodes(isy): 13 | """ iter though Isy Class object and print returned 14 | node's infomation 15 | """ 16 | if "-l" in myisy.unknown_args: 17 | pfmt = "{:<22} {:>12}\t{:<12}{!s:<12} {!s:}" 18 | else: 19 | pfmt = "{:<22} {:>12}\t{:<12}{!s:<12}" 20 | 21 | print(pfmt.format("Node Name", "Address", "Status", "Enabled", "Path")) 22 | print(pfmt.format("---------", "-------", "------", "------", "----")) 23 | for nod in isy: 24 | if nod.objtype == "scene": 25 | print(pfmt.format(nod.name, nod.address, "-", "-", "-")) 26 | else: 27 | print(pfmt.format(nod.name, nod.address, 28 | nod.formatted, nod.enabled, nod.path)) 29 | 30 | 31 | if __name__ == '__main__': 32 | myisy = ISY.Isy(parsearg=1) # debug=0x80 33 | list_nodes(myisy) 34 | exit(0) 35 | 36 | -------------------------------------------------------------------------------- /bin/isy_progs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | __author__ = "Peter Shipley" 5 | 6 | 7 | import ISY 8 | 9 | def list_progs(isy): 10 | 11 | pfmt = "{:<5}{:<24} {:<5} {:<5}{!s:<6} {!s:}" 12 | print(pfmt.format("Id", "Node Name", "Stat", "Run", "Enabled", "Path")) 13 | print(pfmt.format("-" * 4, "-" * 10, "-" * 4, "-" * 4,"-" * 4, "-" * 4)) 14 | for p in isy.prog_iter(): 15 | if p.folder: 16 | print(pfmt.format(p.id, p.name, p.status, "-", "-", p.path + "/" )) 17 | else: 18 | print(pfmt.format(p.id, p.name, p.status, p.running, p.enabled, p.path)) 19 | 20 | 21 | if __name__ == '__main__': 22 | myisy = ISY.Isy( parsearg=1 ) 23 | list_progs(myisy) 24 | exit(0) 25 | -------------------------------------------------------------------------------- /bin/isy_showevents.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Peter Shipley" 4 | 5 | 6 | import os 7 | 8 | from ISY.IsyEvent import ISYEvent, _print_event 9 | 10 | def main(): 11 | server = ISYEvent(debug=0x0000) 12 | 13 | # you can subscribe to multiple devices 14 | # server.subscribe('10.1.1.25') 15 | 16 | server.subscribe( 17 | addr=os.getenv('ISY_ADDR', '10.1.1.36'), 18 | userl=os.getenv('ISY_USER', "admin"), 19 | userp=os.getenv('ISY_PASS', "admin") 20 | ) 21 | 22 | server.set_process_func(_print_event, "") 23 | 24 | try: 25 | print('Use Control-C to exit') 26 | server.events_loop() #no return 27 | # for d in server.event_iter( ignorelist=["_0", "_11"] ): 28 | # server._print_event(d, "") 29 | except KeyboardInterrupt: 30 | print('Exiting') 31 | 32 | 33 | 34 | if __name__ == '__main__': 35 | main() 36 | exit(0) 37 | -------------------------------------------------------------------------------- /bin/isy_var.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """list ISY vars demo app 4 | 5 | Usage: %(program)s [options] [var=val] 6 | 7 | Options: 8 | 9 | --list 10 | 11 | """ 12 | #this is Ugly code and should be cleaned up 13 | 14 | __author__ = "Peter Shipley" 15 | 16 | import ISY 17 | import sys 18 | 19 | import getopt 20 | 21 | 22 | def list_vars_bash(myisy, csh=0): 23 | 24 | if csh: 25 | fmt = "set {0}={1}" 26 | else: 27 | fmt = "{0}={1}" 28 | 29 | for var in myisy.var_iter(): 30 | print fmt.format( var.name, var.value ) 31 | 32 | def list_vars(myisy): 33 | fmt = "{:<4} : {:<19}{:<5}\t{:<5}\t{:}" 34 | print fmt.format( "ID", "NAME", "VAL", "INIT", "DATE" ) 35 | for var in myisy.var_iter(): 36 | print fmt.format( var.id, var.name, var.value, var.init, var.ts ) 37 | 38 | def usage(code, msg=''): 39 | print >> sys.stderr, __doc__ 40 | print "globals ", globals() 41 | # % globals() 42 | if msg: 43 | print >> sys.stderr, msg 44 | sys.exit(code) 45 | 46 | class Options: 47 | debug = 0 48 | olist = 0 # olist cause "list" was taken 49 | bash = 0 50 | csh = 0 51 | 52 | def parseargs(): 53 | options = Options() 54 | 55 | # shortcut 56 | if len(sys.argv) == 1: 57 | options.olist = 1 58 | return options, [] 59 | 60 | try: 61 | opts, args = getopt.getopt( 62 | sys.argv[1:], "hbldc", 63 | ['bash', 'csh', 'list', 'help', 'debug=']) 64 | except getopt.error, e: 65 | usage(1, e) 66 | 67 | for opt, arg in opts: 68 | if opt in ('-h', '--help'): 69 | usage(0) 70 | elif opt in ('-b', '--bash'): 71 | options.bash = 1 72 | elif opt in ('-c', '--csh'): 73 | options.csh = 1 74 | elif opt in ('-l', '--list'): 75 | options.olist = 1 76 | elif opt in ('-d', '--debug'): 77 | options.debug = arg 78 | 79 | return options, args 80 | 81 | def set_vars(isy, *arg): 82 | # print "set_vars arg: ", arg 83 | for ar in arg: 84 | name, val = str(ar).split('=') 85 | print "set ", name, " to ", val 86 | if str(val).isdigit: 87 | try: 88 | isy.var_set_value(name, val) 89 | except LookupError: 90 | print "bad Var name: ", ar 91 | else: 92 | print "bad Value: ", ar 93 | return 94 | 95 | if __name__ == '__main__': 96 | 97 | options, arg = parseargs() 98 | myisy = ISY.Isy( debug=options.debug ) 99 | 100 | if options.olist: 101 | list_vars(myisy) 102 | elif options.bash or options.csh: 103 | list_vars_bash(myisy, options.csh) 104 | 105 | if len(arg): 106 | set_vars(myisy, *arg) 107 | 108 | -------------------------------------------------------------------------------- /bin/isy_web.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 5 | Simple example to List, Upload and download user webserver files from ISY 6 | 7 | 8 | {0} COMMAND [arg1 [arg2] ... ] 9 | 10 | optional arguments: 11 | -h, --help show this help message and exit 12 | -d [DEBUG]] debug options 13 | -a ADDR hostname or IP device 14 | -u USER Admin Username 15 | -p PASS Admin Password 16 | 17 | 18 | where command can be one owhere command can be one of: 19 | SEND : send / upload a file 20 | MKDIR : make directory 21 | RMDIR : remove / delete a directory 22 | RM : remove / delete a file 23 | DELETE : same as rm 24 | MV : move / rename a file or directory 25 | GET : get (download) a file 26 | DF : display free disk space 27 | LS : list files and directories 28 | LIST : same as ls 29 | DIR : same as ls 30 | 31 | All non-absolute remote filenames are assumed to be relative to /USER/WEB 32 | 33 | """ 34 | 35 | __author__ = "Peter Shipley" 36 | 37 | import os 38 | import sys 39 | import ISY 40 | # from ISY.IsyClass import Isy 41 | # from ISY.IsyExceptionClass import IsyResponseError, IsyValueError 42 | # import pprint 43 | 44 | __doc__ = __doc__.format( os.path.basename(sys.argv[0])) 45 | def main(isy): 46 | 47 | systemconf = 0 48 | 49 | # print "sys.argv[1:] = {!s}".format( len(sys.argv[1:]) ) 50 | 51 | # print "unknown_args : ", isy.unknown_args 52 | # print "sys.argv : ", sys.argv 53 | argv = isy.unknown_args[:] 54 | # print "argv : ", argv 55 | 56 | if len(argv) == 0: 57 | print_listing(isy) 58 | # print_listing_sort(isy) 59 | print_fsstat(isy) 60 | else: 61 | 62 | if argv.pop in ["-s", "--system"]: 63 | _ = argv.pop(0) 64 | systemconf = 1 65 | 66 | cmd = argv.pop(0).upper() 67 | # print "cmd = ", cmd 68 | if cmd in ["SEND", "PUT"]: 69 | 70 | srcfile = argv.pop(0) 71 | 72 | if ( len(argv) > 0 ): 73 | dstfile = argv.pop(0) 74 | else: 75 | dstfile = os.path.basename(srcfile) 76 | 77 | print "SEND {!s} {!s}".format(srcfile, dstfile) 78 | 79 | res = isy.user_uploadfile(srcfile=srcfile, name=dstfile) 80 | # if res: 81 | # print "res = ", res 82 | elif cmd in [ "MKDIR", "MD" ]: 83 | if ( len(argv) > 0 ): 84 | dstfile = argv.pop(0) 85 | res = isy.user_mkdir(name=dstfile) 86 | # if res: 87 | # print "res = ", res 88 | else: 89 | print "Missing Arg:\n\t{!s} ".format(cmd) 90 | elif cmd in ["RMDIR" , "RD"]: 91 | if ( len(argv) > 0 ): 92 | dstdir = argv.pop(0) 93 | res = isy.user_rmdir(name=dstdir) 94 | #if res: 95 | # print "res = ", res 96 | else: 97 | print "Missing Arg:\n\t{!s} ".format(cmd) 98 | elif cmd in [ "RM", "DEL", "DELETE"]: 99 | if ( len(argv) > 0 ): 100 | dstdir = argv.pop(0) 101 | res = isy.user_rm(name=dstdir) 102 | # if res: 103 | # print "res = ", res 104 | else: 105 | print "Missing Arg:\n\t{!s} ".format(cmd) 106 | elif cmd in ["MV", "RENAME"]: 107 | if ( len(argv) > 1 ): 108 | old = argv.pop(0) 109 | new = argv.pop(0) 110 | res = isy.user_mv(name=old, newName=new) 111 | # if res: 112 | # print "res = ", res 113 | else: 114 | print "Missing Arg:\n\t{!s} ".format(cmd) 115 | elif cmd == "GET": 116 | if ( len(argv) < 1 ): 117 | print "Missing Arg:\n\t{!s} [local_filename]".format(cmd) 118 | exit(0) 119 | 120 | name = argv.pop(0) 121 | 122 | if ( len(argv) > 0 ): 123 | dstfile = argv.pop(0) 124 | elif str(name).upper().startswith("/"): 125 | dstfile = os.path.basename(name) 126 | else: 127 | dstfile = name 128 | 129 | if systemconf: 130 | res = self.soapcomm("GetSysConf", name=varpath) 131 | else: 132 | res = isy.user_getfile(name=name) 133 | 134 | with open(dstfile, 'w') as fh: 135 | fh.write(res) 136 | print "{!s} bytes written to {!s}".format(len(res), dstfile) 137 | 138 | elif cmd == "DF": 139 | print_fsstat(isy) 140 | elif cmd in ["LS", "LIST", "DIR"]: 141 | print_listing_sort(isy) 142 | print_fsstat(isy) 143 | elif cmd == "HELP": 144 | help() 145 | exit(0) 146 | else: 147 | print "unknown command : {!s}".format(cmd) 148 | help() 149 | exit(0) 150 | 151 | 152 | def print_listing_sort(isy): 153 | """ print sorted file list """ 154 | mytotal = 0 155 | mylist = dict() 156 | flist = isy.user_dir() 157 | # pprint.pprint(flist) 158 | for key, val in flist.items(): 159 | if ( key == 'dir'): 160 | if isinstance(val, list): 161 | for l in val: 162 | if l["dir-name"] not in mylist: 163 | mylist[l["dir-name"]] = [ ] 164 | else: 165 | if val["dir-name"] not in mylist: 166 | mylist[val["dir-name"]] = [ ] 167 | elif ( key == 'file' ): 168 | if isinstance(val, list): 169 | for l in val: 170 | dirn = os.path.dirname(l["file-name"]) 171 | if dirn not in mylist: 172 | mylist[dirn] = [ ] 173 | 174 | mylist[dirn].append( "\t{!s}\t{!s}".format( 175 | sizeof_fmt(int(l["file-size"])), l["file-name"])) 176 | mytotal += int(l["file-size"]) 177 | else: 178 | dirn = os.path.dirname(val["file-name"]) 179 | if dirn not in mylist: 180 | mylist[dirn] = [ ] 181 | mylist[dirn].append( "\t{!s}\t{!s}".format( 182 | sizeof_fmt(int(val["file-size"])), val["file-name"])) 183 | mytotal += int(l["file-size"]) 184 | else: 185 | print "unknown list type : ", key 186 | # 187 | for key in sorted(mylist.iterkeys()): 188 | print key, ":" 189 | for l in mylist[key]: 190 | print l 191 | # print "Total:\t{:.1f}K".format( (float(mytotal) / 1024) ) 192 | print "Total:\t{!s}".format( sizeof_fmt(mytotal) ) 193 | 194 | def print_listing(isy): 195 | # 196 | flist = isy.user_dir() 197 | # pprint.pprint(flist) 198 | for key, val in flist.items(): 199 | if ( key == 'dir'): 200 | if isinstance(val, list): 201 | for l in val: 202 | print "dir\t{!s}".format(l["dir-name"]) 203 | else: 204 | print "dir\t{!s}".format(val["dir-name"]) 205 | elif ( key == 'file' ): 206 | # print type(key), type(val) 207 | # print "key val = ", key, val 208 | if isinstance(val, list): 209 | for l in val: 210 | print "file\t{!s}\t{!s}".format(l["file-size"], 211 | l["file-name"]) 212 | else: 213 | print "file\t{!s}\t{!s}".format(val["file-size"], 214 | val["file-name"]) 215 | else: 216 | print "unknown list type : ", key 217 | 218 | def sizeof_fmt(num): 219 | if num < 1024.0: 220 | return num 221 | for x in ['', 'KB', 'MB', 'GB', 'TB']: 222 | if num < 1024.0: 223 | return "{:3.1f} {!s}".format(num, x) 224 | num /= 1024.0 225 | 226 | def print_fsstat(isy): 227 | # 228 | flist = isy.user_fsstat() 229 | # pprint.pprint(flist) 230 | print "free={!s} used={!s} reserved={!s} total={!s}".format( 231 | sizeof_fmt(int(flist["free"])), 232 | sizeof_fmt(int(flist["used"])), 233 | sizeof_fmt(int(flist["reserved"])), 234 | sizeof_fmt(int(flist["total"])), 235 | ) 236 | 237 | def help(): 238 | print >> sys.stderr, __doc__ 239 | 240 | 241 | if __name__ == "__main__": 242 | 243 | myisy = ISY.Isy(faststart=2, eventupdates=0, parsearg=1) 244 | main(myisy) 245 | exit(0) 246 | -------------------------------------------------------------------------------- /docs/Using_IsyNode_Class.txt: -------------------------------------------------------------------------------- 1 | 2 | Using the IsyNode Class 3 | ====================== 4 | 5 | 6 | Examples 7 | -------- 8 | 9 | Get and print the status for the node called "Garage Light" 10 | 11 | import ISY 12 | myisy = ISY.Isy() 13 | 14 | garage_light = myisy.get_node("Garage Light") 15 | 16 | print "Node {:} is {:}".format(garage_light.name, garage_light.formatted) 17 | 18 | 19 | -- 20 | 21 | Get an object that represents the node called "Garage Light" 22 | and turn it off if it is on 23 | 24 | import ISY 25 | myisy = ISY.Isy() 26 | 27 | garage_light = myisy.get_node("Garage Light") 28 | if garage_light : 29 | garage_light.off() 30 | 31 | -- 32 | 33 | Alternately you can obtain a Node's object by indexing 34 | a Isy obj my the node name or address 35 | 36 | import ISY 37 | myisy = ISY.Isy() 38 | myisy["Garage Light"].off() 39 | 40 | Note this works by instantiating a IsyNode class obj then calling 41 | the IsyNode class mothod off() and discarding referance to the instance. 42 | (Note that the Isy Class caches created instances, so this not realy a loss) 43 | 44 | In this case using the Isy class obj method node_comm() is more 45 | efficient since the IsyNode mothod on() & off() simply call node_comm() 46 | 47 | -- 48 | 49 | Turn Node on to 50% ( assuming Node a dimable light ) 50 | 51 | import ISY 52 | myisy = ISY.Isy() 53 | 54 | garage_light = myisy.get_node("Garage Light") 55 | 56 | garage_light.on( 255 * .5 ) 57 | 58 | Note that devices are set with values from 0 - 255 59 | thus setting a light to the value of 50 will actually set it to 20% 60 | 61 | Note: the following are equivalent 62 | 63 | garage_light.on( 255 * .5 ) 64 | myisy.node_comm("Garage Light", "DON", 128) 65 | 66 | The following are "redirected" to the on command 67 | 68 | garage_light.status = 128 69 | garage_light.set_status(128) 70 | 71 | since the status property is readonly 72 | 73 | -- 74 | 75 | Turn on the "Garage Light" 76 | without instantiating a IsyNode class obj 77 | 78 | import ISY 79 | myisy = ISY.Isy() 80 | 81 | myisy.node_comm("Garage Light", "DON") 82 | 83 | -- 84 | 85 | List all Node names and some of their attributes 86 | 87 | import ISY 88 | 89 | myisy = ISY.Isy( ) 90 | 91 | pfmt = "{:<20} {:<12}\t{:<12}" 92 | print(pfmt.format("Node Name", "Address", "Status")) 93 | print(pfmt.format("---------", "-------", "------")) 94 | for nod in myisy : 95 | if nod.type == "scene" : 96 | print(pfmt.format(nod.name, nod.address, "-")) 97 | else : 98 | print(pfmt.format(nod.name, nod.address, nod.formatted)) 99 | # print(" members : ", len(nod.members_list())) 100 | 101 | 102 | -- 103 | 104 | 105 | Members 106 | -------- 107 | 108 | 109 | def on(self, *args) : 110 | def off(self) : 111 | def beep(self) : 112 | def member_iter(self): 113 | def member_list(self): 114 | def is_member(self, obj) : 115 | def __contains__(self, other): 116 | # def __contains__(self, other): 117 | def __init__(self, isy, ndict) : 118 | def _get_prop(self, prop): 119 | def _set_prop(self, prop, new_value): 120 | def get_rr(self): 121 | def set_rr(self, new_value): 122 | def get_ol(self): 123 | def set_ol(self, new_value): 124 | # def get_fm(self): 125 | def get_status(self): 126 | def set_status(self, new_value): 127 | # def on(self) : 128 | # def off(self) : 129 | # def beep(self) : 130 | def update(self) : 131 | def __bool__(self) : 132 | def __hash__(self) : 133 | # def __str__(self): 134 | def __float__(self): 135 | def set_status(self, new_value): 136 | def _gettype(self): 137 | def _getmembers(self) : 138 | def member_list(self) : 139 | def is_member(self, obj) : 140 | def member_iter(self, flag=0): 141 | def __iter__(self): 142 | def __contains__(self, other): 143 | def _gettype(self): 144 | def __iter__(self): 145 | def __contains__(self, other): 146 | -------------------------------------------------------------------------------- /docs/Using_IsyProg_Class: -------------------------------------------------------------------------------- 1 | 2 | Using the IsyVar Class 3 | ====================== 4 | 5 | 6 | Run the "then" code block of the program named "Xmas Lights off" 7 | 8 | import ISY 9 | myisy = ISY.Isy( ) 10 | 11 | pg = myisy.get_prog("Xmas Lights off") 12 | 13 | pg.send_command("runThen") 14 | 15 | print "Program ", pg.name, "state is", pg.status 16 | 17 | -- 18 | 19 | Do the same thing without instantiating a IsyProgram class obj 20 | 21 | 22 | import ISY 23 | myisy = ISY.Isy( ) 24 | 25 | myisy.prog_comm("Xmas Lights off", "runThen") 26 | st = myisy.prog_get_prop("Xmas Lights off", "status") 27 | 28 | print ""Program Xmas Lights off state is", st 29 | 30 | 31 | 32 | -- 33 | 34 | List all Programs and print their name and status 35 | 36 | pfmt = "{:<5}{:<12} {:<5}{:<5}{:<5}" 37 | print(pfmt.format("Id", "Node Name", "Stat", "Run", "Enabled")) 38 | print(pfmt.format("-" * 4, "-" * 10, "-" * 4, "-" * 4,"-" * 4)) 39 | for p in myisy.prog_iter(): 40 | if p.folder == "false" : 41 | print(pfmt.format(p.id, p.name, p.status, p.running, p.enabled)) 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Methods 51 | ------ 52 | def load_prog(self): 53 | def get_prog(self, pname) : 54 | def prog_iter(self): 55 | def prog_comm(self, paddr, cmd) : 56 | 57 | -------------------------------------------------------------------------------- /docs/Using_IsyVar_Class.txt: -------------------------------------------------------------------------------- 1 | 2 | Using the IsyVar Class 3 | ====================== 4 | 5 | Set the value if ISY var named 'some other var' to 10 6 | 7 | import ISY 8 | myisy = ISY.Isy( ) 9 | 10 | some_other_var = myisy.get_var("some other var") 11 | some_other_var.value = 10 12 | 13 | -- 14 | 15 | Get the value if ISY var named 'some other var' 16 | without instantiating a IsyVar class obj 17 | 18 | import ISY 19 | myisy = ISY.Isy( ) 20 | 21 | some_other_var = myisy.var_get_value('some other var') 22 | 23 | -- 24 | 25 | Set the value if ISY var named 'some other var' to 10 26 | without instantiating a IsyVar class obj 27 | 28 | import ISY 29 | myisy = ISY.Isy( ) 30 | 31 | myisy.var_set_value('some other var', 10) 32 | 33 | -- 34 | 35 | get and compair the value of two vars 36 | 37 | import ISY 38 | myisy = ISY.Isy( ) 39 | 40 | some_var = myisy.get_var("some var") 41 | some_other_var = myisy.get_var("some other var") 42 | 43 | if some_var > some_other_var : 44 | print "some var is more then some other var" 45 | 46 | -- 47 | 48 | list all var name and their attributes 49 | 50 | import ISY 51 | myisy = ISY.Isy( ) 52 | 53 | fmt = "{:<4} : {:<19}{:<5}\t{:<5}\t{:}" 54 | print fmt.format( "ID", "NAME", "VAL", "INIT", "DATE" ) 55 | for var in myisy.var_iter() : 56 | print fmt.format( var.id, var.name, var.value, var.init, var.ts ) 57 | 58 | -- 59 | 60 | Here is a program that get the ISY var named 'somevar', 61 | reports if it's value is over 100 62 | 63 | import ISY 64 | myisy = ISY.Isy( ) 65 | 66 | # 67 | # Get the Isy varable called "somevar" 68 | # if is exists and is over 100, divid it by 2 69 | try: 70 | some_var = myisy.get_var("somevar") 71 | except LookupError : 72 | print "Isy Var 'somevar' does not exist" 73 | else : 74 | # if the value of 'somevar' is over 100 75 | # divide the value in half 76 | if some_var > 100 : 77 | print "The ISY var 'somevar' is over 30" 78 | some_var /= 2 79 | 80 | 81 | 82 | # def get_var_ts(self): 83 | # def get_var_type(self): 84 | def get_var_init(self): 85 | def set_var_init(self, new_value): 86 | def get_var_value(self): 87 | def set_var_value(self, new_value): 88 | # def get_var_id(self): 89 | # def get_var_name(self): 90 | def __cast(self, other): 91 | def bit_length(self): return bit_length(self._mydict["val"]) 92 | def __str__(self): return str(self._mydict["val"]) 93 | def __long__(self): return long(self._mydict["val"]) 94 | def __float__(self): return float(self._mydict["val"]) 95 | def __int__(self): return int(self._mydict["val"]) 96 | def __bool__(self) : return int( self._mydict["val"]) != 0 97 | def __abs__(self): return abs(self._mydict["val"]) 98 | def __lt__(self, n): return self._mydict["val"] < self.__cast(n) 99 | def __le__(self, n): return self._mydict["val"] <= self.__cast(n) 100 | def __eq__(self, n): return self._mydict["val"] == self.__cast(n) 101 | def __ne__(self, n): return self._mydict["val"] != self.__cast(n) 102 | def __gt__(self, n): return self._mydict["val"] > self.__cast(n) 103 | def __ge__(self, n): return self._mydict["val"] >= self.__cast(n) 104 | def __cmp__(self, n): return cmp(self._mydict["val"], self.__cast(n)) 105 | def __add__(self, n): 106 | def __iadd__(self, n): 107 | def __sub__(self, n): 108 | def __isub__(self, n): 109 | def __mul__(self, n): return (self._mydict["val"]*n) 110 | def __imul__(self, n): 111 | def __floordiv__(self, n): return self._mydict["val"] // self.__cast(n) 112 | def __ifloordiv__(self, n): 113 | def __truediv__(self, n): return (self._mydict["val"] / self.__cast(n)) 114 | def __itruediv__(self, n): 115 | def __imod__(self, n): 116 | def __ipow__(self, n): 117 | def __neg__(self): return - self._mydict["val"] 118 | def __and__(self, n): return self._mydict["val"] & self.__cast(n) 119 | def __iand__(self, n): 120 | def __or__(self, n): return self._mydict["val"] | self.__cast(n) 121 | def __ior__(self, n): 122 | def __ixor__(self, n): 123 | def __xor__(self, n): return self._mydict["val"] ^ self.__cast(n) 124 | def __invert__(self): return ~ self._mydict["val"] 125 | def __irshift__(self, n): 126 | def __ilshift__(self, n): 127 | def __repr__(self): 128 | -------------------------------------------------------------------------------- /docs/Using_Isy_Class.txt: -------------------------------------------------------------------------------- 1 | 2 | Using the Isy Class 3 | ====================== 4 | 5 | 6 | 7 | Args 8 | ---- 9 | 10 | Obj class the represents the ISY device 11 | 12 | Keyword Args : 13 | addr : IP address of ISY 14 | userl/userp : User Login / Password 15 | 16 | debug : Debug flags (default 0) 17 | cachetime : cache experation time [NOT USED] (default 0) 18 | faststart : ( ignored if eventupdate is used ) 19 | 0=preload cache as startup 20 | 1=load cache on demand 21 | eventupdates: run a sub-thread and stream events updates from ISY 22 | same effect as calling Isy().start_event_thread() 23 | 24 | 25 | Update Threads 26 | ------------- 27 | 28 | 29 | The Isy Class Obj can maintain near realtime updates by subscribing to 30 | a event stream from the ISY device, This data is processed via a subthread 31 | 32 | this can be stated automaticly by setting the keyword option : 33 | 34 | eventupdates=1 35 | 36 | or with the command : 37 | 38 | start_event_thread() 39 | 40 | this thread process can be halted with the command : 41 | 42 | start_event_thread() 43 | 44 | 45 | Methods 46 | ------- 47 | 48 | * get/set var val 49 | 50 | * get/set node properties 51 | 52 | * get program properties, run programs 53 | 54 | * get log info 55 | 56 | * get event stream info 57 | 58 | 59 | Examples 60 | ------- 61 | 62 | -- 63 | 64 | 65 | Turn on the "Garage Light" 66 | (without using the IsyNode class obj 67 | 68 | import ISY 69 | myisy = ISY.Isy() 70 | 71 | myisy.node_comm("Garage Light", "DON") 72 | 73 | 74 | -- 75 | 76 | Most Isy classes can be instantiated by calling the mothod 77 | get_, ( eg : get_node(), get_var() 78 | (see Using_IsyNode_Class.txt and Using_IsyVar_Class.txt for more info) 79 | 80 | Node's object by indexing a Isy obj my the node name or address 81 | 82 | import ISY 83 | myisy = ISY.Isy() 84 | myisy["Garage Light"].off() 85 | 86 | -- 87 | 88 | Get the value if ISY var named 'some other var' 89 | without instantiating a IsyVar class obj 90 | 91 | import ISY 92 | myisy = ISY.Isy( ) 93 | 94 | some_other_var = myisy.var_get_value('some other var') 95 | 96 | 97 | -- 98 | 99 | 100 | 101 | WOL (Wake On Lan) 102 | ----------------- 103 | def load_wol(self) : 104 | def wol(self, wid) : 105 | def wol_names(self, vname) : 106 | def wol_iter(): 107 | 108 | 109 | Climate Info 110 | ------------- 111 | 112 | def load_clim(self) : 113 | def clim_get_val(self, prop): 114 | def clim_query(self): 115 | def clim_iter(self): 116 | 117 | Device Log 118 | ------------- 119 | 120 | def load_log_type(self): 121 | def load_log_id(self): 122 | def log_reset(self, errorlog = 0 ): 123 | def log_iter(self, error = 0 ): 124 | def log_query(self, errorlog = 0, resetlog = 0 ): 125 | def log_format_line(self, line) : 126 | 127 | ====== 128 | 129 | def start_event_thread(self, mask=0): 130 | def stop_event_tread(self) : 131 | 132 | def load_conf(self) : 133 | def load_nodes(self) : 134 | def load_node_types(self) : 135 | def load_vars(self) : 136 | def load_prog(self): 137 | 138 | 139 | 140 | def node_names(self) : 141 | def scene_names(self) : 142 | def node_addrs(self) : 143 | def scene_addrs(self) : 144 | 145 | def get_node(self, node_id) : 146 | 147 | def node_set_prop(self, naddr, prop, val) : 148 | def node_comm(self, naddr, cmd, *args) : 149 | def node_get_type(self, typid) : 150 | def node_iter(self, nodetype=""): 151 | ## def set_var_value(self, vname, val, init=0): 152 | def var_set_value(self, var, val, prop="val") : 153 | def var_get_value(self, var, prop="val") : 154 | # def var_names(self) : 155 | def var_addrs(self) : 156 | def get_var(self, vname) : 157 | def var_get_type(self, var) : 158 | def var_iter(self, vartype=0): 159 | 160 | 161 | 162 | def get_prog(self, pname) : 163 | def prog_iter(self): 164 | def prog_comm(self, paddr, cmd) : 165 | 166 | 167 | def x10_comm(self, unit, cmd) : 168 | # def debugerror(self) : 169 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | # python setup.py --dry-run --verbose install 3 | 4 | import os.path 5 | from setuptools import setup, find_packages 6 | from distutils.command.install_scripts import install_scripts 7 | import ssl 8 | 9 | # ssl._create_default_https_context = ssl._create_unverified_context 10 | 11 | 12 | from distutils.core import setup 13 | 14 | class install_scripts_and_symlinks(install_scripts): 15 | '''Like install_scripts, but also replicating nonexistent symlinks''' 16 | def run(self): 17 | # print "=============install_scripts_and_symlinks run" 18 | install_scripts.run(self) 19 | # Replicate symlinks if they don't exist 20 | for script in self.distribution.scripts: 21 | # print "\n---script = ",script 22 | if os.path.islink(script): 23 | target = os.readlink(script) 24 | newlink = os.path.join(self.install_dir, os.path.basename(script)) 25 | 26 | setup( 27 | name='ISYlib', 28 | version='0.1.20160710', 29 | author='Peter Shipley', 30 | author_email='Peter.Shipley@gmail.com', 31 | packages=find_packages(), 32 | scripts=[ 'bin/isy_find.py', 'bin/isy_log.py', 'bin/isy_nestset.py', 33 | 'bin/isy_net_wol.py', 'bin/isy_progs.py', 34 | 'bin/isy_showevents.py', 'bin/isy_web.py', 35 | 'bin/isy_nodes.py', 'bin/isy_var.py' 36 | ], 37 | # data_files=[ 38 | # ('examples', ['bin/isy_find.py', 'bin/isy_progs.py', 39 | # 'bin/isy_log.py', 'bin/isy_net_wol.py']), 40 | # ('bin', ['bin/isy_nodes.py', 'bin/isy_var.py']) 41 | # ], 42 | url='https://github.com/evilpete/ISYlib-python', 43 | license='BSD', 44 | download_url='https://github.com/evilpete/ISYlib-python/archive/0.1.20160110.tar.gz', 45 | description='Python API for the ISY home automation controller.', 46 | long_description=open('README.txt').read(), 47 | cmdclass = { 'install_scripts': install_scripts_and_symlinks } 48 | ) 49 | 50 | 51 | 52 | 53 | --------------------------------------------------------------------------------