From 60fbc51a2daf503c36baa462a1d3c1c138844603 Mon Sep 17 00:00:00 2001 From: jamespcole Date: Sun, 22 Mar 2015 06:10:24 +1100 Subject: [PATCH 01/59] added in line graph support for state history --- .../polymer/components/state-timeline.html | 188 ++++++++++++++---- 1 file changed, 146 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html b/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html index 9ded10dd3ae..997a8c2e528 100644 --- a/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html +++ b/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html @@ -12,16 +12,17 @@
- +
- + -/* Palette generated by Material Palette - materialpalette.com/light-blue/orange */ +if(this.removeAttribute("value"),c)return j(this,"value",b);var e=b,f=m(this,"value",e);return j(this,"value",e.open(k(this,"value",d))),t(this,a,f)},HTMLOptionElement.prototype.bind=function(a,b,c){if("value"!==a)return HTMLElement.prototype.bind.call(this,a,b,c);if(this.removeAttribute("value"),c)return q(this,b);var d=b,e=m(this,"value",d);return q(this,d.open(r(this))),t(this,a,e)},HTMLSelectElement.prototype.bind=function(a,c,d){if("selectedindex"===a&&(a="selectedIndex"),"selectedIndex"!==a&&"value"!==a)return HTMLElement.prototype.bind.call(this,a,c,d);if(this.removeAttribute(a),d)return j(this,a,c);var e=c,f=m(this,a,e);return j(this,a,e.open(k(this,a))),b(this,a,f)}}(this),function(a){"use strict";function b(a){if(!a)throw new Error("Assertion failed")}function c(a){for(var b;b=a.parentNode;)a=b;return a}function d(a,b){if(b){for(var d,e="#"+b;!d&&(a=c(a),a.protoContent_?d=a.protoContent_.querySelector(e):a.getElementById&&(d=a.getElementById(b)),!d&&a.templateCreator_);)a=a.templateCreator_;return d}}function e(a){return"template"==a.tagName&&"http://www.w3.org/2000/svg"==a.namespaceURI}function f(a){return"TEMPLATE"==a.tagName&&"http://www.w3.org/1999/xhtml"==a.namespaceURI}function g(a){return Boolean(L[a.tagName]&&a.hasAttribute("template"))}function h(a){return void 0===a.isTemplate_&&(a.isTemplate_="TEMPLATE"==a.tagName||g(a)),a.isTemplate_}function i(a,b){var c=a.querySelectorAll(N);h(a)&&b(a),G(c,b)}function j(a){function b(a){HTMLTemplateElement.decorate(a)||j(a.content)}i(a,b)}function k(a,b){Object.getOwnPropertyNames(b).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))})}function l(a){var b=a.ownerDocument;if(!b.defaultView)return b;var c=b.templateContentsOwner_;if(!c){for(c=b.implementation.createHTMLDocument("");c.lastChild;)c.removeChild(c.lastChild);b.templateContentsOwner_=c}return c}function m(a){if(!a.stagingDocument_){var b=a.ownerDocument;if(!b.stagingDocument_){b.stagingDocument_=b.implementation.createHTMLDocument(""),b.stagingDocument_.isStagingDocument=!0;var c=b.stagingDocument_.createElement("base");c.href=document.baseURI,b.stagingDocument_.head.appendChild(c),b.stagingDocument_.stagingDocument_=b.stagingDocument_}a.stagingDocument_=b.stagingDocument_}return a.stagingDocument_}function n(a){var b=a.ownerDocument.createElement("template");a.parentNode.insertBefore(b,a);for(var c=a.attributes,d=c.length;d-->0;){var e=c[d];K[e.name]&&("template"!==e.name&&b.setAttribute(e.name,e.value),a.removeAttribute(e.name))}return b}function o(a){var b=a.ownerDocument.createElement("template");a.parentNode.insertBefore(b,a);for(var c=a.attributes,d=c.length;d-->0;){var e=c[d];b.setAttribute(e.name,e.value),a.removeAttribute(e.name)}return a.parentNode.removeChild(a),b}function p(a,b,c){var d=a.content;if(c)return void d.appendChild(b);for(var e;e=b.firstChild;)d.appendChild(e)}function q(a){P?a.__proto__=HTMLTemplateElement.prototype:k(a,HTMLTemplateElement.prototype)}function r(a){a.setModelFn_||(a.setModelFn_=function(){a.setModelFnScheduled_=!1;var b=z(a,a.delegate_&&a.delegate_.prepareBinding);w(a,b,a.model_)}),a.setModelFnScheduled_||(a.setModelFnScheduled_=!0,Observer.runEOM_(a.setModelFn_))}function s(a,b,c,d){if(a&&a.length){for(var e,f=a.length,g=0,h=0,i=0,j=!0;f>h;){var g=a.indexOf("{{",h),k=a.indexOf("[[",h),l=!1,m="}}";if(k>=0&&(0>g||g>k)&&(g=k,l=!0,m="]]"),i=0>g?-1:a.indexOf(m,g+2),0>i){if(!e)return;e.push(a.slice(h));break}e=e||[],e.push(a.slice(h,g));var n=a.slice(g+2,i).trim();e.push(l),j=j&&l;var o=d&&d(n,b,c);e.push(null==o?Path.get(n):null),e.push(o),h=i+2}return h===f&&e.push(""),e.hasOnePath=5===e.length,e.isSimplePath=e.hasOnePath&&""==e[0]&&""==e[4],e.onlyOneTime=j,e.combinator=function(a){for(var b=e[0],c=1;cc?(this.keys.push(a),this.values.push(b)):this.values[c]=b},get:function(a){var b=this.keys.indexOf(a);if(!(0>b))return this.values[b]},"delete":function(a){var b=this.keys.indexOf(a);return 0>b?!1:(this.keys.splice(b,1),this.values.splice(b,1),!0)},forEach:function(a,b){for(var c=0;cb;)this.reportInstanceMoved(b),b++},closeInstanceBindings:function(a){for(var b=a.bindings_,c=0;c32&&127>b&&-1==[34,35,60,62,63,96].indexOf(b)?a:encodeURIComponent(a)}function f(a){var b=a.charCodeAt(0);return b>32&&127>b&&-1==[34,35,60,62,96].indexOf(b)?a:encodeURIComponent(a)}function g(a,g,h){function i(a){t.push(a)}var j=g||"scheme start",k=0,l="",r=!1,s=!1,t=[];a:for(;(a[k-1]!=o||0==k)&&!this._isInvalid;){var u=a[k];switch(j){case"scheme start":if(!u||!p.test(u)){if(g){i("Invalid scheme.");break a}l="",j="no scheme";continue}l+=u.toLowerCase(),j="scheme";break;case"scheme":if(u&&q.test(u))l+=u.toLowerCase();else{if(":"!=u){if(g){if(o==u)break a;i("Code point not allowed in scheme: "+u);break a}l="",k=0,j="no scheme";continue}if(this._scheme=l,l="",g)break a;b(this._scheme)&&(this._isRelative=!0),j="file"==this._scheme?"relative":this._isRelative&&h&&h._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==u?(query="?",j="query"):"#"==u?(this._fragment="#",j="fragment"):o!=u&&" "!=u&&"\n"!=u&&"\r"!=u&&(this._schemeData+=e(u));break;case"no scheme":if(h&&b(h._scheme)){j="relative";continue}i("Missing scheme."),c.call(this);break;case"relative or authority":if("/"!=u||"/"!=a[k+1]){i("Expected /, got: "+u),j="relative";continue}j="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=h._scheme),o==u){this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._query=h._query;break a}if("/"==u||"\\"==u)"\\"==u&&i("\\ is an invalid code point."),j="relative slash";else if("?"==u)this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._query="?",j="query";else{if("#"!=u){var v=a[k+1],w=a[k+2];("file"!=this._scheme||!p.test(u)||":"!=v&&"|"!=v||o!=w&&"/"!=w&&"\\"!=w&&"?"!=w&&"#"!=w)&&(this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._path.pop()),j="relative path";continue}this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._query=h._query,this._fragment="#",j="fragment"}break;case"relative slash":if("/"!=u&&"\\"!=u){"file"!=this._scheme&&(this._host=h._host,this._port=h._port),j="relative path";continue}"\\"==u&&i("\\ is an invalid code point."),j="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=u){i("Expected '/', got: "+u),j="authority ignore slashes";continue}j="authority second slash";break;case"authority second slash":if(j="authority ignore slashes","/"!=u){i("Expected '/', got: "+u);continue}break;case"authority ignore slashes":if("/"!=u&&"\\"!=u){j="authority";continue}i("Expected authority, got: "+u);break;case"authority":if("@"==u){r&&(i("@ already seen."),l+="%40"),r=!0;for(var x=0;xg;g++)f.unshift("..");var i=b.href.slice(-1)===m?m:b.hash;return f.join("/")+b.search+i}var g={resolveDom:function(a,c){c=c||b(a),this.resolveAttributes(a,c),this.resolveStyles(a,c);var d=a.querySelectorAll("template");if(d)for(var e,f=0,g=d.length;g>f&&(e=d[f]);f++)e.content&&this.resolveDom(e.content,c)},resolveTemplate:function(a){this.resolveDom(a.content,b(a))},resolveStyles:function(a,b){var c=a.querySelectorAll("style");if(c)for(var d,e=0,f=c.length;f>e&&(d=c[e]);e++)this.resolveStyle(d,b)},resolveStyle:function(a,c){c=c||b(a),a.textContent=this.resolveCssText(a.textContent,c)},resolveCssText:function(a,b,d){return a=c(a,b,d,h),c(a,b,d,i)},resolveAttributes:function(a,b){a.hasAttributes&&a.hasAttributes()&&this.resolveElementAttributes(a,b);var c=a&&a.querySelectorAll(k);if(c)for(var d,e=0,f=c.length;f>e&&(d=c[e]);e++)this.resolveElementAttributes(d,b)},resolveElementAttributes:function(a,e){e=e||b(a),j.forEach(function(b){var f,g=a.attributes[b],i=g&&g.value;i&&i.search(l)<0&&(f="style"===b?c(i,e,!1,h):d(e,i),g.value=f)})}},h=/(url\()([^)]*)(\))/g,i=/(@import[\s]+(?!url\())([^;]*)(;)/g,j=["href","src","action","style","url"],k="["+j.join("],[")+"]",l="{{.*}}",m="#";a.urlResolver=g}(Polymer),function(a){function b(a){this.cache=Object.create(null),this.map=Object.create(null),this.requests=0,this.regex=a}var c=Polymer.endOfMicrotask;b.prototype={extractUrls:function(a,b){for(var c,d,e=[];c=this.regex.exec(a);)d=new URL(c[1],b),e.push({matched:c[0],url:d.href});return e},process:function(a,b,c){var d=this.extractUrls(a,b),e=c.bind(null,this.map);this.fetch(d,e)},fetch:function(a,b){var c=a.length;if(!c)return b();for(var d,e,f,g=function(){0===--c&&b()},h=0;c>h;h++)d=a[h],f=d.url,e=this.cache[f],e||(e=this.xhr(f),e.match=d,this.cache[f]=e),e.wait(g)},handleXhr:function(a){var b=a.match,c=b.url,d=a.response||a.responseText||"";this.map[c]=d,this.fetch(this.extractUrls(d,c),a.resolve)},xhr:function(a){this.requests++;var b=new XMLHttpRequest;return b.open("GET",a,!0),b.send(),b.onerror=b.onload=this.handleXhr.bind(this,b),b.pending=[],b.resolve=function(){for(var a=b.pending,c=0;ch&&(e=a[h]);h++)this.resolveNode(e,b,d)}};var e=new b;a.styleResolver=e}(Polymer),function(a){function b(a,b){return a&&b&&Object.getOwnPropertyNames(b).forEach(function(c){var d=Object.getOwnPropertyDescriptor(b,c);d&&(Object.defineProperty(a,c,d),"function"==typeof d.value&&(d.value.nom=c))}),a}function c(a){for(var b=a||{},c=1;ce&&(c=d[e]);e++){var g=Object.getOwnPropertyDescriptor(b,c);if("function"==typeof g.value&&g.value===a)return c}b=b.__proto__}}function d(a,b,c){var d=e(c,b,a);return d[b]&&(d[b].nom=b),a._super=d}function e(a,b,c){for(;a;){if(a[b]!==c&&a[b])return a;a=f(a)}return Object}function f(a){return a.__proto__}a["super"]=b}(Polymer),function(a){function b(a){return a}function c(a,b){var c=typeof b;return b instanceof Date&&(c="date"),d[c](a,b)}var d={string:b,undefined:b,date:function(a){return new Date(Date.parse(a)||Date.now())},"boolean":function(a){return""===a?!0:"false"===a?!1:!!a},number:function(a){var b=parseFloat(a);return 0===b&&(b=parseInt(a)),isNaN(b)?a:b},object:function(a,b){if(null===b)return a;try{return JSON.parse(a.replace(/'/g,'"'))}catch(c){return a}},"function":function(a,b){return b}};a.deserializeValue=c}(Polymer),function(a){var b=a.extend,c={};c.declaration={},c.instance={},c.publish=function(a,c){for(var d in a)b(c,a[d])},a.api=c}(Polymer),function(a){var b={async:function(a,b,c){Polymer.flush(),b=b&&b.length?b:[b];var d=function(){(this[a]||a).apply(this,b)}.bind(this),e=c?setTimeout(d,c):requestAnimationFrame(d);return c?e:~e},cancelAsync:function(a){0>a?cancelAnimationFrame(~a):clearTimeout(a)},fire:function(a,b,c,d,e){var f=c||this,b=null===b||void 0===b?{}:b,g=new CustomEvent(a,{bubbles:void 0!==d?d:!0,cancelable:void 0!==e?e:!0,detail:b});return f.dispatchEvent(g),g},asyncFire:function(){this.async("fire",arguments)},classFollows:function(a,b,c){ +b&&b.classList.remove(c),a&&a.classList.add(c)},injectBoundHTML:function(a,b){var c=document.createElement("template");c.innerHTML=a;var d=this.instanceTemplate(c);return b&&(b.textContent="",b.appendChild(d)),d}},c=function(){},d={};b.asyncMethod=b.async,a.api.instance.utils=b,a.nop=c,a.nob=d}(Polymer),function(a){var b=window.WebComponents?WebComponents.flags.log:{},c="on-",d={EVENT_PREFIX:c,addHostListeners:function(){var a=this.eventDelegates;b.events&&Object.keys(a).length>0&&console.log("[%s] addHostListeners:",this.localName,a);for(var c in a){var d=a[c];PolymerGestures.addEventListener(this,c,this.element.getEventHandler(this,this,d))}},dispatchMethod:function(a,c,d){if(a){b.events&&console.group("[%s] dispatch [%s]",a.localName,c);var e="function"==typeof c?c:a[c];e&&e[d?"apply":"call"](a,d),b.events&&console.groupEnd(),Polymer.flush()}}};a.api.instance.events=d,a.addEventListener=function(a,b,c,d){PolymerGestures.addEventListener(wrap(a),b,c,d)},a.removeEventListener=function(a,b,c,d){PolymerGestures.removeEventListener(wrap(a),b,c,d)}}(Polymer),function(a){var b={copyInstanceAttributes:function(){var a=this._instanceAttributes;for(var b in a)this.hasAttribute(b)||this.setAttribute(b,a[b])},takeAttributes:function(){if(this._publishLC)for(var a,b=0,c=this.attributes,d=c.length;(a=c[b])&&d>b;b++)this.attributeToProperty(a.name,a.value)},attributeToProperty:function(b,c){var b=this.propertyForAttribute(b);if(b){if(c&&c.search(a.bindPattern)>=0)return;var d=this[b],c=this.deserializeValue(c,d);c!==d&&(this[b]=c)}},propertyForAttribute:function(a){var b=this._publishLC&&this._publishLC[a];return b},deserializeValue:function(b,c){return a.deserializeValue(b,c)},serializeValue:function(a,b){return"boolean"===b?a?"":void 0:"object"!==b&&"function"!==b&&void 0!==a?a:void 0},reflectPropertyToAttribute:function(a){var b=typeof this[a],c=this.serializeValue(this[a],b);void 0!==c?this.setAttribute(a,c):"boolean"===b&&this.removeAttribute(a)}};a.api.instance.attributes=b}(Polymer),function(a){function b(a,b){return a===b?0!==a||1/a===1/b:f(a)&&f(b)?!0:a!==a&&b!==b}function c(a,b){return void 0===b&&null===a?b:null===b||void 0===b?a:b}var d=window.WebComponents?WebComponents.flags.log:{},e={object:void 0,type:"update",name:void 0,oldValue:void 0},f=Number.isNaN||function(a){return"number"==typeof a&&isNaN(a)},g={createPropertyObserver:function(){var a=this._observeNames;if(a&&a.length){var b=this._propertyObserver=new CompoundObserver(!0);this.registerObserver(b);for(var c,d=0,e=a.length;e>d&&(c=a[d]);d++)b.addPath(this,c),this.observeArrayValue(c,this[c],null)}},openPropertyObserver:function(){this._propertyObserver&&this._propertyObserver.open(this.notifyPropertyChanges,this)},notifyPropertyChanges:function(a,b,c){var d,e,f={};for(var g in b)if(d=c[2*g+1],e=this.observe[d]){var h=b[g],i=a[g];this.observeArrayValue(d,i,h),f[e]||(void 0!==h&&null!==h||void 0!==i&&null!==i)&&(f[e]=!0,this.invokeMethod(e,[h,i,arguments]))}},invokeMethod:function(a,b){var c=this[a]||a;"function"==typeof c&&c.apply(this,b)},deliverChanges:function(){this._propertyObserver&&this._propertyObserver.deliver()},observeArrayValue:function(a,b,c){var e=this.observe[a];if(e&&(Array.isArray(c)&&(d.observe&&console.log("[%s] observeArrayValue: unregister observer [%s]",this.localName,a),this.closeNamedObserver(a+"__array")),Array.isArray(b))){d.observe&&console.log("[%s] observeArrayValue: register observer [%s]",this.localName,a,b);var f=new ArrayObserver(b);f.open(function(a){this.invokeMethod(e,[a])},this),this.registerNamedObserver(a+"__array",f)}},emitPropertyChangeRecord:function(a,c,d){if(!b(c,d)&&(this._propertyChanged(a,c,d),Observer.hasObjectObserve)){var f=this._objectNotifier;f||(f=this._objectNotifier=Object.getNotifier(this)),e.object=this,e.name=a,e.oldValue=d,f.notify(e)}},_propertyChanged:function(a){this.reflect[a]&&this.reflectPropertyToAttribute(a)},bindProperty:function(a,b,d){if(d)return void(this[a]=b);var e=this.element.prototype.computed;if(e&&e[a]){var f=a+"ComputedBoundObservable_";return void(this[f]=b)}return this.bindToAccessor(a,b,c)},bindToAccessor:function(a,c,d){function e(b,c){j[f]=b;var d=j[h];d&&"function"==typeof d.setValue&&d.setValue(b),j.emitPropertyChangeRecord(a,b,c)}var f=a+"_",g=a+"Observable_",h=a+"ComputedBoundObservable_";this[g]=c;var i=this[f],j=this,k=c.open(e);if(d&&!b(i,k)){var l=d(i,k);b(k,l)||(k=l,c.setValue&&c.setValue(k))}e(k,i);var m={close:function(){c.close(),j[g]=void 0,j[h]=void 0}};return this.registerObserver(m),m},createComputedProperties:function(){if(this._computedNames)for(var a=0;ae&&(c=d[e]);e++)b[c.id]=c},onMutation:function(a,b){var c=new MutationObserver(function(a){b.call(this,c,a),c.disconnect()}.bind(this));c.observe(a,{childList:!0,subtree:!0})}};c.prototype=d,d.constructor=c,a.Base=c,a.isBase=b,a.api.instance.base=d}(Polymer),function(a){function b(a){return a.__proto__}function c(a,b){var c="",d=!1;b&&(c=b.localName,d=b.hasAttribute("is"));var e=WebComponents.ShadowCSS.makeScopeSelector(c,d);return WebComponents.ShadowCSS.shimCssText(a,e)}var d=(window.WebComponents?WebComponents.flags.log:{},window.ShadowDOMPolyfill),e="element",f="controller",g={STYLE_SCOPE_ATTRIBUTE:e,installControllerStyles:function(){var a=this.findStyleScope();if(a&&!this.scopeHasNamedStyle(a,this.localName)){for(var c=b(this),d="";c&&c.element;)d+=c.element.cssTextForScope(f),c=b(c);d&&this.installScopeCssText(d,a)}},installScopeStyle:function(a,b,c){var c=c||this.findStyleScope(),b=b||"";if(c&&!this.scopeHasNamedStyle(c,this.localName+b)){var d="";if(a instanceof Array)for(var e,f=0,g=a.length;g>f&&(e=a[f]);f++)d+=e.textContent+"\n\n";else d=a.textContent;this.installScopeCssText(d,c,b)}},installScopeCssText:function(a,b,e){if(b=b||this.findStyleScope(),e=e||"",b){d&&(a=c(a,b.host));var g=this.element.cssTextToScopeStyle(a,f);Polymer.applyStyleToScope(g,b),this.styleCacheForScope(b)[this.localName+e]=!0}},findStyleScope:function(a){for(var b=a||this;b.parentNode;)b=b.parentNode;return b},scopeHasNamedStyle:function(a,b){var c=this.styleCacheForScope(a);return c[b]},styleCacheForScope:function(a){if(d){var b=a.host?a.host.localName:a.localName;return h[b]||(h[b]={})}return a._scopeStyles=a._scopeStyles||{}}},h={};a.api.instance.styles=g}(Polymer),function(a){function b(a,b){if("string"!=typeof a){var c=b||document._currentScript;if(b=a,a=c&&c.parentNode&&c.parentNode.getAttribute?c.parentNode.getAttribute("name"):"",!a)throw"Element name could not be inferred."}if(f(a))throw"Already registered (Polymer) prototype for element "+a;e(a,b),d(a)}function c(a,b){i[a]=b}function d(a){i[a]&&(i[a].registerWhenReady(),delete i[a])}function e(a,b){return j[a]=b||{}}function f(a){return j[a]}function g(a,b){if("string"!=typeof b)return!1;var c=HTMLElement.getPrototypeForTag(b),d=c&&c.constructor;return d?CustomElements["instanceof"]?CustomElements["instanceof"](a,d):a instanceof d:!1}var h=a.extend,i=(a.api,{}),j={};a.getRegisteredPrototype=f,a.waitingForPrototype=c,a.instanceOfType=g,window.Polymer=b,h(Polymer,a),WebComponents.consumeDeclarations&&WebComponents.consumeDeclarations(function(a){if(a)for(var c,d=0,e=a.length;e>d&&(c=a[d]);d++)b.apply(null,c)})}(Polymer),function(a){var b={resolveElementPaths:function(a){Polymer.urlResolver.resolveDom(a)},addResolvePathApi:function(){var a=this.getAttribute("assetpath")||"",b=new URL(a,this.ownerDocument.baseURI);this.prototype.resolvePath=function(a,c){var d=new URL(a,c||b);return d.href}}};a.api.declaration.path=b}(Polymer),function(a){function b(a,b){var c=new URL(a.getAttribute("href"),b).href;return"@import '"+c+"';"}function c(a,b){if(a){b===document&&(b=document.head),i&&(b=document.head);var c=d(a.textContent),e=a.getAttribute(h);e&&c.setAttribute(h,e);var f=b.firstElementChild;if(b===document.head){var g="style["+h+"]",j=document.head.querySelectorAll(g);j.length&&(f=j[j.length-1].nextElementSibling)}b.insertBefore(c,f)}}function d(a,b){b=b||document,b=b.createElement?b:b.ownerDocument;var c=b.createElement("style");return c.textContent=a,c}function e(a){return a&&a.__resource||""}function f(a,b){return q?q.call(a,b):void 0}var g=(window.WebComponents?WebComponents.flags.log:{},a.api.instance.styles),h=g.STYLE_SCOPE_ATTRIBUTE,i=window.ShadowDOMPolyfill,j="style",k="@import",l="link[rel=stylesheet]",m="global",n="polymer-scope",o={loadStyles:function(a){var b=this.fetchTemplate(),c=b&&this.templateContent();if(c){this.convertSheetsToStyles(c);var d=this.findLoadableStyles(c);if(d.length){var e=b.ownerDocument.baseURI;return Polymer.styleResolver.loadStyles(d,e,a)}}a&&a()},convertSheetsToStyles:function(a){for(var c,e,f=a.querySelectorAll(l),g=0,h=f.length;h>g&&(c=f[g]);g++)e=d(b(c,this.ownerDocument.baseURI),this.ownerDocument),this.copySheetAttributes(e,c),c.parentNode.replaceChild(e,c)},copySheetAttributes:function(a,b){for(var c,d=0,e=b.attributes,f=e.length;(c=e[d])&&f>d;d++)"rel"!==c.name&&"href"!==c.name&&a.setAttribute(c.name,c.value)},findLoadableStyles:function(a){var b=[];if(a)for(var c,d=a.querySelectorAll(j),e=0,f=d.length;f>e&&(c=d[e]);e++)c.textContent.match(k)&&b.push(c);return b},installSheets:function(){this.cacheSheets(),this.cacheStyles(),this.installLocalSheets(),this.installGlobalStyles()},cacheSheets:function(){this.sheets=this.findNodes(l),this.sheets.forEach(function(a){a.parentNode&&a.parentNode.removeChild(a)})},cacheStyles:function(){this.styles=this.findNodes(j+"["+n+"]"),this.styles.forEach(function(a){a.parentNode&&a.parentNode.removeChild(a)})},installLocalSheets:function(){var a=this.sheets.filter(function(a){return!a.hasAttribute(n)}),b=this.templateContent();if(b){var c="";if(a.forEach(function(a){c+=e(a)+"\n"}),c){var f=d(c,this.ownerDocument);b.insertBefore(f,b.firstChild)}}},findNodes:function(a,b){var c=this.querySelectorAll(a).array(),d=this.templateContent();if(d){var e=d.querySelectorAll(a).array();c=c.concat(e)}return b?c.filter(b):c},installGlobalStyles:function(){var a=this.styleForScope(m);c(a,document.head)},cssTextForScope:function(a){var b="",c="["+n+"="+a+"]",d=function(a){return f(a,c)},g=this.sheets.filter(d);g.forEach(function(a){b+=e(a)+"\n\n"});var h=this.styles.filter(d);return h.forEach(function(a){b+=a.textContent+"\n\n"}),b},styleForScope:function(a){var b=this.cssTextForScope(a);return this.cssTextToScopeStyle(b,a)},cssTextToScopeStyle:function(a,b){if(a){var c=d(a);return c.setAttribute(h,this.getAttribute("name")+"-"+b),c}}},p=HTMLElement.prototype,q=p.matches||p.matchesSelector||p.webkitMatchesSelector||p.mozMatchesSelector;a.api.declaration.styles=o,a.applyStyleToScope=c}(Polymer),function(a){var b=(window.WebComponents?WebComponents.flags.log:{},a.api.instance.events),c=b.EVENT_PREFIX,d={};["webkitAnimationStart","webkitAnimationEnd","webkitTransitionEnd","DOMFocusOut","DOMFocusIn","DOMMouseScroll"].forEach(function(a){d[a.toLowerCase()]=a});var e={parseHostEvents:function(){var a=this.prototype.eventDelegates;this.addAttributeDelegates(a)},addAttributeDelegates:function(a){for(var b,c=0;b=this.attributes[c];c++)this.hasEventPrefix(b.name)&&(a[this.removeEventPrefix(b.name)]=b.value.replace("{{","").replace("}}","").trim())},hasEventPrefix:function(a){return a&&"o"===a[0]&&"n"===a[1]&&"-"===a[2]},removeEventPrefix:function(a){return a.slice(f)},findController:function(a){for(;a.parentNode;){if(a.eventController)return a.eventController;a=a.parentNode}return a.host},getEventHandler:function(a,b,c){var d=this;return function(e){a&&a.PolymerBase||(a=d.findController(b));var f=[e,e.detail,e.currentTarget];a.dispatchMethod(a,c,f)}},prepareEventBinding:function(a,b){if(this.hasEventPrefix(b)){var c=this.removeEventPrefix(b);c=d[c]||c;var e=this;return function(b,d,f){function g(){return"{{ "+a+" }}"}var h=e.getEventHandler(void 0,d,a);return PolymerGestures.addEventListener(d,c,h),f?void 0:{open:g,discardChanges:g,close:function(){PolymerGestures.removeEventListener(d,c,h)}}}}}},f=c.length;a.api.declaration.events=e}(Polymer),function(a){var b=["attribute"],c={inferObservers:function(a){var b,c=a.observe;for(var d in a)"Changed"===d.slice(-7)&&(b=d.slice(0,-7),this.canObserveProperty(b)&&(c||(c=a.observe={}),c[b]=c[b]||d))},canObserveProperty:function(a){return b.indexOf(a)<0},explodeObservers:function(a){var b=a.observe;if(b){var c={};for(var d in b)for(var e,f=d.split(" "),g=0;e=f[g];g++)c[e]=b[d];a.observe=c}},optimizePropertyMaps:function(a){if(a.observe){var b=a._observeNames=[];for(var c in a.observe)for(var d,e=c.split(" "),f=0;d=e[f];f++)b.push(d)}if(a.publish){var b=a._publishNames=[];for(var c in a.publish)b.push(c)}if(a.computed){var b=a._computedNames=[];for(var c in a.computed)b.push(c)}},publishProperties:function(a,b){var c=a.publish;c&&(this.requireProperties(c,a,b),this.filterInvalidAccessorNames(c),a._publishLC=this.lowerCaseMap(c));var d=a.computed;d&&this.filterInvalidAccessorNames(d)},filterInvalidAccessorNames:function(a){for(var b in a)this.propertyNameBlacklist[b]&&(console.warn('Cannot define property "'+b+'" for element "'+this.name+'" because it has the same name as an HTMLElement property, and not all browsers support overriding that. Consider giving it a different name.'),delete a[b])},requireProperties:function(a,b){b.reflect=b.reflect||{};for(var c in a){var d=a[c];d&&void 0!==d.reflect&&(b.reflect[c]=Boolean(d.reflect),d=d.value),void 0!==d&&(b[c]=d)}},lowerCaseMap:function(a){var b={};for(var c in a)b[c.toLowerCase()]=c;return b},createPropertyAccessor:function(a,b){var c=this.prototype,d=a+"_",e=a+"Observable_";c[d]=c[a],Object.defineProperty(c,a,{get:function(){var a=this[e];return a&&a.deliver(),this[d]},set:function(c){if(b)return this[d];var f=this[e];if(f)return void f.setValue(c);var g=this[d];return this[d]=c,this.emitPropertyChangeRecord(a,c,g),c},configurable:!0})},createPropertyAccessors:function(a){var b=a._computedNames;if(b&&b.length)for(var c,d=0,e=b.length;e>d&&(c=b[d]);d++)this.createPropertyAccessor(c,!0);var b=a._publishNames;if(b&&b.length)for(var c,d=0,e=b.length;e>d&&(c=b[d]);d++)a.computed&&a.computed[c]||this.createPropertyAccessor(c)},propertyNameBlacklist:{children:1,"class":1,id:1,hidden:1,style:1,title:1}};a.api.declaration.properties=c}(Polymer),function(a){var b="attributes",c=/\s|,/,d={inheritAttributesObjects:function(a){this.inheritObject(a,"publishLC"),this.inheritObject(a,"_instanceAttributes")},publishAttributes:function(a){var d=this.getAttribute(b);if(d)for(var e,f=a.publish||(a.publish={}),g=d.split(c),h=0,i=g.length;i>h;h++)e=g[h].trim(),e&&void 0===f[e]&&(f[e]=void 0)},accumulateInstanceAttributes:function(){for(var a,b=this.prototype._instanceAttributes,c=this.attributes,d=0,e=c.length;e>d&&(a=c[d]);d++)this.isInstanceAttribute(a.name)&&(b[a.name]=a.value)},isInstanceAttribute:function(a){return!this.blackList[a]&&"on-"!==a.slice(0,3)},blackList:{name:1,"extends":1,constructor:1,noscript:1,assetpath:1,"cache-csstext":1}};d.blackList[b]=1,a.api.declaration.attributes=d}(Polymer),function(a){var b=a.api.declaration.events,c=new PolymerExpressions,d=c.prepareBinding;c.prepareBinding=function(a,e,f){return b.prepareEventBinding(a,e,f)||d.call(c,a,e,f)};var e={syntax:c,fetchTemplate:function(){return this.querySelector("template")},templateContent:function(){var a=this.fetchTemplate();return a&&a.content},installBindingDelegate:function(a){a&&(a.bindingDelegate=this.syntax)}};a.api.declaration.mdv=e}(Polymer),function(a){function b(a){if(!Object.__proto__){var b=Object.getPrototypeOf(a);a.__proto__=b,d(b)&&(b.__proto__=Object.getPrototypeOf(b))}}var c=a.api,d=a.isBase,e=a.extend,f=window.ShadowDOMPolyfill,g={register:function(a,b){this.buildPrototype(a,b),this.registerPrototype(a,b),this.publishConstructor()},buildPrototype:function(b,c){var d=a.getRegisteredPrototype(b),e=this.generateBasePrototype(c);this.desugarBeforeChaining(d,e),this.prototype=this.chainPrototypes(d,e),this.desugarAfterChaining(b,c)},desugarBeforeChaining:function(a,b){a.element=this,this.publishAttributes(a,b),this.publishProperties(a,b),this.inferObservers(a),this.explodeObservers(a)},chainPrototypes:function(a,c){this.inheritMetaData(a,c);var d=this.chainObject(a,c);return b(d),d},inheritMetaData:function(a,b){this.inheritObject("observe",a,b),this.inheritObject("publish",a,b),this.inheritObject("reflect",a,b),this.inheritObject("_publishLC",a,b),this.inheritObject("_instanceAttributes",a,b),this.inheritObject("eventDelegates",a,b)},desugarAfterChaining:function(a,b){this.optimizePropertyMaps(this.prototype),this.createPropertyAccessors(this.prototype),this.installBindingDelegate(this.fetchTemplate()),this.installSheets(),this.resolveElementPaths(this),this.accumulateInstanceAttributes(),this.parseHostEvents(),this.addResolvePathApi(),f&&WebComponents.ShadowCSS.shimStyling(this.templateContent(),a,b),this.prototype.registerCallback&&this.prototype.registerCallback(this)},publishConstructor:function(){var a=this.getAttribute("constructor");a&&(window[a]=this.ctor)},generateBasePrototype:function(a){var b=this.findBasePrototype(a);if(!b){var b=HTMLElement.getPrototypeForTag(a);b=this.ensureBaseApi(b),h[a]=b}return b},findBasePrototype:function(a){return h[a]},ensureBaseApi:function(a){if(a.PolymerBase)return a;var b=Object.create(a);return c.publish(c.instance,b),this.mixinMethod(b,a,c.instance.mdv,"bind"),b},mixinMethod:function(a,b,c,d){var e=function(a){return b[d].apply(this,a)};a[d]=function(){return this.mixinSuper=e,c[d].apply(this,arguments)}},inheritObject:function(a,b,c){var d=b[a]||{};b[a]=this.chainObject(d,c[a])},registerPrototype:function(a,b){var c={prototype:this.prototype},d=this.findTypeExtension(b);d&&(c["extends"]=d),HTMLElement.register(a,this.prototype),this.ctor=document.registerElement(a,c)},findTypeExtension:function(a){if(a&&a.indexOf("-")<0)return a;var b=this.findBasePrototype(a);return b.element?this.findTypeExtension(b.element["extends"]):void 0}},h={};g.chainObject=Object.__proto__?function(a,b){return a&&b&&a!==b&&(a.__proto__=b),a}:function(a,b){if(a&&b&&a!==b){var c=Object.create(b);a=e(c,a)}return a},c.declaration.prototype=g}(Polymer),function(a){function b(a){return document.contains(a)?j:i}function c(){return i.length?i[0]:j[0]}function d(a){f.waitToReady=!0,Polymer.endOfMicrotask(function(){HTMLImports.whenReady(function(){f.addReadyCallback(a),f.waitToReady=!1,f.check()})})}function e(a){if(void 0===a)return void f.ready();var b=setTimeout(function(){f.ready()},a);Polymer.whenReady(function(){clearTimeout(b)})}var f={wait:function(a){a.__queue||(a.__queue={},g.push(a))},enqueue:function(a,c,d){var e=a.__queue&&!a.__queue.check;return e&&(b(a).push(a),a.__queue.check=c,a.__queue.go=d),0!==this.indexOf(a)},indexOf:function(a){var c=b(a).indexOf(a);return c>=0&&document.contains(a)&&(c+=HTMLImports.useNative||HTMLImports.ready?i.length:1e9),c},go:function(a){var b=this.remove(a);b&&(a.__queue.flushable=!0,this.addToFlushQueue(b),this.check())},remove:function(a){var c=this.indexOf(a);if(0===c)return b(a).shift()},check:function(){var a=this.nextElement();return a&&a.__queue.check.call(a),this.canReady()?(this.ready(),!0):void 0},nextElement:function(){return c()},canReady:function(){return!this.waitToReady&&this.isEmpty()},isEmpty:function(){for(var a,b=0,c=g.length;c>b&&(a=g[b]);b++)if(a.__queue&&!a.__queue.flushable)return;return!0},addToFlushQueue:function(a){h.push(a)},flush:function(){if(!this.flushing){this.flushing=!0;for(var a;h.length;)a=h.shift(),a.__queue.go.call(a),a.__queue=null;this.flushing=!1}},ready:function(){var a=CustomElements.ready;CustomElements.ready=!1,this.flush(),CustomElements.useNative||CustomElements.upgradeDocumentTree(document),CustomElements.ready=a,Polymer.flush(),requestAnimationFrame(this.flushReadyCallbacks)},addReadyCallback:function(a){a&&k.push(a)},flushReadyCallbacks:function(){if(k)for(var a;k.length;)(a=k.shift())()},waitingFor:function(){for(var a,b=[],c=0,d=g.length;d>c&&(a=g[c]);c++)a.__queue&&!a.__queue.flushable&&b.push(a);return b},waitToReady:!0},g=[],h=[],i=[],j=[],k=[];a.elements=g,a.waitingFor=f.waitingFor.bind(f),a.forceReady=e,a.queue=f,a.whenReady=a.whenPolymerReady=d}(Polymer),function(a){function b(a){return Boolean(HTMLElement.getPrototypeForTag(a))}function c(a){return a&&a.indexOf("-")>=0}var d=a.extend,e=a.api,f=a.queue,g=a.whenReady,h=a.getRegisteredPrototype,i=a.waitingForPrototype,j=d(Object.create(HTMLElement.prototype),{createdCallback:function(){this.getAttribute("name")&&this.init()},init:function(){this.name=this.getAttribute("name"),this["extends"]=this.getAttribute("extends"),f.wait(this),this.loadResources(),this.registerWhenReady()},registerWhenReady:function(){this.registered||this.waitingForPrototype(this.name)||this.waitingForQueue()||this.waitingForResources()||f.go(this)},_register:function(){c(this["extends"])&&!b(this["extends"])&&console.warn("%s is attempting to extend %s, an unregistered element or one that was not registered with Polymer.",this.name,this["extends"]),this.register(this.name,this["extends"]),this.registered=!0},waitingForPrototype:function(a){return h(a)?void 0:(i(a,this),this.handleNoScript(a),!0)},handleNoScript:function(a){this.hasAttribute("noscript")&&!this.noscript&&(this.noscript=!0,Polymer(a))},waitingForResources:function(){return this._needsResources},waitingForQueue:function(){return f.enqueue(this,this.registerWhenReady,this._register)},loadResources:function(){this._needsResources=!0,this.loadStyles(function(){this._needsResources=!1,this.registerWhenReady()}.bind(this))}});e.publish(e.declaration,j),g(function(){document.body.removeAttribute("unresolved"),document.dispatchEvent(new CustomEvent("polymer-ready",{bubbles:!0}))}),document.registerElement("polymer-element",{prototype:j})}(Polymer),function(a){function b(a,b){a?(document.head.appendChild(a),d(b)):b&&b()}function c(a,c){if(a&&a.length){for(var d,e,f=document.createDocumentFragment(),g=0,h=a.length;h>g&&(d=a[g]);g++)e=document.createElement("link"),e.rel="import",e.href=d,f.appendChild(e);b(f,c)}else c&&c()}var d=a.whenReady;a["import"]=c,a.importElements=b}(Polymer),function(){var a=document.createElement("polymer-element");a.setAttribute("name","auto-binding"),a.setAttribute("extends","template"),a.init(),Polymer("auto-binding",{createdCallback:function(){this.syntax=this.bindingDelegate=this.makeSyntax(),Polymer.whenPolymerReady(function(){this.model=this,this.setAttribute("bind",""),this.async(function(){this.marshalNodeReferences(this.parentNode),this.fire("template-bound")})}.bind(this))},makeSyntax:function(){var a=Object.create(Polymer.api.declaration.events),b=this;a.findController=function(){return b.model};var c=new PolymerExpressions,d=c.prepareBinding;return c.prepareBinding=function(b,e,f){return a.prepareEventBinding(b,e,f)||d.call(c,b,e,f)},c}})}(); - - - - - +return pickBy("isBefore",args)};moment.max=function(){var args=[].slice.call(arguments,0);return pickBy("isAfter",args)};moment.utc=function(input,format,locale,strict){var c;if(typeof locale==="boolean"){strict=locale;locale=undefined}c={};c._isAMomentObject=true;c._useUTC=true;c._isUTC=true;c._l=locale;c._i=input;c._f=format;c._strict=strict;c._pf=defaultParsingFlags();return makeMoment(c).utc()};moment.unix=function(input){return moment(input*1e3)};moment.duration=function(input,key){var duration=input,match=null,sign,ret,parseIso,diffRes;if(moment.isDuration(input)){duration={ms:input._milliseconds,d:input._days,M:input._months}}else if(typeof input==="number"){duration={};if(key){duration[key]=input}else{duration.milliseconds=input}}else if(!!(match=aspNetTimeSpanJsonRegex.exec(input))){sign=match[1]==="-"?-1:1;duration={y:0,d:toInt(match[DATE])*sign,h:toInt(match[HOUR])*sign,m:toInt(match[MINUTE])*sign,s:toInt(match[SECOND])*sign,ms:toInt(match[MILLISECOND])*sign}}else if(!!(match=isoDurationRegex.exec(input))){sign=match[1]==="-"?-1:1;parseIso=function(inp){var res=inp&&parseFloat(inp.replace(",","."));return(isNaN(res)?0:res)*sign};duration={y:parseIso(match[2]),M:parseIso(match[3]),d:parseIso(match[4]),h:parseIso(match[5]),m:parseIso(match[6]),s:parseIso(match[7]),w:parseIso(match[8])}}else if(duration==null){duration={}}else if(typeof duration==="object"&&("from"in duration||"to"in duration)){diffRes=momentsDifference(moment(duration.from),moment(duration.to));duration={};duration.ms=diffRes.milliseconds;duration.M=diffRes.months}ret=new Duration(duration);if(moment.isDuration(input)&&hasOwnProp(input,"_locale")){ret._locale=input._locale}return ret};moment.version=VERSION;moment.defaultFormat=isoFormat;moment.ISO_8601=function(){};moment.momentProperties=momentProperties;moment.updateOffset=function(){};moment.relativeTimeThreshold=function(threshold,limit){if(relativeTimeThresholds[threshold]===undefined){return false}if(limit===undefined){return relativeTimeThresholds[threshold]}relativeTimeThresholds[threshold]=limit;return true};moment.lang=deprecate("moment.lang is deprecated. Use moment.locale instead.",function(key,value){return moment.locale(key,value)});moment.locale=function(key,values){var data;if(key){if(typeof values!=="undefined"){data=moment.defineLocale(key,values)}else{data=moment.localeData(key)}if(data){moment.duration._locale=moment._locale=data}}return moment._locale._abbr};moment.defineLocale=function(name,values){if(values!==null){values.abbr=name;if(!locales[name]){locales[name]=new Locale}locales[name].set(values);moment.locale(name);return locales[name]}else{delete locales[name];return null}};moment.langData=deprecate("moment.langData is deprecated. Use moment.localeData instead.",function(key){return moment.localeData(key)});moment.localeData=function(key){var locale;if(key&&key._locale&&key._locale._abbr){key=key._locale._abbr}if(!key){return moment._locale}if(!isArray(key)){locale=loadLocale(key);if(locale){return locale}key=[key]}return chooseLocale(key)};moment.isMoment=function(obj){return obj instanceof Moment||obj!=null&&hasOwnProp(obj,"_isAMomentObject")};moment.isDuration=function(obj){return obj instanceof Duration};for(i=lists.length-1;i>=0;--i){makeList(lists[i])}moment.normalizeUnits=function(units){return normalizeUnits(units)};moment.invalid=function(flags){var m=moment.utc(NaN);if(flags!=null){extend(m._pf,flags)}else{m._pf.userInvalidated=true}return m};moment.parseZone=function(){return moment.apply(null,arguments).parseZone()};moment.parseTwoDigitYear=function(input){return toInt(input)+(toInt(input)>68?1900:2e3)};moment.isDate=isDate;extend(moment.fn=Moment.prototype,{clone:function(){return moment(this)},valueOf:function(){return+this._d-(this._offset||0)*6e4},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var m=moment(this).utc();if(00}return false},parsingFlags:function(){return extend({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(keepLocalTime){return this.utcOffset(0,keepLocalTime)},local:function(keepLocalTime){if(this._isUTC){this.utcOffset(0,keepLocalTime);this._isUTC=false;if(keepLocalTime){this.subtract(this._dateUtcOffset(),"m")}}return this},format:function(inputString){var output=formatMoment(this,inputString||moment.defaultFormat);return this.localeData().postformat(output)},add:createAdder(1,"add"),subtract:createAdder(-1,"subtract"),diff:function(input,units,asFloat){var that=makeAs(input,this),zoneDiff=(that.utcOffset()-this.utcOffset())*6e4,anchor,diff,output,daysAdjust;units=normalizeUnits(units);if(units==="year"||units==="month"||units==="quarter"){output=monthDiff(this,that);if(units==="quarter"){output=output/3}else if(units==="year"){output=output/12}}else{diff=this-that;output=units==="second"?diff/1e3:units==="minute"?diff/6e4:units==="hour"?diff/36e5:units==="day"?(diff-zoneDiff)/864e5:units==="week"?(diff-zoneDiff)/6048e5:diff}return asFloat?output:absRound(output)},from:function(time,withoutSuffix){return moment.duration({to:this,from:time}).locale(this.locale()).humanize(!withoutSuffix)},fromNow:function(withoutSuffix){return this.from(moment(),withoutSuffix)},calendar:function(time){var now=time||moment(),sod=makeAs(now,this).startOf("day"),diff=this.diff(sod,"days",true),format=diff<-6?"sameElse":diff<-1?"lastWeek":diff<0?"lastDay":diff<1?"sameDay":diff<2?"nextDay":diff<7?"nextWeek":"sameElse";return this.format(this.localeData().calendar(format,this,moment(now)))},isLeapYear:function(){return isLeapYear(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(input){var day=this._isUTC?this._d.getUTCDay():this._d.getDay();if(input!=null){input=parseWeekday(input,this.localeData());return this.add(input-day,"d")}else{return day}},month:makeAccessor("Month",true),startOf:function(units){units=normalizeUnits(units);switch(units){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}if(units==="week"){this.weekday(0)}else if(units==="isoWeek"){this.isoWeekday(1)}if(units==="quarter"){this.month(Math.floor(this.month()/3)*3)}return this},endOf:function(units){units=normalizeUnits(units);if(units===undefined||units==="millisecond"){return this}return this.startOf(units).add(1,units==="isoWeek"?"week":units).subtract(1,"ms")},isAfter:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this>+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return inputMs<+this.clone().startOf(units)}},isBefore:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this<+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return+this.clone().endOf(units)this?this:other}),zone:deprecate("moment().zone is deprecated, use moment().utcOffset instead. "+"https://github.com/moment/moment/issues/1779",function(input,keepLocalTime){if(input!=null){if(typeof input!=="string"){input=-input}this.utcOffset(input,keepLocalTime);return this}else{return-this.utcOffset()}}),utcOffset:function(input,keepLocalTime){var offset=this._offset||0,localAdjust;if(input!=null){if(typeof input==="string"){input=utcOffsetFromString(input)}if(Math.abs(input)<16){input=input*60}if(!this._isUTC&&keepLocalTime){localAdjust=this._dateUtcOffset()}this._offset=input;this._isUTC=true;if(localAdjust!=null){this.add(localAdjust,"m")}if(offset!==input){if(!keepLocalTime||this._changeInProgress){addOrSubtractDurationFromMoment(this,moment.duration(input-offset,"m"),1,false)}else if(!this._changeInProgress){this._changeInProgress=true;moment.updateOffset(this,true);this._changeInProgress=null}}return this}else{return this._isUTC?offset:this._dateUtcOffset()}},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&this._offset===0},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){if(this._tzm){this.utcOffset(this._tzm)}else if(typeof this._i==="string"){this.utcOffset(utcOffsetFromString(this._i))}return this},hasAlignedHourOffset:function(input){if(!input){input=0}else{input=moment(input).utcOffset()}return(this.utcOffset()-input)%60===0},daysInMonth:function(){return daysInMonth(this.year(),this.month())},dayOfYear:function(input){var dayOfYear=round((moment(this).startOf("day")-moment(this).startOf("year"))/864e5)+1;return input==null?dayOfYear:this.add(input-dayOfYear,"d")},quarter:function(input){return input==null?Math.ceil((this.month()+1)/3):this.month((input-1)*3+this.month()%3)},weekYear:function(input){var year=weekOfYear(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return input==null?year:this.add(input-year,"y")},isoWeekYear:function(input){var year=weekOfYear(this,1,4).year;return input==null?year:this.add(input-year,"y")},week:function(input){var week=this.localeData().week(this);return input==null?week:this.add((input-week)*7,"d")},isoWeek:function(input){var week=weekOfYear(this,1,4).week;return input==null?week:this.add((input-week)*7,"d")},weekday:function(input){var weekday=(this.day()+7-this.localeData()._week.dow)%7;return input==null?weekday:this.add(input-weekday,"d")},isoWeekday:function(input){return input==null?this.day()||7:this.day(this.day()%7?input:input-7)},isoWeeksInYear:function(){return weeksInYear(this.year(),1,4)},weeksInYear:function(){var weekInfo=this.localeData()._week;return weeksInYear(this.year(),weekInfo.dow,weekInfo.doy)},get:function(units){units=normalizeUnits(units);return this[units]()},set:function(units,value){var unit;if(typeof units==="object"){for(unit in units){this.set(unit,units[unit])}}else{units=normalizeUnits(units);if(typeof this[units]==="function"){this[units](value)}}return this},locale:function(key){var newLocaleData;if(key===undefined){return this._locale._abbr}else{newLocaleData=moment.localeData(key);if(newLocaleData!=null){this._locale=newLocaleData}return this}},lang:deprecate("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(key){if(key===undefined){return this.localeData()}else{return this.locale(key)}}),localeData:function(){return this._locale},_dateUtcOffset:function(){return-Math.round(this._d.getTimezoneOffset()/15)*15}});function rawMonthSetter(mom,value){var dayOfMonth;if(typeof value==="string"){value=mom.localeData().monthsParse(value);if(typeof value!=="number"){return mom}}dayOfMonth=Math.min(mom.date(),daysInMonth(mom.year(),value));mom._d["set"+(mom._isUTC?"UTC":"")+"Month"](value,dayOfMonth);return mom}function rawGetter(mom,unit){return mom._d["get"+(mom._isUTC?"UTC":"")+unit]()}function rawSetter(mom,unit,value){if(unit==="Month"){return rawMonthSetter(mom,value)}else{return mom._d["set"+(mom._isUTC?"UTC":"")+unit](value)}}function makeAccessor(unit,keepTime){return function(value){if(value!=null){rawSetter(this,unit,value);moment.updateOffset(this,keepTime);return this}else{return rawGetter(this,unit)}}}moment.fn.millisecond=moment.fn.milliseconds=makeAccessor("Milliseconds",false);moment.fn.second=moment.fn.seconds=makeAccessor("Seconds",false);moment.fn.minute=moment.fn.minutes=makeAccessor("Minutes",false);moment.fn.hour=moment.fn.hours=makeAccessor("Hours",true);moment.fn.date=makeAccessor("Date",true);moment.fn.dates=deprecate("dates accessor is deprecated. Use date instead.",makeAccessor("Date",true));moment.fn.year=makeAccessor("FullYear",true);moment.fn.years=deprecate("years accessor is deprecated. Use year instead.",makeAccessor("FullYear",true));moment.fn.days=moment.fn.day;moment.fn.months=moment.fn.month;moment.fn.weeks=moment.fn.week;moment.fn.isoWeeks=moment.fn.isoWeek;moment.fn.quarters=moment.fn.quarter;moment.fn.toJSON=moment.fn.toISOString;moment.fn.isUTC=moment.fn.isUtc;function daysToYears(days){return days*400/146097}function yearsToDays(years){return years*146097/400}extend(moment.duration.fn=Duration.prototype,{_bubble:function(){var milliseconds=this._milliseconds,days=this._days,months=this._months,data=this._data,seconds,minutes,hours,years=0;data.milliseconds=milliseconds%1e3;seconds=absRound(milliseconds/1e3);data.seconds=seconds%60;minutes=absRound(seconds/60);data.minutes=minutes%60;hours=absRound(minutes/60);data.hours=hours%24;days+=absRound(hours/24);years=absRound(daysToYears(days));days-=absRound(yearsToDays(years));months+=absRound(days/30);days%=30;years+=absRound(months/12);months%=12;data.days=days;data.months=months;data.years=years},abs:function(){this._milliseconds=Math.abs(this._milliseconds);this._days=Math.abs(this._days);this._months=Math.abs(this._months);this._data.milliseconds=Math.abs(this._data.milliseconds);this._data.seconds=Math.abs(this._data.seconds);this._data.minutes=Math.abs(this._data.minutes);this._data.hours=Math.abs(this._data.hours);this._data.months=Math.abs(this._data.months);this._data.years=Math.abs(this._data.years);return this},weeks:function(){return absRound(this.days()/7)},valueOf:function(){return this._milliseconds+this._days*864e5+this._months%12*2592e6+toInt(this._months/12)*31536e6},humanize:function(withSuffix){var output=relativeTime(this,!withSuffix,this.localeData());if(withSuffix){output=this.localeData().pastFuture(+this,output)}return this.localeData().postformat(output)},add:function(input,val){var dur=moment.duration(input,val);this._milliseconds+=dur._milliseconds;this._days+=dur._days;this._months+=dur._months;this._bubble();return this},subtract:function(input,val){var dur=moment.duration(input,val);this._milliseconds-=dur._milliseconds;this._days-=dur._days;this._months-=dur._months;this._bubble();return this},get:function(units){units=normalizeUnits(units);return this[units.toLowerCase()+"s"]()},as:function(units){var days,months;units=normalizeUnits(units);if(units==="month"||units==="year"){days=this._days+this._milliseconds/864e5;months=this._months+daysToYears(days)*12;return units==="month"?months:months/12}else{days=this._days+Math.round(yearsToDays(this._months/12));switch(units){case"week":return days/7+this._milliseconds/6048e5;case"day":return days+this._milliseconds/864e5;case"hour":return days*24+this._milliseconds/36e5;case"minute":return days*24*60+this._milliseconds/6e4;case"second":return days*24*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(days*24*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+units)}}},lang:moment.fn.lang,locale:moment.fn.locale,toIsoString:deprecate("toIsoString() is deprecated. Please use toISOString() instead "+"(notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var years=Math.abs(this.years()),months=Math.abs(this.months()),days=Math.abs(this.days()),hours=Math.abs(this.hours()),minutes=Math.abs(this.minutes()),seconds=Math.abs(this.seconds()+this.milliseconds()/1e3);if(!this.asSeconds()){return"P0D"}return(this.asSeconds()<0?"-":"")+"P"+(years?years+"Y":"")+(months?months+"M":"")+(days?days+"D":"")+(hours||minutes||seconds?"T":"")+(hours?hours+"H":"")+(minutes?minutes+"M":"")+(seconds?seconds+"S":"")},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}});moment.duration.fn.toString=moment.duration.fn.toISOString;function makeDurationGetter(name){moment.duration.fn[name]=function(){return this._data[name]}}for(i in unitMillisecondFactors){if(hasOwnProp(unitMillisecondFactors,i)){makeDurationGetter(i.toLowerCase())}}moment.duration.fn.asMilliseconds=function(){return this.as("ms")};moment.duration.fn.asSeconds=function(){return this.as("s")};moment.duration.fn.asMinutes=function(){return this.as("m")};moment.duration.fn.asHours=function(){return this.as("h")};moment.duration.fn.asDays=function(){return this.as("d")};moment.duration.fn.asWeeks=function(){return this.as("weeks")};moment.duration.fn.asMonths=function(){return this.as("M")};moment.duration.fn.asYears=function(){return this.as("y")};moment.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(number){var b=number%10,output=toInt(number%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th";return number+output}});function makeGlobal(shouldDeprecate){if(typeof ender!=="undefined"){return}oldGlobalMoment=globalScope.moment;if(shouldDeprecate){globalScope.moment=deprecate("Accessing Moment through the global scope is "+"deprecated, and will be removed in an upcoming "+"release.",moment)}else{globalScope.moment=moment}}if(hasModule){module.exports=moment}else if(typeof define==="function"&&define.amd){define(function(require,exports,module){if(module.config&&module.config()&&module.config().noGlobal===true){globalScope.moment=oldGlobalMoment}return moment});makeGlobal(true)}else{makeGlobal()}}).call(this); \ No newline at end of file From ba13f78d49a1e8c4be485bf645a61c8c1f542964 Mon Sep 17 00:00:00 2001 From: jamespcole Date: Mon, 6 Apr 2015 22:13:04 +1000 Subject: [PATCH 18/59] Added initial Transmission torrent client sensor --- .../components/sensor/transmission.py | 176 ++++++++++++++++++ requirements.txt | 9 +- 2 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/sensor/transmission.py diff --git a/homeassistant/components/sensor/transmission.py b/homeassistant/components/sensor/transmission.py new file mode 100644 index 00000000000..5738e791596 --- /dev/null +++ b/homeassistant/components/sensor/transmission.py @@ -0,0 +1,176 @@ +""" +homeassistant.components.sensor.sabnzbd +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Monitors SABnzbd NZB client API + +Configuration: + +To use the SABnzbd sensor you will need to add something like the following to +your config/configuration.yaml + +sensor: + platform: sabnzbd + name: SAB + api_key: YOUR_API_KEY + base_url: YOUR_SABNZBD_BASE_URL + monitored_variables: + - type: 'current_status' + - type: 'speed' + - type: 'queue_size' + - type: 'queue_remaining' + - type: 'disk_size' + - type: 'disk_free' + +VARIABLES: + +base_url +*Required +This is the base URL of your SABnzbd instance including the port number if not +running on 80 +Example: http://192.168.1.32:8124/ + + +name +*Optional +The name to use when displaying this SABnzbd instance + +monitored_variables +*Required +An array specifying the variables to monitor. + +These are the variables for the monitored_variables array: + +type +*Required +The variable you wish to monitor, see the configuration example above for a +list of all available variables + + +""" + +from homeassistant.util import Throttle +from datetime import timedelta +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD + +from homeassistant.helpers.entity import Entity +# pylint: disable=no-name-in-module, import-error +import transmissionrpc + +from transmissionrpc.error import TransmissionError + +import logging + +SENSOR_TYPES = { + 'current_status': ['Status', ''], + 'download_speed': ['Down Speed', 'MB/s'], + 'upload_speed': ['Up Speed', 'MB/s'], + 'queue_size': ['Queue', 'MB'], + 'queue_remaining': ['Left', 'MB'], + 'disk_size': ['Disk', 'GB'], + 'disk_free': ['Disk Free', 'GB'], +} + +_LOGGER = logging.getLogger(__name__) + +_THROTTLED_REFRESH = None + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the sensors """ + host = config.get(CONF_HOST) + username = config.get(CONF_USERNAME, None) + password = config.get(CONF_PASSWORD, None) + port = config.get('port') + + name = config.get("name", "Transmission") + if not host: + _LOGGER.error('Missing config variable %s', CONF_HOST) + return False + + # import logging + # logging.getLogger('transmissionrpc').setLevel(logging.DEBUG) + + transmission_api = transmissionrpc.Client(host, port=port, user=username, password=password) + try: + session = transmission_api.session_stats() + print(transmission_api.session) + except TransmissionError: + _LOGGER.exception("Connection to Transmission API failed.") + return False + + # pylint: disable=global-statement + global _THROTTLED_REFRESH + _THROTTLED_REFRESH = Throttle(timedelta(seconds=1))(transmission_api.session_stats) + + dev = [] + for variable in config['monitored_variables']: + if variable['type'] not in SENSOR_TYPES: + _LOGGER.error('Sensor type: "%s" does not exist', variable['type']) + else: + dev.append(TransmissionSensor(variable['type'], transmission_api, name)) + + add_devices(dev) + + +class TransmissionSensor(Entity): + """ A Transmission sensor """ + + def __init__(self, sensor_type, transmission_client, client_name): + self._name = SENSOR_TYPES[sensor_type][0] + self.transmission_client = transmission_client + self.type = sensor_type + self.client_name = client_name + self._state = None + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + + @property + def name(self): + return self.client_name + ' ' + self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + return self._unit_of_measurement + + def refresh_transmission_data(self): + """ Calls the throttled Transmission refresh method. """ + if _THROTTLED_REFRESH is not None: + try: + _THROTTLED_REFRESH() + except TransmissionError: + _LOGGER.exception( + self.name + " Connection to Transmission API failed." + ) + + def update(self): + """ Gets the latest from Transmission and updates the state. """ + self.refresh_transmission_data() + if self.type == 'current_status': + if self.transmission_client.session: + upload = self.transmission_client.session.uploadSpeed + download = self.transmission_client.session.downloadSpeed + if upload > 0 and download > 0: + self._state = 'Up/Down' + elif upload > 0 and download == 0: + self._state = 'Seeding' + elif upload == 0 and download > 0: + self._state = 'Downloading' + else: + self._state = 'Idle' + else: + self._state = 'Unknown' + + if self.transmission_client.session: + if self.type == 'download_speed': + mb_spd = float(self.transmission_client.session.downloadSpeed) / 1024 / 1024 + self._state = round(mb_spd, 2 if mb_spd < 0.1 else 1) + elif self.type == 'upload_speed': + mb_spd = float(self.transmission_client.session.uploadSpeed) / 1024 / 1024 + self._state = round(mb_spd, 2 if mb_spd < 0.1 else 1) diff --git a/requirements.txt b/requirements.txt index 9f0352ccef1..8ceecf58908 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,8 +34,11 @@ python-nest>=2.1 # z-wave pydispatcher>=2.0.5 -# sensor.systemmonitor +# sensor.systemmonitor psutil>=2.2.1 -#pushover notifications -python-pushover>=0.2 \ No newline at end of file +# pushover notifications +python-pushover>=0.2 + +# Transmission Torrent Client +transmissionrpc>=0.11 \ No newline at end of file From 100a75908bd1a93c9a9ac7b5bbab69cb7f449649 Mon Sep 17 00:00:00 2001 From: jamespcole Date: Tue, 7 Apr 2015 02:25:03 +1000 Subject: [PATCH 19/59] Fixed bug in history component where the entity id filtering was not being applied correctly --- homeassistant/components/history.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index c547190b91a..ddce81b3476 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -50,8 +50,10 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None): result = defaultdict(list) + entity_ids = [entity_id] if entity_id is not None else None + # Get the states at the start time - for state in get_states(start_time): + for state in get_states(start_time, entity_ids): state.last_changed = start_time result[state.entity_id].append(state) From 56184daf59bfb82f0c590fdde1eb78e950ca3486 Mon Sep 17 00:00:00 2001 From: jamespcole Date: Tue, 7 Apr 2015 18:01:23 +1000 Subject: [PATCH 20/59] Fixed linting warnings --- homeassistant/components/history.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index ddce81b3476..a288d5471d9 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -100,6 +100,7 @@ def get_state(point_in_time, entity_id, run=None): return states[0] if states else None +# pylint: disable=unused-argument def setup(hass, config): """ Setup history hooks. """ hass.http.register_path( @@ -115,6 +116,7 @@ def setup(hass, config): return True +# pylint: disable=unused-argument # pylint: disable=invalid-name def _api_last_5_states(handler, path_match, data): """ Return the last 5 states for an entity id as JSON. """ From e65ad67b3203e670ae2bd668836e2281502ed57a Mon Sep 17 00:00:00 2001 From: jamespcole Date: Tue, 7 Apr 2015 22:21:13 +1000 Subject: [PATCH 21/59] Fixed some alignment issues and added loading spinner --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 2 +- .../polymer/components/state-timeline.html | 62 +++++++++++++------ .../polymer/dialogs/more-info-dialog.html | 6 +- .../polymer/layouts/partial-history.html | 8 ++- 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 5461cfa0cc1..1b2e5cdc99a 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "ddbbcdc7da3b2fe5b4e59051328c60c0" +VERSION = "f51c439b587ce03928e2db4cc08ef492" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 5e5c5cf3efb..ab19f02acda 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -19,4 +19,4 @@ imports:{_:e}};var yu=function(){function e(){}return function(n){if(hi(n)){e.pr }},statesLoaded:{get:function(){return r(f.ACTION_NEW_STATES)}}}),e}(l),y=new v;y.dispatchToken=c.register(function(t){switch(t.actionType){case f.ACTION_FETCH_ALL:_=[],y.emitChange();break;case f.ACTION_NEW_LOADED_COMPONENTS:case f.ACTION_NEW_EVENTS:case f.ACTION_NEW_SERVICES:case f.ACTION_NEW_STATES:r(t.actionType)||(_.push(t.actionType),p=p||i(),y.emitChange());break;case f.ACTION_LOG_OUT:p=!1,_=[],y.emitChange()}}),t.exports=y},function(t,e,n){"use strict";var r=function(t){return t&&t.__esModule?t["default"]:t},i=function(){function t(t,e){for(var n in e){var r=e[n];r.configurable=!0,r.value&&(r.writable=!0)}Object.defineProperties(t,e)}return function(e,n,r){return n&&t(e.prototype,n),r&&t(e,r),e}}(),o=function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(t.__proto__=e)},u=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},a=r(n(1)),s=r(n(2)),c=r(n(3)),f="STATE_LISTENING",l="STATE_TRANSMITTING",h="STATE_IDLE",p="STATE_ERROR",_=h,v="",y="",d=function(t){function e(){u(this,e),null!=t&&t.apply(this,arguments)}return o(e,t),i(e,{state:{get:function(){return _}},isListening:{get:function(){return _===f}},isTransmitting:{get:function(){return _===l}},hasError:{get:function(){return _===p}},interimTranscript:{get:function(){return v}},finalTranscript:{get:function(){return y}}}),e}(c),g=new d;g.STATE_LISTENING=f,g.STATE_TRANSMITTING=l,g.STATE_IDLE=h,g.STATE_ERROR=p,g.dispatchToken=a.register(function(t){switch(t.actionType){case s.ACTION_LISTENING_START:_=f,v="",y="",g.emitChange();break;case s.ACTION_LISTENING_TRANSMITTING:_=l,v="",y=t.finalTranscript,g.emitChange();break;case s.ACTION_LISTENING_DONE:_=h,g.emitChange();break;case s.ACTION_LISTENING_ERROR:_=p,g.emitChange();break;case s.ACTION_LISTENING_RESULT:v=t.interimTranscript,y=t.finalTranscript,g.emitChange()}}),t.exports=g},function(t,e){"use strict";function n(t){var e=t.split(" "),n=r(e,2),i=n[0],o=n[1],u=i.split(":"),a=r(u,3),s=a[0],c=a[1],f=a[2],l=o.split("-"),h=r(l,3),p=h[0],_=h[1],v=h[2];return new Date(v,parseInt(_)-1,p,s,c,f)}var r=function(t,e){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t)){for(var n,r=[],i=t[Symbol.iterator]();!(n=i.next()).done&&(r.push(n.value),!e||r.length!==e););return r}throw new TypeError("Invalid attempt to destructure non-iterable instance")};e.parseDateTime=n,Object.defineProperty(e,"__esModule",{value:!0})},function(t,e,n){"use strict";function r(t){a.dispatch({actionType:s.ACTION_NEW_LOADED_COMPONENTS,components:t})}function i(){return u("GET","components").then(r)}var o=function(t){return t&&t.__esModule?t["default"]:t};e.newLoaded=r,e.fetchAll=i,Object.defineProperty(e,"__esModule",{value:!0});var u=o(n(5)),a=o(n(1)),s=o(n(2))},function(t,e,n){"use strict";function r(t){s.dispatch({actionType:c.ACTION_NEW_EVENTS,events:t})}function i(){a("GET","events").then(r)}function o(t){var e=void 0===arguments[1]?{}:arguments[1];return a("POST","events/"+t,e).then(function(){f("Event "+t+" successful fired!"),s.dispatch({actionType:c.ACTION_EVENT_FIRED,eventType:t,eventData:e})})}var u=function(t){return t&&t.__esModule?t["default"]:t};e.newEvents=r,e.fetchAll=i,e.fire=o,Object.defineProperty(e,"__esModule",{value:!0});var a=u(n(5)),s=u(n(1)),c=u(n(2)),f=n(10).notify},function(t,e,n){"use strict";function r(){return"EventSource"in window}function i(t){null!==p&&y();var e="/api/stream";t&&(e+="?api_password="+t),p=new EventSource(e),_=t,p.addEventListener("open",function(){v(),c.dispatch({actionType:f.ACTION_STREAM_START}),l.stop(),l.fetchAll()},!1),p.addEventListener("message",function(t){v(),"ping"!==t.data&&c.dispatch({actionType:f.ACTION_REMOTE_EVENT_RECEIVED,event:JSON.parse(t.data)})},!1),p.addEventListener("error",function(){p.readyState!==EventSource.CLOSED&&c.dispatch({actionType:f.ACTION_STREAM_ERROR})},!1)}function o(){y(),c.dispatch({actionType:f.ACTION_STREAM_STOP}),l.start()}var u=function(t){return t&&t.__esModule?t:{"default":t}},a=function(t){return t&&t.__esModule?t["default"]:t};e.isSupported=r,e.start=i,e.stop=o,Object.defineProperty(e,"__esModule",{value:!0});var s=a(n(6)),c=a(n(1)),f=a(n(2)),l=u(n(13)),h=6e4,p=null,_=null,v=s.debounce(function(){i(_)},h),y=function(){p.close(),p=null,_=null,v.cancel()}},function(t,e,n){"use strict";function r(t,e){var n=e.useStreaming,r=void 0===n?l.isSupported:n,i=e.rememberLogin,o=void 0===i?!1:i;s.dispatch({actionType:c.ACTION_VALIDATING_AUTH_TOKEN}),a("GET","",!1,{authToken:t}).then(function(){s.dispatch({actionType:c.ACTION_VALID_AUTH_TOKEN,authToken:t,rememberLogin:o}),r?l.start(t):f.start()},function(t){s.dispatch({actionType:c.ACTION_INVALID_AUTH_TOKEN,message:t.message})})}function i(){s.dispatch({actionType:c.ACTION_LOG_OUT})}var o=function(t){return t&&t.__esModule?t:{"default":t}},u=function(t){return t&&t.__esModule?t["default"]:t};e.validate=r,e.logOut=i,Object.defineProperty(e,"__esModule",{value:!0});var a=u(n(5)),s=u(n(1)),c=u(n(2)),f=o(n(13)),l=o(n(27))},function(t,e,n){"use strict";function r(t){a.dispatch({actionType:s.ACTION_NEW_LOGBOOK,logbookEntries:t})}function i(){u("GET","logbook").then(r)}var o=function(t){return t&&t.__esModule?t["default"]:t};e.fetch=i,Object.defineProperty(e,"__esModule",{value:!0});var u=o(n(5)),a=o(n(1)),s=o(n(2))},function(t,e,n){"use strict";function r(t,e){(t||e.length>0)&&s.dispatch({actionType:c.ACTION_NEW_STATE_HISTORY,stateHistory:e.map(function(t){return t.map(f.fromJSON)}),isFetchAll:t})}function i(){a("GET","history/period").then(function(t){return r(!0,t)})}function o(t){a("GET","history/period?filter_entity_id="+t).then(function(t){return r(!1,t)})}var u=function(t){return t&&t.__esModule?t["default"]:t};e.fetchAll=i,e.fetch=o,Object.defineProperty(e,"__esModule",{value:!0});var a=u(n(5)),s=u(n(1)),c=u(n(2)),f=u(n(14))},function(t,e,n){"use strict";function r(){return"webkitSpeechRecognition"in window}function i(){var t=v||_;c.dispatch({actionType:f.ACTION_LISTENING_TRANSMITTING,finalTranscript:t}),l("conversation","process",{text:t}).then(function(){c.dispatch({actionType:f.ACTION_LISTENING_DONE,finalTranscript:t})},function(){c.dispatch({actionType:f.ACTION_LISTENING_ERROR})})}function o(){null!==p&&(p.onstart=null,p.onresult=null,p.onerror=null,p.onend=null,p.stop(),p=null,i()),_="",v=""}function u(){o(),window.r=p=new webkitSpeechRecognition,p.interimResults=!0,p.onstart=function(){c.dispatch({actionType:f.ACTION_LISTENING_START})},p.onresult=function(t){_="";for(var e=t.resultIndex;et||isNaN(t))throw TypeError("n must be a positive number");return this._maxListeners=t,this},e.prototype.emit=function(t){var e,r,u,a,s,c;if(this._events||(this._events={}),"error"===t&&(!this._events.error||i(this._events.error)&&!this._events.error.length)){if(e=arguments[1],e instanceof Error)throw e;throw TypeError('Uncaught, unspecified "error" event.')}if(r=this._events[t],o(r))return!1;if(n(r))switch(arguments.length){case 1:r.call(this);break;case 2:r.call(this,arguments[1]);break;case 3:r.call(this,arguments[1],arguments[2]);break;default:for(u=arguments.length,a=new Array(u-1),s=1;u>s;s++)a[s-1]=arguments[s];r.apply(this,a)}else if(i(r)){for(u=arguments.length,a=new Array(u-1),s=1;u>s;s++)a[s-1]=arguments[s];for(c=r.slice(),u=c.length,s=0;u>s;s++)c[s].apply(this,a)}return!0},e.prototype.addListener=function(t,r){var u;if(!n(r))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",t,n(r.listener)?r.listener:r),this._events[t]?i(this._events[t])?this._events[t].push(r):this._events[t]=[this._events[t],r]:this._events[t]=r,i(this._events[t])&&!this._events[t].warned){var u;u=o(this._maxListeners)?e.defaultMaxListeners:this._maxListeners,u&&u>0&&this._events[t].length>u&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),"function"==typeof console.trace&&console.trace())}return this},e.prototype.on=e.prototype.addListener,e.prototype.once=function(t,e){function r(){this.removeListener(t,r),i||(i=!0,e.apply(this,arguments))}if(!n(e))throw TypeError("listener must be a function");var i=!1;return r.listener=e,this.on(t,r),this},e.prototype.removeListener=function(t,e){var r,o,u,a;if(!n(e))throw TypeError("listener must be a function");if(!this._events||!this._events[t])return this;if(r=this._events[t],u=r.length,o=-1,r===e||n(r.listener)&&r.listener===e)delete this._events[t],this._events.removeListener&&this.emit("removeListener",t,e);else if(i(r)){for(a=u;a-->0;)if(r[a]===e||r[a].listener&&r[a].listener===e){o=a;break}if(0>o)return this;1===r.length?(r.length=0,delete this._events[t]):r.splice(o,1),this._events.removeListener&&this.emit("removeListener",t,e)}return this},e.prototype.removeAllListeners=function(t){var e,r;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[t]&&delete this._events[t],this;if(0===arguments.length){for(e in this._events)"removeListener"!==e&&this.removeAllListeners(e);return this.removeAllListeners("removeListener"),this._events={},this}if(r=this._events[t],n(r))this.removeListener(t,r);else for(;r.length;)this.removeListener(t,r[r.length-1]);return delete this._events[t],this},e.prototype.listeners=function(t){var e;return e=this._events&&this._events[t]?n(this._events[t])?[this._events[t]]:this._events[t].slice():[]},e.listenerCount=function(t,e){var r;return r=t._events&&t._events[e]?n(t._events[e])?1:t._events[e].length:0}},function(t,e,n){function r(t){return n(i(t))}function i(t){return o[t]||function(){throw new Error("Cannot find module '"+t+"'.")}()}var o={"./auth":7,"./auth.js":7,"./component":15,"./component.js":15,"./event":16,"./event.js":16,"./logbook":17,"./logbook.js":17,"./notification":18,"./notification.js":18,"./preference":19,"./preference.js":19,"./service":8,"./service.js":8,"./state":20,"./state.js":20,"./state_history":21,"./state_history.js":21,"./store":3,"./store.js":3,"./stream":9,"./stream.js":9,"./sync":22,"./sync.js":22,"./voice":23,"./voice.js":23};r.keys=function(){return Object.keys(o)},r.resolve=i,t.exports=r,r.id=40}]); \ No newline at end of file +return pickBy("isBefore",args)};moment.max=function(){var args=[].slice.call(arguments,0);return pickBy("isAfter",args)};moment.utc=function(input,format,locale,strict){var c;if(typeof locale==="boolean"){strict=locale;locale=undefined}c={};c._isAMomentObject=true;c._useUTC=true;c._isUTC=true;c._l=locale;c._i=input;c._f=format;c._strict=strict;c._pf=defaultParsingFlags();return makeMoment(c).utc()};moment.unix=function(input){return moment(input*1e3)};moment.duration=function(input,key){var duration=input,match=null,sign,ret,parseIso,diffRes;if(moment.isDuration(input)){duration={ms:input._milliseconds,d:input._days,M:input._months}}else if(typeof input==="number"){duration={};if(key){duration[key]=input}else{duration.milliseconds=input}}else if(!!(match=aspNetTimeSpanJsonRegex.exec(input))){sign=match[1]==="-"?-1:1;duration={y:0,d:toInt(match[DATE])*sign,h:toInt(match[HOUR])*sign,m:toInt(match[MINUTE])*sign,s:toInt(match[SECOND])*sign,ms:toInt(match[MILLISECOND])*sign}}else if(!!(match=isoDurationRegex.exec(input))){sign=match[1]==="-"?-1:1;parseIso=function(inp){var res=inp&&parseFloat(inp.replace(",","."));return(isNaN(res)?0:res)*sign};duration={y:parseIso(match[2]),M:parseIso(match[3]),d:parseIso(match[4]),h:parseIso(match[5]),m:parseIso(match[6]),s:parseIso(match[7]),w:parseIso(match[8])}}else if(duration==null){duration={}}else if(typeof duration==="object"&&("from"in duration||"to"in duration)){diffRes=momentsDifference(moment(duration.from),moment(duration.to));duration={};duration.ms=diffRes.milliseconds;duration.M=diffRes.months}ret=new Duration(duration);if(moment.isDuration(input)&&hasOwnProp(input,"_locale")){ret._locale=input._locale}return ret};moment.version=VERSION;moment.defaultFormat=isoFormat;moment.ISO_8601=function(){};moment.momentProperties=momentProperties;moment.updateOffset=function(){};moment.relativeTimeThreshold=function(threshold,limit){if(relativeTimeThresholds[threshold]===undefined){return false}if(limit===undefined){return relativeTimeThresholds[threshold]}relativeTimeThresholds[threshold]=limit;return true};moment.lang=deprecate("moment.lang is deprecated. Use moment.locale instead.",function(key,value){return moment.locale(key,value)});moment.locale=function(key,values){var data;if(key){if(typeof values!=="undefined"){data=moment.defineLocale(key,values)}else{data=moment.localeData(key)}if(data){moment.duration._locale=moment._locale=data}}return moment._locale._abbr};moment.defineLocale=function(name,values){if(values!==null){values.abbr=name;if(!locales[name]){locales[name]=new Locale}locales[name].set(values);moment.locale(name);return locales[name]}else{delete locales[name];return null}};moment.langData=deprecate("moment.langData is deprecated. Use moment.localeData instead.",function(key){return moment.localeData(key)});moment.localeData=function(key){var locale;if(key&&key._locale&&key._locale._abbr){key=key._locale._abbr}if(!key){return moment._locale}if(!isArray(key)){locale=loadLocale(key);if(locale){return locale}key=[key]}return chooseLocale(key)};moment.isMoment=function(obj){return obj instanceof Moment||obj!=null&&hasOwnProp(obj,"_isAMomentObject")};moment.isDuration=function(obj){return obj instanceof Duration};for(i=lists.length-1;i>=0;--i){makeList(lists[i])}moment.normalizeUnits=function(units){return normalizeUnits(units)};moment.invalid=function(flags){var m=moment.utc(NaN);if(flags!=null){extend(m._pf,flags)}else{m._pf.userInvalidated=true}return m};moment.parseZone=function(){return moment.apply(null,arguments).parseZone()};moment.parseTwoDigitYear=function(input){return toInt(input)+(toInt(input)>68?1900:2e3)};moment.isDate=isDate;extend(moment.fn=Moment.prototype,{clone:function(){return moment(this)},valueOf:function(){return+this._d-(this._offset||0)*6e4},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var m=moment(this).utc();if(00}return false},parsingFlags:function(){return extend({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(keepLocalTime){return this.utcOffset(0,keepLocalTime)},local:function(keepLocalTime){if(this._isUTC){this.utcOffset(0,keepLocalTime);this._isUTC=false;if(keepLocalTime){this.subtract(this._dateUtcOffset(),"m")}}return this},format:function(inputString){var output=formatMoment(this,inputString||moment.defaultFormat);return this.localeData().postformat(output)},add:createAdder(1,"add"),subtract:createAdder(-1,"subtract"),diff:function(input,units,asFloat){var that=makeAs(input,this),zoneDiff=(that.utcOffset()-this.utcOffset())*6e4,anchor,diff,output,daysAdjust;units=normalizeUnits(units);if(units==="year"||units==="month"||units==="quarter"){output=monthDiff(this,that);if(units==="quarter"){output=output/3}else if(units==="year"){output=output/12}}else{diff=this-that;output=units==="second"?diff/1e3:units==="minute"?diff/6e4:units==="hour"?diff/36e5:units==="day"?(diff-zoneDiff)/864e5:units==="week"?(diff-zoneDiff)/6048e5:diff}return asFloat?output:absRound(output)},from:function(time,withoutSuffix){return moment.duration({to:this,from:time}).locale(this.locale()).humanize(!withoutSuffix)},fromNow:function(withoutSuffix){return this.from(moment(),withoutSuffix)},calendar:function(time){var now=time||moment(),sod=makeAs(now,this).startOf("day"),diff=this.diff(sod,"days",true),format=diff<-6?"sameElse":diff<-1?"lastWeek":diff<0?"lastDay":diff<1?"sameDay":diff<2?"nextDay":diff<7?"nextWeek":"sameElse";return this.format(this.localeData().calendar(format,this,moment(now)))},isLeapYear:function(){return isLeapYear(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(input){var day=this._isUTC?this._d.getUTCDay():this._d.getDay();if(input!=null){input=parseWeekday(input,this.localeData());return this.add(input-day,"d")}else{return day}},month:makeAccessor("Month",true),startOf:function(units){units=normalizeUnits(units);switch(units){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}if(units==="week"){this.weekday(0)}else if(units==="isoWeek"){this.isoWeekday(1)}if(units==="quarter"){this.month(Math.floor(this.month()/3)*3)}return this},endOf:function(units){units=normalizeUnits(units);if(units===undefined||units==="millisecond"){return this}return this.startOf(units).add(1,units==="isoWeek"?"week":units).subtract(1,"ms")},isAfter:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this>+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return inputMs<+this.clone().startOf(units)}},isBefore:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this<+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return+this.clone().endOf(units)this?this:other}),zone:deprecate("moment().zone is deprecated, use moment().utcOffset instead. "+"https://github.com/moment/moment/issues/1779",function(input,keepLocalTime){if(input!=null){if(typeof input!=="string"){input=-input}this.utcOffset(input,keepLocalTime);return this}else{return-this.utcOffset()}}),utcOffset:function(input,keepLocalTime){var offset=this._offset||0,localAdjust;if(input!=null){if(typeof input==="string"){input=utcOffsetFromString(input)}if(Math.abs(input)<16){input=input*60}if(!this._isUTC&&keepLocalTime){localAdjust=this._dateUtcOffset()}this._offset=input;this._isUTC=true;if(localAdjust!=null){this.add(localAdjust,"m")}if(offset!==input){if(!keepLocalTime||this._changeInProgress){addOrSubtractDurationFromMoment(this,moment.duration(input-offset,"m"),1,false)}else if(!this._changeInProgress){this._changeInProgress=true;moment.updateOffset(this,true);this._changeInProgress=null}}return this}else{return this._isUTC?offset:this._dateUtcOffset()}},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&this._offset===0},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){if(this._tzm){this.utcOffset(this._tzm)}else if(typeof this._i==="string"){this.utcOffset(utcOffsetFromString(this._i))}return this},hasAlignedHourOffset:function(input){if(!input){input=0}else{input=moment(input).utcOffset()}return(this.utcOffset()-input)%60===0},daysInMonth:function(){return daysInMonth(this.year(),this.month())},dayOfYear:function(input){var dayOfYear=round((moment(this).startOf("day")-moment(this).startOf("year"))/864e5)+1;return input==null?dayOfYear:this.add(input-dayOfYear,"d")},quarter:function(input){return input==null?Math.ceil((this.month()+1)/3):this.month((input-1)*3+this.month()%3)},weekYear:function(input){var year=weekOfYear(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return input==null?year:this.add(input-year,"y")},isoWeekYear:function(input){var year=weekOfYear(this,1,4).year;return input==null?year:this.add(input-year,"y")},week:function(input){var week=this.localeData().week(this);return input==null?week:this.add((input-week)*7,"d")},isoWeek:function(input){var week=weekOfYear(this,1,4).week;return input==null?week:this.add((input-week)*7,"d")},weekday:function(input){var weekday=(this.day()+7-this.localeData()._week.dow)%7;return input==null?weekday:this.add(input-weekday,"d")},isoWeekday:function(input){return input==null?this.day()||7:this.day(this.day()%7?input:input-7)},isoWeeksInYear:function(){return weeksInYear(this.year(),1,4)},weeksInYear:function(){var weekInfo=this.localeData()._week;return weeksInYear(this.year(),weekInfo.dow,weekInfo.doy)},get:function(units){units=normalizeUnits(units);return this[units]()},set:function(units,value){var unit;if(typeof units==="object"){for(unit in units){this.set(unit,units[unit])}}else{units=normalizeUnits(units);if(typeof this[units]==="function"){this[units](value)}}return this},locale:function(key){var newLocaleData;if(key===undefined){return this._locale._abbr}else{newLocaleData=moment.localeData(key);if(newLocaleData!=null){this._locale=newLocaleData}return this}},lang:deprecate("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(key){if(key===undefined){return this.localeData()}else{return this.locale(key)}}),localeData:function(){return this._locale},_dateUtcOffset:function(){return-Math.round(this._d.getTimezoneOffset()/15)*15}});function rawMonthSetter(mom,value){var dayOfMonth;if(typeof value==="string"){value=mom.localeData().monthsParse(value);if(typeof value!=="number"){return mom}}dayOfMonth=Math.min(mom.date(),daysInMonth(mom.year(),value));mom._d["set"+(mom._isUTC?"UTC":"")+"Month"](value,dayOfMonth);return mom}function rawGetter(mom,unit){return mom._d["get"+(mom._isUTC?"UTC":"")+unit]()}function rawSetter(mom,unit,value){if(unit==="Month"){return rawMonthSetter(mom,value)}else{return mom._d["set"+(mom._isUTC?"UTC":"")+unit](value)}}function makeAccessor(unit,keepTime){return function(value){if(value!=null){rawSetter(this,unit,value);moment.updateOffset(this,keepTime);return this}else{return rawGetter(this,unit)}}}moment.fn.millisecond=moment.fn.milliseconds=makeAccessor("Milliseconds",false);moment.fn.second=moment.fn.seconds=makeAccessor("Seconds",false);moment.fn.minute=moment.fn.minutes=makeAccessor("Minutes",false);moment.fn.hour=moment.fn.hours=makeAccessor("Hours",true);moment.fn.date=makeAccessor("Date",true);moment.fn.dates=deprecate("dates accessor is deprecated. Use date instead.",makeAccessor("Date",true));moment.fn.year=makeAccessor("FullYear",true);moment.fn.years=deprecate("years accessor is deprecated. Use year instead.",makeAccessor("FullYear",true));moment.fn.days=moment.fn.day;moment.fn.months=moment.fn.month;moment.fn.weeks=moment.fn.week;moment.fn.isoWeeks=moment.fn.isoWeek;moment.fn.quarters=moment.fn.quarter;moment.fn.toJSON=moment.fn.toISOString;moment.fn.isUTC=moment.fn.isUtc;function daysToYears(days){return days*400/146097}function yearsToDays(years){return years*146097/400}extend(moment.duration.fn=Duration.prototype,{_bubble:function(){var milliseconds=this._milliseconds,days=this._days,months=this._months,data=this._data,seconds,minutes,hours,years=0;data.milliseconds=milliseconds%1e3;seconds=absRound(milliseconds/1e3);data.seconds=seconds%60;minutes=absRound(seconds/60);data.minutes=minutes%60;hours=absRound(minutes/60);data.hours=hours%24;days+=absRound(hours/24);years=absRound(daysToYears(days));days-=absRound(yearsToDays(years));months+=absRound(days/30);days%=30;years+=absRound(months/12);months%=12;data.days=days;data.months=months;data.years=years},abs:function(){this._milliseconds=Math.abs(this._milliseconds);this._days=Math.abs(this._days);this._months=Math.abs(this._months);this._data.milliseconds=Math.abs(this._data.milliseconds);this._data.seconds=Math.abs(this._data.seconds);this._data.minutes=Math.abs(this._data.minutes);this._data.hours=Math.abs(this._data.hours);this._data.months=Math.abs(this._data.months);this._data.years=Math.abs(this._data.years);return this},weeks:function(){return absRound(this.days()/7)},valueOf:function(){return this._milliseconds+this._days*864e5+this._months%12*2592e6+toInt(this._months/12)*31536e6},humanize:function(withSuffix){var output=relativeTime(this,!withSuffix,this.localeData());if(withSuffix){output=this.localeData().pastFuture(+this,output)}return this.localeData().postformat(output)},add:function(input,val){var dur=moment.duration(input,val);this._milliseconds+=dur._milliseconds;this._days+=dur._days;this._months+=dur._months;this._bubble();return this},subtract:function(input,val){var dur=moment.duration(input,val);this._milliseconds-=dur._milliseconds;this._days-=dur._days;this._months-=dur._months;this._bubble();return this},get:function(units){units=normalizeUnits(units);return this[units.toLowerCase()+"s"]()},as:function(units){var days,months;units=normalizeUnits(units);if(units==="month"||units==="year"){days=this._days+this._milliseconds/864e5;months=this._months+daysToYears(days)*12;return units==="month"?months:months/12}else{days=this._days+Math.round(yearsToDays(this._months/12));switch(units){case"week":return days/7+this._milliseconds/6048e5;case"day":return days+this._milliseconds/864e5;case"hour":return days*24+this._milliseconds/36e5;case"minute":return days*24*60+this._milliseconds/6e4;case"second":return days*24*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(days*24*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+units)}}},lang:moment.fn.lang,locale:moment.fn.locale,toIsoString:deprecate("toIsoString() is deprecated. Please use toISOString() instead "+"(notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var years=Math.abs(this.years()),months=Math.abs(this.months()),days=Math.abs(this.days()),hours=Math.abs(this.hours()),minutes=Math.abs(this.minutes()),seconds=Math.abs(this.seconds()+this.milliseconds()/1e3);if(!this.asSeconds()){return"P0D"}return(this.asSeconds()<0?"-":"")+"P"+(years?years+"Y":"")+(months?months+"M":"")+(days?days+"D":"")+(hours||minutes||seconds?"T":"")+(hours?hours+"H":"")+(minutes?minutes+"M":"")+(seconds?seconds+"S":"")},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}});moment.duration.fn.toString=moment.duration.fn.toISOString;function makeDurationGetter(name){moment.duration.fn[name]=function(){return this._data[name]}}for(i in unitMillisecondFactors){if(hasOwnProp(unitMillisecondFactors,i)){makeDurationGetter(i.toLowerCase())}}moment.duration.fn.asMilliseconds=function(){return this.as("ms")};moment.duration.fn.asSeconds=function(){return this.as("s")};moment.duration.fn.asMinutes=function(){return this.as("m")};moment.duration.fn.asHours=function(){return this.as("h")};moment.duration.fn.asDays=function(){return this.as("d")};moment.duration.fn.asWeeks=function(){return this.as("weeks")};moment.duration.fn.asMonths=function(){return this.as("M")};moment.duration.fn.asYears=function(){return this.as("y")};moment.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(number){var b=number%10,output=toInt(number%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th";return number+output}});function makeGlobal(shouldDeprecate){if(typeof ender!=="undefined"){return}oldGlobalMoment=globalScope.moment;if(shouldDeprecate){globalScope.moment=deprecate("Accessing Moment through the global scope is "+"deprecated, and will be removed in an upcoming "+"release.",moment)}else{globalScope.moment=moment}}if(hasModule){module.exports=moment}else if(typeof define==="function"&&define.amd){define(function(require,exports,module){if(module.config&&module.config()&&module.config().noGlobal===true){globalScope.moment=oldGlobalMoment}return moment});makeGlobal(true)}else{makeGlobal()}}).call(this); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html b/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html index 13e2f4d719d..50a72473117 100644 --- a/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html +++ b/homeassistant/components/frontend/www_static/polymer/components/state-timeline.html @@ -2,7 +2,7 @@ - + \ No newline at end of file +return pickBy("isBefore",args)};moment.max=function(){var args=[].slice.call(arguments,0);return pickBy("isAfter",args)};moment.utc=function(input,format,locale,strict){var c;if(typeof locale==="boolean"){strict=locale;locale=undefined}c={};c._isAMomentObject=true;c._useUTC=true;c._isUTC=true;c._l=locale;c._i=input;c._f=format;c._strict=strict;c._pf=defaultParsingFlags();return makeMoment(c).utc()};moment.unix=function(input){return moment(input*1e3)};moment.duration=function(input,key){var duration=input,match=null,sign,ret,parseIso,diffRes;if(moment.isDuration(input)){duration={ms:input._milliseconds,d:input._days,M:input._months}}else if(typeof input==="number"){duration={};if(key){duration[key]=input}else{duration.milliseconds=input}}else if(!!(match=aspNetTimeSpanJsonRegex.exec(input))){sign=match[1]==="-"?-1:1;duration={y:0,d:toInt(match[DATE])*sign,h:toInt(match[HOUR])*sign,m:toInt(match[MINUTE])*sign,s:toInt(match[SECOND])*sign,ms:toInt(match[MILLISECOND])*sign}}else if(!!(match=isoDurationRegex.exec(input))){sign=match[1]==="-"?-1:1;parseIso=function(inp){var res=inp&&parseFloat(inp.replace(",","."));return(isNaN(res)?0:res)*sign};duration={y:parseIso(match[2]),M:parseIso(match[3]),d:parseIso(match[4]),h:parseIso(match[5]),m:parseIso(match[6]),s:parseIso(match[7]),w:parseIso(match[8])}}else if(duration==null){duration={}}else if(typeof duration==="object"&&("from"in duration||"to"in duration)){diffRes=momentsDifference(moment(duration.from),moment(duration.to));duration={};duration.ms=diffRes.milliseconds;duration.M=diffRes.months}ret=new Duration(duration);if(moment.isDuration(input)&&hasOwnProp(input,"_locale")){ret._locale=input._locale}return ret};moment.version=VERSION;moment.defaultFormat=isoFormat;moment.ISO_8601=function(){};moment.momentProperties=momentProperties;moment.updateOffset=function(){};moment.relativeTimeThreshold=function(threshold,limit){if(relativeTimeThresholds[threshold]===undefined){return false}if(limit===undefined){return relativeTimeThresholds[threshold]}relativeTimeThresholds[threshold]=limit;return true};moment.lang=deprecate("moment.lang is deprecated. Use moment.locale instead.",function(key,value){return moment.locale(key,value)});moment.locale=function(key,values){var data;if(key){if(typeof values!=="undefined"){data=moment.defineLocale(key,values)}else{data=moment.localeData(key)}if(data){moment.duration._locale=moment._locale=data}}return moment._locale._abbr};moment.defineLocale=function(name,values){if(values!==null){values.abbr=name;if(!locales[name]){locales[name]=new Locale}locales[name].set(values);moment.locale(name);return locales[name]}else{delete locales[name];return null}};moment.langData=deprecate("moment.langData is deprecated. Use moment.localeData instead.",function(key){return moment.localeData(key)});moment.localeData=function(key){var locale;if(key&&key._locale&&key._locale._abbr){key=key._locale._abbr}if(!key){return moment._locale}if(!isArray(key)){locale=loadLocale(key);if(locale){return locale}key=[key]}return chooseLocale(key)};moment.isMoment=function(obj){return obj instanceof Moment||obj!=null&&hasOwnProp(obj,"_isAMomentObject")};moment.isDuration=function(obj){return obj instanceof Duration};for(i=lists.length-1;i>=0;--i){makeList(lists[i])}moment.normalizeUnits=function(units){return normalizeUnits(units)};moment.invalid=function(flags){var m=moment.utc(NaN);if(flags!=null){extend(m._pf,flags)}else{m._pf.userInvalidated=true}return m};moment.parseZone=function(){return moment.apply(null,arguments).parseZone()};moment.parseTwoDigitYear=function(input){return toInt(input)+(toInt(input)>68?1900:2e3)};moment.isDate=isDate;extend(moment.fn=Moment.prototype,{clone:function(){return moment(this)},valueOf:function(){return+this._d-(this._offset||0)*6e4},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var m=moment(this).utc();if(00}return false},parsingFlags:function(){return extend({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(keepLocalTime){return this.utcOffset(0,keepLocalTime)},local:function(keepLocalTime){if(this._isUTC){this.utcOffset(0,keepLocalTime);this._isUTC=false;if(keepLocalTime){this.subtract(this._dateUtcOffset(),"m")}}return this},format:function(inputString){var output=formatMoment(this,inputString||moment.defaultFormat);return this.localeData().postformat(output)},add:createAdder(1,"add"),subtract:createAdder(-1,"subtract"),diff:function(input,units,asFloat){var that=makeAs(input,this),zoneDiff=(that.utcOffset()-this.utcOffset())*6e4,anchor,diff,output,daysAdjust;units=normalizeUnits(units);if(units==="year"||units==="month"||units==="quarter"){output=monthDiff(this,that);if(units==="quarter"){output=output/3}else if(units==="year"){output=output/12}}else{diff=this-that;output=units==="second"?diff/1e3:units==="minute"?diff/6e4:units==="hour"?diff/36e5:units==="day"?(diff-zoneDiff)/864e5:units==="week"?(diff-zoneDiff)/6048e5:diff}return asFloat?output:absRound(output)},from:function(time,withoutSuffix){return moment.duration({to:this,from:time}).locale(this.locale()).humanize(!withoutSuffix)},fromNow:function(withoutSuffix){return this.from(moment(),withoutSuffix)},calendar:function(time){var now=time||moment(),sod=makeAs(now,this).startOf("day"),diff=this.diff(sod,"days",true),format=diff<-6?"sameElse":diff<-1?"lastWeek":diff<0?"lastDay":diff<1?"sameDay":diff<2?"nextDay":diff<7?"nextWeek":"sameElse";return this.format(this.localeData().calendar(format,this,moment(now)))},isLeapYear:function(){return isLeapYear(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(input){var day=this._isUTC?this._d.getUTCDay():this._d.getDay();if(input!=null){input=parseWeekday(input,this.localeData());return this.add(input-day,"d")}else{return day}},month:makeAccessor("Month",true),startOf:function(units){units=normalizeUnits(units);switch(units){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}if(units==="week"){this.weekday(0)}else if(units==="isoWeek"){this.isoWeekday(1)}if(units==="quarter"){this.month(Math.floor(this.month()/3)*3)}return this},endOf:function(units){units=normalizeUnits(units);if(units===undefined||units==="millisecond"){return this}return this.startOf(units).add(1,units==="isoWeek"?"week":units).subtract(1,"ms")},isAfter:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this>+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return inputMs<+this.clone().startOf(units)}},isBefore:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this<+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return+this.clone().endOf(units)this?this:other}),zone:deprecate("moment().zone is deprecated, use moment().utcOffset instead. "+"https://github.com/moment/moment/issues/1779",function(input,keepLocalTime){if(input!=null){if(typeof input!=="string"){input=-input}this.utcOffset(input,keepLocalTime);return this}else{return-this.utcOffset()}}),utcOffset:function(input,keepLocalTime){var offset=this._offset||0,localAdjust;if(input!=null){if(typeof input==="string"){input=utcOffsetFromString(input)}if(Math.abs(input)<16){input=input*60}if(!this._isUTC&&keepLocalTime){localAdjust=this._dateUtcOffset()}this._offset=input;this._isUTC=true;if(localAdjust!=null){this.add(localAdjust,"m")}if(offset!==input){if(!keepLocalTime||this._changeInProgress){addOrSubtractDurationFromMoment(this,moment.duration(input-offset,"m"),1,false)}else if(!this._changeInProgress){this._changeInProgress=true;moment.updateOffset(this,true);this._changeInProgress=null}}return this}else{return this._isUTC?offset:this._dateUtcOffset()}},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&this._offset===0},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){if(this._tzm){this.utcOffset(this._tzm)}else if(typeof this._i==="string"){this.utcOffset(utcOffsetFromString(this._i))}return this},hasAlignedHourOffset:function(input){if(!input){input=0}else{input=moment(input).utcOffset()}return(this.utcOffset()-input)%60===0},daysInMonth:function(){return daysInMonth(this.year(),this.month())},dayOfYear:function(input){var dayOfYear=round((moment(this).startOf("day")-moment(this).startOf("year"))/864e5)+1;return input==null?dayOfYear:this.add(input-dayOfYear,"d")},quarter:function(input){return input==null?Math.ceil((this.month()+1)/3):this.month((input-1)*3+this.month()%3)},weekYear:function(input){var year=weekOfYear(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return input==null?year:this.add(input-year,"y")},isoWeekYear:function(input){var year=weekOfYear(this,1,4).year;return input==null?year:this.add(input-year,"y")},week:function(input){var week=this.localeData().week(this);return input==null?week:this.add((input-week)*7,"d")},isoWeek:function(input){var week=weekOfYear(this,1,4).week;return input==null?week:this.add((input-week)*7,"d")},weekday:function(input){var weekday=(this.day()+7-this.localeData()._week.dow)%7;return input==null?weekday:this.add(input-weekday,"d")},isoWeekday:function(input){return input==null?this.day()||7:this.day(this.day()%7?input:input-7)},isoWeeksInYear:function(){return weeksInYear(this.year(),1,4)},weeksInYear:function(){var weekInfo=this.localeData()._week;return weeksInYear(this.year(),weekInfo.dow,weekInfo.doy)},get:function(units){units=normalizeUnits(units);return this[units]()},set:function(units,value){var unit;if(typeof units==="object"){for(unit in units){this.set(unit,units[unit])}}else{units=normalizeUnits(units);if(typeof this[units]==="function"){this[units](value)}}return this},locale:function(key){var newLocaleData;if(key===undefined){return this._locale._abbr}else{newLocaleData=moment.localeData(key);if(newLocaleData!=null){this._locale=newLocaleData}return this}},lang:deprecate("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(key){if(key===undefined){return this.localeData()}else{return this.locale(key)}}),localeData:function(){return this._locale},_dateUtcOffset:function(){return-Math.round(this._d.getTimezoneOffset()/15)*15}});function rawMonthSetter(mom,value){var dayOfMonth;if(typeof value==="string"){value=mom.localeData().monthsParse(value);if(typeof value!=="number"){return mom}}dayOfMonth=Math.min(mom.date(),daysInMonth(mom.year(),value));mom._d["set"+(mom._isUTC?"UTC":"")+"Month"](value,dayOfMonth);return mom}function rawGetter(mom,unit){return mom._d["get"+(mom._isUTC?"UTC":"")+unit]()}function rawSetter(mom,unit,value){if(unit==="Month"){return rawMonthSetter(mom,value)}else{return mom._d["set"+(mom._isUTC?"UTC":"")+unit](value)}}function makeAccessor(unit,keepTime){return function(value){if(value!=null){rawSetter(this,unit,value);moment.updateOffset(this,keepTime);return this}else{return rawGetter(this,unit)}}}moment.fn.millisecond=moment.fn.milliseconds=makeAccessor("Milliseconds",false);moment.fn.second=moment.fn.seconds=makeAccessor("Seconds",false);moment.fn.minute=moment.fn.minutes=makeAccessor("Minutes",false);moment.fn.hour=moment.fn.hours=makeAccessor("Hours",true);moment.fn.date=makeAccessor("Date",true);moment.fn.dates=deprecate("dates accessor is deprecated. Use date instead.",makeAccessor("Date",true));moment.fn.year=makeAccessor("FullYear",true);moment.fn.years=deprecate("years accessor is deprecated. Use year instead.",makeAccessor("FullYear",true));moment.fn.days=moment.fn.day;moment.fn.months=moment.fn.month;moment.fn.weeks=moment.fn.week;moment.fn.isoWeeks=moment.fn.isoWeek;moment.fn.quarters=moment.fn.quarter;moment.fn.toJSON=moment.fn.toISOString;moment.fn.isUTC=moment.fn.isUtc;function daysToYears(days){return days*400/146097}function yearsToDays(years){return years*146097/400}extend(moment.duration.fn=Duration.prototype,{_bubble:function(){var milliseconds=this._milliseconds,days=this._days,months=this._months,data=this._data,seconds,minutes,hours,years=0;data.milliseconds=milliseconds%1e3;seconds=absRound(milliseconds/1e3);data.seconds=seconds%60;minutes=absRound(seconds/60);data.minutes=minutes%60;hours=absRound(minutes/60);data.hours=hours%24;days+=absRound(hours/24);years=absRound(daysToYears(days));days-=absRound(yearsToDays(years));months+=absRound(days/30);days%=30;years+=absRound(months/12);months%=12;data.days=days;data.months=months;data.years=years},abs:function(){this._milliseconds=Math.abs(this._milliseconds);this._days=Math.abs(this._days);this._months=Math.abs(this._months);this._data.milliseconds=Math.abs(this._data.milliseconds);this._data.seconds=Math.abs(this._data.seconds);this._data.minutes=Math.abs(this._data.minutes);this._data.hours=Math.abs(this._data.hours);this._data.months=Math.abs(this._data.months);this._data.years=Math.abs(this._data.years);return this},weeks:function(){return absRound(this.days()/7)},valueOf:function(){return this._milliseconds+this._days*864e5+this._months%12*2592e6+toInt(this._months/12)*31536e6},humanize:function(withSuffix){var output=relativeTime(this,!withSuffix,this.localeData());if(withSuffix){output=this.localeData().pastFuture(+this,output)}return this.localeData().postformat(output)},add:function(input,val){var dur=moment.duration(input,val);this._milliseconds+=dur._milliseconds;this._days+=dur._days;this._months+=dur._months;this._bubble();return this},subtract:function(input,val){var dur=moment.duration(input,val);this._milliseconds-=dur._milliseconds;this._days-=dur._days;this._months-=dur._months;this._bubble();return this},get:function(units){units=normalizeUnits(units);return this[units.toLowerCase()+"s"]()},as:function(units){var days,months;units=normalizeUnits(units);if(units==="month"||units==="year"){days=this._days+this._milliseconds/864e5;months=this._months+daysToYears(days)*12;return units==="month"?months:months/12}else{days=this._days+Math.round(yearsToDays(this._months/12));switch(units){case"week":return days/7+this._milliseconds/6048e5;case"day":return days+this._milliseconds/864e5;case"hour":return days*24+this._milliseconds/36e5;case"minute":return days*24*60+this._milliseconds/6e4;case"second":return days*24*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(days*24*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+units)}}},lang:moment.fn.lang,locale:moment.fn.locale,toIsoString:deprecate("toIsoString() is deprecated. Please use toISOString() instead "+"(notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var years=Math.abs(this.years()),months=Math.abs(this.months()),days=Math.abs(this.days()),hours=Math.abs(this.hours()),minutes=Math.abs(this.minutes()),seconds=Math.abs(this.seconds()+this.milliseconds()/1e3);if(!this.asSeconds()){return"P0D"}return(this.asSeconds()<0?"-":"")+"P"+(years?years+"Y":"")+(months?months+"M":"")+(days?days+"D":"")+(hours||minutes||seconds?"T":"")+(hours?hours+"H":"")+(minutes?minutes+"M":"")+(seconds?seconds+"S":"")},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}});moment.duration.fn.toString=moment.duration.fn.toISOString;function makeDurationGetter(name){moment.duration.fn[name]=function(){return this._data[name]}}for(i in unitMillisecondFactors){if(hasOwnProp(unitMillisecondFactors,i)){makeDurationGetter(i.toLowerCase())}}moment.duration.fn.asMilliseconds=function(){return this.as("ms")};moment.duration.fn.asSeconds=function(){return this.as("s")};moment.duration.fn.asMinutes=function(){return this.as("m")};moment.duration.fn.asHours=function(){return this.as("h")};moment.duration.fn.asDays=function(){return this.as("d")};moment.duration.fn.asWeeks=function(){return this.as("weeks")};moment.duration.fn.asMonths=function(){return this.as("M")};moment.duration.fn.asYears=function(){return this.as("y")};moment.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(number){var b=number%10,output=toInt(number%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th";return number+output}});function makeGlobal(shouldDeprecate){if(typeof ender!=="undefined"){return}oldGlobalMoment=globalScope.moment;if(shouldDeprecate){globalScope.moment=deprecate("Accessing Moment through the global scope is "+"deprecated, and will be removed in an upcoming "+"release.",moment)}else{globalScope.moment=moment}}if(hasModule){module.exports=moment}else if(typeof define==="function"&&define.amd){define(function(require,exports,module){if(module.config&&module.config()&&module.config().noGlobal===true){globalScope.moment=oldGlobalMoment}return moment});makeGlobal(true)}else{makeGlobal()}}).call(this); \ No newline at end of file From f6d75f2db2a2dc32b1cc9acafa3747f3acce8e87 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sun, 12 Apr 2015 16:45:23 -0400 Subject: [PATCH 27/59] Cleaned up ISY994 light and sensor code to use the same abstract class. --- homeassistant/components/isy994.py | 132 +++++++++++++++++++--- homeassistant/components/light/isy994.py | 76 ++----------- homeassistant/components/sensor/isy994.py | 89 +++------------ 3 files changed, 141 insertions(+), 156 deletions(-) diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 84299cef6a6..fe5d5516cb6 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -1,5 +1,6 @@ """ -Connects to an ISY-994 controller and loads relevant components to control its devices. +Connects to an ISY-994 controller and loads relevant components to control its +devices. Also contains the base classes for ISY Sensors, Lights, and Switches. """ # system imports import logging @@ -14,26 +15,27 @@ from homeassistant.loader import get_component from homeassistant.helpers import validate_config from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( - CONF_HOST, CONF_USERNAME, CONF_PASSWORD, - EVENT_PLATFORM_DISCOVERED, + CONF_HOST, CONF_USERNAME, CONF_PASSWORD, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME) # homeassistant constants DOMAIN = "isy994" DEPENDENCIES = [] DISCOVER_LIGHTS = "isy994.lights" -#DISCOVER_SWITCHES = "isy994.switches" +# DISCOVER_SWITCHES = "isy994.switches" DISCOVER_SENSORS = "isy994.sensors" ISY = None -def setup(hass, config): - """ Sets up the ISY994 component. """ - logger = logging.getLogger(__name__) - logger.setLevel(logging.DEBUG) +# setup logger +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +def setup(hass, config): # pull values from configuration file - if not validate_config(config, - {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, logger): + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + logger): return False else: user = config[DOMAIN][CONF_USERNAME] @@ -47,8 +49,8 @@ def setup(hass, config): addr = addr.replace('https://', '') https = True else: - logger.error('isy994 host value in configuration ' + - 'file is invalid.') + logger.error('isy994 host value in configuration ' + + 'file is invalid.') return False port = host.port addr = addr.replace(':{}'.format(port), '') @@ -64,11 +66,107 @@ def setup(hass, config): ('light', DISCOVER_LIGHTS))): component = get_component(comp_name) bootstrap.setup_component(hass, component.DOMAIN, config) - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, - { - ATTR_SERVICE: discovery, - ATTR_DISCOVERED: {} - }) + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, + {ATTR_SERVICE: discovery, + ATTR_DISCOVERED: {}}) ISY.auto_update = True return True + + +class ISYDeviceABC(ToggleEntity): + """ Abstract Class for an ISY device within home assistant. """ + + _attrs = {} + _onattrs = [] + _states = [] + _dtype = None + _domain = None + + def __init__(self, node): + # setup properties + self.node = node + + # track changes + self._changeHandler = self.node.status. \ + subscribe('changed', self.onUpdate) + + def __del__(self): + """ cleanup subscriptions because it is the right thing to do. """ + self._changeHandler.unsubscribe() + + @property + def domain(self): + return self._domain + + @property + def dtype(self): + if self._dtype in ['analog', 'binary']: + return self._dtype + return 'binary' if self._units is None else 'analog' + + @property + def should_poll(self): + return False + + @property + def value(self): + """ returns the unclean value from the controller """ + return self.node.status._val + + @property + def state_attributes(self): + attr = {ATTR_FRIENDLY_NAME: self.name} + for name, prop in self._attrs.items(): + attr[name] = getattr(self, prop) + return attr + + @property + def unique_id(self): + """ Returns the id of this isy sensor """ + return self.node._id + + @property + def name(self): + """ Returns the name of the node if any. """ + return self.node.name + + def update(self): + """ Update state of the sensor. """ + # ISY objects are automatically updated by the ISY's event stream + pass + + def onUpdate(self, e): + """ Handles the update recieved event. """ + self.update_ha_state() + + @property + def is_on(self): + return self.value > 0 + + @property + def is_open(self): + return self.is_on + + @property + def state(self): + """ Returns the state of the node. """ + if len(self._states) > 0: + return self._states[0] if self.is_on else self._states[1] + return self.value + + def turn_on(self, **kwargs): + """ turns the device on """ + attrs = [kwargs.get(name) for name in self._onattrs] + self.node.on(*attrs) + + def turn_off(self, **kwargs): + """ turns the device off """ + self.node.off() + + @property + def unit_of_measurement(self): + try: + return self.node.units + except AttributeError: + return None diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index 3f498955487..16d7f6b052c 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -1,17 +1,15 @@ -""" Support for ISY994 sensors. """ +""" Support for ISY994 lights. """ # system imports import logging # homeassistant imports -from ..isy994 import ISY -from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.components.isy994 import ISYDeviceABC, ISY from homeassistant.components.light import ATTR_BRIGHTNESS +from homeassistant.const import STATE_ON, STATE_OFF def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the isy994 platform. """ - print('************ RUNNING') logger = logging.getLogger(__name__) devs = [] # verify connection @@ -27,67 +25,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(devs) -class ISYLightDevice(ToggleEntity): +class ISYLightDevice(ISYDeviceABC): """ represents as isy light within home assistant. """ - domain = 'light' - - def __init__(self, node): - # setup properties - self.node = node - #self.entity_id = self.domain + '.' + self.name.replace(' ', '_') - - # track changes - self._changeHandler = self.node.status. \ - subscribe('changed', self.onUpdate) - - def __del__(self): - self._changeHandler.unsubscribe() - - @property - def should_poll(self): - return False - - @property - def dtype(self): - return 'analog' - - @property - def value(self): - """ return the integer setting of the light (brightness) """ - return self.node.status._val - - @property - def is_on(self): - return self.value > 0 - - @property - def state_attributes(self): - return {ATTR_BRIGHTNESS: self.value} - - @property - def unique_id(self): - """ Returns the id of this isy sensor """ - return self.node._id - - @property - def name(self): - """ Returns the name of the sensor if any. """ - return self.node.name - - def update(self): - """ Update state of the sensor. """ - # ISY objects are automatically updated by the ISY's event stream - pass - - def onUpdate(self, e): - self.update_ha_state() - - def turn_on(self, **kwargs): - """ turns the device on """ - brightness = kwargs.get(ATTR_BRIGHTNESS) - self.node.on(brightness) - - def turn_off(self, **kwargs): - """ turns the device off """ - self.node.off() + _domain = 'light' + _dtype = 'analog' + _attrs = {ATTR_BRIGHTNESS: 'value'} + _onattrs = [ATTR_BRIGHTNESS] + _states = [STATE_ON, STATE_OFF] diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 66f5eb84fab..3d007724219 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -3,7 +3,7 @@ import logging # homeassistant imports -from ..isy994 import ISY +from homeassistant.components.isy994 import ISY, ISYDeviceABC from homeassistant.helpers.entity import Entity from homeassistant.const import STATE_OPEN, STATE_CLOSED @@ -21,80 +21,25 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if ISY.climate is not None: for prop in ISY.climate._id2name: if prop is not None: - devs.append(ISYSensorDevice('ISY.weather.' + prop, prop, - getattr(ISY.climate, prop), - getattr(ISY.climate, prop + '_units'))) - + node = WeatherPseudoNode('ISY.weather.' + prop, prop, + getattr(ISY.climate, prop), + getattr(ISY.climate, prop + '_units')) + devs.append(ISYSensorDevice(node)) add_devices(devs) -class ISYSensorDevice(Entity): +class WeatherPseudoNode(object): + """ This class allows weather variable to act as regular nodes. """ + + def __init__(self, device_id, name, status, units=None): + self._id = device_id + self.name = name + self.status = status + self.units = units + + +class ISYSensorDevice(ISYDeviceABC): """ represents a isy sensor within home assistant. """ - domain = 'sensor' - - def __init__(self, device_id, name, source, units=None): - # setup properties - self._id = device_id - self._name = name - self.entity_id = self.domain + '.' + self.name.replace(' ', '_') - self._source = source - self._units = units - - # track changes - self._changeHandler = self._source.subscribe('changed', self.onUpdate) - - def __del__(self): - self._changeHandler.unsubscribe() - - @property - def should_poll(self): - return False - - @property - def dtype(self): - return 'binary' if self._units is None else 'analog' - - @property - def state(self): - """ Returns the state. """ - if self.dtype is 'binary': - return STATE_OPEN if self.is_open >= 255 else STATE_CLOSED - else: - return self.value - - @property - def state_attributes(self): - return {} - - @property - def unit_of_measurement(self): - return self._units - - @property - def unique_id(self): - """ Returns the id of this isy sensor """ - return self._id - - @property - def name(self): - """ Returns the name of the sensor if any. """ - return self._name - - def update(self): - """ Update state of the sensor. """ - # ISY objects are automatically updated by the ISY's event stream - pass - - @property - def is_open(self): - """ True if door is open. """ - return self.value >= 255 - - @property - def value(self): - return self._source._val - - def onUpdate(self, e): - self.update_ha_state() + _domain = 'sensor' From 4e3ccfffbb55ade14ef85277587f2ce5959e1049 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sun, 12 Apr 2015 17:18:14 -0400 Subject: [PATCH 28/59] Added not dimming Insteon devices on an ISY controller as switches. --- homeassistant/components/isy994.py | 19 ++++++++---- homeassistant/components/switch/__init__.py | 3 +- homeassistant/components/switch/isy994.py | 33 +++++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/switch/isy994.py diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index fe5d5516cb6..489df94c22e 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -22,7 +22,7 @@ from homeassistant.const import ( DOMAIN = "isy994" DEPENDENCIES = [] DISCOVER_LIGHTS = "isy994.lights" -# DISCOVER_SWITCHES = "isy994.switches" +DISCOVER_SWITCHES = "isy994.switches" DISCOVER_SENSORS = "isy994.sensors" ISY = None @@ -63,7 +63,8 @@ def setup(hass, config): # Load components for the devices in the ISY controller that we support for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), - ('light', DISCOVER_LIGHTS))): + ('light', DISCOVER_LIGHTS), + ('switch', DISCOVER_SWITCHES))): component = get_component(comp_name) bootstrap.setup_component(hass, component.DOMAIN, config) hass.bus.fire(EVENT_PLATFORM_DISCOVERED, @@ -137,7 +138,7 @@ class ISYDeviceABC(ToggleEntity): pass def onUpdate(self, e): - """ Handles the update recieved event. """ + """ Handles the update received event. """ self.update_ha_state() @property @@ -157,12 +158,18 @@ class ISYDeviceABC(ToggleEntity): def turn_on(self, **kwargs): """ turns the device on """ - attrs = [kwargs.get(name) for name in self._onattrs] - self.node.on(*attrs) + if self.domain is not 'sensor': + attrs = [kwargs.get(name) for name in self._onattrs] + self.node.on(*attrs) + else: + logger.error('ISY cannot turn on sensors.') def turn_off(self, **kwargs): """ turns the device off """ - self.node.off() + if self.domain is not 'sensor': + self.node.off() + else: + logger.error('ISY cannot turn off sensors.') @property def unit_of_measurement(self): diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index f246692560d..359839a3946 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -10,7 +10,7 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) -from homeassistant.components import group, discovery, wink +from homeassistant.components import group, discovery, wink, isy994 DOMAIN = 'switch' DEPENDENCIES = [] @@ -30,6 +30,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) DISCOVERY_PLATFORMS = { discovery.services.BELKIN_WEMO: 'wemo', wink.DISCOVER_SWITCHES: 'wink', + isy994.DISCOVER_SWITCHES: 'isy994', } _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py new file mode 100644 index 00000000000..ae4f7b552e4 --- /dev/null +++ b/homeassistant/components/switch/isy994.py @@ -0,0 +1,33 @@ +""" Support for ISY994 lights. """ +# system imports +import logging + +# homeassistant imports +from homeassistant.components.isy994 import ISY, ISYDeviceABC +from homeassistant.const import STATE_ON, STATE_OFF + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the isy994 platform. """ + logger = logging.getLogger(__name__) + devs = [] + # verify connection + if ISY is None or not ISY.connected: + logger.error('A connection has not been made to the ISY controller.') + return False + + # import not dimmable nodes and groups + for node in ISY.nodes: + if not node.dimmable: + devs.append(ISYSwitchDevice(node)) + # import ISY programs + + add_devices(devs) + + +class ISYSwitchDevice(ISYDeviceABC): + """ represents as isy light within home assistant. """ + + _domain = 'switch' + _dtype = 'binary' + _states = [STATE_ON, STATE_OFF] From 510064d9c84b042700eedfb05304e8f1fb538ddb Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sun, 12 Apr 2015 22:30:14 -0400 Subject: [PATCH 29/59] Added support for the creation of custom switches using the ISY994 device. --- homeassistant/components/isy994.py | 7 +++- homeassistant/components/switch/isy994.py | 49 ++++++++++++++++++++++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 489df94c22e..6c4a431c100 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -130,7 +130,10 @@ class ISYDeviceABC(ToggleEntity): @property def name(self): """ Returns the name of the node if any. """ - return self.node.name + try: + return self._name + except AttributeError: + return self.node.name def update(self): """ Update state of the sensor. """ @@ -143,7 +146,7 @@ class ISYDeviceABC(ToggleEntity): @property def is_on(self): - return self.value > 0 + return bool(self.value) @property def is_open(self): diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index ae4f7b552e4..c33e0666fd3 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -4,7 +4,10 @@ import logging # homeassistant imports from homeassistant.components.isy994 import ISY, ISYDeviceABC -from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED +# The frontend doesn't seem to fully support the open and closed states yet. +# Once it does, the HA.doors programs should report open and closed instead of +# off and on. It appears that on should be open and off should be closed. def setup_platform(hass, config, add_devices, discovery_info=None): @@ -20,7 +23,28 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for node in ISY.nodes: if not node.dimmable: devs.append(ISYSwitchDevice(node)) - # import ISY programs + + # import ISY doors programs + for folder_name, states in (('HA.doors', [STATE_ON, STATE_OFF]), + ('HA.switches', [STATE_ON, STATE_OFF])): + try: + folder = ISY.programs['My Programs'][folder_name] + except KeyError: + # HA.doors folder does not exist + pass + else: + for dtype, name, node_id in folder.children: + if dtype is 'folder': + custom_switch = folder[node_id] + try: + actions = custom_switch['actions'].leaf + assert actions.dtype == 'program', 'Not a program' + node = custom_switch['status'].leaf + except (KeyError, AssertionError): + pass + else: + devs.append(ISYProgramDevice(name, node, actions, + states)) add_devices(devs) @@ -31,3 +55,24 @@ class ISYSwitchDevice(ISYDeviceABC): _domain = 'switch' _dtype = 'binary' _states = [STATE_ON, STATE_OFF] + + +class ISYProgramDevice(ISYSwitchDevice): + """ represents a door that can be manipulated within home assistant. """ + + _domain = 'switch' + _dtype = 'binary' + + def __init__(self, name, node, actions, states): + super().__init__(node) + self._states = states + self._name = name + self.action_node = actions + + def turn_on(self, **kwargs): + """ turns the device on/closes the device """ + self.action_node.runThen() + + def turn_off(self, **kwargs): + """ turns the device off/opens the device """ + self.action_node.runElse() From 0e9a8a7cc2f06f0f5a7cf637cd6f1a44aa971cce Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Mon, 13 Apr 2015 01:47:32 -0400 Subject: [PATCH 30/59] Added custom program sensors to the isy994 component. --- homeassistant/components/sensor/isy994.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 3d007724219..a05257f2c3b 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -4,8 +4,8 @@ import logging # homeassistant imports from homeassistant.components.isy994 import ISY, ISYDeviceABC -from homeassistant.helpers.entity import Entity -from homeassistant.const import STATE_OPEN, STATE_CLOSED +from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME, + STATE_NOT_HOME, STATE_ON, STATE_OFF) def setup_platform(hass, config, add_devices, discovery_info=None): @@ -26,6 +26,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): getattr(ISY.climate, prop + '_units')) devs.append(ISYSensorDevice(node)) + # import sensor programs + for (folder_name, states) in ( + ('HA.locations', [STATE_HOME, STATE_NOT_HOME]), + ('HA.sensors', [STATE_OPEN, STATE_CLOSED]), + ('HA.states', [STATE_ON, STATE_OFF])): + try: + folder = ISY.programs['My Programs'][folder_name] + except KeyError: + # folder does not exist + pass + else: + for dtype, name, node_id in folder.children: + node = folder[node_id].leaf + devs.append(ISYSensorDevice(node, states)) + add_devices(devs) @@ -43,3 +58,7 @@ class ISYSensorDevice(ISYDeviceABC): """ represents a isy sensor within home assistant. """ _domain = 'sensor' + + def __init__(self, node, states=[]): + super().__init__(node) + self._states = states From 83aea10f06101c7dc6cf7f232cd4b413d2ebbc14 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Mon, 13 Apr 2015 12:56:37 -0400 Subject: [PATCH 31/59] Added hidden_string and sensor_string properties to the isy994 configuration to allow nodes to be hidden and to be handled as sensors. Implimented the sensor_string. Any node name that contains the sensor_string in its name will be treated as a sensor instead of a switch or light. The hidden_string will be implimented later. --- homeassistant/components/isy994.py | 42 ++++++++++++++--------- homeassistant/components/light/isy994.py | 4 +-- homeassistant/components/sensor/isy994.py | 7 +++- homeassistant/components/switch/isy994.py | 4 +-- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 6c4a431c100..7fa0524d32f 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -25,6 +25,8 @@ DISCOVER_LIGHTS = "isy994.lights" DISCOVER_SWITCHES = "isy994.switches" DISCOVER_SENSORS = "isy994.sensors" ISY = None +SENSOR_STRING = 'Sensor' +HIDDEN_STRING = '{HIDE ME}' # setup logger logger = logging.getLogger(__name__) @@ -32,28 +34,34 @@ logger.setLevel(logging.DEBUG) def setup(hass, config): - # pull values from configuration file + # check for required values in configuration file if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, logger): return False + + # pull and parse standard configuration + user = config[DOMAIN][CONF_USERNAME] + password = config[DOMAIN][CONF_PASSWORD] + host = urlparse(config[DOMAIN][CONF_HOST]) + addr = host.geturl() + if host.scheme == 'http': + addr = addr.replace('http://', '') + https = False + elif host.scheme == 'https': + addr = addr.replace('https://', '') + https = True else: - user = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] - host = urlparse(config[DOMAIN][CONF_HOST]) - addr = host.geturl() - if host.scheme == 'http': - addr = addr.replace('http://', '') - https = False - elif host.scheme == 'https': - addr = addr.replace('https://', '') - https = True - else: - logger.error('isy994 host value in configuration ' + - 'file is invalid.') - return False - port = host.port - addr = addr.replace(':{}'.format(port), '') + logger.error('isy994 host value in configuration file is invalid.') + return False + port = host.port + addr = addr.replace(':{}'.format(port), '') + + # pull and parse optional configuration + global SENSOR_STRING + global HIDDEN_STRING + SENSOR_STRING = config[DOMAIN].get('sensor_string', SENSOR_STRING) + HIDDEN_STRING = config[DOMAIN].get('hidden_string', HIDDEN_STRING) # connect to ISY controller global ISY diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index 16d7f6b052c..60a4faafe13 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -3,7 +3,7 @@ import logging # homeassistant imports -from homeassistant.components.isy994 import ISYDeviceABC, ISY +from homeassistant.components.isy994 import ISYDeviceABC, ISY, SENSOR_STRING from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.const import STATE_ON, STATE_OFF @@ -19,7 +19,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # import dimmable nodes for node in ISY.nodes: - if node.dimmable: + if node.dimmable and SENSOR_STRING not in node.name: devs.append(ISYLightDevice(node)) add_devices(devs) diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index a05257f2c3b..aa98c594910 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -3,7 +3,7 @@ import logging # homeassistant imports -from homeassistant.components.isy994 import ISY, ISYDeviceABC +from homeassistant.components.isy994 import ISY, ISYDeviceABC, SENSOR_STRING from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF) @@ -26,6 +26,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): getattr(ISY.climate, prop + '_units')) devs.append(ISYSensorDevice(node)) + # import sensor nodes + for node in ISY.nodes: + if SENSOR_STRING in node.name: + devs.append(ISYSensorDevice(node, [STATE_ON, STATE_OFF])) + # import sensor programs for (folder_name, states) in ( ('HA.locations', [STATE_HOME, STATE_NOT_HOME]), diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index c33e0666fd3..1ea87e3fc2e 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -3,7 +3,7 @@ import logging # homeassistant imports -from homeassistant.components.isy994 import ISY, ISYDeviceABC +from homeassistant.components.isy994 import ISY, ISYDeviceABC, SENSOR_STRING from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED # The frontend doesn't seem to fully support the open and closed states yet. # Once it does, the HA.doors programs should report open and closed instead of @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # import not dimmable nodes and groups for node in ISY.nodes: - if not node.dimmable: + if not node.dimmable and SENSOR_STRING not in node.name: devs.append(ISYSwitchDevice(node)) # import ISY doors programs From c76644323fdcfcb1da95b0bb0a5df2c70c6f4ac1 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 14 Apr 2015 01:44:39 -0400 Subject: [PATCH 32/59] Updated the broken link to the apple-touch icon in the frontend. --- homeassistant/components/frontend/index.html.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/frontend/index.html.template b/homeassistant/components/frontend/index.html.template index c4da4f0369d..4844eb46760 100644 --- a/homeassistant/components/frontend/index.html.template +++ b/homeassistant/components/frontend/index.html.template @@ -15,8 +15,8 @@ - + From a3d6972268895b0f5475dc201a543a585b6cbe21 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 14 Apr 2015 22:57:32 -0400 Subject: [PATCH 33/59] 1) Added basic back-end framework for supporting hidden entities. 2) Enabled hidden suggestions in the isy994 component entities. --- homeassistant/__init__.py | 12 +++++++- homeassistant/bootstrap.py | 3 ++ homeassistant/components/isy994.py | 17 +++++++---- homeassistant/const.py | 3 ++ homeassistant/helpers/entity.py | 47 +++++++++++++++++++++++++----- 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 5e54cbd5d0a..80916011d55 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -22,7 +22,7 @@ from homeassistant.const import ( SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL, EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, - TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME) + TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_HIDDEN) import homeassistant.util as util DOMAIN = "homeassistant" @@ -621,6 +621,16 @@ class StateMachine(object): new_state = str(new_state) attributes = attributes or {} + # Last chance to enforce the visibility property. This is required for + # components that don't use the Entity base class for their entities. + # The sun component is an example of this. The Entity class cannot be + # imported cleanly, so assume the state is shown. This means that for + # visibility to be supported, the state must originate from a class that + # uses the base class Entity or it must manually put the hidden + # attribute in its attributes dictionary. + if ATTR_HIDDEN not in attributes: + attributes[ATTR_HIDDEN] = False + with self._lock: old_state = self._states.get(entity_id) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 4a6aab53483..c0b8fe09af3 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -20,6 +20,7 @@ import homeassistant import homeassistant.loader as loader import homeassistant.components as core_components import homeassistant.components.group as group +from homeassistant.helpers.entity import Entity from homeassistant.const import ( EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, TEMP_CELCIUS, @@ -207,6 +208,8 @@ def process_ha_core_config(hass, config): if key in config: setattr(hass.config, attr, config[key]) + Entity._visibility.update(config.get('visibility', [{}])[0]) + if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 7fa0524d32f..d364007d12c 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -60,8 +60,8 @@ def setup(hass, config): # pull and parse optional configuration global SENSOR_STRING global HIDDEN_STRING - SENSOR_STRING = config[DOMAIN].get('sensor_string', SENSOR_STRING) - HIDDEN_STRING = config[DOMAIN].get('hidden_string', HIDDEN_STRING) + SENSOR_STRING = str(config[DOMAIN].get('sensor_string', SENSOR_STRING)) + HIDDEN_STRING = str(config[DOMAIN].get('hidden_string', HIDDEN_STRING)) # connect to ISY controller global ISY @@ -95,6 +95,7 @@ class ISYDeviceABC(ToggleEntity): def __init__(self, node): # setup properties self.node = node + self.hidden = HIDDEN_STRING in self.raw_name # track changes self._changeHandler = self.node.status. \ @@ -135,13 +136,17 @@ class ISYDeviceABC(ToggleEntity): """ Returns the id of this isy sensor """ return self.node._id + @property + def raw_name(self): + try: + return str(self._name) + except AttributeError: + return str(self.node.name) + @property def name(self): """ Returns the name of the node if any. """ - try: - return self._name - except AttributeError: - return self.node.name + return self.raw_name.replace(HIDDEN_STRING, '').strip() def update(self): """ Update state of the sensor. """ diff --git a/homeassistant/const.py b/homeassistant/const.py index 467bb692399..a1aa0df40d1 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -86,6 +86,9 @@ ATTR_TRIPPED = "device_tripped" # time the device was tripped ATTR_LAST_TRIP_TIME = "last_tripped_time" +# For all entity's, this hold whether or not it should be hidden +ATTR_HIDDEN = "hidden" + # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = "stop" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index a8ee712b0f7..d7599eaf971 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -8,16 +8,19 @@ Provides ABC for entities in HA. from homeassistant import NoEntitySpecifiedError from homeassistant.const import ( - ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, - DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT) + ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, STATE_ON, + STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT) class Entity(object): """ ABC for Home Assistant entities. """ # pylint: disable=no-self-use - hass = None - entity_id = None + # SAFE TO OVERWRITE + # The properties and methods here are safe to overwrite when inherting this + # class. These may be used to customize the behavior of the entity. + + _hidden = False # suggestion as to whether the entity should be hidden @property def should_poll(self): @@ -52,6 +55,10 @@ class Entity(object): """ Unit of measurement of this entity, if any. """ return None + def update(self): + """ Retrieve latest state. """ + pass + # DEPRECATION NOTICE: # Device is moving from getters to properties. # For now the new properties will call the old functions @@ -69,9 +76,14 @@ class Entity(object): """ Returns optional state attributes. """ return None - def update(self): - """ Retrieve latest state. """ - pass + # DO NOT OVERWRITE + # These properties and methods are either managed by Home Assistant or they + # are used to perform a very specific function. Overwriting these may + # produce undesirable effects in the entity's operation. + + hass = None + entity_id = None + _visibility = {} def update_ha_state(self, force_refresh=False): """ @@ -97,6 +109,9 @@ class Entity(object): if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement: attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement + if ATTR_HIDDEN not in attr: + attr[ATTR_HIDDEN] = bool(self.hidden) + # Convert temperature if we detect one if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS, TEMP_FAHRENHEIT): @@ -115,6 +130,24 @@ class Entity(object): def __repr__(self): return "".format(self.name, self.state) + @property + def hidden(self): + """ + Returns the official decision of whether the entity should be hidden. + Any value set by the user in the configuration file will overwrite + whatever the component sets for visibility. + """ + if self.entity_id is not None and \ + self.entity_id.lower() in self._visibility: + return self._visibility[self.entity_id.lower()] is 'hide' + else: + return self._hidden + + @hidden.setter + def hidden(self, val): + """ Sets the suggestion for visibility. """ + self._hidden = bool(val) + class ToggleEntity(Entity): """ ABC for entities that can be turned on and off. """ From 0334074a522e20aacf3b4f2b0c746eec45f5619c Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 14 Apr 2015 23:38:14 -0400 Subject: [PATCH 34/59] Quick fix to the comparison to validate if an entity is hidden. --- homeassistant/helpers/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index d7599eaf971..3e559d39532 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -139,7 +139,7 @@ class Entity(object): """ if self.entity_id is not None and \ self.entity_id.lower() in self._visibility: - return self._visibility[self.entity_id.lower()] is 'hide' + return self._visibility[self.entity_id.lower()] == 'hide' else: return self._hidden From caed69d5ea2f591e48b8e94e868cb36d9bb913b4 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 14 Apr 2015 23:55:08 -0400 Subject: [PATCH 35/59] Added state card hiding to the STATE view on the frontend. --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 272 ++---------------- .../polymer/layouts/partial-states.html | 5 +- scripts/build_frontend | 2 +- 4 files changed, 23 insertions(+), 258 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 095769c9c28..72b1fa41d39 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "1e004712440afc642a44ad927559587e" +VERSION = "9e21c99d2991dd288287d3d3bc3e64e0" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 10ac2336a3c..31c22f9bfc7 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,257 +1,21 @@ - - + -/* Palette generated by Material Palette - materialpalette.com/light-blue/orange */ +if(this.removeAttribute("value"),c)return j(this,"value",b);var e=b,f=m(this,"value",e);return j(this,"value",e.open(k(this,"value",d))),t(this,a,f)},HTMLOptionElement.prototype.bind=function(a,b,c){if("value"!==a)return HTMLElement.prototype.bind.call(this,a,b,c);if(this.removeAttribute("value"),c)return q(this,b);var d=b,e=m(this,"value",d);return q(this,d.open(r(this))),t(this,a,e)},HTMLSelectElement.prototype.bind=function(a,c,d){if("selectedindex"===a&&(a="selectedIndex"),"selectedIndex"!==a&&"value"!==a)return HTMLElement.prototype.bind.call(this,a,c,d);if(this.removeAttribute(a),d)return j(this,a,c);var e=c,f=m(this,a,e);return j(this,a,e.open(k(this,a))),b(this,a,f)}}(this),function(a){"use strict";function b(a){if(!a)throw new Error("Assertion failed")}function c(a){for(var b;b=a.parentNode;)a=b;return a}function d(a,b){if(b){for(var d,e="#"+b;!d&&(a=c(a),a.protoContent_?d=a.protoContent_.querySelector(e):a.getElementById&&(d=a.getElementById(b)),!d&&a.templateCreator_);)a=a.templateCreator_;return d}}function e(a){return"template"==a.tagName&&"http://www.w3.org/2000/svg"==a.namespaceURI}function f(a){return"TEMPLATE"==a.tagName&&"http://www.w3.org/1999/xhtml"==a.namespaceURI}function g(a){return Boolean(L[a.tagName]&&a.hasAttribute("template"))}function h(a){return void 0===a.isTemplate_&&(a.isTemplate_="TEMPLATE"==a.tagName||g(a)),a.isTemplate_}function i(a,b){var c=a.querySelectorAll(N);h(a)&&b(a),G(c,b)}function j(a){function b(a){HTMLTemplateElement.decorate(a)||j(a.content)}i(a,b)}function k(a,b){Object.getOwnPropertyNames(b).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))})}function l(a){var b=a.ownerDocument;if(!b.defaultView)return b;var c=b.templateContentsOwner_;if(!c){for(c=b.implementation.createHTMLDocument("");c.lastChild;)c.removeChild(c.lastChild);b.templateContentsOwner_=c}return c}function m(a){if(!a.stagingDocument_){var b=a.ownerDocument;if(!b.stagingDocument_){b.stagingDocument_=b.implementation.createHTMLDocument(""),b.stagingDocument_.isStagingDocument=!0;var c=b.stagingDocument_.createElement("base");c.href=document.baseURI,b.stagingDocument_.head.appendChild(c),b.stagingDocument_.stagingDocument_=b.stagingDocument_}a.stagingDocument_=b.stagingDocument_}return a.stagingDocument_}function n(a){var b=a.ownerDocument.createElement("template");a.parentNode.insertBefore(b,a);for(var c=a.attributes,d=c.length;d-->0;){var e=c[d];K[e.name]&&("template"!==e.name&&b.setAttribute(e.name,e.value),a.removeAttribute(e.name))}return b}function o(a){var b=a.ownerDocument.createElement("template");a.parentNode.insertBefore(b,a);for(var c=a.attributes,d=c.length;d-->0;){var e=c[d];b.setAttribute(e.name,e.value),a.removeAttribute(e.name)}return a.parentNode.removeChild(a),b}function p(a,b,c){var d=a.content;if(c)return void d.appendChild(b);for(var e;e=b.firstChild;)d.appendChild(e)}function q(a){P?a.__proto__=HTMLTemplateElement.prototype:k(a,HTMLTemplateElement.prototype)}function r(a){a.setModelFn_||(a.setModelFn_=function(){a.setModelFnScheduled_=!1;var b=z(a,a.delegate_&&a.delegate_.prepareBinding);w(a,b,a.model_)}),a.setModelFnScheduled_||(a.setModelFnScheduled_=!0,Observer.runEOM_(a.setModelFn_))}function s(a,b,c,d){if(a&&a.length){for(var e,f=a.length,g=0,h=0,i=0,j=!0;f>h;){var g=a.indexOf("{{",h),k=a.indexOf("[[",h),l=!1,m="}}";if(k>=0&&(0>g||g>k)&&(g=k,l=!0,m="]]"),i=0>g?-1:a.indexOf(m,g+2),0>i){if(!e)return;e.push(a.slice(h));break}e=e||[],e.push(a.slice(h,g));var n=a.slice(g+2,i).trim();e.push(l),j=j&&l;var o=d&&d(n,b,c);e.push(null==o?Path.get(n):null),e.push(o),h=i+2}return h===f&&e.push(""),e.hasOnePath=5===e.length,e.isSimplePath=e.hasOnePath&&""==e[0]&&""==e[4],e.onlyOneTime=j,e.combinator=function(a){for(var b=e[0],c=1;cc?(this.keys.push(a),this.values.push(b)):this.values[c]=b},get:function(a){var b=this.keys.indexOf(a);if(!(0>b))return this.values[b]},"delete":function(a){var b=this.keys.indexOf(a);return 0>b?!1:(this.keys.splice(b,1),this.values.splice(b,1),!0)},forEach:function(a,b){for(var c=0;cb;)this.reportInstanceMoved(b),b++},closeInstanceBindings:function(a){for(var b=a.bindings_,c=0;c32&&127>b&&-1==[34,35,60,62,63,96].indexOf(b)?a:encodeURIComponent(a)}function f(a){var b=a.charCodeAt(0);return b>32&&127>b&&-1==[34,35,60,62,96].indexOf(b)?a:encodeURIComponent(a)}function g(a,g,h){function i(a){t.push(a)}var j=g||"scheme start",k=0,l="",r=!1,s=!1,t=[];a:for(;(a[k-1]!=o||0==k)&&!this._isInvalid;){var u=a[k];switch(j){case"scheme start":if(!u||!p.test(u)){if(g){i("Invalid scheme.");break a}l="",j="no scheme";continue}l+=u.toLowerCase(),j="scheme";break;case"scheme":if(u&&q.test(u))l+=u.toLowerCase();else{if(":"!=u){if(g){if(o==u)break a;i("Code point not allowed in scheme: "+u);break a}l="",k=0,j="no scheme";continue}if(this._scheme=l,l="",g)break a;b(this._scheme)&&(this._isRelative=!0),j="file"==this._scheme?"relative":this._isRelative&&h&&h._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==u?(query="?",j="query"):"#"==u?(this._fragment="#",j="fragment"):o!=u&&" "!=u&&"\n"!=u&&"\r"!=u&&(this._schemeData+=e(u));break;case"no scheme":if(h&&b(h._scheme)){j="relative";continue}i("Missing scheme."),c.call(this);break;case"relative or authority":if("/"!=u||"/"!=a[k+1]){i("Expected /, got: "+u),j="relative";continue}j="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=h._scheme),o==u){this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._query=h._query;break a}if("/"==u||"\\"==u)"\\"==u&&i("\\ is an invalid code point."),j="relative slash";else if("?"==u)this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._query="?",j="query";else{if("#"!=u){var v=a[k+1],w=a[k+2];("file"!=this._scheme||!p.test(u)||":"!=v&&"|"!=v||o!=w&&"/"!=w&&"\\"!=w&&"?"!=w&&"#"!=w)&&(this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._path.pop()),j="relative path";continue}this._host=h._host,this._port=h._port,this._path=h._path.slice(),this._query=h._query,this._fragment="#",j="fragment"}break;case"relative slash":if("/"!=u&&"\\"!=u){"file"!=this._scheme&&(this._host=h._host,this._port=h._port),j="relative path";continue}"\\"==u&&i("\\ is an invalid code point."),j="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=u){i("Expected '/', got: "+u),j="authority ignore slashes";continue}j="authority second slash";break;case"authority second slash":if(j="authority ignore slashes","/"!=u){i("Expected '/', got: "+u);continue}break;case"authority ignore slashes":if("/"!=u&&"\\"!=u){j="authority";continue}i("Expected authority, got: "+u);break;case"authority":if("@"==u){r&&(i("@ already seen."),l+="%40"),r=!0;for(var x=0;xg;g++)f.unshift("..");var i=b.href.slice(-1)===m?m:b.hash;return f.join("/")+b.search+i}var g={resolveDom:function(a,c){c=c||b(a),this.resolveAttributes(a,c),this.resolveStyles(a,c);var d=a.querySelectorAll("template");if(d)for(var e,f=0,g=d.length;g>f&&(e=d[f]);f++)e.content&&this.resolveDom(e.content,c)},resolveTemplate:function(a){this.resolveDom(a.content,b(a))},resolveStyles:function(a,b){var c=a.querySelectorAll("style");if(c)for(var d,e=0,f=c.length;f>e&&(d=c[e]);e++)this.resolveStyle(d,b)},resolveStyle:function(a,c){c=c||b(a),a.textContent=this.resolveCssText(a.textContent,c)},resolveCssText:function(a,b,d){return a=c(a,b,d,h),c(a,b,d,i)},resolveAttributes:function(a,b){a.hasAttributes&&a.hasAttributes()&&this.resolveElementAttributes(a,b);var c=a&&a.querySelectorAll(k);if(c)for(var d,e=0,f=c.length;f>e&&(d=c[e]);e++)this.resolveElementAttributes(d,b)},resolveElementAttributes:function(a,e){e=e||b(a),j.forEach(function(b){var f,g=a.attributes[b],i=g&&g.value;i&&i.search(l)<0&&(f="style"===b?c(i,e,!1,h):d(e,i),g.value=f)})}},h=/(url\()([^)]*)(\))/g,i=/(@import[\s]+(?!url\())([^;]*)(;)/g,j=["href","src","action","style","url"],k="["+j.join("],[")+"]",l="{{.*}}",m="#";a.urlResolver=g}(Polymer),function(a){function b(a){this.cache=Object.create(null),this.map=Object.create(null),this.requests=0,this.regex=a}var c=Polymer.endOfMicrotask;b.prototype={extractUrls:function(a,b){for(var c,d,e=[];c=this.regex.exec(a);)d=new URL(c[1],b),e.push({matched:c[0],url:d.href});return e},process:function(a,b,c){var d=this.extractUrls(a,b),e=c.bind(null,this.map);this.fetch(d,e)},fetch:function(a,b){var c=a.length;if(!c)return b();for(var d,e,f,g=function(){0===--c&&b()},h=0;c>h;h++)d=a[h],f=d.url,e=this.cache[f],e||(e=this.xhr(f),e.match=d,this.cache[f]=e),e.wait(g)},handleXhr:function(a){var b=a.match,c=b.url,d=a.response||a.responseText||"";this.map[c]=d,this.fetch(this.extractUrls(d,c),a.resolve)},xhr:function(a){this.requests++;var b=new XMLHttpRequest;return b.open("GET",a,!0),b.send(),b.onerror=b.onload=this.handleXhr.bind(this,b),b.pending=[],b.resolve=function(){for(var a=b.pending,c=0;ch&&(e=a[h]);h++)this.resolveNode(e,b,d)}};var e=new b;a.styleResolver=e}(Polymer),function(a){function b(a,b){return a&&b&&Object.getOwnPropertyNames(b).forEach(function(c){var d=Object.getOwnPropertyDescriptor(b,c);d&&(Object.defineProperty(a,c,d),"function"==typeof d.value&&(d.value.nom=c))}),a}function c(a){for(var b=a||{},c=1;ce&&(c=d[e]);e++){var g=Object.getOwnPropertyDescriptor(b,c);if("function"==typeof g.value&&g.value===a)return c}b=b.__proto__}}function d(a,b,c){var d=e(c,b,a);return d[b]&&(d[b].nom=b),a._super=d}function e(a,b,c){for(;a;){if(a[b]!==c&&a[b])return a;a=f(a)}return Object}function f(a){return a.__proto__}a["super"]=b}(Polymer),function(a){function b(a){return a}function c(a,b){var c=typeof b;return b instanceof Date&&(c="date"),d[c](a,b)}var d={string:b,undefined:b,date:function(a){return new Date(Date.parse(a)||Date.now())},"boolean":function(a){return""===a?!0:"false"===a?!1:!!a},number:function(a){var b=parseFloat(a);return 0===b&&(b=parseInt(a)),isNaN(b)?a:b},object:function(a,b){if(null===b)return a;try{return JSON.parse(a.replace(/'/g,'"'))}catch(c){return a}},"function":function(a,b){return b}};a.deserializeValue=c}(Polymer),function(a){var b=a.extend,c={};c.declaration={},c.instance={},c.publish=function(a,c){for(var d in a)b(c,a[d])},a.api=c}(Polymer),function(a){var b={async:function(a,b,c){Polymer.flush(),b=b&&b.length?b:[b];var d=function(){(this[a]||a).apply(this,b)}.bind(this),e=c?setTimeout(d,c):requestAnimationFrame(d);return c?e:~e},cancelAsync:function(a){0>a?cancelAnimationFrame(~a):clearTimeout(a)},fire:function(a,b,c,d,e){var f=c||this,b=null===b||void 0===b?{}:b,g=new CustomEvent(a,{bubbles:void 0!==d?d:!0,cancelable:void 0!==e?e:!0,detail:b});return f.dispatchEvent(g),g},asyncFire:function(){this.async("fire",arguments)},classFollows:function(a,b,c){ +b&&b.classList.remove(c),a&&a.classList.add(c)},injectBoundHTML:function(a,b){var c=document.createElement("template");c.innerHTML=a;var d=this.instanceTemplate(c);return b&&(b.textContent="",b.appendChild(d)),d}},c=function(){},d={};b.asyncMethod=b.async,a.api.instance.utils=b,a.nop=c,a.nob=d}(Polymer),function(a){var b=window.WebComponents?WebComponents.flags.log:{},c="on-",d={EVENT_PREFIX:c,addHostListeners:function(){var a=this.eventDelegates;b.events&&Object.keys(a).length>0&&console.log("[%s] addHostListeners:",this.localName,a);for(var c in a){var d=a[c];PolymerGestures.addEventListener(this,c,this.element.getEventHandler(this,this,d))}},dispatchMethod:function(a,c,d){if(a){b.events&&console.group("[%s] dispatch [%s]",a.localName,c);var e="function"==typeof c?c:a[c];e&&e[d?"apply":"call"](a,d),b.events&&console.groupEnd(),Polymer.flush()}}};a.api.instance.events=d,a.addEventListener=function(a,b,c,d){PolymerGestures.addEventListener(wrap(a),b,c,d)},a.removeEventListener=function(a,b,c,d){PolymerGestures.removeEventListener(wrap(a),b,c,d)}}(Polymer),function(a){var b={copyInstanceAttributes:function(){var a=this._instanceAttributes;for(var b in a)this.hasAttribute(b)||this.setAttribute(b,a[b])},takeAttributes:function(){if(this._publishLC)for(var a,b=0,c=this.attributes,d=c.length;(a=c[b])&&d>b;b++)this.attributeToProperty(a.name,a.value)},attributeToProperty:function(b,c){var b=this.propertyForAttribute(b);if(b){if(c&&c.search(a.bindPattern)>=0)return;var d=this[b],c=this.deserializeValue(c,d);c!==d&&(this[b]=c)}},propertyForAttribute:function(a){var b=this._publishLC&&this._publishLC[a];return b},deserializeValue:function(b,c){return a.deserializeValue(b,c)},serializeValue:function(a,b){return"boolean"===b?a?"":void 0:"object"!==b&&"function"!==b&&void 0!==a?a:void 0},reflectPropertyToAttribute:function(a){var b=typeof this[a],c=this.serializeValue(this[a],b);void 0!==c?this.setAttribute(a,c):"boolean"===b&&this.removeAttribute(a)}};a.api.instance.attributes=b}(Polymer),function(a){function b(a,b){return a===b?0!==a||1/a===1/b:f(a)&&f(b)?!0:a!==a&&b!==b}function c(a,b){return void 0===b&&null===a?b:null===b||void 0===b?a:b}var d=window.WebComponents?WebComponents.flags.log:{},e={object:void 0,type:"update",name:void 0,oldValue:void 0},f=Number.isNaN||function(a){return"number"==typeof a&&isNaN(a)},g={createPropertyObserver:function(){var a=this._observeNames;if(a&&a.length){var b=this._propertyObserver=new CompoundObserver(!0);this.registerObserver(b);for(var c,d=0,e=a.length;e>d&&(c=a[d]);d++)b.addPath(this,c),this.observeArrayValue(c,this[c],null)}},openPropertyObserver:function(){this._propertyObserver&&this._propertyObserver.open(this.notifyPropertyChanges,this)},notifyPropertyChanges:function(a,b,c){var d,e,f={};for(var g in b)if(d=c[2*g+1],e=this.observe[d]){var h=b[g],i=a[g];this.observeArrayValue(d,i,h),f[e]||(void 0!==h&&null!==h||void 0!==i&&null!==i)&&(f[e]=!0,this.invokeMethod(e,[h,i,arguments]))}},invokeMethod:function(a,b){var c=this[a]||a;"function"==typeof c&&c.apply(this,b)},deliverChanges:function(){this._propertyObserver&&this._propertyObserver.deliver()},observeArrayValue:function(a,b,c){var e=this.observe[a];if(e&&(Array.isArray(c)&&(d.observe&&console.log("[%s] observeArrayValue: unregister observer [%s]",this.localName,a),this.closeNamedObserver(a+"__array")),Array.isArray(b))){d.observe&&console.log("[%s] observeArrayValue: register observer [%s]",this.localName,a,b);var f=new ArrayObserver(b);f.open(function(a){this.invokeMethod(e,[a])},this),this.registerNamedObserver(a+"__array",f)}},emitPropertyChangeRecord:function(a,c,d){if(!b(c,d)&&(this._propertyChanged(a,c,d),Observer.hasObjectObserve)){var f=this._objectNotifier;f||(f=this._objectNotifier=Object.getNotifier(this)),e.object=this,e.name=a,e.oldValue=d,f.notify(e)}},_propertyChanged:function(a){this.reflect[a]&&this.reflectPropertyToAttribute(a)},bindProperty:function(a,b,d){if(d)return void(this[a]=b);var e=this.element.prototype.computed;if(e&&e[a]){var f=a+"ComputedBoundObservable_";return void(this[f]=b)}return this.bindToAccessor(a,b,c)},bindToAccessor:function(a,c,d){function e(b,c){j[f]=b;var d=j[h];d&&"function"==typeof d.setValue&&d.setValue(b),j.emitPropertyChangeRecord(a,b,c)}var f=a+"_",g=a+"Observable_",h=a+"ComputedBoundObservable_";this[g]=c;var i=this[f],j=this,k=c.open(e);if(d&&!b(i,k)){var l=d(i,k);b(k,l)||(k=l,c.setValue&&c.setValue(k))}e(k,i);var m={close:function(){c.close(),j[g]=void 0,j[h]=void 0}};return this.registerObserver(m),m},createComputedProperties:function(){if(this._computedNames)for(var a=0;ae&&(c=d[e]);e++)b[c.id]=c},onMutation:function(a,b){var c=new MutationObserver(function(a){b.call(this,c,a),c.disconnect()}.bind(this));c.observe(a,{childList:!0,subtree:!0})}};c.prototype=d,d.constructor=c,a.Base=c,a.isBase=b,a.api.instance.base=d}(Polymer),function(a){function b(a){return a.__proto__}function c(a,b){var c="",d=!1;b&&(c=b.localName,d=b.hasAttribute("is"));var e=WebComponents.ShadowCSS.makeScopeSelector(c,d);return WebComponents.ShadowCSS.shimCssText(a,e)}var d=(window.WebComponents?WebComponents.flags.log:{},window.ShadowDOMPolyfill),e="element",f="controller",g={STYLE_SCOPE_ATTRIBUTE:e,installControllerStyles:function(){var a=this.findStyleScope();if(a&&!this.scopeHasNamedStyle(a,this.localName)){for(var c=b(this),d="";c&&c.element;)d+=c.element.cssTextForScope(f),c=b(c);d&&this.installScopeCssText(d,a)}},installScopeStyle:function(a,b,c){var c=c||this.findStyleScope(),b=b||"";if(c&&!this.scopeHasNamedStyle(c,this.localName+b)){var d="";if(a instanceof Array)for(var e,f=0,g=a.length;g>f&&(e=a[f]);f++)d+=e.textContent+"\n\n";else d=a.textContent;this.installScopeCssText(d,c,b)}},installScopeCssText:function(a,b,e){if(b=b||this.findStyleScope(),e=e||"",b){d&&(a=c(a,b.host));var g=this.element.cssTextToScopeStyle(a,f);Polymer.applyStyleToScope(g,b),this.styleCacheForScope(b)[this.localName+e]=!0}},findStyleScope:function(a){for(var b=a||this;b.parentNode;)b=b.parentNode;return b},scopeHasNamedStyle:function(a,b){var c=this.styleCacheForScope(a);return c[b]},styleCacheForScope:function(a){if(d){var b=a.host?a.host.localName:a.localName;return h[b]||(h[b]={})}return a._scopeStyles=a._scopeStyles||{}}},h={};a.api.instance.styles=g}(Polymer),function(a){function b(a,b){if("string"!=typeof a){var c=b||document._currentScript;if(b=a,a=c&&c.parentNode&&c.parentNode.getAttribute?c.parentNode.getAttribute("name"):"",!a)throw"Element name could not be inferred."}if(f(a))throw"Already registered (Polymer) prototype for element "+a;e(a,b),d(a)}function c(a,b){i[a]=b}function d(a){i[a]&&(i[a].registerWhenReady(),delete i[a])}function e(a,b){return j[a]=b||{}}function f(a){return j[a]}function g(a,b){if("string"!=typeof b)return!1;var c=HTMLElement.getPrototypeForTag(b),d=c&&c.constructor;return d?CustomElements["instanceof"]?CustomElements["instanceof"](a,d):a instanceof d:!1}var h=a.extend,i=(a.api,{}),j={};a.getRegisteredPrototype=f,a.waitingForPrototype=c,a.instanceOfType=g,window.Polymer=b,h(Polymer,a),WebComponents.consumeDeclarations&&WebComponents.consumeDeclarations(function(a){if(a)for(var c,d=0,e=a.length;e>d&&(c=a[d]);d++)b.apply(null,c)})}(Polymer),function(a){var b={resolveElementPaths:function(a){Polymer.urlResolver.resolveDom(a)},addResolvePathApi:function(){var a=this.getAttribute("assetpath")||"",b=new URL(a,this.ownerDocument.baseURI);this.prototype.resolvePath=function(a,c){var d=new URL(a,c||b);return d.href}}};a.api.declaration.path=b}(Polymer),function(a){function b(a,b){var c=new URL(a.getAttribute("href"),b).href;return"@import '"+c+"';"}function c(a,b){if(a){b===document&&(b=document.head),i&&(b=document.head);var c=d(a.textContent),e=a.getAttribute(h);e&&c.setAttribute(h,e);var f=b.firstElementChild;if(b===document.head){var g="style["+h+"]",j=document.head.querySelectorAll(g);j.length&&(f=j[j.length-1].nextElementSibling)}b.insertBefore(c,f)}}function d(a,b){b=b||document,b=b.createElement?b:b.ownerDocument;var c=b.createElement("style");return c.textContent=a,c}function e(a){return a&&a.__resource||""}function f(a,b){return q?q.call(a,b):void 0}var g=(window.WebComponents?WebComponents.flags.log:{},a.api.instance.styles),h=g.STYLE_SCOPE_ATTRIBUTE,i=window.ShadowDOMPolyfill,j="style",k="@import",l="link[rel=stylesheet]",m="global",n="polymer-scope",o={loadStyles:function(a){var b=this.fetchTemplate(),c=b&&this.templateContent();if(c){this.convertSheetsToStyles(c);var d=this.findLoadableStyles(c);if(d.length){var e=b.ownerDocument.baseURI;return Polymer.styleResolver.loadStyles(d,e,a)}}a&&a()},convertSheetsToStyles:function(a){for(var c,e,f=a.querySelectorAll(l),g=0,h=f.length;h>g&&(c=f[g]);g++)e=d(b(c,this.ownerDocument.baseURI),this.ownerDocument),this.copySheetAttributes(e,c),c.parentNode.replaceChild(e,c)},copySheetAttributes:function(a,b){for(var c,d=0,e=b.attributes,f=e.length;(c=e[d])&&f>d;d++)"rel"!==c.name&&"href"!==c.name&&a.setAttribute(c.name,c.value)},findLoadableStyles:function(a){var b=[];if(a)for(var c,d=a.querySelectorAll(j),e=0,f=d.length;f>e&&(c=d[e]);e++)c.textContent.match(k)&&b.push(c);return b},installSheets:function(){this.cacheSheets(),this.cacheStyles(),this.installLocalSheets(),this.installGlobalStyles()},cacheSheets:function(){this.sheets=this.findNodes(l),this.sheets.forEach(function(a){a.parentNode&&a.parentNode.removeChild(a)})},cacheStyles:function(){this.styles=this.findNodes(j+"["+n+"]"),this.styles.forEach(function(a){a.parentNode&&a.parentNode.removeChild(a)})},installLocalSheets:function(){var a=this.sheets.filter(function(a){return!a.hasAttribute(n)}),b=this.templateContent();if(b){var c="";if(a.forEach(function(a){c+=e(a)+"\n"}),c){var f=d(c,this.ownerDocument);b.insertBefore(f,b.firstChild)}}},findNodes:function(a,b){var c=this.querySelectorAll(a).array(),d=this.templateContent();if(d){var e=d.querySelectorAll(a).array();c=c.concat(e)}return b?c.filter(b):c},installGlobalStyles:function(){var a=this.styleForScope(m);c(a,document.head)},cssTextForScope:function(a){var b="",c="["+n+"="+a+"]",d=function(a){return f(a,c)},g=this.sheets.filter(d);g.forEach(function(a){b+=e(a)+"\n\n"});var h=this.styles.filter(d);return h.forEach(function(a){b+=a.textContent+"\n\n"}),b},styleForScope:function(a){var b=this.cssTextForScope(a);return this.cssTextToScopeStyle(b,a)},cssTextToScopeStyle:function(a,b){if(a){var c=d(a);return c.setAttribute(h,this.getAttribute("name")+"-"+b),c}}},p=HTMLElement.prototype,q=p.matches||p.matchesSelector||p.webkitMatchesSelector||p.mozMatchesSelector;a.api.declaration.styles=o,a.applyStyleToScope=c}(Polymer),function(a){var b=(window.WebComponents?WebComponents.flags.log:{},a.api.instance.events),c=b.EVENT_PREFIX,d={};["webkitAnimationStart","webkitAnimationEnd","webkitTransitionEnd","DOMFocusOut","DOMFocusIn","DOMMouseScroll"].forEach(function(a){d[a.toLowerCase()]=a});var e={parseHostEvents:function(){var a=this.prototype.eventDelegates;this.addAttributeDelegates(a)},addAttributeDelegates:function(a){for(var b,c=0;b=this.attributes[c];c++)this.hasEventPrefix(b.name)&&(a[this.removeEventPrefix(b.name)]=b.value.replace("{{","").replace("}}","").trim())},hasEventPrefix:function(a){return a&&"o"===a[0]&&"n"===a[1]&&"-"===a[2]},removeEventPrefix:function(a){return a.slice(f)},findController:function(a){for(;a.parentNode;){if(a.eventController)return a.eventController;a=a.parentNode}return a.host},getEventHandler:function(a,b,c){var d=this;return function(e){a&&a.PolymerBase||(a=d.findController(b));var f=[e,e.detail,e.currentTarget];a.dispatchMethod(a,c,f)}},prepareEventBinding:function(a,b){if(this.hasEventPrefix(b)){var c=this.removeEventPrefix(b);c=d[c]||c;var e=this;return function(b,d,f){function g(){return"{{ "+a+" }}"}var h=e.getEventHandler(void 0,d,a);return PolymerGestures.addEventListener(d,c,h),f?void 0:{open:g,discardChanges:g,close:function(){PolymerGestures.removeEventListener(d,c,h)}}}}}},f=c.length;a.api.declaration.events=e}(Polymer),function(a){var b=["attribute"],c={inferObservers:function(a){var b,c=a.observe;for(var d in a)"Changed"===d.slice(-7)&&(b=d.slice(0,-7),this.canObserveProperty(b)&&(c||(c=a.observe={}),c[b]=c[b]||d))},canObserveProperty:function(a){return b.indexOf(a)<0},explodeObservers:function(a){var b=a.observe;if(b){var c={};for(var d in b)for(var e,f=d.split(" "),g=0;e=f[g];g++)c[e]=b[d];a.observe=c}},optimizePropertyMaps:function(a){if(a.observe){var b=a._observeNames=[];for(var c in a.observe)for(var d,e=c.split(" "),f=0;d=e[f];f++)b.push(d)}if(a.publish){var b=a._publishNames=[];for(var c in a.publish)b.push(c)}if(a.computed){var b=a._computedNames=[];for(var c in a.computed)b.push(c)}},publishProperties:function(a,b){var c=a.publish;c&&(this.requireProperties(c,a,b),this.filterInvalidAccessorNames(c),a._publishLC=this.lowerCaseMap(c));var d=a.computed;d&&this.filterInvalidAccessorNames(d)},filterInvalidAccessorNames:function(a){for(var b in a)this.propertyNameBlacklist[b]&&(console.warn('Cannot define property "'+b+'" for element "'+this.name+'" because it has the same name as an HTMLElement property, and not all browsers support overriding that. Consider giving it a different name.'),delete a[b])},requireProperties:function(a,b){b.reflect=b.reflect||{};for(var c in a){var d=a[c];d&&void 0!==d.reflect&&(b.reflect[c]=Boolean(d.reflect),d=d.value),void 0!==d&&(b[c]=d)}},lowerCaseMap:function(a){var b={};for(var c in a)b[c.toLowerCase()]=c;return b},createPropertyAccessor:function(a,b){var c=this.prototype,d=a+"_",e=a+"Observable_";c[d]=c[a],Object.defineProperty(c,a,{get:function(){var a=this[e];return a&&a.deliver(),this[d]},set:function(c){if(b)return this[d];var f=this[e];if(f)return void f.setValue(c);var g=this[d];return this[d]=c,this.emitPropertyChangeRecord(a,c,g),c},configurable:!0})},createPropertyAccessors:function(a){var b=a._computedNames;if(b&&b.length)for(var c,d=0,e=b.length;e>d&&(c=b[d]);d++)this.createPropertyAccessor(c,!0);var b=a._publishNames;if(b&&b.length)for(var c,d=0,e=b.length;e>d&&(c=b[d]);d++)a.computed&&a.computed[c]||this.createPropertyAccessor(c)},propertyNameBlacklist:{children:1,"class":1,id:1,hidden:1,style:1,title:1}};a.api.declaration.properties=c}(Polymer),function(a){var b="attributes",c=/\s|,/,d={inheritAttributesObjects:function(a){this.inheritObject(a,"publishLC"),this.inheritObject(a,"_instanceAttributes")},publishAttributes:function(a){var d=this.getAttribute(b);if(d)for(var e,f=a.publish||(a.publish={}),g=d.split(c),h=0,i=g.length;i>h;h++)e=g[h].trim(),e&&void 0===f[e]&&(f[e]=void 0)},accumulateInstanceAttributes:function(){for(var a,b=this.prototype._instanceAttributes,c=this.attributes,d=0,e=c.length;e>d&&(a=c[d]);d++)this.isInstanceAttribute(a.name)&&(b[a.name]=a.value)},isInstanceAttribute:function(a){return!this.blackList[a]&&"on-"!==a.slice(0,3)},blackList:{name:1,"extends":1,constructor:1,noscript:1,assetpath:1,"cache-csstext":1}};d.blackList[b]=1,a.api.declaration.attributes=d}(Polymer),function(a){var b=a.api.declaration.events,c=new PolymerExpressions,d=c.prepareBinding;c.prepareBinding=function(a,e,f){return b.prepareEventBinding(a,e,f)||d.call(c,a,e,f)};var e={syntax:c,fetchTemplate:function(){return this.querySelector("template")},templateContent:function(){var a=this.fetchTemplate();return a&&a.content},installBindingDelegate:function(a){a&&(a.bindingDelegate=this.syntax)}};a.api.declaration.mdv=e}(Polymer),function(a){function b(a){if(!Object.__proto__){var b=Object.getPrototypeOf(a);a.__proto__=b,d(b)&&(b.__proto__=Object.getPrototypeOf(b))}}var c=a.api,d=a.isBase,e=a.extend,f=window.ShadowDOMPolyfill,g={register:function(a,b){this.buildPrototype(a,b),this.registerPrototype(a,b),this.publishConstructor()},buildPrototype:function(b,c){var d=a.getRegisteredPrototype(b),e=this.generateBasePrototype(c);this.desugarBeforeChaining(d,e),this.prototype=this.chainPrototypes(d,e),this.desugarAfterChaining(b,c)},desugarBeforeChaining:function(a,b){a.element=this,this.publishAttributes(a,b),this.publishProperties(a,b),this.inferObservers(a),this.explodeObservers(a)},chainPrototypes:function(a,c){this.inheritMetaData(a,c);var d=this.chainObject(a,c);return b(d),d},inheritMetaData:function(a,b){this.inheritObject("observe",a,b),this.inheritObject("publish",a,b),this.inheritObject("reflect",a,b),this.inheritObject("_publishLC",a,b),this.inheritObject("_instanceAttributes",a,b),this.inheritObject("eventDelegates",a,b)},desugarAfterChaining:function(a,b){this.optimizePropertyMaps(this.prototype),this.createPropertyAccessors(this.prototype),this.installBindingDelegate(this.fetchTemplate()),this.installSheets(),this.resolveElementPaths(this),this.accumulateInstanceAttributes(),this.parseHostEvents(),this.addResolvePathApi(),f&&WebComponents.ShadowCSS.shimStyling(this.templateContent(),a,b),this.prototype.registerCallback&&this.prototype.registerCallback(this)},publishConstructor:function(){var a=this.getAttribute("constructor");a&&(window[a]=this.ctor)},generateBasePrototype:function(a){var b=this.findBasePrototype(a);if(!b){var b=HTMLElement.getPrototypeForTag(a);b=this.ensureBaseApi(b),h[a]=b}return b},findBasePrototype:function(a){return h[a]},ensureBaseApi:function(a){if(a.PolymerBase)return a;var b=Object.create(a);return c.publish(c.instance,b),this.mixinMethod(b,a,c.instance.mdv,"bind"),b},mixinMethod:function(a,b,c,d){var e=function(a){return b[d].apply(this,a)};a[d]=function(){return this.mixinSuper=e,c[d].apply(this,arguments)}},inheritObject:function(a,b,c){var d=b[a]||{};b[a]=this.chainObject(d,c[a])},registerPrototype:function(a,b){var c={prototype:this.prototype},d=this.findTypeExtension(b);d&&(c["extends"]=d),HTMLElement.register(a,this.prototype),this.ctor=document.registerElement(a,c)},findTypeExtension:function(a){if(a&&a.indexOf("-")<0)return a;var b=this.findBasePrototype(a);return b.element?this.findTypeExtension(b.element["extends"]):void 0}},h={};g.chainObject=Object.__proto__?function(a,b){return a&&b&&a!==b&&(a.__proto__=b),a}:function(a,b){if(a&&b&&a!==b){var c=Object.create(b);a=e(c,a)}return a},c.declaration.prototype=g}(Polymer),function(a){function b(a){return document.contains(a)?j:i}function c(){return i.length?i[0]:j[0]}function d(a){f.waitToReady=!0,Polymer.endOfMicrotask(function(){HTMLImports.whenReady(function(){f.addReadyCallback(a),f.waitToReady=!1,f.check()})})}function e(a){if(void 0===a)return void f.ready();var b=setTimeout(function(){f.ready()},a);Polymer.whenReady(function(){clearTimeout(b)})}var f={wait:function(a){a.__queue||(a.__queue={},g.push(a))},enqueue:function(a,c,d){var e=a.__queue&&!a.__queue.check;return e&&(b(a).push(a),a.__queue.check=c,a.__queue.go=d),0!==this.indexOf(a)},indexOf:function(a){var c=b(a).indexOf(a);return c>=0&&document.contains(a)&&(c+=HTMLImports.useNative||HTMLImports.ready?i.length:1e9),c},go:function(a){var b=this.remove(a);b&&(a.__queue.flushable=!0,this.addToFlushQueue(b),this.check())},remove:function(a){var c=this.indexOf(a);if(0===c)return b(a).shift()},check:function(){var a=this.nextElement();return a&&a.__queue.check.call(a),this.canReady()?(this.ready(),!0):void 0},nextElement:function(){return c()},canReady:function(){return!this.waitToReady&&this.isEmpty()},isEmpty:function(){for(var a,b=0,c=g.length;c>b&&(a=g[b]);b++)if(a.__queue&&!a.__queue.flushable)return;return!0},addToFlushQueue:function(a){h.push(a)},flush:function(){if(!this.flushing){this.flushing=!0;for(var a;h.length;)a=h.shift(),a.__queue.go.call(a),a.__queue=null;this.flushing=!1}},ready:function(){var a=CustomElements.ready;CustomElements.ready=!1,this.flush(),CustomElements.useNative||CustomElements.upgradeDocumentTree(document),CustomElements.ready=a,Polymer.flush(),requestAnimationFrame(this.flushReadyCallbacks)},addReadyCallback:function(a){a&&k.push(a)},flushReadyCallbacks:function(){if(k)for(var a;k.length;)(a=k.shift())()},waitingFor:function(){for(var a,b=[],c=0,d=g.length;d>c&&(a=g[c]);c++)a.__queue&&!a.__queue.flushable&&b.push(a);return b},waitToReady:!0},g=[],h=[],i=[],j=[],k=[];a.elements=g,a.waitingFor=f.waitingFor.bind(f),a.forceReady=e,a.queue=f,a.whenReady=a.whenPolymerReady=d}(Polymer),function(a){function b(a){return Boolean(HTMLElement.getPrototypeForTag(a))}function c(a){return a&&a.indexOf("-")>=0}var d=a.extend,e=a.api,f=a.queue,g=a.whenReady,h=a.getRegisteredPrototype,i=a.waitingForPrototype,j=d(Object.create(HTMLElement.prototype),{createdCallback:function(){this.getAttribute("name")&&this.init()},init:function(){this.name=this.getAttribute("name"),this["extends"]=this.getAttribute("extends"),f.wait(this),this.loadResources(),this.registerWhenReady()},registerWhenReady:function(){this.registered||this.waitingForPrototype(this.name)||this.waitingForQueue()||this.waitingForResources()||f.go(this)},_register:function(){c(this["extends"])&&!b(this["extends"])&&console.warn("%s is attempting to extend %s, an unregistered element or one that was not registered with Polymer.",this.name,this["extends"]),this.register(this.name,this["extends"]),this.registered=!0},waitingForPrototype:function(a){return h(a)?void 0:(i(a,this),this.handleNoScript(a),!0)},handleNoScript:function(a){this.hasAttribute("noscript")&&!this.noscript&&(this.noscript=!0,Polymer(a))},waitingForResources:function(){return this._needsResources},waitingForQueue:function(){return f.enqueue(this,this.registerWhenReady,this._register)},loadResources:function(){this._needsResources=!0,this.loadStyles(function(){this._needsResources=!1,this.registerWhenReady()}.bind(this))}});e.publish(e.declaration,j),g(function(){document.body.removeAttribute("unresolved"),document.dispatchEvent(new CustomEvent("polymer-ready",{bubbles:!0}))}),document.registerElement("polymer-element",{prototype:j})}(Polymer),function(a){function b(a,b){a?(document.head.appendChild(a),d(b)):b&&b()}function c(a,c){if(a&&a.length){for(var d,e,f=document.createDocumentFragment(),g=0,h=a.length;h>g&&(d=a[g]);g++)e=document.createElement("link"),e.rel="import",e.href=d,f.appendChild(e);b(f,c)}else c&&c()}var d=a.whenReady;a["import"]=c,a.importElements=b}(Polymer),function(){var a=document.createElement("polymer-element");a.setAttribute("name","auto-binding"),a.setAttribute("extends","template"),a.init(),Polymer("auto-binding",{createdCallback:function(){this.syntax=this.bindingDelegate=this.makeSyntax(),Polymer.whenPolymerReady(function(){this.model=this,this.setAttribute("bind",""),this.async(function(){this.marshalNodeReferences(this.parentNode),this.fire("template-bound")})}.bind(this))},makeSyntax:function(){var a=Object.create(Polymer.api.declaration.events),b=this;a.findController=function(){return b.model};var c=new PolymerExpressions,d=c.prepareBinding;return c.prepareBinding=function(b,e,f){return a.prepareEventBinding(b,e,f)||d.call(c,b,e,f)},c}})}(); - - - - - +return pickBy("isBefore",args)};moment.max=function(){var args=[].slice.call(arguments,0);return pickBy("isAfter",args)};moment.utc=function(input,format,locale,strict){var c;if(typeof locale==="boolean"){strict=locale;locale=undefined}c={};c._isAMomentObject=true;c._useUTC=true;c._isUTC=true;c._l=locale;c._i=input;c._f=format;c._strict=strict;c._pf=defaultParsingFlags();return makeMoment(c).utc()};moment.unix=function(input){return moment(input*1e3)};moment.duration=function(input,key){var duration=input,match=null,sign,ret,parseIso,diffRes;if(moment.isDuration(input)){duration={ms:input._milliseconds,d:input._days,M:input._months}}else if(typeof input==="number"){duration={};if(key){duration[key]=input}else{duration.milliseconds=input}}else if(!!(match=aspNetTimeSpanJsonRegex.exec(input))){sign=match[1]==="-"?-1:1;duration={y:0,d:toInt(match[DATE])*sign,h:toInt(match[HOUR])*sign,m:toInt(match[MINUTE])*sign,s:toInt(match[SECOND])*sign,ms:toInt(match[MILLISECOND])*sign}}else if(!!(match=isoDurationRegex.exec(input))){sign=match[1]==="-"?-1:1;parseIso=function(inp){var res=inp&&parseFloat(inp.replace(",","."));return(isNaN(res)?0:res)*sign};duration={y:parseIso(match[2]),M:parseIso(match[3]),d:parseIso(match[4]),h:parseIso(match[5]),m:parseIso(match[6]),s:parseIso(match[7]),w:parseIso(match[8])}}else if(duration==null){duration={}}else if(typeof duration==="object"&&("from"in duration||"to"in duration)){diffRes=momentsDifference(moment(duration.from),moment(duration.to));duration={};duration.ms=diffRes.milliseconds;duration.M=diffRes.months}ret=new Duration(duration);if(moment.isDuration(input)&&hasOwnProp(input,"_locale")){ret._locale=input._locale}return ret};moment.version=VERSION;moment.defaultFormat=isoFormat;moment.ISO_8601=function(){};moment.momentProperties=momentProperties;moment.updateOffset=function(){};moment.relativeTimeThreshold=function(threshold,limit){if(relativeTimeThresholds[threshold]===undefined){return false}if(limit===undefined){return relativeTimeThresholds[threshold]}relativeTimeThresholds[threshold]=limit;return true};moment.lang=deprecate("moment.lang is deprecated. Use moment.locale instead.",function(key,value){return moment.locale(key,value)});moment.locale=function(key,values){var data;if(key){if(typeof values!=="undefined"){data=moment.defineLocale(key,values)}else{data=moment.localeData(key)}if(data){moment.duration._locale=moment._locale=data}}return moment._locale._abbr};moment.defineLocale=function(name,values){if(values!==null){values.abbr=name;if(!locales[name]){locales[name]=new Locale}locales[name].set(values);moment.locale(name);return locales[name]}else{delete locales[name];return null}};moment.langData=deprecate("moment.langData is deprecated. Use moment.localeData instead.",function(key){return moment.localeData(key)});moment.localeData=function(key){var locale;if(key&&key._locale&&key._locale._abbr){key=key._locale._abbr}if(!key){return moment._locale}if(!isArray(key)){locale=loadLocale(key);if(locale){return locale}key=[key]}return chooseLocale(key)};moment.isMoment=function(obj){return obj instanceof Moment||obj!=null&&hasOwnProp(obj,"_isAMomentObject")};moment.isDuration=function(obj){return obj instanceof Duration};for(i=lists.length-1;i>=0;--i){makeList(lists[i])}moment.normalizeUnits=function(units){return normalizeUnits(units)};moment.invalid=function(flags){var m=moment.utc(NaN);if(flags!=null){extend(m._pf,flags)}else{m._pf.userInvalidated=true}return m};moment.parseZone=function(){return moment.apply(null,arguments).parseZone()};moment.parseTwoDigitYear=function(input){return toInt(input)+(toInt(input)>68?1900:2e3)};moment.isDate=isDate;extend(moment.fn=Moment.prototype,{clone:function(){return moment(this)},valueOf:function(){return+this._d-(this._offset||0)*6e4},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var m=moment(this).utc();if(00}return false},parsingFlags:function(){return extend({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(keepLocalTime){return this.utcOffset(0,keepLocalTime)},local:function(keepLocalTime){if(this._isUTC){this.utcOffset(0,keepLocalTime);this._isUTC=false;if(keepLocalTime){this.subtract(this._dateUtcOffset(),"m")}}return this},format:function(inputString){var output=formatMoment(this,inputString||moment.defaultFormat);return this.localeData().postformat(output)},add:createAdder(1,"add"),subtract:createAdder(-1,"subtract"),diff:function(input,units,asFloat){var that=makeAs(input,this),zoneDiff=(that.utcOffset()-this.utcOffset())*6e4,anchor,diff,output,daysAdjust;units=normalizeUnits(units);if(units==="year"||units==="month"||units==="quarter"){output=monthDiff(this,that);if(units==="quarter"){output=output/3}else if(units==="year"){output=output/12}}else{diff=this-that;output=units==="second"?diff/1e3:units==="minute"?diff/6e4:units==="hour"?diff/36e5:units==="day"?(diff-zoneDiff)/864e5:units==="week"?(diff-zoneDiff)/6048e5:diff}return asFloat?output:absRound(output)},from:function(time,withoutSuffix){return moment.duration({to:this,from:time}).locale(this.locale()).humanize(!withoutSuffix)},fromNow:function(withoutSuffix){return this.from(moment(),withoutSuffix)},calendar:function(time){var now=time||moment(),sod=makeAs(now,this).startOf("day"),diff=this.diff(sod,"days",true),format=diff<-6?"sameElse":diff<-1?"lastWeek":diff<0?"lastDay":diff<1?"sameDay":diff<2?"nextDay":diff<7?"nextWeek":"sameElse";return this.format(this.localeData().calendar(format,this,moment(now)))},isLeapYear:function(){return isLeapYear(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(input){var day=this._isUTC?this._d.getUTCDay():this._d.getDay();if(input!=null){input=parseWeekday(input,this.localeData());return this.add(input-day,"d")}else{return day}},month:makeAccessor("Month",true),startOf:function(units){units=normalizeUnits(units);switch(units){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}if(units==="week"){this.weekday(0)}else if(units==="isoWeek"){this.isoWeekday(1)}if(units==="quarter"){this.month(Math.floor(this.month()/3)*3)}return this},endOf:function(units){units=normalizeUnits(units);if(units===undefined||units==="millisecond"){return this}return this.startOf(units).add(1,units==="isoWeek"?"week":units).subtract(1,"ms")},isAfter:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this>+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return inputMs<+this.clone().startOf(units)}},isBefore:function(input,units){var inputMs;units=normalizeUnits(typeof units!=="undefined"?units:"millisecond");if(units==="millisecond"){input=moment.isMoment(input)?input:moment(input);return+this<+input}else{inputMs=moment.isMoment(input)?+input:+moment(input);return+this.clone().endOf(units)this?this:other}),zone:deprecate("moment().zone is deprecated, use moment().utcOffset instead. "+"https://github.com/moment/moment/issues/1779",function(input,keepLocalTime){if(input!=null){if(typeof input!=="string"){input=-input}this.utcOffset(input,keepLocalTime);return this}else{return-this.utcOffset()}}),utcOffset:function(input,keepLocalTime){var offset=this._offset||0,localAdjust;if(input!=null){if(typeof input==="string"){input=utcOffsetFromString(input)}if(Math.abs(input)<16){input=input*60}if(!this._isUTC&&keepLocalTime){localAdjust=this._dateUtcOffset()}this._offset=input;this._isUTC=true;if(localAdjust!=null){this.add(localAdjust,"m")}if(offset!==input){if(!keepLocalTime||this._changeInProgress){addOrSubtractDurationFromMoment(this,moment.duration(input-offset,"m"),1,false)}else if(!this._changeInProgress){this._changeInProgress=true;moment.updateOffset(this,true);this._changeInProgress=null}}return this}else{return this._isUTC?offset:this._dateUtcOffset()}},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&this._offset===0},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){if(this._tzm){this.utcOffset(this._tzm)}else if(typeof this._i==="string"){this.utcOffset(utcOffsetFromString(this._i))}return this},hasAlignedHourOffset:function(input){if(!input){input=0}else{input=moment(input).utcOffset()}return(this.utcOffset()-input)%60===0},daysInMonth:function(){return daysInMonth(this.year(),this.month())},dayOfYear:function(input){var dayOfYear=round((moment(this).startOf("day")-moment(this).startOf("year"))/864e5)+1;return input==null?dayOfYear:this.add(input-dayOfYear,"d")},quarter:function(input){return input==null?Math.ceil((this.month()+1)/3):this.month((input-1)*3+this.month()%3)},weekYear:function(input){var year=weekOfYear(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return input==null?year:this.add(input-year,"y")},isoWeekYear:function(input){var year=weekOfYear(this,1,4).year;return input==null?year:this.add(input-year,"y")},week:function(input){var week=this.localeData().week(this);return input==null?week:this.add((input-week)*7,"d")},isoWeek:function(input){var week=weekOfYear(this,1,4).week;return input==null?week:this.add((input-week)*7,"d")},weekday:function(input){var weekday=(this.day()+7-this.localeData()._week.dow)%7;return input==null?weekday:this.add(input-weekday,"d")},isoWeekday:function(input){return input==null?this.day()||7:this.day(this.day()%7?input:input-7)},isoWeeksInYear:function(){return weeksInYear(this.year(),1,4)},weeksInYear:function(){var weekInfo=this.localeData()._week;return weeksInYear(this.year(),weekInfo.dow,weekInfo.doy)},get:function(units){units=normalizeUnits(units);return this[units]()},set:function(units,value){var unit;if(typeof units==="object"){for(unit in units){this.set(unit,units[unit])}}else{units=normalizeUnits(units);if(typeof this[units]==="function"){this[units](value)}}return this},locale:function(key){var newLocaleData;if(key===undefined){return this._locale._abbr}else{newLocaleData=moment.localeData(key);if(newLocaleData!=null){this._locale=newLocaleData}return this}},lang:deprecate("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(key){if(key===undefined){return this.localeData()}else{return this.locale(key)}}),localeData:function(){return this._locale},_dateUtcOffset:function(){return-Math.round(this._d.getTimezoneOffset()/15)*15}});function rawMonthSetter(mom,value){var dayOfMonth;if(typeof value==="string"){value=mom.localeData().monthsParse(value);if(typeof value!=="number"){return mom}}dayOfMonth=Math.min(mom.date(),daysInMonth(mom.year(),value));mom._d["set"+(mom._isUTC?"UTC":"")+"Month"](value,dayOfMonth);return mom}function rawGetter(mom,unit){return mom._d["get"+(mom._isUTC?"UTC":"")+unit]()}function rawSetter(mom,unit,value){if(unit==="Month"){return rawMonthSetter(mom,value)}else{return mom._d["set"+(mom._isUTC?"UTC":"")+unit](value)}}function makeAccessor(unit,keepTime){return function(value){if(value!=null){rawSetter(this,unit,value);moment.updateOffset(this,keepTime);return this}else{return rawGetter(this,unit)}}}moment.fn.millisecond=moment.fn.milliseconds=makeAccessor("Milliseconds",false);moment.fn.second=moment.fn.seconds=makeAccessor("Seconds",false);moment.fn.minute=moment.fn.minutes=makeAccessor("Minutes",false);moment.fn.hour=moment.fn.hours=makeAccessor("Hours",true);moment.fn.date=makeAccessor("Date",true);moment.fn.dates=deprecate("dates accessor is deprecated. Use date instead.",makeAccessor("Date",true));moment.fn.year=makeAccessor("FullYear",true);moment.fn.years=deprecate("years accessor is deprecated. Use year instead.",makeAccessor("FullYear",true));moment.fn.days=moment.fn.day;moment.fn.months=moment.fn.month;moment.fn.weeks=moment.fn.week;moment.fn.isoWeeks=moment.fn.isoWeek;moment.fn.quarters=moment.fn.quarter;moment.fn.toJSON=moment.fn.toISOString;moment.fn.isUTC=moment.fn.isUtc;function daysToYears(days){return days*400/146097}function yearsToDays(years){return years*146097/400}extend(moment.duration.fn=Duration.prototype,{_bubble:function(){var milliseconds=this._milliseconds,days=this._days,months=this._months,data=this._data,seconds,minutes,hours,years=0;data.milliseconds=milliseconds%1e3;seconds=absRound(milliseconds/1e3);data.seconds=seconds%60;minutes=absRound(seconds/60);data.minutes=minutes%60;hours=absRound(minutes/60);data.hours=hours%24;days+=absRound(hours/24);years=absRound(daysToYears(days));days-=absRound(yearsToDays(years));months+=absRound(days/30);days%=30;years+=absRound(months/12);months%=12;data.days=days;data.months=months;data.years=years},abs:function(){this._milliseconds=Math.abs(this._milliseconds);this._days=Math.abs(this._days);this._months=Math.abs(this._months);this._data.milliseconds=Math.abs(this._data.milliseconds);this._data.seconds=Math.abs(this._data.seconds);this._data.minutes=Math.abs(this._data.minutes);this._data.hours=Math.abs(this._data.hours);this._data.months=Math.abs(this._data.months);this._data.years=Math.abs(this._data.years);return this},weeks:function(){return absRound(this.days()/7)},valueOf:function(){return this._milliseconds+this._days*864e5+this._months%12*2592e6+toInt(this._months/12)*31536e6},humanize:function(withSuffix){var output=relativeTime(this,!withSuffix,this.localeData());if(withSuffix){output=this.localeData().pastFuture(+this,output)}return this.localeData().postformat(output)},add:function(input,val){var dur=moment.duration(input,val);this._milliseconds+=dur._milliseconds;this._days+=dur._days;this._months+=dur._months;this._bubble();return this},subtract:function(input,val){var dur=moment.duration(input,val);this._milliseconds-=dur._milliseconds;this._days-=dur._days;this._months-=dur._months;this._bubble();return this},get:function(units){units=normalizeUnits(units);return this[units.toLowerCase()+"s"]()},as:function(units){var days,months;units=normalizeUnits(units);if(units==="month"||units==="year"){days=this._days+this._milliseconds/864e5;months=this._months+daysToYears(days)*12;return units==="month"?months:months/12}else{days=this._days+Math.round(yearsToDays(this._months/12));switch(units){case"week":return days/7+this._milliseconds/6048e5;case"day":return days+this._milliseconds/864e5;case"hour":return days*24+this._milliseconds/36e5;case"minute":return days*24*60+this._milliseconds/6e4;case"second":return days*24*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(days*24*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+units)}}},lang:moment.fn.lang,locale:moment.fn.locale,toIsoString:deprecate("toIsoString() is deprecated. Please use toISOString() instead "+"(notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var years=Math.abs(this.years()),months=Math.abs(this.months()),days=Math.abs(this.days()),hours=Math.abs(this.hours()),minutes=Math.abs(this.minutes()),seconds=Math.abs(this.seconds()+this.milliseconds()/1e3);if(!this.asSeconds()){return"P0D"}return(this.asSeconds()<0?"-":"")+"P"+(years?years+"Y":"")+(months?months+"M":"")+(days?days+"D":"")+(hours||minutes||seconds?"T":"")+(hours?hours+"H":"")+(minutes?minutes+"M":"")+(seconds?seconds+"S":"")},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}});moment.duration.fn.toString=moment.duration.fn.toISOString;function makeDurationGetter(name){moment.duration.fn[name]=function(){return this._data[name]}}for(i in unitMillisecondFactors){if(hasOwnProp(unitMillisecondFactors,i)){makeDurationGetter(i.toLowerCase())}}moment.duration.fn.asMilliseconds=function(){return this.as("ms")};moment.duration.fn.asSeconds=function(){return this.as("s")};moment.duration.fn.asMinutes=function(){return this.as("m")};moment.duration.fn.asHours=function(){return this.as("h")};moment.duration.fn.asDays=function(){return this.as("d")};moment.duration.fn.asWeeks=function(){return this.as("weeks")};moment.duration.fn.asMonths=function(){return this.as("M")};moment.duration.fn.asYears=function(){return this.as("y")};moment.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(number){var b=number%10,output=toInt(number%100/10)===1?"th":b===1?"st":b===2?"nd":b===3?"rd":"th";return number+output}});function makeGlobal(shouldDeprecate){if(typeof ender!=="undefined"){return}oldGlobalMoment=globalScope.moment;if(shouldDeprecate){globalScope.moment=deprecate("Accessing Moment through the global scope is "+"deprecated, and will be removed in an upcoming "+"release.",moment)}else{globalScope.moment=moment}}if(hasModule){module.exports=moment}else if(typeof define==="function"&&define.amd){define(function(require,exports,module){if(module.config&&module.config()&&module.config().noGlobal===true){globalScope.moment=oldGlobalMoment}return moment});makeGlobal(true)}else{makeGlobal()}}).call(this); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/polymer/layouts/partial-states.html b/homeassistant/components/frontend/www_static/polymer/layouts/partial-states.html index 11c29c05a55..079044f3fea 100644 --- a/homeassistant/components/frontend/www_static/polymer/layouts/partial-states.html +++ b/homeassistant/components/frontend/www_static/polymer/layouts/partial-states.html @@ -143,8 +143,9 @@ return !(state.domain in uiConstants.STATE_FILTERS); }); } - - this.states = states.toArray(); + + this.states = states.toArray().filter( + function (el) {return !el.attributes.hidden}); }, handleRefreshClick: function() { diff --git a/scripts/build_frontend b/scripts/build_frontend index 406aed45f46..434f4b24d98 100755 --- a/scripts/build_frontend +++ b/scripts/build_frontend @@ -34,5 +34,5 @@ if [ $(command -v md5) ]; then elif [ $(command -v md5sum) ]; then echo 'VERSION = "'`md5sum www_static/frontend.html | cut -c-32`'"' >> version.py else - echo 'Could not find a MD5 utility' + echo 'Could not find an MD5 utility' fi From b20424261cb96b54f308e0a7f426e9d7fc925337 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 15 Apr 2015 02:05:34 -0400 Subject: [PATCH 36/59] 1) Performed many pylint and flake8 fixes to clean up isy994 integration and hidden entities addition. 2) Added necessary code to allow groups to also be hidden. 3) Made most of the weather data from the isy994 component be hidden by default. --- homeassistant/__init__.py | 4 +- homeassistant/bootstrap.py | 2 +- homeassistant/components/group.py | 28 +++++++++++++- homeassistant/components/isy994.py | 47 +++++++++++++++-------- homeassistant/components/sensor/isy994.py | 28 +++++++++++--- homeassistant/helpers/entity.py | 6 +-- 6 files changed, 85 insertions(+), 30 deletions(-) diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 80916011d55..1f8f88d3723 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -625,8 +625,8 @@ class StateMachine(object): # components that don't use the Entity base class for their entities. # The sun component is an example of this. The Entity class cannot be # imported cleanly, so assume the state is shown. This means that for - # visibility to be supported, the state must originate from a class that - # uses the base class Entity or it must manually put the hidden + # visibility to be supported, the state must originate from a class + # that uses the base class Entity or it must manually put the hidden # attribute in its attributes dictionary. if ATTR_HIDDEN not in attributes: attributes[ATTR_HIDDEN] = False diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index c0b8fe09af3..e31a4bb66c7 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -208,7 +208,7 @@ def process_ha_core_config(hass, config): if key in config: setattr(hass.config, attr, config[key]) - Entity._visibility.update(config.get('visibility', [{}])[0]) + Entity.visibility.update(config.get('visibility', [{}])[0]) if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 4c5e6adb2c6..badd5ac66fe 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -7,10 +7,11 @@ Provides functionality to group devices that can be turned on or off. import homeassistant as ha from homeassistant.helpers import generate_entity_id +from homeassistant.helpers.entity import Entity import homeassistant.util as util from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF, - STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN) + STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN, ATTR_HIDDEN) DOMAIN = "group" DEPENDENCIES = [] @@ -112,6 +113,10 @@ def setup(hass, config): class Group(object): """ Tracks a group of entity ids. """ + + visibility = Entity.visibility + _hidden = False + def __init__(self, hass, name, entity_ids=None, user_defined=True): self.hass = hass self.name = name @@ -138,7 +143,8 @@ class Group(object): return { ATTR_ENTITY_ID: self.tracking, ATTR_AUTO: not self.user_defined, - ATTR_FRIENDLY_NAME: self.name + ATTR_FRIENDLY_NAME: self.name, + ATTR_HIDDEN: self.hidden } def update_tracked_entity_ids(self, entity_ids): @@ -213,6 +219,24 @@ class Group(object): self.hass.states.set( self.entity_id, group_off, self.state_attr) + @property + def hidden(self): + """ + Returns the official decision of whether the entity should be hidden. + Any value set by the user in the configuration file will overwrite + whatever the component sets for visibility. + """ + if self.entity_id is not None and \ + self.entity_id.lower() in self.visibility: + return self.visibility[self.entity_id.lower()] == 'hide' + else: + return self._hidden + + @hidden.setter + def hidden(self, val): + """ Sets the suggestion for visibility. """ + self._hidden = bool(val) + def setup_group(hass, name, entity_ids, user_defined=True): """ Sets up a group state that is the combined state of diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index d364007d12c..33d11c8ebbe 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -29,15 +29,19 @@ SENSOR_STRING = 'Sensor' HIDDEN_STRING = '{HIDE ME}' # setup logger -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +_LOGGER = logging.getLogger(__name__) def setup(hass, config): + """ + Setup isy994 component. + This will automatically import associated lights, switches, and sensors. + """ + # pylint: disable=global-statement # check for required values in configuration file if not validate_config(config, {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, - logger): + _LOGGER): return False # pull and parse standard configuration @@ -52,7 +56,7 @@ def setup(hass, config): addr = addr.replace('https://', '') https = True else: - logger.error('isy994 host value in configuration file is invalid.') + _LOGGER.error('isy994 host value in configuration file is invalid.') return False port = host.port addr = addr.replace(':{}'.format(port), '') @@ -65,7 +69,7 @@ def setup(hass, config): # connect to ISY controller global ISY - ISY = PyISY.ISY(addr, port, user, password, use_https=https, log=logger) + ISY = PyISY.ISY(addr, port, user, password, use_https=https, log=_LOGGER) if not ISY.connected: return False @@ -91,6 +95,7 @@ class ISYDeviceABC(ToggleEntity): _states = [] _dtype = None _domain = None + _name = None def __init__(self, node): # setup properties @@ -98,34 +103,39 @@ class ISYDeviceABC(ToggleEntity): self.hidden = HIDDEN_STRING in self.raw_name # track changes - self._changeHandler = self.node.status. \ - subscribe('changed', self.onUpdate) + self._change_handler = self.node.status. \ + subscribe('changed', self.on_update) def __del__(self): """ cleanup subscriptions because it is the right thing to do. """ - self._changeHandler.unsubscribe() + self._change_handler.unsubscribe() @property def domain(self): + """ Returns the domain of the entity. """ return self._domain @property def dtype(self): + """ Returns the data type of the entity (binary or analog). """ if self._dtype in ['analog', 'binary']: return self._dtype - return 'binary' if self._units is None else 'analog' + return 'binary' if self.unit_of_measurement is None else 'analog' @property def should_poll(self): + """ Tells Home Assistant not to poll this entity. """ return False @property def value(self): """ returns the unclean value from the controller """ + # pylint: disable=protected-access return self.node.status._val @property def state_attributes(self): + """ Returns the state attributes for the node. """ attr = {ATTR_FRIENDLY_NAME: self.name} for name, prop in self._attrs.items(): attr[name] = getattr(self, prop) @@ -134,18 +144,18 @@ class ISYDeviceABC(ToggleEntity): @property def unique_id(self): """ Returns the id of this isy sensor """ + # pylint: disable=protected-access return self.node._id @property def raw_name(self): - try: - return str(self._name) - except AttributeError: - return str(self.node.name) + """ Returns the unclean node name. """ + return str(self._name) \ + if self._name is not None else str(self.node.name) @property def name(self): - """ Returns the name of the node if any. """ + """ Returns the cleaned name of the node. """ return self.raw_name.replace(HIDDEN_STRING, '').strip() def update(self): @@ -153,16 +163,18 @@ class ISYDeviceABC(ToggleEntity): # ISY objects are automatically updated by the ISY's event stream pass - def onUpdate(self, e): + def on_update(self, event): """ Handles the update received event. """ self.update_ha_state() @property def is_on(self): + """ Returns boolean response if the node is on. """ return bool(self.value) @property def is_open(self): + """ Returns boolean respons if the node is open. On = Open. """ return self.is_on @property @@ -178,17 +190,18 @@ class ISYDeviceABC(ToggleEntity): attrs = [kwargs.get(name) for name in self._onattrs] self.node.on(*attrs) else: - logger.error('ISY cannot turn on sensors.') + _LOGGER.error('ISY cannot turn on sensors.') def turn_off(self, **kwargs): """ turns the device off """ if self.domain is not 'sensor': self.node.off() else: - logger.error('ISY cannot turn off sensors.') + _LOGGER.error('ISY cannot turn off sensors.') @property def unit_of_measurement(self): + """ Returns the defined units of measurement or None. """ try: return self.node.units except AttributeError: diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index aa98c594910..9b58a574527 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -3,13 +3,29 @@ import logging # homeassistant imports -from homeassistant.components.isy994 import ISY, ISYDeviceABC, SENSOR_STRING +from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING, + HIDDEN_STRING) from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF) +DEFAULT_HIDDEN_WEATHER = ['Temperature_High', 'Temperature_Low', 'Feels_Like', + 'Temperature_Average', 'Pressure', 'Dew_Point', + 'Gust_Speed', 'Evapotranspiration', + 'Irrigation_Requirement', 'Water_Deficit_Yesterday', + 'Elevation', 'Average_Temperature_Tomorrow', + 'High_Temperature_Tomorrow', + 'Low_Temperature_Tomorrow', 'Humidity_Tomorrow', + 'Wind_Speed_Tomorrow', 'Gust_Speed_Tomorrow', + 'Rain_Tomorrow', 'Snow_Tomorrow', + 'Forecast_Average_Temperature', + 'Forecast_High_Temperature', + 'Forecast_Low_Temperature', 'Forecast_Humidity', + 'Forecast_Rain', 'Forecast_Snow'] + def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the isy994 platform. """ + # pylint: disable=protected-access logger = logging.getLogger(__name__) devs = [] # verify connection @@ -21,7 +37,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if ISY.climate is not None: for prop in ISY.climate._id2name: if prop is not None: - node = WeatherPseudoNode('ISY.weather.' + prop, prop, + prefix = HIDDEN_STRING if prop in DEFAULT_HIDDEN_WEATHER else '' + node = WeatherPseudoNode('ISY.weather.' + prop, prefix + prop, getattr(ISY.climate, prop), getattr(ISY.climate, prop + '_units')) devs.append(ISYSensorDevice(node)) @@ -42,7 +59,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # folder does not exist pass else: - for dtype, name, node_id in folder.children: + for _, _, node_id in folder.children: node = folder[node_id].leaf devs.append(ISYSensorDevice(node, states)) @@ -51,6 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class WeatherPseudoNode(object): """ This class allows weather variable to act as regular nodes. """ + # pylint: disable=too-few-public-methods def __init__(self, device_id, name, status, units=None): self._id = device_id @@ -64,6 +82,6 @@ class ISYSensorDevice(ISYDeviceABC): _domain = 'sensor' - def __init__(self, node, states=[]): + def __init__(self, node, states=None): super().__init__(node) - self._states = states + self._states = states or [] diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 3e559d39532..7d6ef65e1a7 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -83,7 +83,7 @@ class Entity(object): hass = None entity_id = None - _visibility = {} + visibility = {} def update_ha_state(self, force_refresh=False): """ @@ -138,8 +138,8 @@ class Entity(object): whatever the component sets for visibility. """ if self.entity_id is not None and \ - self.entity_id.lower() in self._visibility: - return self._visibility[self.entity_id.lower()] == 'hide' + self.entity_id.lower() in self.visibility: + return self.visibility[self.entity_id.lower()] == 'hide' else: return self._hidden From fb6b514c340edf1708f5fd2013783456eba49361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Correia?= Date: Wed, 15 Apr 2015 16:47:42 +0200 Subject: [PATCH 37/59] Adding simplistic support for Modbus sensor and switch (based on pymodbus) --- homeassistant/components/modbus.py | 99 +++++++++++++++++ homeassistant/components/sensor/modbus.py | 123 ++++++++++++++++++++++ homeassistant/components/switch/modbus.py | 109 +++++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 homeassistant/components/modbus.py create mode 100644 homeassistant/components/sensor/modbus.py create mode 100644 homeassistant/components/switch/modbus.py diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py new file mode 100644 index 00000000000..4cda6fcd20c --- /dev/null +++ b/homeassistant/components/modbus.py @@ -0,0 +1,99 @@ +__author__ = "Aurélien Correia" + +""" +components.modbus +~~~~~~~~~~~~~~~~~~~~~~~~~ +Modbus component, using pymodbus (python3 branch) + +typical declaration in configuration.yaml + +#Modbus TCP +modbus: + type: tcp + host: 127.0.0.1 + port: 2020 + +#Modbus RTU +modbus: + type: serial + method: rtu + port: /dev/ttyUSB0 + baudrate: 9600 + stopbits: 1 + bytesize: 8 + parity: N + +""" +import time +import logging + +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP +import homeassistant.loader as loader +from homeassistant.helpers import validate_config +import homeassistant.components as core + +# The domain of your component. Should be equal to the name of your component +DOMAIN = "modbus" + +# List of component names (string) your component depends upon +DEPENDENCIES = [] + +# Type of network +MEDIUM = "type" + +## if MEDIUM == "serial" +METHOD = "method" +SERIAL_PORT = "port" +BAUDRATE = "baudrate" +STOPBITS = "stopbits" +BYTESIZE = "bytesize" +PARITY = "parity" + +## if MEDIUM == "tcp" or "udp" +HOST = "host" +IP_PORT = "port" + +_LOGGER = logging.getLogger(__name__) + +NETWORK = None +TYPE = None + +def setup(hass, config): + """ Setup Modbus component. """ + + global TYPE + TYPE = config[DOMAIN][MEDIUM] + + # Connect to Modbus network + global NETWORK + + if TYPE == "serial": + from pymodbus.client.sync import ModbusSerialClient as ModbusClient + NETWORK = ModbusClient( method=config[DOMAIN][METHOD], + port=config[DOMAIN][SERIAL_PORT], + baudrate=config[DOMAIN][BAUDRATE], + stopbits=config[DOMAIN][STOPBITS], + bytesize=config[DOMAIN][BYTESIZE], + parity=config[DOMAIN][PARITY]) + elif TYPE == "tcp": + from pymodbus.client.sync import ModbusTcpClient as ModbusClient + NETWORK = ModbusClient( host=config[DOMAIN][HOST], + port=config[DOMAIN][IP_PORT]) + elif TYPE == "udp": + from pymodbus.client.sync import ModbusUdpClient as ModbusClient + NETWORK = ModbusClient( host=config[DOMAIN][HOST], + port=config[DOMAIN][IP_PORT]) + else: + return False + + def stop_modbus(event): + NETWORK.close() + + def start_modbus(event): + NETWORK.connect() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_modbus) + + # Tells the bootstrapper that the component was succesfully initialized + return True diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py new file mode 100644 index 00000000000..e186ee01fef --- /dev/null +++ b/homeassistant/components/sensor/modbus.py @@ -0,0 +1,123 @@ +__author__ = "Aurélien Correia" + +""" +Support for Modbus sensors. + +Configuration: +To use the Modbus sensors you will need to add something like the following to +your config/configuration.yaml + +sensor: + platform: modbus + slave: 1 + registers: + 16: + name: My integer sensor + unit: C + 24: + bits: + 0: + name: My boolean sensor + 2: + name: My other boolean sensor + +VARIABLES: + + - "slave" = slave number (ignored and can be omitted if not serial Modbus) + - "unit" = unit to attach to value (optional, ignored for boolean sensors) + - "registers" contains a list of relevant registers to read from + it can contain a "bits" section, listing relevant bits + + - each named register will create an integer sensor + - each named bit will create a boolean sensor +""" + +import logging + +import homeassistant.components.modbus as modbus +from homeassistant.helpers.entity import Entity +from homeassistant.const import ( + TEMP_CELCIUS, TEMP_FAHRENHEIT, + STATE_ON, STATE_OFF) + +_LOGGER = logging.getLogger(__name__) + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Read config and create Modbus devices """ + sensors = [] + slave = config.get("slave", None) + if modbus.TYPE == "serial" and not slave: + _LOGGER.error("No slave number provided for serial Modbus") + return False + registers = config.get("registers") + for regnum, register in registers.items(): + if register.get("name"): + sensors.append(ModbusSensor(register.get("name"), slave, regnum, None, register.get("unit"))) + if register.get("bits"): + bits = register.get("bits") + for bitnum, bit in bits.items(): + if bit.get("name"): + sensors.append(ModbusSensor(bit.get("name"), slave, regnum, bitnum)) + add_devices(sensors) + +class ModbusSensor(Entity): + """ Represents a Modbus Sensor """ + + def __init__(self, name, slave, register, bit=None, unit=None): + self._name = name + self.slave = int(slave) if slave else 1 + self.register = int(register) + self.bit = int(bit) if bit else None + self._value = None + self._unit = unit + + def __str__(self): + return "%s: %s" % (self.name, self.state) + + @property + def should_poll(self): + """ We should poll, because slaves are not allowed to initiate communication on Modbus networks""" + return True + + @property + def unique_id(self): + """ Returns a unique id. """ + return "MODBUS-SENSOR-{}-{}-{}".format(self.slave, self.register, self.bit) + + @property + def state(self): + """ Returns the state of the sensor. """ + if self.bit: + return STATE_ON if self._value else STATE_OFF + else: + return self._value + + @property + def name(self): + """ Get the name of the sensor. """ + return self._name + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + if self._unit == "C": + return TEMP_CELCIUS + elif self._unit == "F": + return TEMP_FAHRENHEIT + else: + return self._unit + + @property + def state_attributes(self): + attr = super().state_attributes + return attr + + def update(self): + result = modbus.NETWORK.read_holding_registers(unit=self.slave,address=self.register,count=1) + val = 0 + for i, e in enumerate(result.registers): + val += e * (2**(i*16)) + if self.bit: + self._value = val & (0x0001 << self.bit) + else: + self._value = val diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py new file mode 100644 index 00000000000..6f26d7997f3 --- /dev/null +++ b/homeassistant/components/switch/modbus.py @@ -0,0 +1,109 @@ +__author__ = "Aurélien Correia" + +""" +Support for Modbus switches. + +Configuration: +To use the Modbus switches you will need to add something like the following to +your config/configuration.yaml + +sensor: + platform: modbus + slave: 1 + registers: + 24: + bits: + 0: + name: My switch + 2: + name: My other switch + +VARIABLES: + + - "slave" = slave number (ignored and can be omitted if not serial Modbus) + - "registers" contains a list of relevant registers to read from + - it must contain a "bits" section, listing relevant bits + + - each named bit will create a switch +""" + +import logging + +import homeassistant.components.modbus as modbus +from homeassistant.helpers.entity import ToggleEntity + +_LOGGER = logging.getLogger(__name__) + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Read config and create Modbus devices """ + switches = [] + slave = config.get("slave", None) + if modbus.TYPE == "serial" and not slave: + _LOGGER.error("No slave number provided for serial Modbus") + return False + registers = config.get("registers") + for regnum, register in registers.items(): + bits = register.get("bits") + for bitnum, bit in bits.items(): + if bit.get("name"): + switches.append(ModbusSwitch(bit.get("name"), slave, regnum, bitnum)) + add_devices(switches) + +class ModbusSwitch(ToggleEntity): + """ Represents a Modbus Switch """ + + def __init__(self, name, slave, register, bit): + self._name = name + self.slave = int(slave) if slave else 1 + self.register = int(register) + self.bit = int(bit) + self._is_on = None + self.register_value = None + + def __str__(self): + return "%s: %s" % (self.name, self.state) + + @property + def should_poll(self): + """ We should poll, because slaves are not allowed to initiate communication on Modbus networks""" + return True + + @property + def unique_id(self): + """ Returns a unique id. """ + return "MODBUS-SWITCH-{}-{}-{}".format(self.slave, self.register, self.bit) + + @property + def is_on(self): + """ Returns True if switch is on. """ + return self._is_on + + @property + def name(self): + """ Get the name of the switch. """ + return self._name + + @property + def state_attributes(self): + attr = super().state_attributes + return attr + + def turn_on(self, **kwargs): + if self.register_value is None: + self.update() + val = self.register_value | (0x0001 << self.bit) + modbus.NETWORK.write_register(unit=self.slave,address=self.register,value=val) + + def turn_off(self, **kwargs): + if self.register_value is None: + self.update() + val = self.register_value & ~(0x0001 << self.bit) + modbus.NETWORK.write_register(unit=self.slave,address=self.register,value=val) + + def update(self): + result = modbus.NETWORK.read_holding_registers(unit=self.slave,address=self.register,count=1) + val = 0 + for i, e in enumerate(result.registers): + val += e * (2**(i*16)) + self.register_value = val + self._is_on = (val & (0x0001 << self.bit) > 0) From 6b2dd69bcb6b516a11b2a4139dbb6ef4aff6ea47 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Fri, 17 Apr 2015 09:27:14 -0400 Subject: [PATCH 38/59] Updated isy994 component to hide any device with the HIDDEN STRING in its ancestry. --- homeassistant/components/group.py | 1 + homeassistant/components/light/isy994.py | 7 +++++-- homeassistant/components/sensor/isy994.py | 7 +++++-- homeassistant/components/switch/isy994.py | 7 +++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index badd5ac66fe..5c1e8268865 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -113,6 +113,7 @@ def setup(hass, config): class Group(object): """ Tracks a group of entity ids. """ + # pylint: disable=too-many-instance-attributes visibility = Entity.visibility _hidden = False diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index 60a4faafe13..ae0225a1e3c 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -3,7 +3,8 @@ import logging # homeassistant imports -from homeassistant.components.isy994 import ISYDeviceABC, ISY, SENSOR_STRING +from homeassistant.components.isy994 import (ISYDeviceABC, ISY, SENSOR_STRING, + HIDDEN_STRING) from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.const import STATE_ON, STATE_OFF @@ -18,8 +19,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False # import dimmable nodes - for node in ISY.nodes: + for (path, node) in ISY.nodes: if node.dimmable and SENSOR_STRING not in node.name: + if HIDDEN_STRING in path: + node.name += HIDDEN_STRING devs.append(ISYLightDevice(node)) add_devices(devs) diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 9b58a574527..739a058d24d 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -37,15 +37,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if ISY.climate is not None: for prop in ISY.climate._id2name: if prop is not None: - prefix = HIDDEN_STRING if prop in DEFAULT_HIDDEN_WEATHER else '' + prefix = HIDDEN_STRING \ + if prop in DEFAULT_HIDDEN_WEATHER else '' node = WeatherPseudoNode('ISY.weather.' + prop, prefix + prop, getattr(ISY.climate, prop), getattr(ISY.climate, prop + '_units')) devs.append(ISYSensorDevice(node)) # import sensor nodes - for node in ISY.nodes: + for (path, node) in ISY.nodes: if SENSOR_STRING in node.name: + if HIDDEN_STRING in path: + node.name += HIDDEN_STRING devs.append(ISYSensorDevice(node, [STATE_ON, STATE_OFF])) # import sensor programs diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index 1ea87e3fc2e..e6432173fc9 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -3,7 +3,8 @@ import logging # homeassistant imports -from homeassistant.components.isy994 import ISY, ISYDeviceABC, SENSOR_STRING +from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING, + HIDDEN_STRING) from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED # The frontend doesn't seem to fully support the open and closed states yet. # Once it does, the HA.doors programs should report open and closed instead of @@ -20,8 +21,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False # import not dimmable nodes and groups - for node in ISY.nodes: + for (path, node) in ISY.nodes: if not node.dimmable and SENSOR_STRING not in node.name: + if HIDDEN_STRING in path: + node.name += HIDDEN_STRING devs.append(ISYSwitchDevice(node)) # import ISY doors programs From da4cf61a09b5f14304f9ebdf012988405cad42a1 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Fri, 17 Apr 2015 09:30:20 -0400 Subject: [PATCH 39/59] Forced the isy994 component to treat underscores as spaces. --- homeassistant/components/isy994.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 33d11c8ebbe..f6ecebb9b2c 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -156,7 +156,8 @@ class ISYDeviceABC(ToggleEntity): @property def name(self): """ Returns the cleaned name of the node. """ - return self.raw_name.replace(HIDDEN_STRING, '').strip() + return self.raw_name.replace(HIDDEN_STRING, '').strip() \ + .replace('_', ' ') def update(self): """ Update state of the sensor. """ From 9a2e6dcba54588d4650167a53763589b4e984d99 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 18 Apr 2015 00:26:40 -0400 Subject: [PATCH 40/59] Added a script for listing entities in running Home Assistant server. Usefule for creating visibility list in configuration file. --- scripts/get_entities.py | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100755 scripts/get_entities.py diff --git a/scripts/get_entities.py b/scripts/get_entities.py new file mode 100755 index 00000000000..e2a5ff50c1a --- /dev/null +++ b/scripts/get_entities.py @@ -0,0 +1,141 @@ +#! /usr/bin/python +""" +get_entities.py + +Usage: get_entities.py [OPTION] ... [ATTRIBUTE] ... + +Query the Home Assistant API for available entities then print them and any +desired attributes to the screen. + +Options: + -h, --help display this text + --password=PASS use the supplied password + --ask-password prompt for password + -a, --address=ADDR use the supplied server address + -p, --port=PORT use the supplied server port +""" +import sys +import getpass +try: + from urllib2 import urlopen + PYTHON = 2 +except ImportError: + from urllib.request import urlopen + PYTHON = 3 +import json + + +def main(password, askpass, attrs, address, port): + """ fetch Home Assistant api json page and post process """ + # ask for password + if askpass: + password = getpass.getpass('Home Assistant API Password: ') + + # fetch API result + url = mk_url(address, port, password) + response = urlopen(url).read() + if PYTHON == 3: + response = response.decode('utf-8') + data = json.loads(response) + + # parse data + output = {'entity_id': []} + output.update([(attr, []) for attr in attrs]) + for item in data: + output['entity_id'].append(item['entity_id']) + for attr in attrs: + output[attr].append(item['attributes'].get(attr, '')) + + # output data + print_table(output, 'entity_id') + + +def print_table(data, first_key): + """ format and print a table of data from a dictionary """ + # get column lengths + lengths = {} + for key, value in data.items(): + lengths[key] = max([len(str(val)) for val in value] + [len(key)]) + + # construct the column order + columns = sorted(list(data.keys())) + ind = columns.index(first_key) + columns.pop(ind) + columns = [first_key] + columns + + # print header + for item in columns: + itemup = item.upper() + sys.stdout.write(itemup + ' ' * (lengths[item] - len(item) + 4)) + sys.stdout.write('\n') + + # print body + for ind in range(len(data[columns[0]])): + for item in columns: + val = str(data[item][ind]) + sys.stdout.write(val + ' ' * (lengths[item] - len(val) + 4)) + sys.stdout.write("\n") + + +def mk_url(address, port, password): + """ construct the url call for the api states page """ + url = '' + if address.startswith('http://'): + url += address + else: + url += 'http://' + address + url += ':' + port + '/api/states?' + if password is not None: + url += 'api_password=' + password + return url + + +def parse(option, all_options): + """ either update the options or set it to be updated next time """ + if len(option) > 1: + all_options[option[0]] = option[1] + return (all_options, None) + else: + return (all_options, option) + + +if __name__ == "__main__": + all_options = {'password': None, 'askpass': False, 'attrs': [], + 'address': 'localhost', 'port': '8123'} + + # parse arguments + next_key = None + for arg in sys.argv[1:]: + if next_key is None: + option = arg.split('=') + + if option[0] in ['-h', '--help']: + print(__doc__) + sys.exit(0) + + elif option[0] == '--password': + all_options['password'] = '='.join(option[1:]) + + elif option[0] == '--ask-password': + all_options['askpass'] = True + + elif option[0] == '-a': + next_key = 'address' + + elif option[0] == '--address': + all_options['address'] = '='.join(option[1:]) + + elif option[0] == '-p': + next_key = 'port' + + elif option[0] == '--port': + all_options['port'] = '='.join(option[1]) + + else: + all_options['attrs'].append('='.join(option)) + + else: + all_options[next_key] = arg + next_key = None + + main(**all_options) From 0032e4b6cf44936f8744fc4612f796ee75872739 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sat, 18 Apr 2015 00:30:09 -0400 Subject: [PATCH 41/59] On second thought, make that script use the specified order, not a sorted order. --- scripts/get_entities.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scripts/get_entities.py b/scripts/get_entities.py index e2a5ff50c1a..f89f83a8562 100755 --- a/scripts/get_entities.py +++ b/scripts/get_entities.py @@ -47,22 +47,16 @@ def main(password, askpass, attrs, address, port): output[attr].append(item['attributes'].get(attr, '')) # output data - print_table(output, 'entity_id') + print_table(output, ['entity_id'] + attrs) -def print_table(data, first_key): +def print_table(data, columns): """ format and print a table of data from a dictionary """ # get column lengths lengths = {} for key, value in data.items(): lengths[key] = max([len(str(val)) for val in value] + [len(key)]) - # construct the column order - columns = sorted(list(data.keys())) - ind = columns.index(first_key) - columns.pop(ind) - columns = [first_key] + columns - # print header for item in columns: itemup = item.upper() From 4d6555441d707f4b2f6440cdb86ce16992fb724b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Correia?= Date: Tue, 21 Apr 2015 16:40:13 +0200 Subject: [PATCH 42/59] Passed Travis CI --- homeassistant/components/modbus.py | 39 ++++++++++++----------- homeassistant/components/sensor/modbus.py | 31 ++++++++++++------ homeassistant/components/switch/modbus.py | 32 +++++++++++++------ 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index 4cda6fcd20c..97b0ec7405a 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -1,5 +1,3 @@ -__author__ = "Aurélien Correia" - """ components.modbus ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -24,13 +22,10 @@ modbus: parity: N """ -import time import logging -from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP -import homeassistant.loader as loader -from homeassistant.helpers import validate_config -import homeassistant.components as core +from homeassistant.const import (EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) # The domain of your component. Should be equal to the name of your component DOMAIN = "modbus" @@ -41,7 +36,7 @@ DEPENDENCIES = [] # Type of network MEDIUM = "type" -## if MEDIUM == "serial" +# if MEDIUM == "serial" METHOD = "method" SERIAL_PORT = "port" BAUDRATE = "baudrate" @@ -49,7 +44,7 @@ STOPBITS = "stopbits" BYTESIZE = "bytesize" PARITY = "parity" -## if MEDIUM == "tcp" or "udp" +# if MEDIUM == "tcp" or "udp" HOST = "host" IP_PORT = "port" @@ -58,38 +53,44 @@ _LOGGER = logging.getLogger(__name__) NETWORK = None TYPE = None + def setup(hass, config): """ Setup Modbus component. """ + # Modbus connection type + # pylint: disable=global-statement, import-error global TYPE TYPE = config[DOMAIN][MEDIUM] # Connect to Modbus network + # pylint: disable=global-statement, import-error global NETWORK if TYPE == "serial": from pymodbus.client.sync import ModbusSerialClient as ModbusClient - NETWORK = ModbusClient( method=config[DOMAIN][METHOD], - port=config[DOMAIN][SERIAL_PORT], - baudrate=config[DOMAIN][BAUDRATE], - stopbits=config[DOMAIN][STOPBITS], - bytesize=config[DOMAIN][BYTESIZE], - parity=config[DOMAIN][PARITY]) + NETWORK = ModbusClient(method=config[DOMAIN][METHOD], + port=config[DOMAIN][SERIAL_PORT], + baudrate=config[DOMAIN][BAUDRATE], + stopbits=config[DOMAIN][STOPBITS], + bytesize=config[DOMAIN][BYTESIZE], + parity=config[DOMAIN][PARITY]) elif TYPE == "tcp": from pymodbus.client.sync import ModbusTcpClient as ModbusClient - NETWORK = ModbusClient( host=config[DOMAIN][HOST], - port=config[DOMAIN][IP_PORT]) + NETWORK = ModbusClient(host=config[DOMAIN][HOST], + port=config[DOMAIN][IP_PORT]) elif TYPE == "udp": from pymodbus.client.sync import ModbusUdpClient as ModbusClient - NETWORK = ModbusClient( host=config[DOMAIN][HOST], - port=config[DOMAIN][IP_PORT]) + NETWORK = ModbusClient(host=config[DOMAIN][HOST], + port=config[DOMAIN][IP_PORT]) else: return False def stop_modbus(event): + """ Stop Modbus service""" NETWORK.close() def start_modbus(event): + """ Start Modbus service""" NETWORK.connect() hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index e186ee01fef..90593875a54 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -1,5 +1,3 @@ -__author__ = "Aurélien Correia" - """ Support for Modbus sensors. @@ -42,6 +40,7 @@ from homeassistant.const import ( _LOGGER = logging.getLogger(__name__) + def setup_platform(hass, config, add_devices, discovery_info=None): """ Read config and create Modbus devices """ sensors = [] @@ -52,15 +51,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None): registers = config.get("registers") for regnum, register in registers.items(): if register.get("name"): - sensors.append(ModbusSensor(register.get("name"), slave, regnum, None, register.get("unit"))) + sensors.append(ModbusSensor(register.get("name"), + slave, + regnum, + None, + register.get("unit"))) if register.get("bits"): bits = register.get("bits") for bitnum, bit in bits.items(): if bit.get("name"): - sensors.append(ModbusSensor(bit.get("name"), slave, regnum, bitnum)) + sensors.append(ModbusSensor(bit.get("name"), + slave, + regnum, + bitnum)) add_devices(sensors) + class ModbusSensor(Entity): + # pylint: disable=too-many-arguments """ Represents a Modbus Sensor """ def __init__(self, name, slave, register, bit=None, unit=None): @@ -76,13 +84,16 @@ class ModbusSensor(Entity): @property def should_poll(self): - """ We should poll, because slaves are not allowed to initiate communication on Modbus networks""" + """ We should poll, because slaves are not allowed to + initiate communication on Modbus networks""" return True @property def unique_id(self): """ Returns a unique id. """ - return "MODBUS-SENSOR-{}-{}-{}".format(self.slave, self.register, self.bit) + return "MODBUS-SENSOR-{}-{}-{}".format(self.slave, + self.register, + self.bit) @property def state(self): @@ -113,10 +124,12 @@ class ModbusSensor(Entity): return attr def update(self): - result = modbus.NETWORK.read_holding_registers(unit=self.slave,address=self.register,count=1) + result = modbus.NETWORK.read_holding_registers(unit=self.slave, + address=self.register, + count=1) val = 0 - for i, e in enumerate(result.registers): - val += e * (2**(i*16)) + for i, res in enumerate(result.registers): + val += res * (2**(i*16)) if self.bit: self._value = val & (0x0001 << self.bit) else: diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index 6f26d7997f3..7e5e039337f 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -1,5 +1,3 @@ -__author__ = "Aurélien Correia" - """ Support for Modbus switches. @@ -34,6 +32,7 @@ from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) + def setup_platform(hass, config, add_devices, discovery_info=None): """ Read config and create Modbus devices """ switches = [] @@ -46,9 +45,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): bits = register.get("bits") for bitnum, bit in bits.items(): if bit.get("name"): - switches.append(ModbusSwitch(bit.get("name"), slave, regnum, bitnum)) + switches.append(ModbusSwitch(bit.get("name"), + slave, + regnum, + bitnum)) add_devices(switches) + class ModbusSwitch(ToggleEntity): """ Represents a Modbus Switch """ @@ -65,13 +68,16 @@ class ModbusSwitch(ToggleEntity): @property def should_poll(self): - """ We should poll, because slaves are not allowed to initiate communication on Modbus networks""" + """ We should poll, because slaves are not allowed to + initiate communication on Modbus networks""" return True @property def unique_id(self): """ Returns a unique id. """ - return "MODBUS-SWITCH-{}-{}-{}".format(self.slave, self.register, self.bit) + return "MODBUS-SWITCH-{}-{}-{}".format(self.slave, + self.register, + self.bit) @property def is_on(self): @@ -92,18 +98,24 @@ class ModbusSwitch(ToggleEntity): if self.register_value is None: self.update() val = self.register_value | (0x0001 << self.bit) - modbus.NETWORK.write_register(unit=self.slave,address=self.register,value=val) + modbus.NETWORK.write_register(unit=self.slave, + address=self.register, + value=val) def turn_off(self, **kwargs): if self.register_value is None: self.update() val = self.register_value & ~(0x0001 << self.bit) - modbus.NETWORK.write_register(unit=self.slave,address=self.register,value=val) + modbus.NETWORK.write_register(unit=self.slave, + address=self.register, + value=val) def update(self): - result = modbus.NETWORK.read_holding_registers(unit=self.slave,address=self.register,count=1) + result = modbus.NETWORK.read_holding_registers(unit=self.slave, + address=self.register, + count=1) val = 0 - for i, e in enumerate(result.registers): - val += e * (2**(i*16)) + for i, res in enumerate(result.registers): + val += res * (2**(i*16)) self.register_value = val self._is_on = (val & (0x0001 << self.bit) > 0) From 4d91c4a51bb22985b4bb5cb67c3b027196cfb7b7 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 21 Apr 2015 23:58:41 -0400 Subject: [PATCH 43/59] Updated contributing documentation to include details about hidding states. --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 973415c1866..83b95535e0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,7 @@ A state can have several attributes that will help the frontend in displaying yo - `friendly_name`: this name will be used as the name of the device - `entity_picture`: this picture will be shown instead of the domain icon - `unit_of_measurement`: this will be appended to the state in the interface + - `hidden`: This is a suggestion to the frontend on if the state should be hidden These attributes are defined in [homeassistant.components](https://github.com/balloob/home-assistant/blob/master/homeassistant/components/__init__.py#L25). From 99ea0dc59dd08240538e158cfe7e4ad7c08d33ae Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Tue, 21 Apr 2015 23:59:56 -0400 Subject: [PATCH 44/59] Updated requirements to include PyISY. --- requirements.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9f0352ccef1..c7560c073e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,8 +34,11 @@ python-nest>=2.1 # z-wave pydispatcher>=2.0.5 +# isy994 +PyISY>=1.0.0 + # sensor.systemmonitor psutil>=2.2.1 #pushover notifications -python-pushover>=0.2 \ No newline at end of file +python-pushover>=0.2 From d566a328a31878b629d81c355e4be9d5ff6c2417 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 22 Apr 2015 00:22:48 -0400 Subject: [PATCH 45/59] pylint fix to isy switches. --- homeassistant/components/switch/isy994.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index e6432173fc9..192b3c4a3d2 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -13,6 +13,7 @@ from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the isy994 platform. """ + # pylint: disable=too-many-locals logger = logging.getLogger(__name__) devs = [] # verify connection From 8fcf814eb6e050abb61d4033f2165d8d74196382 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 22 Apr 2015 21:07:23 -0400 Subject: [PATCH 46/59] Changed visbility property in configuration.yaml to a hash instead of a list of a single hash. --- homeassistant/bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index e31a4bb66c7..a87deaa76d5 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -208,7 +208,7 @@ def process_ha_core_config(hass, config): if key in config: setattr(hass.config, attr, config[key]) - Entity.visibility.update(config.get('visibility', [{}])[0]) + Entity.visibility.update(config.get('visibility', {})) if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] From ff3dacedc0cb314e799e14bfd1c8f81256393f98 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 22 Apr 2015 21:21:50 -0400 Subject: [PATCH 47/59] Moved card visibility logic out of the Entity class and into a VisibilityABC. Then made the Group class inherit the VisibilityABC. No duplication of code now. This is definitely better. --- homeassistant/bootstrap.py | 4 +-- homeassistant/components/group.py | 26 ++------------ homeassistant/helpers/entity.py | 56 +++++++++++++++++++------------ 3 files changed, 38 insertions(+), 48 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index a87deaa76d5..17e8cb3391b 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -20,7 +20,7 @@ import homeassistant import homeassistant.loader as loader import homeassistant.components as core_components import homeassistant.components.group as group -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import VisibilityABC from homeassistant.const import ( EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, TEMP_CELCIUS, @@ -208,7 +208,7 @@ def process_ha_core_config(hass, config): if key in config: setattr(hass.config, attr, config[key]) - Entity.visibility.update(config.get('visibility', {})) + VisibilityABC.visibility.update(config.get('visibility', {})) if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 5c1e8268865..d3b4d628842 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -7,7 +7,7 @@ Provides functionality to group devices that can be turned on or off. import homeassistant as ha from homeassistant.helpers import generate_entity_id -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import VisibilityABC import homeassistant.util as util from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF, @@ -111,12 +111,8 @@ def setup(hass, config): return True -class Group(object): +class Group(VisibilityABC): """ Tracks a group of entity ids. """ - # pylint: disable=too-many-instance-attributes - - visibility = Entity.visibility - _hidden = False def __init__(self, hass, name, entity_ids=None, user_defined=True): self.hass = hass @@ -220,24 +216,6 @@ class Group(object): self.hass.states.set( self.entity_id, group_off, self.state_attr) - @property - def hidden(self): - """ - Returns the official decision of whether the entity should be hidden. - Any value set by the user in the configuration file will overwrite - whatever the component sets for visibility. - """ - if self.entity_id is not None and \ - self.entity_id.lower() in self.visibility: - return self.visibility[self.entity_id.lower()] == 'hide' - else: - return self._hidden - - @hidden.setter - def hidden(self, val): - """ Sets the suggestion for visibility. """ - self._hidden = bool(val) - def setup_group(hass, name, entity_ids, user_defined=True): """ Sets up a group state that is the combined state of diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 7d6ef65e1a7..737a0e5e359 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -12,7 +12,40 @@ from homeassistant.const import ( STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT) -class Entity(object): +class VisibilityABC(object): + """ + Abstract Class for including visibility logic. This class includes the + necessary methods and properties to consider a visibility suggestion form + the component and then determine visibility based on the options in the + configuration file. When using this abstract class, the value for the + hidden property must still be included in the attributes disctionary. The + Entity class takes care of this automatically. + """ + + entity_id = None + visibility = {} + _hidden = False + + @property + def hidden(self): + """ + Returns the official decision of whether the entity should be hidden. + Any value set by the user in the configuration file will overwrite + whatever the component sets for visibility. + """ + if self.entity_id is not None and \ + self.entity_id.lower() in self.visibility: + return self.visibility[self.entity_id.lower()] == 'hide' + else: + return self._hidden + + @hidden.setter + def hidden(self, val): + """ Sets the suggestion for visibility. """ + self._hidden = bool(val) + + +class Entity(VisibilityABC): """ ABC for Home Assistant entities. """ # pylint: disable=no-self-use @@ -20,8 +53,6 @@ class Entity(object): # The properties and methods here are safe to overwrite when inherting this # class. These may be used to customize the behavior of the entity. - _hidden = False # suggestion as to whether the entity should be hidden - @property def should_poll(self): """ @@ -83,7 +114,6 @@ class Entity(object): hass = None entity_id = None - visibility = {} def update_ha_state(self, force_refresh=False): """ @@ -130,24 +160,6 @@ class Entity(object): def __repr__(self): return "".format(self.name, self.state) - @property - def hidden(self): - """ - Returns the official decision of whether the entity should be hidden. - Any value set by the user in the configuration file will overwrite - whatever the component sets for visibility. - """ - if self.entity_id is not None and \ - self.entity_id.lower() in self.visibility: - return self.visibility[self.entity_id.lower()] == 'hide' - else: - return self._hidden - - @hidden.setter - def hidden(self, val): - """ Sets the suggestion for visibility. """ - self._hidden = bool(val) - class ToggleEntity(Entity): """ ABC for entities that can be turned on and off. """ From 2b6edd153ba5d1969fc32032f48473b631564121 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 22 Apr 2015 21:27:49 -0400 Subject: [PATCH 48/59] Fixed copy pasta error. --- homeassistant/components/switch/isy994.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index 192b3c4a3d2..fe98bce69f9 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -1,4 +1,4 @@ -""" Support for ISY994 lights. """ +""" Support for ISY994 switch. """ # system imports import logging From d779662bdd664d3185077c0a788adace15508a1b Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 22 Apr 2015 22:02:54 -0400 Subject: [PATCH 49/59] Updated get_entities.py script to use argparse module. --- scripts/get_entities.py | 72 ++++++++++------------------------------- 1 file changed, 17 insertions(+), 55 deletions(-) diff --git a/scripts/get_entities.py b/scripts/get_entities.py index f89f83a8562..249a06f0d9b 100755 --- a/scripts/get_entities.py +++ b/scripts/get_entities.py @@ -1,21 +1,12 @@ #! /usr/bin/python """ -get_entities.py - -Usage: get_entities.py [OPTION] ... [ATTRIBUTE] ... - Query the Home Assistant API for available entities then print them and any desired attributes to the screen. - -Options: - -h, --help display this text - --password=PASS use the supplied password - --ask-password prompt for password - -a, --address=ADDR use the supplied server address - -p, --port=PORT use the supplied server port """ + import sys import getpass +import argparse try: from urllib2 import urlopen PYTHON = 2 @@ -84,52 +75,23 @@ def mk_url(address, port, password): return url -def parse(option, all_options): - """ either update the options or set it to be updated next time """ - if len(option) > 1: - all_options[option[0]] = option[1] - return (all_options, None) - else: - return (all_options, option) - - if __name__ == "__main__": all_options = {'password': None, 'askpass': False, 'attrs': [], 'address': 'localhost', 'port': '8123'} - # parse arguments - next_key = None - for arg in sys.argv[1:]: - if next_key is None: - option = arg.split('=') + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('attrs', metavar='ATTRIBUTE', type=str, nargs='*', + help='an attribute to read from the state') + parser.add_argument('--password', dest='password', default=None, + type=str, help='API password for the HA server') + parser.add_argument('--ask-password', dest='askpass', default=False, + action='store_const', const=True, + help='prompt for HA API password') + parser.add_argument('--addr', dest='address', + default='localhost', type=str, + help='address of the HA server') + parser.add_argument('--port', dest='port', default='8123', + type=str, help='port that HA is hosting on') - if option[0] in ['-h', '--help']: - print(__doc__) - sys.exit(0) - - elif option[0] == '--password': - all_options['password'] = '='.join(option[1:]) - - elif option[0] == '--ask-password': - all_options['askpass'] = True - - elif option[0] == '-a': - next_key = 'address' - - elif option[0] == '--address': - all_options['address'] = '='.join(option[1:]) - - elif option[0] == '-p': - next_key = 'port' - - elif option[0] == '--port': - all_options['port'] = '='.join(option[1]) - - else: - all_options['attrs'].append('='.join(option)) - - else: - all_options[next_key] = arg - next_key = None - - main(**all_options) + args = parser.parse_args() + main(args.password, args.askpass, args.attrs, args.address, args.port) From bd3b93f29057252c8d784d3e90ec754a37116cfe Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 22 Apr 2015 22:19:36 -0400 Subject: [PATCH 50/59] 1) Added visibility documentation to the CONTRIBUTING.md documentation. 2) Pylint fixes to homeassistant/helpers/entity.py --- CONTRIBUTING.md | 15 +++++++++++++++ homeassistant/helpers/entity.py | 1 + 2 files changed, 16 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 83b95535e0c..e6bc947d771 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,21 @@ A state can have several attributes that will help the frontend in displaying yo These attributes are defined in [homeassistant.components](https://github.com/balloob/home-assistant/blob/master/homeassistant/components/__init__.py#L25). +## Proper Visibility Handling ## + +Generally, when creating a new entity for Home Assistant you will want it to be a class that inherits the [homeassistant.helpers.entity.Entity](https://github.com/balloob/home-assistant/blob/master/homeassistant/helpers/entity.py] Class. If this is done, visibility will be handled for you. +You can set a suggestion for your entitie's visibility by setting the hidden property by doing something similar to the following. + +```python +self.hidden = True +``` + +This will SUGGEST that the active frontend hide the entity. This requires that the active frontend support hidden cards (the default frontend does) and that the value of hidden be included in your attributes dictionary (see above). The Entity abstract class will take care of this for you. + +Remember: The suggestion set by your component's code will always be overwritten by manual settings in the configuration.yaml file. This is why you may set hidden to be False, but the property may remain True (or vice-versa). + +If you would not like to use the Entity Abstract Class, you may also inherity the Visibility Abstract Class which will include the logic for the hidden property but not automatically add the hidden property to the attributes dictionary. If you use this class, ensure that your class correctly adds the hidden property to the attributes. + ## Working on the frontend The frontend is composed of Polymer web-components and compiled into the file `frontend.html`. During development you do not want to work with the compiled version but with the seperate files. To have Home Assistant serve the seperate files, set `development=1` for the http-component in your config. diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 737a0e5e359..07aaf9a234c 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -21,6 +21,7 @@ class VisibilityABC(object): hidden property must still be included in the attributes disctionary. The Entity class takes care of this automatically. """ + # pylint: disable=too-few-public-methods entity_id = None visibility = {} From dc4ff25d5bace9493d84cd442145c0a792e867d5 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Wed, 22 Apr 2015 23:10:51 -0400 Subject: [PATCH 51/59] 1) Upped the requirement for PyISY to version 1.0.2. 2) Omitted isy994 components from coveralls tests because it requires an external controller. --- .coveragerc | 5 +++++ requirements.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index cc639df0c44..7cfcacaaa2d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -30,6 +30,11 @@ omit = homeassistant/components/device_tracker/ddwrt.py homeassistant/components/sensor/transmission.py + homeassistant/components/isy994.py + homeassistant/components/light/isy994.py + homeassistant/components/switch/isy994.py + homeassistant/components/sensor/isy994.py + [report] # Regexes for lines to exclude from consideration diff --git a/requirements.txt b/requirements.txt index d6ff370f941..e1b0a942f80 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,7 @@ python-nest>=2.1 pydispatcher>=2.0.5 # isy994 -PyISY>=1.0.0 +PyISY>=1.0.2 # sensor.systemmonitor psutil>=2.2.1 From bbdb0320f14b8cef61bc79c185e636a0e5775bc7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 22 Apr 2015 22:19:21 -0700 Subject: [PATCH 52/59] Have group inherit from entity --- homeassistant/components/group.py | 104 ++++++++++++++++-------------- tests/test_component_group.py | 6 +- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index d3b4d628842..7dc1cb54282 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -7,11 +7,11 @@ Provides functionality to group devices that can be turned on or off. import homeassistant as ha from homeassistant.helpers import generate_entity_id -from homeassistant.helpers.entity import VisibilityABC +from homeassistant.helpers.entity import Entity import homeassistant.util as util from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF, - STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN, ATTR_HIDDEN) + ATTR_ENTITY_ID, STATE_ON, STATE_OFF, + STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN) DOMAIN = "group" DEPENDENCIES = [] @@ -111,37 +111,43 @@ def setup(hass, config): return True -class Group(VisibilityABC): +class Group(Entity): """ Tracks a group of entity ids. """ + # pylint: disable=too-many-instance-attributes + def __init__(self, hass, name, entity_ids=None, user_defined=True): self.hass = hass - self.name = name + self._name = name + self._state = STATE_UNKNOWN self.user_defined = user_defined - self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass) - self.tracking = [] - self.group_on, self.group_off = None, None + self.group_on = None + self.group_off = None if entity_ids is not None: self.update_tracked_entity_ids(entity_ids) else: - self.force_update() + self.update_ha_state(True) + + @property + def should_poll(self): + return False + + @property + def name(self): + return self._name @property def state(self): - """ Return the current state from the group. """ - return self.hass.states.get(self.entity_id) + return self._state @property - def state_attr(self): - """ State attributes of this group. """ + def state_attributes(self): return { ATTR_ENTITY_ID: self.tracking, ATTR_AUTO: not self.user_defined, - ATTR_FRIENDLY_NAME: self.name, - ATTR_HIDDEN: self.hidden } def update_tracked_entity_ids(self, entity_ids): @@ -150,71 +156,69 @@ class Group(VisibilityABC): self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) self.group_on, self.group_off = None, None - self.force_update() + self.update_ha_state(True) self.start() - def force_update(self): - """ Query all the tracked states and update group state. """ - for entity_id in self.tracking: - state = self.hass.states.get(entity_id) - - if state is not None: - self._update_group_state(state.entity_id, None, state) - - # If parsing the entitys did not result in a state, set UNKNOWN - if self.state is None: - self.hass.states.set( - self.entity_id, STATE_UNKNOWN, self.state_attr) - def start(self): """ Starts the tracking. """ - self.hass.states.track_change(self.tracking, self._update_group_state) + self.hass.states.track_change( + self.tracking, self._state_changed_listener) def stop(self): """ Unregisters the group from Home Assistant. """ self.hass.states.remove(self.entity_id) self.hass.bus.remove_listener( - ha.EVENT_STATE_CHANGED, self._update_group_state) + ha.EVENT_STATE_CHANGED, self._state_changed_listener) - def _update_group_state(self, entity_id, old_state, new_state): - """ Updates the group state based on a state change by - a tracked entity. """ + def update(self): + """ Query all the tracked states and determine current group state. """ + self._state = STATE_UNKNOWN + + for entity_id in self.tracking: + state = self.hass.states.get(entity_id) + + if state is not None: + self._process_tracked_state(state) + + def _state_changed_listener(self, entity_id, old_state, new_state): + """ Listener to receive state changes of tracked entities. """ + self._process_tracked_state(new_state) + self.update_ha_state() + + def _process_tracked_state(self, tr_state): + """ Updates group state based on a new state of a tracked entity. """ # We have not determined type of group yet if self.group_on is None: - self.group_on, self.group_off = _get_group_on_off(new_state.state) + self.group_on, self.group_off = _get_group_on_off(tr_state.state) if self.group_on is not None: # New state of the group is going to be based on the first # state that we can recognize - self.hass.states.set( - self.entity_id, new_state.state, self.state_attr) + self._state = tr_state.state return # There is already a group state - cur_gr_state = self.hass.states.get(self.entity_id).state + cur_gr_state = self._state group_on, group_off = self.group_on, self.group_off - # if cur_gr_state = OFF and new_state = ON: set ON - # if cur_gr_state = ON and new_state = OFF: research + # if cur_gr_state = OFF and tr_state = ON: set ON + # if cur_gr_state = ON and tr_state = OFF: research # else: ignore - if cur_gr_state == group_off and new_state.state == group_on: + if cur_gr_state == group_off and tr_state.state == group_on: + self._state = group_on - self.hass.states.set( - self.entity_id, group_on, self.state_attr) + elif cur_gr_state == group_on and tr_state.state == group_off: - elif (cur_gr_state == group_on and - new_state.state == group_off): - - # Check if any of the other states is still on + # Set to off if no other states are on if not any(self.hass.states.is_state(ent_id, group_on) - for ent_id in self.tracking if entity_id != ent_id): - self.hass.states.set( - self.entity_id, group_off, self.state_attr) + for ent_id in self.tracking + if tr_state.entity_id != ent_id): + self._state = group_off def setup_group(hass, name, entity_ids, user_defined=True): diff --git a/tests/test_component_group.py b/tests/test_component_group.py index 36ce2b80319..a476efdeea0 100644 --- a/tests/test_component_group.py +++ b/tests/test_component_group.py @@ -54,7 +54,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass, 'light_and_nothing', ['light.Bowl', 'non.existing']) - self.assertEqual(STATE_ON, grp.state.state) + self.assertEqual(STATE_ON, grp.state) def test_setup_group_with_non_groupable_states(self): self.hass.states.set('cast.living_room', "Plex") @@ -64,13 +64,13 @@ class TestComponentsGroup(unittest.TestCase): self.hass, 'chromecasts', ['cast.living_room', 'cast.bedroom']) - self.assertEqual(STATE_UNKNOWN, grp.state.state) + self.assertEqual(STATE_UNKNOWN, grp.state) def test_setup_empty_group(self): """ Try to setup an empty group. """ grp = group.setup_group(self.hass, 'nothing', []) - self.assertEqual(STATE_UNKNOWN, grp.state.state) + self.assertEqual(STATE_UNKNOWN, grp.state) def test_monitor_group(self): """ Test if the group keeps track of states. """ From b855f422efb0f8c3f9eb8fa89832232772db76cd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 23 Apr 2015 06:41:41 -0700 Subject: [PATCH 53/59] Tweak visibility config --- homeassistant/bootstrap.py | 9 +++-- homeassistant/const.py | 1 + homeassistant/helpers/entity.py | 64 +++++++++++++----------------- tests/test_helper_entity.py | 69 +++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 40 deletions(-) create mode 100644 tests/test_helper_entity.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 17e8cb3391b..ecd5be36dee 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -20,11 +20,11 @@ import homeassistant import homeassistant.loader as loader import homeassistant.components as core_components import homeassistant.components.group as group -from homeassistant.helpers.entity import VisibilityABC +from homeassistant.helpers.entity import Entity from homeassistant.const import ( EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE, - CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, TEMP_CELCIUS, - TEMP_FAHRENHEIT) + CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_VISIBILITY, + TEMP_CELCIUS, TEMP_FAHRENHEIT) _LOGGER = logging.getLogger(__name__) @@ -208,7 +208,8 @@ def process_ha_core_config(hass, config): if key in config: setattr(hass.config, attr, config[key]) - VisibilityABC.visibility.update(config.get('visibility', {})) + for entity_id, hidden in config.get(CONF_VISIBILITY, {}).items(): + Entity.overwrite_hidden(entity_id, hidden == 'hide') if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] diff --git a/homeassistant/const.py b/homeassistant/const.py index a1aa0df40d1..0e859eaf943 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -11,6 +11,7 @@ CONF_LONGITUDE = "longitude" CONF_TEMPERATURE_UNIT = "temperature_unit" CONF_NAME = "name" CONF_TIME_ZONE = "time_zone" +CONF_VISIBILITY = "visibility" CONF_PLATFORM = "platform" CONF_HOST = "host" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 07aaf9a234c..a39d46a29f6 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -11,45 +11,16 @@ from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT) - -class VisibilityABC(object): - """ - Abstract Class for including visibility logic. This class includes the - necessary methods and properties to consider a visibility suggestion form - the component and then determine visibility based on the options in the - configuration file. When using this abstract class, the value for the - hidden property must still be included in the attributes disctionary. The - Entity class takes care of this automatically. - """ - # pylint: disable=too-few-public-methods - - entity_id = None - visibility = {} - _hidden = False - - @property - def hidden(self): - """ - Returns the official decision of whether the entity should be hidden. - Any value set by the user in the configuration file will overwrite - whatever the component sets for visibility. - """ - if self.entity_id is not None and \ - self.entity_id.lower() in self.visibility: - return self.visibility[self.entity_id.lower()] == 'hide' - else: - return self._hidden - - @hidden.setter - def hidden(self, val): - """ Sets the suggestion for visibility. """ - self._hidden = bool(val) +# Dict mapping entity_id to a boolean that overwrites the hidden property +_OVERWRITE_HIDDEN = {} -class Entity(VisibilityABC): +class Entity(object): """ ABC for Home Assistant entities. """ # pylint: disable=no-self-use + _hidden = False + # SAFE TO OVERWRITE # The properties and methods here are safe to overwrite when inherting this # class. These may be used to customize the behavior of the entity. @@ -87,6 +58,16 @@ class Entity(VisibilityABC): """ Unit of measurement of this entity, if any. """ return None + @property + def hidden(self): + """ Suggestion if the entity should be hidden from UIs. """ + return self._hidden + + @hidden.setter + def hidden(self, val): + """ Sets the suggestion for visibility. """ + self._hidden = bool(val) + def update(self): """ Retrieve latest state. """ pass @@ -140,8 +121,8 @@ class Entity(VisibilityABC): if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement: attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement - if ATTR_HIDDEN not in attr: - attr[ATTR_HIDDEN] = bool(self.hidden) + if _OVERWRITE_HIDDEN.get(self.entity_id, self.hidden): + attr[ATTR_HIDDEN] = True # Convert temperature if we detect one if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS, @@ -161,6 +142,17 @@ class Entity(VisibilityABC): def __repr__(self): return "".format(self.name, self.state) + @staticmethod + def overwrite_hidden(entity_id, hidden): + """ + Overwrite the hidden property of an entity. + Set hidden to None to remove any overwritten value in place. + """ + if hidden is None: + _OVERWRITE_HIDDEN.pop(entity_id, None) + else: + _OVERWRITE_HIDDEN[entity_id.lower()] = hidden + class ToggleEntity(Entity): """ ABC for entities that can be turned on and off. """ diff --git a/tests/test_helper_entity.py b/tests/test_helper_entity.py new file mode 100644 index 00000000000..9645bbc40c0 --- /dev/null +++ b/tests/test_helper_entity.py @@ -0,0 +1,69 @@ +""" +tests.test_helper_entity +~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests the entity helper. +""" +# pylint: disable=protected-access,too-many-public-methods +import unittest + +import homeassistant as ha +import homeassistant.helpers.entity as entity +from homeassistant.const import ATTR_HIDDEN + + +class TestHelpersEntity(unittest.TestCase): + """ Tests homeassistant.helpers.entity module. """ + + def setUp(self): # pylint: disable=invalid-name + """ Init needed objects. """ + self.entity = entity.Entity() + self.entity.entity_id = 'test.overwrite_hidden_true' + self.hass = self.entity.hass = ha.HomeAssistant() + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_default_hidden_not_in_attributes(self): + """ Test that the default hidden property is set to False. """ + self.entity.update_ha_state() + + self.assertNotIn( + ATTR_HIDDEN, + self.hass.states.get(self.entity.entity_id).attributes) + + def test_setting_hidden_to_true(self): + self.entity.hidden = True + self.entity.update_ha_state() + + state = self.hass.states.get(self.entity.entity_id) + + self.assertTrue(state.attributes.get(ATTR_HIDDEN)) + + self.entity.hidden = False + + def test_overwriting_hidden_property_to_true(self): + """ Test we can overwrite hidden property to True. """ + entity.Entity.overwrite_hidden(self.entity.entity_id, True) + + self.entity.update_ha_state() + + state = self.hass.states.get(self.entity.entity_id) + + self.assertTrue(state.attributes.get(ATTR_HIDDEN)) + + entity.Entity.overwrite_hidden(self.entity.entity_id, None) + + def test_overwriting_hidden_property_to_false(self): + """ Test we can overwrite hidden property to True. """ + entity.Entity.overwrite_hidden(self.entity.entity_id, False) + + self.entity.hidden = True + self.entity.update_ha_state() + + self.assertNotIn( + ATTR_HIDDEN, + self.hass.states.get(self.entity.entity_id).attributes) + + entity.Entity.overwrite_hidden(self.entity.entity_id, None) From 8f36cf3c8119b12f4de6330ccc14af606a4f7451 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 23 Apr 2015 07:33:59 -0700 Subject: [PATCH 54/59] Clean up Entity helper test code --- tests/test_helper_entity.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/test_helper_entity.py b/tests/test_helper_entity.py index 9645bbc40c0..a36365afc3b 100644 --- a/tests/test_helper_entity.py +++ b/tests/test_helper_entity.py @@ -20,15 +20,15 @@ class TestHelpersEntity(unittest.TestCase): self.entity = entity.Entity() self.entity.entity_id = 'test.overwrite_hidden_true' self.hass = self.entity.hass = ha.HomeAssistant() + self.entity.update_ha_state() def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """ self.hass.stop() + entity.Entity.overwrite_hidden(self.entity.entity_id, None) def test_default_hidden_not_in_attributes(self): """ Test that the default hidden property is set to False. """ - self.entity.update_ha_state() - self.assertNotIn( ATTR_HIDDEN, self.hass.states.get(self.entity.entity_id).attributes) @@ -41,29 +41,20 @@ class TestHelpersEntity(unittest.TestCase): self.assertTrue(state.attributes.get(ATTR_HIDDEN)) - self.entity.hidden = False - def test_overwriting_hidden_property_to_true(self): """ Test we can overwrite hidden property to True. """ entity.Entity.overwrite_hidden(self.entity.entity_id, True) - self.entity.update_ha_state() state = self.hass.states.get(self.entity.entity_id) - self.assertTrue(state.attributes.get(ATTR_HIDDEN)) - entity.Entity.overwrite_hidden(self.entity.entity_id, None) - def test_overwriting_hidden_property_to_false(self): """ Test we can overwrite hidden property to True. """ entity.Entity.overwrite_hidden(self.entity.entity_id, False) - self.entity.hidden = True self.entity.update_ha_state() self.assertNotIn( ATTR_HIDDEN, self.hass.states.get(self.entity.entity_id).attributes) - - entity.Entity.overwrite_hidden(self.entity.entity_id, None) From 968de0557aefc8e5a0b5edc5e4785923d6dc9f6c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 23 Apr 2015 09:58:43 -0700 Subject: [PATCH 55/59] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6bc947d771..48523c1a4fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,18 +33,16 @@ These attributes are defined in [homeassistant.components](https://github.com/ba ## Proper Visibility Handling ## -Generally, when creating a new entity for Home Assistant you will want it to be a class that inherits the [homeassistant.helpers.entity.Entity](https://github.com/balloob/home-assistant/blob/master/homeassistant/helpers/entity.py] Class. If this is done, visibility will be handled for you. -You can set a suggestion for your entitie's visibility by setting the hidden property by doing something similar to the following. +Generally, when creating a new entity for Home Assistant you will want it to be a class that inherits the [homeassistant.helpers.entity.Entity](https://github.com/balloob/home-assistant/blob/master/homeassistant/helpers/entity.py) Class. If this is done, visibility will be handled for you. +You can set a suggestion for your entity's visibility by setting the hidden property by doing something similar to the following. ```python self.hidden = True ``` -This will SUGGEST that the active frontend hide the entity. This requires that the active frontend support hidden cards (the default frontend does) and that the value of hidden be included in your attributes dictionary (see above). The Entity abstract class will take care of this for you. +This will SUGGEST that the active frontend hides the entity. This requires that the active frontend support hidden cards (the default frontend does) and that the value of hidden be included in your attributes dictionary (see above). The Entity abstract class will take care of this for you. -Remember: The suggestion set by your component's code will always be overwritten by manual settings in the configuration.yaml file. This is why you may set hidden to be False, but the property may remain True (or vice-versa). - -If you would not like to use the Entity Abstract Class, you may also inherity the Visibility Abstract Class which will include the logic for the hidden property but not automatically add the hidden property to the attributes dictionary. If you use this class, ensure that your class correctly adds the hidden property to the attributes. +Remember: The suggestion set by your component's code will always be overwritten by user settings in the configuration.yaml file. This is why you may set hidden to be False, but the property may remain True (or vice-versa). ## Working on the frontend From 46819acaff8cfc720cc5b1a33c58eda19720fb78 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 24 Apr 2015 08:26:41 +0200 Subject: [PATCH 56/59] Fix two typos --- homeassistant/components/switch/demo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/switch/demo.py index 998597c3e8c..96a48d01a9e 100644 --- a/homeassistant/components/switch/demo.py +++ b/homeassistant/components/switch/demo.py @@ -1,4 +1,4 @@ -""" Demo platform that has two fake switchces. """ +""" Demo platform that has two fake switches. """ from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME @@ -30,7 +30,7 @@ class DemoSwitch(ToggleEntity): @property def state(self): - """ Returns the name of the device if any. """ + """ Returns the state of the device if any. """ return self._state @property From 424f05a4daec336356e9f5244b64baa979915f69 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2015 08:47:11 -0700 Subject: [PATCH 57/59] Add logbook to default configuration --- homeassistant/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 13703dee416..c5e775bd9f9 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -85,6 +85,7 @@ def ensure_config_path(config_dir): conf.write("frontend:\n\n") conf.write("discovery:\n\n") conf.write("history:\n\n") + conf.write("logbook:\n\n") except IOError: print(('Fatal Error: No configuration file found and unable ' 'to write a default one to {}').format(config_path)) From 24bb89df23e94df1d69a6ac46d6b50cb3db785d3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2015 08:52:37 -0700 Subject: [PATCH 58/59] Compiled new version of frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 12 ++++---- .../polymer/components/state-timeline.html | 30 ++++++++----------- .../www_static/polymer/home-assistant-js | 2 +- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 984ddf18ab7..bf7aed7c7fd 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "e7801905cc2ea1ee349ec199604fb984" +VERSION = "93774c7a1643c7e3f9cbbb1554b36683" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index fd47ffa638d..5995df6e07d 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -11,11 +11,11 @@ window.PolymerGestures={},function(a){var b=!1,c=document.createElement("meta"); LogicalExpression:"LogicalExpression",MemberExpression:"MemberExpression",ObjectExpression:"ObjectExpression",Program:"Program",Property:"Property",ThisExpression:"ThisExpression",UnaryExpression:"UnaryExpression"},V={UnexpectedToken:"Unexpected token %0",UnknownLabel:"Undefined label '%0'",Redeclaration:"%0 '%1' has already been declared"};var ab=H,bb=L;a.esprima={parse:R}}(this),function(a){"use strict";function b(a,b,d,e){var f;try{if(f=c(a),f.scopeIdent&&(d.nodeType!==Node.ELEMENT_NODE||"TEMPLATE"!==d.tagName||"bind"!==b&&"repeat"!==b))throw Error("as and in can only be used within