diff --git a/tests/test_comnav_0_108_burglar_alarm_on.py b/tests/test_comnav_0_108_burglar_alarm_on.py new file mode 100644 index 0000000..bc30e9c --- /dev/null +++ b/tests/test_comnav_0_108_burglar_alarm_on.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2020 Chris Caron +# All rights reserved. +# +# This code is licensed under the MIT License. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files(the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions : +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import mock +import requests +from os.path import join +from os.path import dirname + +from ultrasync import UltraSync +from ultrasync.common import NX595EVendor + +# Disable logging for a cleaner testing output +import logging +logging.disable(logging.CRITICAL) + +# Reference Directory +ULTRASYNC_TEST_VAR_DIR = join( + dirname(__file__), 'var', NX595EVendor.COMNAV, '0.108-burglar-alarm-on') + + +@mock.patch('requests.Session.post') +def test_comnav_0_108_active_burglary(mock_post): + """ + Test ComNav v0.108 Hub Communication with Burglary in Progress + + """ + + # A area response object + arobj = mock.Mock() + + # Simulate a valid login return + with open(join(ULTRASYNC_TEST_VAR_DIR, 'area.htm'), 'rb') as f: + arobj.content = f.read() + arobj.status_code = requests.codes.ok + + # A zone response object + zrobj = mock.Mock() + + # Simulate initial zone configuration + with open(join(ULTRASYNC_TEST_VAR_DIR, 'zones.htm'), 'rb') as f: + zrobj.content = f.read() + zrobj.status_code = requests.codes.ok + + # A sequence response object + seq_obj = mock.Mock() + + # Simulate initial sequence configuration + with open(join(ULTRASYNC_TEST_VAR_DIR, 'seq.xml'), 'rb') as f: + seq_obj.content = f.read() + seq_obj.status_code = requests.codes.ok + + # A zone state response object + zst_obj = mock.Mock() + + # Simulate initial zone fetch configuration + with open(join(ULTRASYNC_TEST_VAR_DIR, 'zstate.xml'), 'rb') as f: + zst_obj.content = f.read() + zst_obj.status_code = requests.codes.ok + + # An area state response object + ast_obj = mock.Mock() + + # Simulate initial area status fetch configuration + with open(join(ULTRASYNC_TEST_VAR_DIR, 'status.xml'), 'rb') as f: + ast_obj.content = f.read() + ast_obj.status_code = requests.codes.ok + + # Assign our response object to our mocked instance of requests + mock_post.side_effect = (arobj, zrobj) + + uobj = UltraSync() + + # Perform a login which under the hood queries both area.htm and zones.htm + # (in that order) + assert uobj.login() + assert uobj.vendor is NX595EVendor.COMNAV + assert uobj.version == '0.108' + assert uobj.release == 'm' + + assert isinstance(uobj.areas, dict) + # we only have 1 area defined in our test file + assert len(uobj.areas) == 1 + assert uobj.areas[0]['name'] == 'Home' + assert uobj.areas[0]['bank'] == 0 + assert uobj.areas[0]['sequence'] == 208 + assert uobj.areas[0]['status'] == 'Burglar Alarm' + + assert isinstance(uobj.zones, dict) + # our total zones defined + assert len(uobj.zones) == 17 + zone_map = [ + { + 'bank': 0, + 'name': 'MasterBed Motion', + }, { + 'bank': 1, + 'name': 'Living Room Motion', + }, { + 'bank': 2, + 'name': 'Kitchen/DiningMotion', + }, { + 'bank': 3, + 'name': 'Theater Motion', + }, { + 'bank': 4, + 'name': 'Hallway Motion', + } + ] + for entry in zone_map: + assert entry['bank'] in uobj.zones + assert uobj.zones[entry['bank']]['bank'] == entry['bank'] + assert uobj.zones[entry['bank']]['name'] == entry['name'] + assert uobj.zones[entry['bank']]['sequence'] == 1 + assert uobj.zones[entry['bank']]['status'] == \ + entry.get('status', 'Ready') + assert uobj.zones[entry['bank']]['can_bypass'] is None + + # A call to login.cgi (which fetches area.html) and then zones.htm + assert mock_post.call_count == 2 + assert mock_post.call_args_list[0][0][0] == \ + 'http://zerowire/login.cgi' + assert mock_post.call_args_list[1][0][0] == \ + 'http://zerowire/user/zones.htm' diff --git a/tests/var/comnav/0.108-burglar-alarm-on/area.htm b/tests/var/comnav/0.108-burglar-alarm-on/area.htm new file mode 100644 index 0000000..959ad9d --- /dev/null +++ b/tests/var/comnav/0.108-burglar-alarm-on/area.htm @@ -0,0 +1,71 @@ + + + + + ComNav :: Secure Network + + + + + + + + + + +
+ + + +
+ + + + diff --git a/tests/var/comnav/0.108-burglar-alarm-on/master.js b/tests/var/comnav/0.108-burglar-alarm-on/master.js new file mode 100644 index 0000000..a7794ca --- /dev/null +++ b/tests/var/comnav/0.108-burglar-alarm-on/master.js @@ -0,0 +1,759 @@ +// Determines when a request is considered "timed out" +var timeOutMS = 5000; //ms +var profwin; +var globalXmlHttp; +var hillsBuild = false; +var variant = 1; + +// Stores a queue of AJAX events to process +var ajaxList = new Array(); + +/** + * stores the number of dots current being shown in the rescan button + */ +var scanDots = 0; + +/** + * current iteration of the bss info read from WiFi module. + */ +var currBss = 0; + +/** + * whether to destroy or build other networks table + */ +var otherNetworkExpanded = 1; + + +// Initiates a new AJAX command +// url: the url to access +// container: the document ID to fill, or a function to call with response XML (optional) +// repeat: true to repeat this call indefinitely (optional) +// data: an URL encoded string to be submitted as POST data (optional) +function newAJAXCommand(url, container, repeat, data) +{ + // Set up our object + var newAjax = new Object(); + var theTimer = new Date(); + + newAjax.url = url; + newAjax.container = container; + newAjax.repeat = repeat; + newAjax.ajaxReq = null; + + if (data == null) + data = "sess=" + getSession(); + else { + data = "sess=" + getSession() + '&' + data; + } + + // Create and send the request + if (window.XMLHttpRequest) { + newAjax.ajaxReq = new XMLHttpRequest(); + newAjax.ajaxReq.open((data == null) ? "GET" : "POST", newAjax.url, true); + newAjax.ajaxReq.send(data); + // If we're using IE6 style (maybe 5.5 compatible too) + } else if (window.ActiveXObject) { + newAjax.ajaxReq = new ActiveXObject("Microsoft.XMLHTTP"); + if (newAjax.ajaxReq) { + newAjax.ajaxReq.open((data == null) ? "GET" : "POST", newAjax.url, true); + newAjax.ajaxReq.send(data); + } + } + + newAjax.lastCalled = theTimer.getTime(); + // Store in our array + ajaxList.push(newAjax); +} + +// Loops over all pending AJAX events to determine if any action is required +function pollAJAX() +{ + var curAjax = new Object(); + var theTimer = new Date(); + var elapsed; + + // Read off the ajaxList objects one by one + for (i = ajaxList.length; i > 0; i--) { + curAjax = ajaxList.shift(); + if (!curAjax) + continue; + elapsed = theTimer.getTime() - curAjax.lastCalled; + + // If we succeeded + if (curAjax.ajaxReq.readyState == 4 && curAjax.ajaxReq.status == 200 && curAjax.ajaxReq.responseText.substring(0, 9).toUpperCase() != " timeOutMS) { + // Invoke the user function with null input + if (typeof(curAjax.container) == 'function') { + curAjax.container(null); + + } else { + // Alert the user + alert("Command failed.\nConnection to Panel was lost."); + window.location.replace("/login.htm"); + + } + + curAjax.ajaxReq.abort(); + curAjax.ajaxReq = null; + + // If it's a repeatable request, then do so + if (curAjax.repeat) + newAJAXCommand(curAjax.url, curAjax.container, curAjax.repeat); + continue; + } + + // Otherwise, just keep waiting + ajaxList.push(curAjax); + } + + // Call ourselves again in 10ms + setTimeout("pollAJAX()", 300); +} + +function callCalendar(what) +{ + try { + cal.select(what, 'anchor1', 'dd/MM/yyyy'); + } catch (err) { + cal.popupWindow.close(); + return; + } +} + +function getXMLValue(xmlData, field) +{ + result = ""; + + if (xmlData != null) { + tag = '<' + field + '>'; + var s = xmlData.indexOf(tag); + var e = xmlData.indexOf(''); + if (s >= 0 && e > s) + result = xmlData.substring(s + tag.length, e); + } + + return result; +} + +function GetXmlHttpObject() +{ + var xmlHttp = null; + + try { + // Firefox, Opera 8.0+, Safari + xmlHttp = new XMLHttpRequest(); + } catch (e) { + // Internet Explorer + try { + xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + } + + return xmlHttp; +} + +function hideElement(whichElement) +{ + document.getElementById(whichElement).style.display = "none"; +} + +function viewElement(whichElement) +{ + document.getElementById(whichElement).style.display = "inline"; +} + +function textToNumber(text) +{ + var i; + var mult = 1; + var result = 0; + + for (i = 0; i < (text.length) / 2; i++) { + result += parseInt(text.substring((2 * i), (2 * i) + 2), 16) * mult; + mult *= 256; + } + return result; +} + +function numberToText(text, digits) +{ + var number = parseInt(text); + if (number < 0) { + number += Math.pow(256, digits / 2); + } + var hexnumber = number.toString(16).toUpperCase(); + var result = ""; + if (hexnumber == "NAN") + return result; + while ((hexnumber.length) < digits) + hexnumber = "0" + hexnumber; + for (i = hexnumber.length; i > 0; i -= 2) + result += hexnumber.substring(i - 2, i); + return result; +} + +function dateAndTimeToInt(value) +{ + fs = value.indexOf('/', 0); + ss = value.indexOf('/', fs + 1); + sp = value.indexOf(' ', 0); + fc = value.indexOf(':', 0); + sc = value.indexOf(':', fc + 1); + year = parseInt(value.substring(ss + 1, sp), 10); + if (year < 100) + year += 2000; + month = parseInt(value.substring(fs + 1, ss), 10); + day = parseInt(value.substring(0, fs), 10); + hr = parseInt(value.substring(sp + 1, fc), 10); + if (sc == -1) { + sec = 0; + min = parseInt(value.substring(fc + 1, value.length), 10); + } else { + sec = parseInt(value.substring(sc + 1), 10); + min = parseInt(value.substring(fc + 1, sc), 10); + } + if (sec < 0 || sec > 59) + sec = 0; + return Date.UTC(year, month - 1, day, hr, min, sec) / 1000; +} + +function RFC3339dateAndTimeToInt(value) +{ + fs = value.indexOf('-', 0); + ss = value.indexOf('-', fs + 1); + sp = value.indexOf(' ', 0); + fc = value.indexOf(':', 0); + sc = value.indexOf(':', fc + 1); + year = parseInt(value.substring(0, fs), 10); + if (year < 100) + year += 2000; + month = parseInt(value.substring(fs + 1, ss), 10); + day = parseInt(value.substring(ss + 1, sp), 10); + hr = parseInt(value.substring(sp + 1, fc), 10); + if (sc == -1) { + sec = 0; + min = parseInt(value.substring(fc + 1, value.length), 10); + } else { + sec = parseInt(value.substring(sc + 1), 10); + min = parseInt(value.substring(fc + 1, sc), 10); + } + if (sec < 0 || sec > 59) + sec = 0; + + return Date.UTC(year, month - 1, day, hr, min, sec) / 1000; +} + +// convert "YYYY-MM-DD" to days since 1970-1-1 +function RFC3339dateToInt(value) +{ + asa = value.split('-'); + year = parseInt(asa[0], 10); + month = parseInt(asa[1], 10); + day = parseInt(asa[2], 10); + if (year < 100) + year += 2000; + result = Date.UTC(year, month - 1, day) / 86400000; + + return result; +} + +function dateToInt(value) +{ + asa = value.split('/'); + year = parseInt(asa[2], 10); + month = parseInt(asa[1], 10); + day = parseInt(asa[0], 10); + if (year < 100) + year += 2000; + result = Date.UTC(year, month - 1, day) / 86400000; + + return result; +} + +function intToDate(k) +{ + myDate = new Date(k * 86400000); + resp = myDate.getUTCDate() + '/' + (myDate.getUTCMonth() + 1) + '/' + myDate.getUTCFullYear(); + + return resp; +} + +function timeToInt(value) +{ + fc = value.indexOf(':', 0); + hr = parseInt(value.substring(0, fc), 10); + min = parseInt(value.substring(fc + 1), 10); + + return (hr * 60) + min; +} + +function intToTime(dt) +{ + myDate = new Date(dt * 60000); + resp = myDate.getUTCHours() + ':'; + if (myDate.getUTCMinutes() < 10) + resp += '0'; + resp += myDate.getUTCMinutes(); + + return resp; +} + +function intToDateAndTime(dt) +{ + var myDate = new Date(dt * 1000); + resp = myDate.getUTCDate() + '/' + + (myDate.getUTCMonth() + 1) + '/' + + myDate.getUTCFullYear() + ' ' + + myDate.getUTCHours() + ':'; + if (myDate.getUTCMinutes() < 10) + resp += '0'; + resp += myDate.getUTCMinutes() + ':'; + if (myDate.getUTCSeconds() < 10) + resp += '0'; + resp += myDate.getUTCSeconds(); + + return resp; +} + +function pad(n) +{ + return n < 10 ? '0' + n : n +} + +function justRFC3339Date(dt) +{ + var d = new Date(dt * 1000); + resp = d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate()); + return resp; +} + +function justDate(dt) +{ + dandt = intToDateAndTime(dt); + return dandt.substring(0, dandt.indexOf(' ', 0)); +} + +function justTime(dt) +{ + dandt = intToDateAndTime(dt); + return dandt.substring(dandt.indexOf(' ', 0) + 1); +} + +function df(element) +{ + e = document.getElementById(element); + e.select(); + e.focus(); +} + +function changeInputType(oldObject, oType) +{ + var newObject = document.createElement('input'); + + newObject.type = oType; + + if (oldObject.value) newObject.value = oldObject.value; + if (oldObject.readonly) newObject.readonly = oldObject.readonly; + if (oldObject.disabled) newObject.disabled = oldObject.disabled; + if (oldObject.onmouseover) newObject.onmouseover = oldObject.onmouseover; + if (oldObject.onkeyup) newObject.onkeyup = oldObject.onkeyup; + if (oldObject.onkeydown) newObject.onkeydown = oldObject.onkeydown; + if (oldObject.onfocus) newObject.onfocus = oldObject.onfocus; + if (oldObject.name) newObject.name = oldObject.name; + if (oldObject.id) newObject.id = oldObject.id; + if (oldObject.onclick) newObject.onclick = oldObject.onclick; + if (oldObject.className) newObject.className = oldObject.className; + oldObject.parentNode.replaceChild(newObject, oldObject); + + return newObject; +} + +function getChannels(thisForm, formNo, formName) +{ + with(thisForm) { + globalXmlHttp = GetXmlHttpObject(); + if (globalXmlHttp == null) { + alert("Your browser does not support AJAX!"); + return; + } + var url = "/muser/chan.xml"; + var params = "sess=" + getSession(); + globalXmlHttp.open("POST", url, true); + globalXmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + + globalXmlHttp.onreadystatechange = function() { + if (globalXmlHttp.readyState == 4) { + if (globalXmlHttp.status == 200 && globalXmlHttp.responseText.substring(0, 9).toUpperCase() != " 1 && cd.substring(0, 2) == formNo) { + tbl1.insertRow(s); + tbl1.rows[s].insertCell(0); + tbl1.rows[s].insertCell(1); + var lab = document.createTextNode(formName + (s + 1)); + tbl1.rows[s].insertCell(0).setAttribute('class', 'lab'); + tbl1.rows[s].cells[0].appendChild(lab); + var ip = document.createElement('input'); + ip.setAttribute('type', 'text'); + ip.setAttribute('id', 'dest' + i); + ip.setAttribute('class', 'txt'); + ip.value = cd.substring(2); + tbl1.rows[s].cells[1].appendChild(ip); + s++; + } + } + document.getElementById('ct').appendChild(tbl1); + + + } else + if (globalXmlHttp.status == 403 || globalXmlHttp.status == 302 || globalXmlHttp.status == 404) { + window.location.replace("/login.htm"); + } + globalXmlHttp = null; + } + } + globalXmlHttp.send(params); + } +} + +function putChannels(thisForm, params) +{ + with(thisForm) { + globalXmlHttp = GetXmlHttpObject(); + if (globalXmlHttp == null) { + alert("Your browser does not support AJAX!"); + return; + } + var url = "/muser/chan.cgi"; + var i = 0; + for (; i < 16; i++) { + if (document.getElementById('dest' + i) != null) + params += "&dest" + i + "=" + document.getElementById('dest' + i).value; + } + globalXmlHttp.open("POST", url, true); + globalXmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + globalXmlHttp.onreadystatechange = function() { + if (globalXmlHttp.readyState == 4 && globalXmlHttp.status == 200) { + if (globalXmlHttp.responseText.substring(0, 9).toUpperCase() == " + +

xGen

+ + */ + var d = document.createElement('div'); + + d.id = 'ban'; + d.setAttribute('class', 'ban'); + d.innerHTML = + '

'; + d.getElementsByTagName("p")[0].innerHTML = name; + document.getElementById('w').appendChild(d); +} + +function buildMenu(active, ma0, ma1, ma2, ma3, ma4) +{ + /* Build this html and append as child of
+
+
+ +
+ +
+ */ + var d = document.createElement("div"); + d.id = 'mnu'; + var mstring = + '
\ + \ +
\ + '; + d.innerHTML = mstring; + d.getElementsByTagName('input')[0].setAttribute('value', getSession()); + d.getElementsByTagName('li')[active].className = 'active'; + document.getElementById('w').appendChild(d); +} + +// function called by iPhone to check the number of menus and their names +// authorisation is checked separately by getMenu() which must be in the html as it contains variables +function getMenuNames() +{ + return appMenuNames; +} + +// function called by iPhone to check the icons for menus +// function called by iPhone to check the icons for menus +function getMenuIcons() +{ + return '["lock","pir","lock","clock","users","email","sms","phone","spanner","network","spanner","spanner","spanner"]'; +} + +// function called by iPhone to goto a new page +// the param n is the index into the menunames array above +// ie is not in the same order as ~ma(x)~ +// menus: +// n URL +// 0 /user/area.htm ~ma(3)~ +// 1 /user/zones.htm ~ma(4)~ +// 2 /user/outputs.htm ~ma(4)~ +// 3 /user/history.htm ~ma(2)~ +// 4 /muser/configpins.htm ~ma(1)~ +// 5 /muser/config6 ~ma(1)~ //Email +// 6 /muser/config3 ~ma(1)~ //sms +// 7 /muser/config2 ~ma(1)~ //Voice +// 8 /protect/config1.htm ~ma(0)~ //Features +// 9 /protect/config5 ~ma(0)~ //Network +//10 /protect/config7 ~ma(0)~ //Outputs +//11 /protect/Config8 ~ma(0)~ //IP Reporting +//12 /muser/textnames ~ma(0)~ //Name Editor +function submitMenu(n) +{ + if (n == 0) + document.getElementById('newpage').setAttribute('action', '/user/area.htm'); + else if (n == 1) + document.getElementById('newpage').setAttribute('action', '/user/zones.htm'); + else if (n == 2) + document.getElementById('newpage').setAttribute('action', '/user/outputs.htm'); + else if (n == 3) + document.getElementById('newpage').setAttribute('action', '/user/history.htm'); + else if (n == 4) + document.getElementById('newpage').setAttribute('action', '/muser/configpins.htm'); + else if (n == 5) + document.getElementById('newpage').setAttribute('action', '/muser/config6.htm'); + else if (n == 6) + document.getElementById('newpage').setAttribute('action', '/muser/config3.htm'); + else if (n == 7) + document.getElementById('newpage').setAttribute('action', '/muser/config2.htm'); + else if (n == 8) + document.getElementById('newpage').setAttribute('action', '/protect/config1.htm'); + else if (n == 9) + document.getElementById('newpage').setAttribute('action', '/protect/config5.htm'); + else if (n == 10) + document.getElementById('newpage').setAttribute('action', '/protect/config7.htm'); + else if (n == 11) + document.getElementById('newpage').setAttribute('action', '/protect/config8.htm'); + else if (n == 12) + document.getElementById('newpage').setAttribute('action', '/muser/textnames.htm'); + else if (n == 999) + document.getElementById('newpage').setAttribute('action', '/logout.cgi'); + else + return; + + document.getElementById('newpage').submit(); + + return; +} + +// function used by iPhone app to setup web page to run in an app +function setAppMode() +{ + var e = document.getElementById('ban'); + e.style.display = 'none'; // hide the banner + var e = document.getElementById('mnu'); + e.style.display = 'none'; // hide the side menu (shows on ipad) +} + +//checks a name string for illegal characters +function checkNameEntry(nid, length) +{ + var newData = document.getElementById(nid).value + + if (newData.match(nameRegex) != null) { + alert("Data must only contain the following characters" + nameAllowedChars); + newData = removeBadCharacters(newData, nameRegex); + document.getElementById(nid).value = newData; + return true; + } + + if (newData.length > length) { + alert("Data limited to " + length + " characters!"); + document.getElementById(nid).value = newData.substring(0, length); + } + + return false; +} + +function removeBadCharacters(thestring, theregexp) +{ + while (thestring.match(theregexp) != null) { + var badone = thestring.indexOf(thestring.match(theregexp)); + thestring = thestring.substring(0, badone) + thestring.substring(badone + 1); + } + + return thestring; +} + +function fillNames(id, namearray) +{ + var optionarray = document.getElementById(id); + + for (var i = 0; i < optionarray.options.length; i++) + optionarray.options[i].innerHTML = namearray[i]; +} + +function convertNames(namearray) +{ + var e; + + for (var i = 0; i < namearray.length; i++) { + try { + namearray[i] = decodeURIComponent(decode_utf8(namearray[i])); + } catch (err) { + alert(namearray[i]); + } + } +} + +function encode_utf8(s) +{ + //return unescape( encodeURIComponent( s ) ); + //need to use the language encoder + return s; +} + +function decode_utf8(s) +{ + //return escape( decodeURIComponent( s ) ); + //need to use the language decoder + return s; +} + +//kick off the AJAX Updater +setTimeout("pollAJAX()", 300); diff --git a/tests/var/comnav/0.108-burglar-alarm-on/seq.xml b/tests/var/comnav/0.108-burglar-alarm-on/seq.xml new file mode 100644 index 0000000..7096a2b --- /dev/null +++ b/tests/var/comnav/0.108-burglar-alarm-on/seq.xml @@ -0,0 +1,4 @@ + + 208 + 167,0,0,26,4,5,0,0,0,0,0,1,0,0 + diff --git a/tests/var/comnav/0.108-burglar-alarm-on/status.js b/tests/var/comnav/0.108-burglar-alarm-on/status.js new file mode 100644 index 0000000..965919e --- /dev/null +++ b/tests/var/comnav/0.108-burglar-alarm-on/status.js @@ -0,0 +1,636 @@ +function checkAreaSeq(response) +{ + var curSeq = getXMLValue(response, 'areas').split(','); + var data; + var numeric; + + for (var i = 0; i < curSeq.length; i++) { + var n = parseInt("" + curSeq[i], 10); + if (n != areaSequence[i]) { + data = "arsel=" + i; + newAJAXCommand('/user/status.xml', updateAstate, false, data); + } + } +} + +function updateAstate(response) +{ + if (response == null) + return; + + var bank = parseInt(getXMLValue(response, 'abank'), 10); + + areaSequence[bank] = parseInt(getXMLValue(response, 'aseq'), 10); + bank *= 17; + + for (var i = 0; i < 17; i++) { + areaStatus[bank + i] = getXMLValue(response, 'stat' + i); + } + + sysStatus = getXMLValue(response, 'sysflt').split('\r\n'); + + return bank; +} + +function updateSystemDisplay(updating) +{ + if (areaCount < 2) + return; + + var stat = document.getElementById('a0'); // gets the div + var bgtype = 'bg-grn'; + if (stat == null) + return; + + if (allpriority == 1 || allpriority == 4) + bgtype = 'bg-red'; + else + if (allpriority == 2) + bgtype = 'bg-blu'; + else + if (allpriority == 3) + bgtype = 'bg-yel'; + else + if (allpriority == 5) + bgtype = 'bg-gry'; + + var buttons = stat.getElementsByTagName('button'); + if (allAway) { + buttons[0].className = 'ba bg-red'; + buttons[1].className = 'bs bg-gry'; + } else if (allStay) { + buttons[0].className = 'ba bg-gry'; + buttons[1].className = 'bs bg-yel'; + } else { + buttons[0].className = 'ba bg-gry'; + buttons[1].className = 'bs bg-gry'; + } + if (allChime) { + if (buttons[3].className != 'bc bg-blu') { + buttons[3].className = 'bc bg-blu'; + cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false,'comm=80&data0=2&data2=1&data1=255')"; + buttons[3].setAttribute('onclick', cmd); + } + } else { + if (buttons[3].className != 'bc bg-gry') { + buttons[3].className = 'bc bg-gry'; + cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false,'comm=80&data0=2&data2=1&data1=255')"; + buttons[3].setAttribute('onclick', cmd); + } + } + if (updating) + systemString = systemStates[1]; + else + if (sysStatus.length > 1) { + allSequence++; + if (allSequence >= (sysStatus.length - 1)) + allSequence = 0; + systemString = sysStatus[allSequence]; + } else { + systemString = systemStates[0]; + } + + if (stat.getElementsByTagName('h2')[0].innerHTML != systemString) { + stat.getElementsByTagName('h2')[0].innerHTML = systemString; + stat.firstChild.className = bgtype; // for h1 element + } else + if (stat.firstChild.className != bgtype) { + stat.firstChild.className = bgtype; + } +} + +function updateAllAreas() +{ + allAway = true; + allStay = true; + allChime = true; + allpriority = 6; + + for (var i = 0; i < areaNames.length; i++) { + if (areaNames[i] != "!") + updateArea(i + 1); + } + + updateSystemDisplay(false); + setTimeout("updateAllAreas()", 1500); +} + +function buildArea(area) +{ + var mask = 255; + var start = 0; + if (area != 0) { + mask = (0x01 << ((area - 1) % 8)); + start = (Math.floor((area - 1) / 8)); + } + var div1 = document.createElement("div"); + div1.id = ("a" + area); + div1.className = 'box'; + div1.innerHTML = + '

\ +

\ + \ + \ + \ + '; + + if (area == 0) + div1.getElementsByTagName('h1')[0].innerHTML = miscLabels[0]; + else + div1.getElementsByTagName('h1')[0].innerHTML = areaNames[area - 1]; + + var cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false," + "'comm=80&data0=2&data2=17&data1=" + mask + "')"; + div1.getElementsByTagName('button')[0].setAttribute('onclick', cmd); + var cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false," + "'comm=80&data0=2&data2=18&data1=" + mask + "')"; + div1.getElementsByTagName('button')[1].setAttribute('onclick', cmd); + var cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false," + "'comm=80&data0=2&data2=16&data1=" + mask + "')"; + div1.getElementsByTagName('button')[2].setAttribute('onclick', cmd); + var cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false," + "'comm=80&data0=2&data2=1&data1=" + mask + "')"; + div1.getElementsByTagName('button')[3].setAttribute('onclick', cmd); + document.getElementById('at').appendChild(div1); + + areaDisplay.push(0); + if (area == 0) + updateSystemDisplay(true); + else + updateArea(area); +} + +function updateArea(area) +{ + try { + var stat = document.getElementById('a' + area); // gets the div + if (stat == null) + return; + var buttons = stat.getElementsByTagName('button'); + var i = area - 1; + var areaString = ""; + var bgtype = 'bg-grn'; + var byteindex = (Math.floor(i / 8) * 17); + var mask = (0x01 << (i % 8)); + var starm = parseInt(areaStatus[byteindex], 10); + var stpartial = parseInt(areaStatus[byteindex + 1], 10); + var stchime = parseInt(areaStatus[byteindex + 15], 10); + var stexit1 = parseInt(areaStatus[byteindex + 7], 10); + var stexit2 = parseInt(areaStatus[byteindex + 8], 10); + var cmd; + + + if ((parseInt(areaStatus[byteindex + 3], 10) & mask) != 0 || + (parseInt(areaStatus[byteindex + 4], 10) & mask) != 0 || + (parseInt(areaStatus[byteindex + 5], 10) & mask) != 0 || + (parseInt(areaStatus[byteindex + 6], 10) & mask) != 0) { + allpriority = 1; + bgtype = "bg-red"; + } else + if ((parseInt(areaStatus[byteindex + 11], 10) & mask) != 0 || + (parseInt(areaStatus[byteindex + 12], 10) & mask) != 0 || + (parseInt(areaStatus[byteindex + 13], 10) & mask) != 0 || + (parseInt(areaStatus[byteindex + 14], 10) & mask) != 0 || + (sysStatus != null && sysStatus.length > 1)) { + if (allpriority != 1) + allpriority = 2; + bgtype = "bg-blue"; + } else + if ((parseInt(areaStatus[byteindex + 10]) & mask) != 0 || (stpartial & mask) != 0) { + if (allpriority > 3) + allpriority = 3; + + bgtype = "bg-yel"; + } else + if ((starm & mask) != 0) { + if (allpriority > 4) + allpriority = 4; + + bgtype = "bg-red"; + } else + if ((parseInt(areaStatus[byteindex + 2], 10) & mask) == 0) { + if (allpriority > 5) + allpriority = 5; + + bgtype = "bg-gry"; + } + + + while (areaString == "") { + if (areaDisplay[i] >= areaStates.length) { + var maxCount; + + if (areaCount > 1) + maxCount = (areaStates.length); + else + maxCount = (areaStates.length + sysStatus.length); + + if (areaDisplay[i] >= maxCount) { + if (((stexit1 & mask) != 0) || ((stexit2 & mask) != 0)) + areaDisplay[i] = 3; + else + areaDisplay[i] = 0; + } else { + areaString = sysStatus[areaDisplay[i] - areaStates.length]; + if (areaString == "No System Faults") { + areaString = systemStates[0]; + } + areaDisplay[i]++; + } + } else { + var st = parseInt(areaStatus[areaDisplay[i] + byteindex], 10); + + if ((st & mask) != 0) { + if ((areaDisplay[i] != 2) || ((starm & mask) == 0 && (stpartial & mask) == 0)) { + areaString = areaStates[areaDisplay[i]]; + } + if (areaDisplay[i] == 7) + areaDisplay[i]++; + } else if (areaDisplay[i] == 2 && (starm & mask) == 0 && (stpartial & mask) == 0) { + areaString = areaLanguage[4]; + } + + areaDisplay[i]++; + } + } + + if (stat.getElementsByTagName('h2')[0].innerHTML != areaString) { + stat.getElementsByTagName('h2')[0].innerHTML = areaString; + stat.firstChild.className = bgtype; // for h1 element + } else + if (stat.firstChild.className != bgtype) { + stat.firstChild.className = bgtype; + } + + if ((starm & mask) != 0) { + buttons[0].className = 'ba bg-red'; + buttons[1].className = 'bs bg-gry'; + allStay = false; + } else if ((stpartial & mask) != 0) { + buttons[0].className = 'ba bg-gry'; + buttons[1].className = 'bs bg-yel'; + allAway = false; + } else { + buttons[0].className = 'ba bg-gry'; + buttons[1].className = 'bs bg-gry'; + allStay = false; + allAway = false; + + } + + if ((stchime & mask) != 0) { + if (buttons[3].className != 'bc bg-blu') { + buttons[3].className = 'bc bg-blu'; + cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false," + "'comm=80&data0=2&data2=1&data1=" + mask + "')"; + buttons[3].setAttribute('onclick', cmd); + } + } else { + allChime = false; + if (buttons[3].className != 'bc bg-gry') { + buttons[3].className = 'bc bg-gry'; + cmd = "newAJAXCommand('/user/keyfunction.cgi', cgiResponse, false," + "'comm=80&data0=2&data2=1&data1=" + mask + "')"; + buttons[3].setAttribute('onclick', cmd); + } + } + } catch (err) { + alert("" + err); + } +} + +function buildAreas() +{ + /* Build this html to insert areas into: +
+
+ */ + var d = document.createElement("div"); + + d.id = ("at"); + d.className = 'main'; + document.getElementById('w').appendChild(d); + areaCount = 0; + + for (var i = 0; i < areaNames.length; i++) + if (areaNames[i] != "!") + areaCount++; + if (areaCount > 1) + buildArea(0) + var j = 1; + + for (; j < areaNames.length + 1; j++) { + if (areaNames[j - 1] == "") + areaNames[j - 1] = config7NamesLab[3 + j]; + if (areaNames[j - 1] != "!") + buildArea(j) + else + areaDisplay.push(0); + } +} + +function cgiResponse(responseText) +{ + try { + if (responseText != null) { + var bank = updateAstate(responseText); + for (var i = 0; i < 8; i++) { + if (areaNames[i + bank] != "!") + updateArea(i + 1); + } + } + } catch (err) { + alert("" + err); + } + newAJAXCommand('/user/seq.xml', checkAreaSeq, false); +} + +function pollSequence() +{ + newAJAXCommand('/user/seq.xml', checkAreaSeq, false); + setTimeout("pollSequence()", 5500); +} + +function checkZoneSeq(response) +{ + if (response == null) + return; + + var curSeq = getXMLValue(response, 'zones').split(','); + var data; + var numeric; + + for (var i = 0; i < curSeq.length; i++) { + var n = parseInt("" + curSeq[i], 10); + if (n != zoneSequence[i]) { + data = "state=" + i; + newAJAXCommand('/user/zstate.xml', updateZstate, false, data); + } + } + setTimeout("newAJAXCommand('/user/seq.xml', checkZoneSeq, false)", 5500); +} + +function updateZstate(response) +{ + if (response == null) + return; + + var bank = parseInt(getXMLValue(response, 'zstate'), 10); + zoneSequence[bank] = parseInt(getXMLValue(response, 'zseq'), 10); + zoneStatus[bank] = getXMLValue(response, 'zdat').split(','); +} + +function updateAllZones() +{ + for (var i = 0; i < zoneNames.length; i++) { + if (zoneNames[i] != "!") + updateZone(i + 1); + } + setTimeout("updateAllZones()", 1500); +} + +function buildZone(zone) +{ + var div1 = document.createElement("div"); + div1.id = ("z" + zone); + div1.className = 'box'; + div1.innerHTML = + '

\ +

\ + ' // \ + // '; + div1.getElementsByTagName('h1')[0].innerHTML = zoneNames[zone - 1]; + document.getElementById('zt').appendChild(div1); + + zoneDisplay.push(0); + updateZone(zone); +} + +function updateZone(zone) +{ + if (zone == null) + return; + + try { + var stat = document.getElementById('z' + zone); // gets the div + var buttons = stat.getElementsByTagName('button'); + + var i = zone - 1; + var zoneString = ""; + var bgtype = 'bg-grn'; + var byteindex = Math.floor(i / 16); + var mask = (0x01 << (i % 16)); + var st; + var cmd; + + if ((parseInt(zoneStatus[5][byteindex], 10) & mask) != 0) + bgtype = "bg-red"; + else + if ((parseInt(zoneStatus[1][byteindex], 10) & mask) != 0 || + (parseInt(zoneStatus[2][byteindex], 10) & mask) != 0 || + (parseInt(zoneStatus[6][byteindex], 10) & mask) != 0 || + (parseInt(zoneStatus[7][byteindex], 10) & mask) != 0) + bgtype = "bg-blue"; + else + if ((parseInt(zoneStatus[3][byteindex], 10) & mask) != 0 || (parseInt(zoneStatus[4][byteindex], 10) & mask) != 0) + bgtype = "bg-yel"; + else + if ((parseInt(zoneStatus[0][byteindex], 10) & mask) != 0) + bgtype = "bg-gry"; + + while (zoneString == "") { + if (zoneDisplay[i] >= zoneStates.length) + zoneDisplay[i] = 0; + + st = parseInt(zoneStatus[zoneDisplay[i]][byteindex], 10); + + if ((st & mask) != 0) { + zoneString = zoneStates[zoneDisplay[i]]; + } else + if (zoneDisplay[i] == 0) { + zoneString = zoneLanguage[1]; + } + zoneDisplay[i]++; + } + + if (stat.getElementsByTagName('h2')[0].innerHTML != zoneString) { + stat.getElementsByTagName('h2')[0].innerHTML = zoneString; + stat.firstChild.className = bgtype; // for h1 element + } else + if (stat.firstChild.className != bgtype) { + stat.firstChild.className = bgtype; + } + /* + st = parseInt(zoneStatus[9][byteindex],10); + if((st & mask)!= 0) + { + if (buttons[1].className != 'bc bg-blu') + { + buttons[1].className = 'bc bg-blu'; + cmd = "newAJAXCommand('/user/zonefunction.cgi', zoneCGIresponse, false,"+ "'comm=82&data0=" + (zone-1) +"')"; + buttons[1].setAttribute('onclick', cmd); + } + } + else + { + if (buttons[1].className != 'bc bg-gry') + { + buttons[1].className = 'bc bg-gry'; + cmd = "newAJAXCommand('/user/zonefunction.cgi', zoneCGIresponse, false,"+ "'comm=82&data0=" + (zone-1) +"')"; + buttons[1].setAttribute('onclick', cmd); + } + } + */ + st = parseInt(zoneStatus[3][byteindex], 10); + if ((st & mask) != 0) { + if (buttons[0].className != 'bc bg-yel') { + buttons[0].className = 'bb bg-yel'; + cmd = "newAJAXCommand('/user/zonefunction.cgi', zoneCGIresponse, false," + "'comm=82&data0=" + (zone - 1) + "')"; + buttons[0].setAttribute('onclick', cmd); + } + } else { + if (buttons[0].className != 'bc bg-gry') { + buttons[0].className = 'bb bg-gry'; + cmd = "newAJAXCommand('/user/zonefunction.cgi', zoneCGIresponse, false," + "'comm=82&data0=" + (zone - 1) + "')"; + buttons[0].setAttribute('onclick', cmd); + } + } + } catch (err) { + alert("" + err); + } +} + +function buildZones() +{ + /* Build this html to insert zones into: +
+
+ */ + var d = document.createElement("div"); + d.id = ("zt"); + d.className = 'main'; + document.getElementById('w').appendChild(d); + + for (var j = 1; j < zoneNames.length + 1; j++) { + if (zoneNames[j - 1] == "") + zoneNames[j - 1] = zoneLanguage[0] + ' ' + j; + if (zoneNames[j - 1] != "!") + buildZone(j) + else + zoneDisplay.push(0); + } +} + +function zoneCGIresponse(responseText) +{ + try { + if (responseText != null) { + updateZstate(responseText); + for (var i = 0; i < zoneNames.length; i++) { + if (zoneNames[i] != "!") + updateZone(i + 1); + } + } + } catch (err) { + alert("" + err); + } +} + +function changeIndex(qty) +{ + var current; + + if (qty > 0 && document.getElementById('nextb').className == 'bg-gry') + return; + if (qty < 0 & document.getElementById('prevb').className == 'bg-gry') + return; + + current = parseInt("" + index, 10); + mr = parseInt("" + mostRecent, 10); + + if (qty < 0) + direction = 'dec'; + else + direction = 'inc'; + + current += qty; + + if (current >= count) + current -= count; + if (current < 0) + current += count; + + index = current; + + newAJAXCommand('/user/history.xml', updateHistory, false, "event=" + index); +} + +function updateHistory(responseText) +{ + var current; + var mr; + var old; + + try { + if (responseText != null) { + current = getXMLValue(responseText, 'cur'); + mr = getXMLValue(responseText, 'last'); + old = getXMLValue(responseText, 'old'); + if (current == "" + index || "" + index == "65535" || "" + index == "65534") { + if ("" + index == "65535") + index = mr; + else + if ("" + index == "65534") + index = old; + + document.getElementById('event').value = getXMLValue(responseText, 'evrsp') /* + "\nindex= "+current+"\nRecent= "+mr+"\noldest= "+old*/ ; + if (current == mr) + document.getElementById('nextb').setAttribute('class', 'bg-gry'); + else + document.getElementById('nextb').setAttribute('class', 'bg-blu'); + + if (current == old) + document.getElementById('prevb').setAttribute('class', 'bg-gry'); + else + document.getElementById('prevb').setAttribute('class', 'bg-blu'); + mostRecent = mr; + + } else + setTimeout("checkHistory()", 500); + + } else + setTimeout("checkHistory()", 500); + + } catch (err) { + alert("" + err); + } +} + +function checkHistory() +{ + if (ajaxList.length == 0) { + newAJAXCommand('/user/history.xml', updateHistory, false, "event=" + index); + } else + setTimeout("checkHistory()", 500); +} + +function buildOutputs() +{ + /* Build this html to insert zones into: +
+
+ */ + var d = document.createElement("div"); + d.id = ("ot"); + d.className = 'main'; + document.getElementById('w').appendChild(d); + buildOutput(1); + buildOutput(2); +} + +function buildOutput(output) +{ + var div1 = document.createElement("div"); + div1.id = ("o" + output); + div1.className = 'box'; + div1.innerHTML = + '

\ + \ + '; + div1.getElementsByTagName('h1')[0].innerHTML = outputLanguage[2] + ' ' + output; + document.getElementById('ot').appendChild(div1); +} diff --git a/tests/var/comnav/0.108-burglar-alarm-on/status.xml b/tests/var/comnav/0.108-burglar-alarm-on/status.xml new file mode 100644 index 0000000..6ab88f3 --- /dev/null +++ b/tests/var/comnav/0.108-burglar-alarm-on/status.xml @@ -0,0 +1,25 @@ + + 0 + 208 + 1 + 0 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + Box Tamper +Siren Trouble + + + diff --git a/tests/var/comnav/0.108-burglar-alarm-on/zones.htm b/tests/var/comnav/0.108-burglar-alarm-on/zones.htm new file mode 100644 index 0000000..c7bc8e9 --- /dev/null +++ b/tests/var/comnav/0.108-burglar-alarm-on/zones.htm @@ -0,0 +1,57 @@ + + + + + ComNav :: Secure Network + + + + + + + + + + +
+ + + +
+ + + + diff --git a/tests/var/comnav/0.108-burglar-alarm-on/zstate.xml b/tests/var/comnav/0.108-burglar-alarm-on/zstate.xml new file mode 100644 index 0000000..b16a35c --- /dev/null +++ b/tests/var/comnav/0.108-burglar-alarm-on/zstate.xml @@ -0,0 +1,5 @@ + + 0 + 167 + 0,0,0,0,0,0,0,0,0 + diff --git a/ultrasync/cli.py b/ultrasync/cli.py index 77ad41f..5b3f3f2 100644 --- a/ultrasync/cli.py +++ b/ultrasync/cli.py @@ -91,7 +91,7 @@ def print_version_msg(): @click.option('--watch', '-w', is_flag=True, help='Watch panel') @click.option('--details', '-d', is_flag=True, help='Print status') @click.option('--scene', '-s', type=str, - metavar='SCENE', + metavar='STATE', help='Specify the alarm scene to change to. Possible values ' 'are "{}", and "{}".'.format( '", "'.join(ALARM_SCENES[:-1]), ALARM_SCENES[-1])) @@ -187,7 +187,7 @@ def main(config, debug_dump, full_debug_dump, scene, details, watch, actioned = True if scene: - if not usync.set_scene(areas=area, scene=scene): + if not usync.set_alarm(areas=area, state=scene): sys.exit(1) actioned = True diff --git a/ultrasync/common.py b/ultrasync/common.py index ff21136..9ba7f3e 100644 --- a/ultrasync/common.py +++ b/ultrasync/common.py @@ -150,7 +150,7 @@ class CNAreaBank(object): class AreaStatus(object): """ - Defines the panel display area status + Defines the possible panel display status messages """ # All sensor are active; occupants are not present. @@ -162,39 +162,83 @@ class AreaStatus(object): READY = 'Ready' - NOT_READY = 'Not Ready' + ALARM_FIRE = 'Fire Alarm' + ALARM_BURGLAR = 'Burglar Alarm' + ALARM_PANIC = 'Panic Alarm' + ALARM_MEDICAL = 'Medical Alarm' - NOT_READY_FORCEABLE = 'Not Ready' + DELAY_EXIT_1 = 'Exit Delay 1' + DELAY_EXIT_2 = 'Exit Delay 2' + DELAY_ENTRY = 'Entry Delay' - DISARMED = 'Disarm' + SENSOR_BYPASS = 'Sensor Bypass' + SENSOR_TROUBLE = 'Sensor Trouble', + SENSOR_TAMPER = 'Sensor Tamper', + SENSOR_BATTERY = 'Sensor Low Battery', + SENSOR_SUPERVISION = 'Sensor Supervision', - EXIT_DELAY_1 = 'Exit Delay 1' + NOT_READY = 'Not Ready' - EXIT_DELAY_2 = 'Exit Delay 2' + NOT_READY_FORCEABLE = 'Not Ready' - ENTRY_DELAY = 'Entry Delay' + DISARMED = 'Disarm' AREA_STATES = ( # These are intentially set and the order is very important # entries missing in this array but defined above is an intentional thing # These states are the same on both the ComNav and Interlogix systems - AreaStatus.ARMED_AWAY, - AreaStatus.ARMED_STAY, - AreaStatus.READY, - 'Fire Alarm', - 'Burg Alarm', - 'Panic Alarm', - 'Medical Alarm', - AreaStatus.EXIT_DELAY_1, - AreaStatus.EXIT_DELAY_2, - AreaStatus.ENTRY_DELAY, - 'Sensor Bypass', - 'Sensor Trouble', - 'Sensor Tamper', - 'Sensor Low Battery', - 'Sensor Supervision', - '', + AreaStatus.ARMED_AWAY, # bank index 0 + AreaStatus.ARMED_STAY, # bank index 1 + AreaStatus.READY, # bank index 2 + + AreaStatus.ALARM_FIRE, # bank index 3 + AreaStatus.ALARM_BURGLAR, # bank index 4 + AreaStatus.ALARM_PANIC, # bank index 5 + AreaStatus.ALARM_MEDICAL, # bank index 6 + + AreaStatus.DELAY_EXIT_1, # bank index 7 + AreaStatus.DELAY_EXIT_2, # bank index 8 + AreaStatus.DELAY_ENTRY, # bank index 9 + + AreaStatus.SENSOR_BYPASS, # bank index 10 + AreaStatus.SENSOR_TROUBLE, # bank index 11 + AreaStatus.SENSOR_TAMPER, # bank index 12 + AreaStatus.SENSOR_BATTERY, # bank index 13 + AreaStatus.SENSOR_SUPERVISION, # bank index 14 + + # Last entry empty + '', # bank index 15 +) + +AREA_STATUS_PROCESS_PRIORITY = ( + # The processing order of the area status messages. We prioritize them to + # ease the on the logic used to determine which one should be displayed if + # more then one is set. The first matched entry when read from top down + # is always used. The index corresponds with the bank index defined above. + + 3, # AreaStatus.ALARM_FIRE + 4, # AreaStatus.ALARM_BURGLAR + 5, # AreaStatus.ALARM_PANIC + 6, # AreaStatus.ALARM_MEDICAL + + 7, # AreaStatus.DELAY_EXIT_1 + 8, # AreaStatus.DELAY_EXIT_2 + 9, # AreaStatus.DELAY_ENTRY + + 0, # AreaStatus.ARMED_AWAY + 1, # AreaStatus.ARMED_STAY + + 10, # AreaStatus.SENSOR_BYPASS + 11, # AreaStatus.SENSOR_TROUBLE + 12, # AreaStatus.SENSOR_TAMPER + 13, # AreaStatus.SENSOR_BATTERY + 14, # AreaStatus.SENSOR_SUPERVISION + + 2, # AreaStatus.READY + + # Last entry empty + 15, ) @@ -237,7 +281,7 @@ class ZoneBank(object): class ZoneStatus(object): """ - Defines the panel display zone/sensor status + Defines the possible panel display zone/sensor status """ READY = 'Ready' diff --git a/ultrasync/logger.py b/ultrasync/logger.py index 44f93e9..a95f1c6 100644 --- a/ultrasync/logger.py +++ b/ultrasync/logger.py @@ -27,6 +27,13 @@ # Define a verbosity level that is a noisier then debug mode logging.TRACE = logging.DEBUG - 1 + +# Define a verbosity level that is always used even when no verbosity is set +# from the command line. The idea here is to allow for deprecation notices +logging.DEPRECATE = logging.ERROR + 1 + +# Assign our Levels into our logging object +logging.addLevelName(logging.DEPRECATE, "DEPRECATION WARNING") logging.addLevelName(logging.TRACE, "TRACE") @@ -38,8 +45,17 @@ def trace(self, message, *args, **kwargs): self._log(logging.TRACE, message, args, **kwargs) +def deprecate(self, message, *args, **kwargs): + """ + Deprication Warning Logging + """ + if self.isEnabledFor(logging.DEPRECATE): + self._log(logging.DEPRECATE, message, args, **kwargs) + + # Assign our Trace Logger for use with the UltraSync wrapper logging.Logger.trace = trace +logging.Logger.deprecate = deprecate # Create ourselve a generic logging reference logger = logging.getLogger('ultrasync') diff --git a/ultrasync/main.py b/ultrasync/main.py index 80aca4f..3e7a3ef 100644 --- a/ultrasync/main.py +++ b/ultrasync/main.py @@ -36,7 +36,7 @@ from .common import ( AlarmScene, AreaStatus, CNAreaBank, ZWAreaBank, ZoneStatus, ZoneBank, ALARM_SCENES, AREA_STATES, ZONE_STATES, CNPanelFunction, - ZWPanelFunction, NX595EVendor) + ZWPanelFunction, NX595EVendor, AREA_STATUS_PROCESS_PRIORITY) from .config import UltraSyncConfig from urllib.parse import unquote from .logger import logger @@ -272,6 +272,7 @@ def debug_dump(self, path=None, mode=0o755, full=False, compress=False, } if self.vendor is NX595EVendor.ZEROWIRE: + urls.update({ # Used to acquire sequence 'seq.json': { @@ -341,6 +342,14 @@ def debug_dump(self, path=None, mode=0o755, full=False, compress=False, 'state': no, }} for no in range(0, 16 if full else 4)}) + # Grab our language file + urls.update({ + 'lang_engau.js': { + 'path': '{}/eng_us.js'.format( + self.__panel_url_path), + }, + }) + else: # self.vendor is NX595EVendor.COMNAV urls.update({ @@ -366,6 +375,15 @@ def debug_dump(self, path=None, mode=0o755, full=False, compress=False, 'state': no, }} for no in range(0, 12 if full else 4)}) + if float(self.version) > 0.106: + # Grab our language file + urls.update({ + 'lang_engau.js': { + 'path': '{}/lang_engau.js'.format( + self.__panel_url_path), + }, + }) + progress_ratio = 100.0 / len(urls) progress_track = 0.0 @@ -409,17 +427,26 @@ def debug_dump(self, path=None, mode=0o755, full=False, compress=False, progress.update(100.001 - progress_track) return - def set_scene(self, areas=None, scene=AlarmScene.DISARMED): + def set(self, area=1, state=AlarmScene.DISARMED): """ - Sets Alarm Scene + This will be removed; it is only present for backwards compatibility + + """ + logger.deprecate( + 'set() is being depricated and replaced with set_alarm()') + return self.set_alarm(areas=area, state=state) + + def set_alarm(self, areas=None, state=AlarmScene.DISARMED): + """ + Sets Alarm """ if not self.session_id and not self.login(): return False - if scene not in ALARM_SCENES: + if state not in ALARM_SCENES: logger.error( - '{} is not valid alarm scene'.format(scene)) + '{} is not valid alarm state'.format(state)) return False if not areas: @@ -462,12 +489,12 @@ def set_scene(self, areas=None, scene=AlarmScene.DISARMED): 'mask': 1 << (area - 1) % 8, }) - if scene == AlarmScene.STAY: + if state == AlarmScene.STAY: payload.update({ 'fnum': ZWPanelFunction.AREA_STAY, }) - elif scene == AlarmScene.AWAY: + elif state == AlarmScene.AWAY: payload.update({ 'fnum': ZWPanelFunction.AREA_AWAY, }) @@ -487,12 +514,12 @@ def set_scene(self, areas=None, scene=AlarmScene.DISARMED): 'data1': 1 << (area - 1) % 8, }) - if scene == AlarmScene.STAY: + if state == AlarmScene.STAY: payload.update({ 'data2': CNPanelFunction.AREA_STAY, }) - elif scene == AlarmScene.AWAY: + elif state == AlarmScene.AWAY: payload.update({ 'data2': CNPanelFunction.AREA_AWAY, }) @@ -510,11 +537,11 @@ def set_scene(self, areas=None, scene=AlarmScene.DISARMED): if not response: logger.info( - 'Failed to send {} scene to Area {}'.format(scene, area)) + 'Failed to send {} state to Area {}'.format(state, area)) has_error = True logger.info( - 'Sent {} scene to Area {} Successfully'.format(scene, area)) + 'Sent {} state to Area {} Successfully'.format(state, area)) return not has_error @@ -742,10 +769,13 @@ def zerowire_process_areas(self): status = None # Our initial index starting point - idx = 3 if st_exit1 or st_exit2 else 0 + idx = -1 while not status: + # Increment our working index + idx += 1 + if idx >= len(AREA_STATES): if self.__extra_area_status: status = \ @@ -754,17 +784,21 @@ def zerowire_process_areas(self): else: # Set status status = AreaStatus.READY - break - elif zw_vbank[idx]: - if AREA_STATES[idx] != AreaStatus.READY \ + continue + + # get our virtual index based on priority + v_idx = AREA_STATUS_PROCESS_PRIORITY[idx] + + if zw_vbank[v_idx]: + if AREA_STATES[v_idx] != AreaStatus.READY \ or not (st_armed or st_partial): - status = AREA_STATES[idx] + status = AREA_STATES[v_idx] if status in (AreaStatus.ARMED_STAY, - AreaStatus.EXIT_DELAY_1, - AreaStatus.EXIT_DELAY_2): + AreaStatus.DELAY_EXIT_1, + AreaStatus.DELAY_EXIT_2): if vbank[ZWAreaBank.NIGHT]: status += ' - Night' @@ -772,21 +806,18 @@ def zerowire_process_areas(self): elif vbank[ZWAreaBank.INSTANT]: status += ' - Instant' - if AREA_STATES[idx] == AreaStatus.EXIT_DELAY_1: - # Bump to EXIT_DELAY_2; we'll eventually hit + if AREA_STATES[v_idx] == AreaStatus.DELAY_EXIT_1: + # Bump to DELAY_EXIT_2; we'll eventually hit # the bottom of our while loop and move past that too idx += 1 - elif AREA_STATES[idx] == AreaStatus.READY \ + elif AREA_STATES[v_idx] == AreaStatus.READY \ and not (st_armed or st_partial): # Update status = AreaStatus.NOT_READY \ if not vbank[ZWAreaBank.UNKWN_01] \ else AreaStatus.NOT_READY_FORCEABLE - # increment our index by one - idx += 1 - if vbank[ZWAreaBank.UNKWN_08] or vbank[ZWAreaBank.UNKWN_09] or \ vbank[ZWAreaBank.UNKWN_10] or vbank[ZWAreaBank.UNKWN_11]: @@ -864,10 +895,13 @@ def comnav_process_areas(self): status = None # Our initial index starting point - idx = 3 if st_exit1 or st_exit2 else 0 + idx = -1 while not status: + # increment our index by one + idx += 1 + if idx >= len(AREA_STATES): if self.__extra_area_status: status = \ @@ -880,28 +914,29 @@ def comnav_process_areas(self): else: # Set status status = AreaStatus.READY - break - elif vbank[idx]: - if AREA_STATES[idx] != AreaStatus.READY \ + continue + + # get our virtual index based on priority + v_idx = AREA_STATUS_PROCESS_PRIORITY[idx] + + if vbank[v_idx]: + if AREA_STATES[v_idx] != AreaStatus.READY \ or not (st_armed or st_partial): - status = AREA_STATES[idx] + status = AREA_STATES[v_idx] - if AREA_STATES[idx] == AreaStatus.EXIT_DELAY_1: - # Bump to EXIT_DELAY_2; we'll eventually hit + if AREA_STATES[v_idx] == AreaStatus.DELAY_EXIT_1: + # Bump to DELAY_EXIT_2; we'll eventually hit # the bottom of our while loop and move past that # too idx += 1 - elif AREA_STATES[idx] == AreaStatus.READY \ + elif AREA_STATES[v_idx] == AreaStatus.READY \ and not (st_armed or st_partial): # Update status = AreaStatus.NOT_READY - # increment our index by one - idx += 1 - if vbank[CNAreaBank.UNKWN_03] or vbank[CNAreaBank.UNKWN_04] or \ vbank[CNAreaBank.UNKWN_05] or vbank[CNAreaBank.UNKWN_06]: @@ -1263,7 +1298,7 @@ def _zerowire_area_status_update(self, bank=0): """ - logger.info( + logger.debug( 'Updating Area information on bank {}'.format(bank)) if not self.session_id and not self.login(): @@ -1325,7 +1360,7 @@ def _comnav_area_status_update(self, bank=0): """ - logger.info( + logger.debug( 'Updating Area information on bank {}'.format(bank)) if not self.session_id and not self.login(): @@ -1386,7 +1421,7 @@ def _zerowire_zone_status_update(self, bank=0): } """ - logger.info( + logger.debug( 'Updating Zone/Sensor information on bank {}'.format(bank)) if not self.session_id and not self.login(): @@ -1426,7 +1461,7 @@ def _comnav_zone_status_update(self, bank=0): """ - logger.info( + logger.debug( 'Updating Zone/Sensor information on bank {}'.format(bank)) if not self.session_id and not self.login():