diff --git a/appdaemon/appdaemon.yaml b/appdaemon/appdaemon.yaml new file mode 100644 index 0000000..0272217 --- /dev/null +++ b/appdaemon/appdaemon.yaml @@ -0,0 +1,14 @@ +log: + logfile: STDOUT + errorfile: STDERR +appdaemon: + threads: 10 + plugins: + HASS: + type: hass + # Running this on the same user defined docker network so we use the other containers hostname and port directly to avoid data leaks and simplify config + ha_url: http://home-assistant:8123 + token: !secret ha_token +hadashboard: + # Using docker + reverse proxy means we want this exposed EVERYWHERE, no matter what + dash_url: http://0.0.0.0:5050 diff --git a/appdaemon/apps/apps.yaml b/appdaemon/apps/apps.yaml new file mode 100644 index 0000000..daff739 --- /dev/null +++ b/appdaemon/apps/apps.yaml @@ -0,0 +1,4 @@ +hello_world: + module: hello + class: HelloWorld + diff --git a/appdaemon/apps/apps.yaml.example b/appdaemon/apps/apps.yaml.example new file mode 100644 index 0000000..daff739 --- /dev/null +++ b/appdaemon/apps/apps.yaml.example @@ -0,0 +1,4 @@ +hello_world: + module: hello + class: HelloWorld + diff --git a/appdaemon/apps/hello.py b/appdaemon/apps/hello.py new file mode 100755 index 0000000..0ef8f01 --- /dev/null +++ b/appdaemon/apps/hello.py @@ -0,0 +1,12 @@ +import appdaemon.plugins.hass.hassapi as hass +# +# Hellow World App +# +# Args: +# + +class HelloWorld(hass.Hass): + + def initialize(self): + self.log("Hello from AppDaemon") + self.log("You are now ready to run Apps!") diff --git a/appdaemon/compiled/css/default/hello_application.css b/appdaemon/compiled/css/default/hello_application.css new file mode 100644 index 0000000..5f109c1 --- /dev/null +++ b/appdaemon/compiled/css/default/hello_application.css @@ -0,0 +1,241 @@ + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + padding: 0; +} + +body { + margin: 0; + background-color: #222; + font-size: 15px; + color: #fff; + padding: 0; + line-height: 1; + font-family: 'Helvetica Neue', 'Helvetica', 'Open Sans', 'Arial' +} + +b, strong { + font-weight: bold; +} + +a { + text-decoration: none; + color: inherit; +} + +img { + border: 0; + -ms-interpolation-mode: bicubic; + vertical-align: middle; +} + +img, object { + max-width: 100%; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px;} + +iframe { + max-width: 100%; +} + +table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; +} + +td { + vertical-align: middle; +} + +ul, ol { + padding: 0; + margin: 0; +} + +h1, h2, h3, h4, h5, p { + padding: 0; + margin: 0; +} +h1 { + margin-bottom: 6px; + text-align: center; + font-size: 100%; + font-weight: 200; +} +h2 { + font-size: 300%; + font-weight: 400; + color: #fff; +} +h3 { + font-size: 125%; + font-weight: 300; + color: #fff; +} + +input { + background-color: #444; + border: none; +} + +.dashboard_main { + margin: 0px auto; +} + +.gridster { + margin: 0px auto; +} + +.icon-background { + pointer-events: none; + width: 100%!important; + height: 100%; + position: absolute; + left: 0; + top: 0; + opacity: 0.1; + font-size: 1375%; + text-align: center; + margin-top: 82px; +} + +.list-nostyle { + list-style: none; +} + +.gridster ul { + list-style: none; +} + +.gs-w { + width: 100%; + display: table; + cursor: pointer; + z-index: auto !important; +} + +.iframe { + position: relative; + overflow: hidden; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px;} + +.widget { + padding: 0px 0px; + text-align: center; + width: 100%; + display: table-cell; + vertical-align: middle; + background-color: #444444; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px;} + +.title { + color: #fff; +} + +.icon-inactive { + color: #888; +} + +.icon-active { + color: #aaff00; +} + + +#container { + padding-top: 0px; +} + +.modalDialog { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0,0,0,0.8); + z-index: 9999; + opacity:0; + -webkit-transition: opacity 400ms ease-in; + -moz-transition: opacity 400ms ease-in; + transition: opacity 400ms ease-in; + pointer-events: none; +} + +.modalDialogOpen { + opacity:0.95; + pointer-events: auto; +} + +.modalDialogClose { + opacity:0; + pointer-events: none; +} + +.modalDialog > div { + width: 275px; + position: relative; + margin: 3% auto; + padding: 5px 20px 13px 20px; + border-radius: 10px; +} + +.modalDialogCloseButton { + line-height: 50px; + position: absolute; + right: -25px; + text-align: center; + top: -20px; + width: 50px; + text-decoration: none; + font-weight: bold; + -webkit-border-radius: 25px; + -moz-border-radius: 25px; + border-radius: 25px; +} + +.modalDialogCloseButton:hover { background: #444; } + +.widget-basedisplay-default-label .unit { + font-size: 225%; + font-weight: 400; + display: inline-block; + vertical-align: top; + margin-left: 5px; + margin-top: 5px; +} + +.widget-basedisplay-default-label .value { + display: inline-block; + vertical-align: middle; +} + +.widget-basedisplay-default-label .valueunit { + width: 100%; + vertical-align: middle; +} + +.widget-basedisplay-default-label .title { + position: absolute; + top: 5px; + width: 100%; +} + +.widget-basedisplay-default-label .title2 { + position: absolute; + top: 23px; + width: 100%; +} + +.widget-basedisplay-default-label .state_text { + position: absolute; + bottom: -3px; + width: 100%; +} + diff --git a/appdaemon/compiled/html/default/hello_body.html b/appdaemon/compiled/html/default/hello_body.html new file mode 100644 index 0000000..20458d0 --- /dev/null +++ b/appdaemon/compiled/html/default/hello_body.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/appdaemon/compiled/html/default/hello_head.html b/appdaemon/compiled/html/default/hello_head.html new file mode 100644 index 0000000..f3d270b --- /dev/null +++ b/appdaemon/compiled/html/default/hello_head.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/appdaemon/compiled/javascript/application.js b/appdaemon/compiled/javascript/application.js new file mode 100644 index 0000000..4021a34 --- /dev/null +++ b/appdaemon/compiled/javascript/application.js @@ -0,0 +1,2524 @@ +function baseswitch(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this; + + // Initialization + + self.widget_id = widget_id; + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters; + + // Toggle needs to be referenced from self for the timeout function + + self.toggle = toggle; + + // Define callbacks for on click events + // They are defined as functions below and can be any name as long as the + // 'self'variables match the callbacks array below + // We need to add them into the object for later reference + + self.OnButtonClick = OnButtonClick; + + if ("enable" in self.parameters && self.parameters.enable == 1) + { + var callbacks = + [ + {"selector": '#' + widget_id + ' > span', "action": "click", "callback": self.OnButtonClick}, + ] + } + else + { + var callbacks = [] + } + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate}, + ]; + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + + function OnStateAvailable(self, state) + { + self.state = state.state; + set_view(self, self.state) + } + + // The OnStateUpdate function will be called when the specific entity + // receives a state update - its new values will be available + // in self.state[] and returned in the state parameter + + function OnStateUpdate(self, state) + { + if (!("ignore_state" in self.parameters) || self.parameters.ignore_state == 0) + { + self.state = state.state; + set_view(self, self.state) + } + } + + function OnButtonClick(self) + { + if (self.state == self.parameters.state_active) + { + args = self.parameters.post_service_inactive + } + else + { + args = self.parameters.post_service_active + } + self.call_service(self, args); + toggle(self); + if ("momentary" in self.parameters) + { + setTimeout(function() { self.toggle(self) }, self.parameters["momentary"]) + } + } + + function toggle(self) + { + if (self.state == self.parameters.state_active) + { + self.state = self.parameters.state_inactive; + } + else + { + self.state = self.parameters.state_active; + } + set_view(self, self.state) + } + + // Set view is a helper function to set all aspects of the widget to its + // current state - it is called by widget code when an update occurs + // or some other event that requires a an update of the view + + function set_view(self, state, level) + { + if (state == self.parameters.state_active || ("active_map" in self.parameters && self.parameters.active_map.includes(state))) + { + self.set_icon(self, "icon", self.icons.icon_on); + self.set_field(self, "icon_style", self.css.icon_style_active) + } + else + { + self.set_icon(self, "icon", self.icons.icon_off); + self.set_field(self, "icon_style", self.css.icon_style_inactive) + } + if ("state_text" in self.parameters && self.parameters.state_text == 1) + { + self.set_field(self, "state_text", self.map_state(self, state)) + } + } +} + +function baseheater(widget_id, url, skin, parameters) +{ + self = this + self.widget_id = widget_id + self.parameters = parameters + + if ("monitored_entity" in self.parameters) + { + entity = self.parameters.monitored_entity + } + else + { + icon_entity = self.parameters.icon_entity + slider_entity = self.parameters.slider_entity + } + + + self.onChange = onChange + self.OnButtonClick = OnButtonClick + + var callbacks = [ + {"selector": '#' + widget_id + ' > span', "action": "click", "callback": self.OnButtonClick}, + {"observable": "Temperature", "action": "change", "callback": self.onChange}, + ] + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("icon_entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.icon_entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate}, + {"entity": parameters.slider_entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate}, + ] + } + else + { + var monitored_entities = [] + } + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + + function OnStateAvailable(self, state) + { + if ("min" in state.attributes) + { + self.minvalue = state.attributes.min + self.maxvalue = state.attributes.max + self.stepvalue = state.attributes.step + self.thermovalue = state.state + set_options(self, self.minvalue, self.maxvalue, self.stepvalue, self.thermovalue) + } + else + { + self.state = state.state + set_iconview(self, self.state) + } + } + + function OnStateUpdate(self, state) + { + if ("min" in state.attributes) + { + self.thermovalue = state.state + set_sliderview(self, self.thermovalue) + } + else + { + self.state = state.state + set_iconview(self, self.state) + } + } + + function OnButtonClick(self) + { + if (self.state == "off") + { + args = self.parameters.post_service_active + } + else + { + args = self.parameters.post_service_inactive + } + //alert(args) + self.call_service(self, args) + toggle(self) + } + + function onChange(self, state) + { + if (self.thermovalue != self.ViewModel.Temperature()) + { + self.thermovalue = self.ViewModel.Temperature() + args = self.parameters.post_service_slider_change + args["value"] = self.thermovalue + self.call_service(self, args) + } + } + + function toggle(self) + { + if (self.state == "on") + { + self.state = "off"; + } + else + { + self.state = "on"; + } + set_iconview(self, self.state) + } + + function set_options(self, minvalue, maxvalue, stepvalue, state) + { + self.set_field(self, "MinValue", minvalue) + self.set_field(self, "MaxValue", maxvalue) + self.set_field(self, "StepValue", stepvalue) + self.set_field(self, "Temperature", state) + } + + function set_iconview(self, state) + { + if (state == "on") + { + self.set_icon(self, "icon", self.icons.icon_on) + self.set_field(self, "icon_style", self.css.icon_style_active) + } + else + { + self.set_icon(self, "icon", self.icons.icon_off) + self.set_field(self, "icon_style", self.css.icon_style_inactive) + } + } + + function set_sliderview(self, state) + { + if (typeof state == 'undefined') + { + self.set_field(self, "Temperature", 0) + } + else + { + self.set_field(self, "Temperature", state) + } + } + +} +function baseinputnumber(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters + + self.onChange = onChange + + var callbacks = [ + {"observable": "SliderValue", "action": "change", "callback": self.onChange}, + ] + + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + self.state = state.state + self.minvalue = state.attributes.min + self.maxvalue = state.attributes.max + self.stepvalue = state.attributes.step + set_options(self, self.minvalue, self.maxvalue, self.stepvalue, state) + set_value(self, state) + } + + function OnStateUpdate(self, state) + { + self.state = state.state + set_value(self, state) + } + + function set_value(self, state) + { + value = self.map_state(self, state.state) + self.set_field(self, "SliderValue", value) + self.set_field(self, "sliderValue", self.format_number(self,value)) + } + + function onChange(self, state) + { + if (self.state != self.ViewModel.SliderValue()) + { + self.state = self.ViewModel.SliderValue() + args = self.parameters.post_service + args["value"] = self.state + self.call_service(self, args) + } + } + + function set_options(self, minvalue, maxvalue, stepvalue, state) + { + //alert(self.maxvalue) + self.set_field(self, "MinValue", minvalue) + self.set_field(self, "MaxValue", maxvalue) + self.set_field(self, "minValue", self.format_number(self,minvalue)) + self.set_field(self, "maxValue", self.format_number(self,maxvalue)) + self.set_field(self, "StepValue", stepvalue) + } + +} +function baseclimate(widget_id, url, skin, parameters) +{ + + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Parameters may come in useful later on + + self.parameters = parameters + + self.OnRaiseLevelClick = OnRaiseLevelClick + self.OnLowerLevelClick = OnLowerLevelClick + + var callbacks = + [ + {"selector": '#' + widget_id + ' #level-up', "action": "click", "callback": self.OnRaiseLevelClick}, + {"selector": '#' + widget_id + ' #level-down', "action": "click", "callback": self.OnLowerLevelClick}, + ] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + + if( "step" in parameters && ! isNaN(self.parameters.step)) + { + self.step = parseFloat(parameters.step) + } + else + { + self.step = 1 + } + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + self.min = state.attributes.min_temp + self.max = state.attributes.max_temp + self.level = state.attributes.temperature + if ("units" in self.parameters) + { + self.set_field(self, "unit", self.parameters.unit) + } + else + { + self.set_field(self, "unit", state.attributes["unit_of_measurement"]) + } + set_view(self, state) + } + + function OnStateUpdate(self, state) + { + self.level = state.attributes.temperature + set_view(self, state) + } + + function OnRaiseLevelClick(self) + { + self.level = parseFloat(self.level) + self.step; + if (self.level > self.max) + { + self.level = self.max + } + args = self.parameters.post_service + args["temperature"] = self.level + self.call_service(self, args) + } + + function OnLowerLevelClick(self, args) + { + self.level = parseFloat(self.level) - self.step; + if (self.level < self.min) + { + self.level = self.min + } + args = self.parameters.post_service; + args["temperature"] = self.level; + self.call_service(self, args) + } + + function set_view(self, state) + { + self.set_field(self, "level", self.format_number(self, state.attributes.current_temperature)); + if ("temperature" in state.attributes && state.attributes.temperature != null) + { + self.set_field(self, "level2", self.format_number(self, state.attributes.temperature)) + } + else + { + self.set_field(self, "level2", "auto") + } + } +} + +function basejavascript(widget_id, url, skin, parameters) +{ + // Store Args + this.widget_id = widget_id + this.parameters = parameters + this.skin = skin + + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters + + // Define callbacks for on click events + // They are defined as functions below and can be any name as long as the + // 'self'variables match the callbacks array below + // We need to add them into the object for later reference + + self.OnButtonClick = OnButtonClick + + var callbacks = + [ + {"selector": '#' + widget_id + ' > span', "action": "click","callback": self.OnButtonClick}, + ] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + var monitored_entities = + [] + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + + if ("command" in parameters) + { + command = parameters.command + } + else if ("url" in parameters || "dashboard" in parameters) + { + if ("url" in parameters) + { + url = parameters.url + } + else + { + url = "/" + parameters.dashboard + } + var i = 0; + + if ("args" in parameters) + { + + url = url + "?"; + + for (var key in parameters.args) + { + if (i != 0) + { + url = url + "&" + } + url = url + key + "=" + parameters.args[key]; + i++ + } + } + if ("skin" in parameters) + { + theskin = parameters.skin + } + else + { + theskin = skin + } + + if (i == 0) + { + url = url + "?skin=" + theskin; + i++ + } + else + { + url = url + "&skin=" + theskin; + i++ + } + + if ("sticky" in parameters) + { + if (i == 0) + { + url = url + "?sticky=" + parameters.sticky; + i++ + } + else + { + url = url + "&sticky=" + parameters.sticky; + i++ + } + } + + if ("return" in parameters) + { + if (i == 0) + { + url = url + "?return=" + parameters.return; + i++ + } + else + { + url = url + "&return=" + parameters.return; + i++ + } + } + + if ("timeout" in parameters) + { + if (i == 0) + { + url = url + "?timeout=" + parameters.timeout; + i++ + } + else + { + url = url + "&timeout=" + parameters.timeout; + i++ + } + } + + + + command = "window.location.href = '" + url + "'" + } + + self.set_icon(self, "icon", self.icons.icon_inactive); + self.set_field(self, "icon_style", self.css.icon_inactive_style); + + self.command = command; + + function OnButtonClick(self) + { + self.set_icon(self, "icon", self.icons.icon_active); + self.set_field(self, "icon_style", self.css.icon_active_style); + eval(self.command); + } +} +function baserss(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this; + + // Initialization + + self.widget_id = widget_id; + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters; + + // + // RSS Info is always in the default namespace + // + self.parameters.namespace = "default"; + + var callbacks = []; + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable; + self.OnStateUpdate = OnStateUpdate; + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + set_value(self, state) + } + + function OnStateUpdate(self, state) + { + set_value(self, state) + } + + function set_value(self, state) + { + self.story = 0 + clearTimeout(self.timer) + show_next_story(self) + self.timer = setInterval(show_next_story, self.parameters.interval * 1000, self); + } + + function show_next_story(self) + { + var stories = self.entity_state[parameters.entity].feed.entries; + self.set_field(self, "text", stories[self.story].title) + if ("show_description" in self.parameters && self.parameters.show_description === 1) + { + if ("summary" in stories[self.story]) + { + self.set_field(self, "description", stories[self.story].summary) + } + if ("description" in stories[self.story]) + { + self.set_field(self, "description", stories[self.story].description) + } + } + self.story = self.story + 1; + if ((self.story >= stories.length) || ("recent" in parameters && self.story >= parameters.recent)) + { + self.story = 0; + } + } +} +function basealarm(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Parameters may come in useful later on + + self.parameters = parameters + + self.OnButtonClick = OnButtonClick + self.OnCloseClick = OnCloseClick + self.OnDigitClick = OnDigitClick + self.OnArmHomeClick = OnArmHomeClick + self.OnArmAwayClick = OnArmAwayClick + self.OnDisarmClick = OnDisarmClick + self.OnTriggerClick = OnTriggerClick + + + var callbacks = + [ + {"selector": '#' + widget_id + ' > span', "action": "click", "callback": self.OnButtonClick}, + {"selector": '#' + widget_id + ' #close', "action": "click", "callback": self.OnCloseClick}, + {"selector": '#' + widget_id + ' #0', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "0"}}, + {"selector": '#' + widget_id + ' #1', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "1"}}, + {"selector": '#' + widget_id + ' #2', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "2"}}, + {"selector": '#' + widget_id + ' #3', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "3"}}, + {"selector": '#' + widget_id + ' #4', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "4"}}, + {"selector": '#' + widget_id + ' #5', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "5"}}, + {"selector": '#' + widget_id + ' #6', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "6"}}, + {"selector": '#' + widget_id + ' #7', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "7"}}, + {"selector": '#' + widget_id + ' #8', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "8"}}, + {"selector": '#' + widget_id + ' #9', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "9"}}, + {"selector": '#' + widget_id + ' #BS', "action": "click", "callback": self.OnDigitClick, "parameters": {"digit" : "BS"}}, + {"selector": '#' + widget_id + ' #AH', "action": "click", "callback": self.OnArmHomeClick}, + {"selector": '#' + widget_id + ' #AA', "action": "click", "callback": self.OnArmAwayClick}, + {"selector": '#' + widget_id + ' #DA', "action": "click", "callback": self.OnDisarmClick}, + {"selector": '#' + widget_id + ' #TR', "action": "click", "callback": self.OnTriggerClick}, + + ] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + self.set_view = set_view + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + self.set_field(self, "state", self.map_state(self, state.state)) + } + + function OnStateUpdate(self, state) + { + self.set_field(self, "state", self.map_state(self, state.state)) + } + + function OnButtonClick(self) + { + self.code = self.parameters.initial_string + self.set_view(self) + + $('#' + widget_id + ' > #Dialog').removeClass("modalDialogClose") + $('#' + widget_id + ' > #Dialog').addClass("modalDialogOpen") + } + + function OnCloseClick(self) + { + $('#' + widget_id + ' > #Dialog').removeClass("modalDialogOpen") + $('#' + widget_id + ' > #Dialog').addClass("modalDialogClose") + } + + function OnDigitClick(self, parameters) + { + if (parameters.digit == "BS") + { + if (self.code != self.parameters.initial_string) + { + if (self.code.length == 1) + { + self.code = self.parameters.initial_string + } + else + { + self.code = self.code.substring(0, self.code.length - 1); + } + } + } + else + { + if (self.code == self.parameters.initial_string) + { + self.code = parameters.digit + } + else + { + self.code = self.code + parameters.digit + } + } + self.set_view(self) + } + + function OnArmHomeClick(self) + { + + args = self.parameters.post_service_ah + args["code"] = self.code + self.call_service(self, args) + + self.code = self.parameters.initial_string + self.set_view(self) + } + + function OnArmAwayClick(self) + { + args = self.parameters.post_service_aa + args["code"] = self.code + self.call_service(self, args) + + self.code = self.parameters.initial_string + self.set_view(self) + } + + function OnDisarmClick(self) + { + args = self.parameters.post_service_da + args["code"] = self.code + self.call_service(self, args) + + self.code = self.parameters.initial_string + self.set_view(self) + } + + function OnTriggerClick(self) + { + args = self.parameters.post_service_tr + args["code"] = self.code + self.call_service(self, args) + + self.code = self.parameters.initial_string + self.set_view(self) + } + + function set_view(self) + { + self.set_field(self, "code", self.code) + } +} +function baseselect(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this; + + // Initialization + + self.widget_id = widget_id; + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters; + + self.initial = 1 + + self.onChange = onChange; + + var callbacks = [ + {"observable": "selectedoption", "action": "change", "callback": self.onChange} + ]; + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable; + self.OnStateUpdate = OnStateUpdate; + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks); + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + self.state = state; + set_options(self, state.attributes.options, state); + set_value(self, state) + } + + function OnStateUpdate(self, state) + { + self.state = state.state; + set_value(self, state) + } + + function set_value(self, state) + { + value = self.map_state(self, state.state); + self.set_field(self, "selectedoption", value) + } + + function onChange(self, state) + { + if (self.state != self.ViewModel.selectedoption()) + { + self.state = self.ViewModel.selectedoption(); + if (self.initial != 1) + { + args = self.parameters.post_service; + args["option"] = self.state; + self.call_service(self, args) + } + else + { + self.initial = 0 + } + } + } + + function set_options(self, options, state) + { + self.set_field(self, "inputoptions", options) + } + +} +function baseicon(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this; + + // Initialization + + self.widget_id = widget_id; + + // Parameters may come in useful later on + + self.parameters = parameters; + + var callbacks = []; + + self.OnStateAvailable = OnStateAvailable; + self.OnStateUpdate = OnStateUpdate; + + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ]; + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks); + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + + function OnStateAvailable(self, state) + { + self.state = state.state; + set_view(self, self.state) + } + + // The OnStateUpdate function will be called when the specific entity + // receives a state update - its new values will be available + // in self.state[] and returned in the state parameter + + function OnStateUpdate(self, state) + { + self.state = state.state; + set_view(self, self.state) + } + + // Set view is a helper function to set all aspects of the widget to its + // current state - it is called by widget code when an update occurs + // or some other event that requires a an update of the view + + function set_view(self, state, level) + { + if ("icons" in self.parameters) + { + if (state in self.parameters.icons) + { + self.set_icon(self, "icon", self.parameters.icons[state].icon); + self.set_field(self, "icon_style", self.parameters.icons[state].style) + } + else if ("default" in self.parameters.icons) + { + self.set_icon(self, "icon", self.parameters.icons.default.icon); + self.set_field(self, "icon_style", self.parameters.icons.default.style) + } + else + { + self.set_icon(self, "icon", "fa-circle-thin"); + self.set_field(self, "icon_style", "color: white") + } + + } + + if ("state_text" in self.parameters && self.parameters.state_text == 1) + { + self.set_field(self, "state_text", self.map_state(self, state)) + } + } +} + +function baseiframe(widget_id, url, skin, parameters) +{ + self = this + + // Initialization + + self.parameters = parameters; + + var callbacks = [] + + var monitored_entities = [] + + // Call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks); + + // Set the url + + if ("url_list" in parameters || "img_list" in parameters || "entity_picture" in parameters) + { + self.index = 0; + refresh_frame(self) + } + + function refresh_frame(self) + { + if ("url_list" in self.parameters) + { + self.set_field(self, "frame_src", self.parameters.url_list[self.index]); + self.set_field(self, "img_src", "/images/Blank.gif"); + size = self.parameters.url_list.length + } + else if ("img_list" in self.parameters) + { + var url = self.parameters.img_list[self.index]; + if (url.indexOf('?') > -1) + { + url = url + "&time=" + Math.floor((new Date).getTime()/1000); + } + else + { + url = url + "?time=" + Math.floor((new Date).getTime()/1000); + } + self.set_field(self, "img_src", url); + size = self.parameters.img_list.length + } + else if ("entity_picture" in self.parameters) + { + var url = self.parameters.entity_picture; + if (url.indexOf('?') > -1) + { + url = url + "&time=" + Math.floor((new Date).getTime()/1000); + } + else + { + url = url + "?time=" + Math.floor((new Date).getTime()/1000); + } + self.set_field(self, "img_src", url); + size = 1 + } + + if ("refresh" in self.parameters) + { + self.index = self.index + 1; + if (self.index == size) + { + self.index = 0; + } + setTimeout(function() {refresh_frame(self)}, self.parameters.refresh * 1000); + } + } +} +function basemedia(widget_id, url, skin, parameters) +{ + self = this; + + // Initialization + + self.widget_id = widget_id; + + // Parameters may come in useful later on + + self.parameters = parameters; + + self.OnPlayButtonClick = OnPlayButtonClick; + self.OnPreviousButtonClick = OnPreviousButtonClick; + self.OnNextButtonClick = OnNextButtonClick; + self.OnRaiseLevelClick = OnRaiseLevelClick; + self.OnLowerLevelClick = OnLowerLevelClick; + + self.min_level = 0; + self.max_level = 1; + + if ("step" in self.parameters) + { + self.step = self.parameters.step / 100; + } + else + { + self.step = 0.1; + } + + var callbacks = + [ + {"selector": '#' + widget_id + ' #play', "action": "click", "callback": self.OnPlayButtonClick}, + {"selector": '#' + widget_id + ' #level-up', "action": "click", "callback": self.OnRaiseLevelClick}, + {"selector": '#' + widget_id + ' #level-down', "action": "click", "callback": self.OnLowerLevelClick}, + {"selector": '#' + widget_id + ' #previous', "action": "click", "callback": self.OnPreviousButtonClick}, + {"selector": '#' + widget_id + ' #next', "action": "click", "callback": self.OnNextButtonClick} + ]; + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable; + self.OnStateUpdate = OnStateUpdate; + + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ]; + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks); + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + + function OnStateAvailable(self, state) + { + self.entity = state.entity_id; + self.level = state.attributes.volume_level; + set_view(self, state) + if ("dump_capabilities" in self.parameters && self.parameters["dump_capabilities"] == "1") + { + display_supported_functions(self) + } + } + + // The OnStateUpdate function will be called when the specific entity + // receives a state update - its new values will be available + // in self.state[] and returned in the state parameter + + function OnStateUpdate(self, state) + { + self.level = state.attributes.volume_level; + set_view(self, state) + } + + function OnPlayButtonClick(self) + { + if (self.entity_state[self.entity].state !== "playing") + { + if (is_supported(self, "PLAY_MEDIA")) + { + args = self.parameters.post_service_play_pause; + self.call_service(self, args) + } + else + { + console.log("Play attribute not supported") + } + } + else + { + if (is_supported(self, "PAUSE")) + { + args = self.parameters.post_service_pause; + self.call_service(self, args) + } + else if (is_supported(self, "STOP")) + { + args = self.parameters.post_service_stop; + self.call_service(self, args) + } + else if (is_supported(self, "STOP")) + { + args = self.parameters.post_service_stop; + self.call_service(self, args) + } + else + { + // Try Play/Pause + args = self.parameters.post_service_play_pause; + self.call_service(self, args) + } + } + } + + function OnPreviousButtonClick(self) + { + if (is_supported(self, "PREVIOUS_TRACK")) + { + args = self.parameters.post_service_previous; + self.call_service(self, args) + } + else + { + console.log("NEXT_TRACK attribute not supported") + } + } + + function OnNextButtonClick(self) + { + if (is_supported(self, "NEXT_TRACK")) + { + args = self.parameters.post_service_next; + self.call_service(self, args) + } + else + { + console.log("NEXT_TRACK attribute not supported") + } + } + + + + function OnRaiseLevelClick(self) + { + self.level = Math.round((self.level + self.step) * 100) / 100; + if (self.level > self.max_level) + { + self.level = self.max_level + } + + args = self.parameters.post_service_level; + args["volume_level"] = self.level; + self.call_service(self, args) + + } + + function OnLowerLevelClick(self) + { + self.level = Math.round((self.level - self.step) * 100) / 100; + if (self.level < self.min_level) + { + self.level = self.min_level + } + + args = self.parameters.post_service_level; + args["volume_level"] = self.level; + self.call_service(self, args) + + + } + + function set_view(self, state) + { + if (state.state === "playing") + { + self.set_field(self, "play_icon_style", self.css.icon_style_active) + self.set_icon(self, "play_icon", self.icons.pause_icon) + } + else + { + self.set_field(self, "play_icon_style", self.css.icon_style_inactive) + self.set_icon(self, "play_icon", self.icons.play_icon) + } + + if ("media_artist" in state.attributes) + { + self.set_field(self, "artist", state.attributes.media_artist); + } + + if ("media_album_name" in state.attributes) + { + self.set_field(self, "album", state.attributes.media_album_name) + } + if ("media_title" in state.attributes) + { + if ("truncate_name" in self.parameters) + { + name = state.attributes.media_title.substring(0, self.parameters.truncate_name); + } + else + { + name = state.attributes.media_title + } + self.set_field(self, "media_title", name); + } + if ("volume_level" in state.attributes) + { + self.set_field(self, "level", Math.round(state.attributes.volume_level * 100)) + } + else + { + self.set_field(self, "level", 0) + } + + } + + function is_supported(self, attr) + { + var support = + { + "PAUSE": 1, + "SEEK": 2, + "VOLUME_SET": 4, + "VOLUME_MUTE": 8, + "PREVIOUS_TRACK": 16, + "NEXT_TRACK": 32, + "TURN_ON": 128, + "TURN_OFF": 256, + "PLAY_MEDIA": 512, + "VOLUME_STEP": 1024, + "SELECT_SOURCE": 2048, + "STOP": 4096, + "CLEAR_PLAYLIST": 8192, + "PLAY": 16384, + "SHUFFLE_SET": 32768 + }; + + var supported = self.entity_state[parameters.entity].attributes.supported_features; + + if (attr in support) + { + var attr_value = support[attr]; + if ((supported & attr_value) == attr_value) + { + return true + } + else + { + return false + } + } + else + { + console.log("Unknown media player attribute: " + attr) + return false + } + } + + function display_supported_functions(self) + { + console.log(self.parameters.entity); + console.log("Supported Features: " + self.entity_state[parameters.entity].attributes.supported_features); + console.log("PAUSE: " + is_supported(self, "PAUSE")) + console.log("SEEK: " + is_supported(self, "SEEK")) + console.log("VOLUME_SET: " + is_supported(self, "VOLUME_SET")) + console.log("VOLUME_MUTE: " + is_supported(self, "VOLUME_MUTE")) + console.log("PREVIOUS_TRACK: " + is_supported(self, "PREVIOUS_TRACK")) + console.log("NEXT_TRACK: " + is_supported(self, "NEXT_TRACK")) + console.log("TURN_ON: " + is_supported(self, "TURN_ON")) + console.log("TURN_OFF: " + is_supported(self, "TURN_OFF")) + console.log("PLAY_MEDIA: " + is_supported(self, "PLAY_MEDIA")) + console.log("VOLUME_STEP: " + is_supported(self, "VOLUME_STEP")) + console.log("SELECT_SOURCE: " + is_supported(self, "SELECT_SOURCE")) + console.log("STOP: " + is_supported(self, "STOP")) + console.log("CLEAR_PLAYLIST: " + is_supported(self, "CLEAR_PLAYLIST")) + console.log("PLAY: " + is_supported(self, "PLAY")) + console.log("SHUFFLE_SET: " + is_supported(self, "SHUFFLE_SET")) + } +} + +function baseradial(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters + + var callbacks = [] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + activateChart(self, state) + } + + function OnStateUpdate(self, state) + { + set_value(self, state) + } + + function set_value(self, state) + { + self.gauge.value = state.state + // self.gauge.update() + } + + function activateChart(self, state) { + self.gauge = new RadialGauge({ + renderTo: document.getElementById(self.widget_id).getElementsByClassName('gaugeclass')[0], + type: 'radial-gauge', + width: '120', + height: '120', + //valueInt: 2, + //valueDec: 1, + colorTitle: '#333', + //minValue: 17, + //maxValue: 25, + //minorTicks: 2, + //strokeTicks: true, + }) + self.gauge.value = state.state + self.gauge.update(self.parameters.settings) + //self.gauge.draw() + } +} +function baseclock(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Parameters may come in useful later on + + self.parameters = parameters + + // Define callbacks for on click events + // They are defined as functions below and can be any name as long as the + // 'self'variables match the callbacks array below + // We need to add them into the object for later reference + + var callbacks = [] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + var monitored_entities = [] + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + + updateTime(self) + + setInterval(updateTime, 500, self); + + function updateTime(self) + { + var today = new Date(); + h = today.getHours(); + m = today.getMinutes(); + s = today.getSeconds(); + m = formatTime(m); + + if ("date_format_country" in self.parameters) + { + if ("date_format_options" in self.parameters) + { + self.set_field(self, "date", today.toLocaleDateString(self.parameters.date_format_country, self.parameters.date_format_options)); + } + else + { + self.set_field(self, "date", today.toLocaleDateString(self.parameters.date_format_country)); + } + } + else + { + self.set_field(self, "date", today.toLocaleDateString()); + } + + if ("time_format" in self.parameters && self.parameters.time_format == "24hr") + { + time = h + ":" + m; + pm = "" + } + else + { + time = formatHours(h) + ":" + m; + pm = " " + formatAmPm(h) + } + + if ("show_seconds" in self.parameters && self.parameters.show_seconds == 1) + { + time = time + ":" + formatTime(s) + } + + time = time + pm + self.set_field(self, "time", time); + } + + function formatTime(i) + { + if (i < 10 ) + { + return "0" + i; + } + else + { + return i; + } + } + + function formatAmPm(h) + { + if (h >= 12) + { + return "PM"; + } + else + { + return "AM"; + } + } + + function formatHours(h) + { + if (h > 12) + { + return h - 12; + } + else if (h == 0) + { + return 12; + } + else + { + return h; + } + } +} + +function baseslider(widget_id, url, skin, parameters) +{ + + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Parameters may come in useful later on + + self.parameters = parameters + + self.OnRaiseLevelClick = OnRaiseLevelClick + self.OnLowerLevelClick = OnLowerLevelClick + + var callbacks = + [ + {"selector": '#' + widget_id + ' #level-up', "action": "click", "callback": self.OnRaiseLevelClick}, + {"selector": '#' + widget_id + ' #level-down', "action": "click", "callback": self.OnLowerLevelClick}, + ] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + self.min = state.attributes.min + self.max = state.attributes.max + self.step = state.attributes.step + self.level = state.state + if ("units" in self.parameters) + { + self.set_field(self, "unit", self.parameters.units) + } + set_view(self, state) + } + + function OnStateUpdate(self, state) + { + self.level = state.state + set_view(self, state) + } + + function OnRaiseLevelClick(self) + { + self.level = parseFloat(self.level) + self.step; + if (self.level > self.max) + { + self.level = self.max + } + args = self.parameters.post_service + args["value"] = self.level + self.call_service(self, args) + } + + function OnLowerLevelClick(self, args) + { + self.level = parseFloat(self.level) - self.step; + if (self.level < self.min) + { + self.level = self.min + } + args = self.parameters.post_service + args["value"] = self.level + self.call_service(self, args) + } + + function set_view(self, state) + { + self.set_field(self, "level", self.format_number(self, state.state)) + } +} +function baseentitypicture(widget_id, url, skin, parameters) +{ + self = this + + // Initialization + + self.parameters = parameters; + + var callbacks = [] + + self.OnStateAvailable = OnStateAvailable; + self.OnStateUpdate = OnStateUpdate; + + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ]; + + if ("base_url" in parameters && parameters.base_url != "") { + self.base_url = parameters.base_url; + }else{ + self.base_url = ""; + } + + // Call the parent constructor to get things moving + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks); + + // Function Definitions + + function OnStateAvailable(self, state) + { + set_view(self, state) + } + + // The OnStateUpdate function will be called when the specific entity + // receives a state update - its new values will be available + // in self.state[] and returned in the state parameter + + function OnStateUpdate(self, state) + { + set_view(self, state) + } + + function set_view(self, state) + { + if("entity_picture" in state.attributes){ + self.set_field(self, "img_inernal_src", self.base_url + state.attributes["entity_picture"]); + self.set_field(self, "img_internal_style", ""); + }else{ + self.set_field(self, "img_inernal_src", ""); + self.set_field(self, "img_internal_style", "display: none;"); + } + } +} +function basedisplay(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this; + + // Initialization + + self.widget_id = widget_id; + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters; + + var callbacks = []; + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable; + self.OnStateUpdate = OnStateUpdate; + self.OnSubStateAvailable = OnSubStateAvailable; + self.OnSubStateUpdate = OnSubStateUpdate; + + var monitored_entities = []; + + if ("entity" in parameters && parameters.entity != "") + { + // Make sure that we monitor the entity, not an attribute of it + split_entity = parameters.entity.split(".") + self.entity = split_entity[0] + "." + split_entity[1] + if (split_entity.length > 2) + { + self.entity_attribute = split_entity[2] + } + // Check if the sub_entity should be created by monitoring an attribute of the entity + if ("entity_to_sub_entity_attribute" in parameters && parameters.entity_to_sub_entity_attribute != "") + { + self.sub_entity = self.entity + self.sub_entity_attribute = parameters.entity_to_sub_entity_attribute + } + } + + // Only set up the sub_entity if it was not created already with the entity + attribute + if ("sub_entity" in parameters && parameters.sub_entity != "" && !("sub_entity" in self)) + { + // Make sure that we monitor the sub_entity, not an attribute of it + split_sub_entity = parameters.sub_entity.split(".") + self.sub_entity = split_sub_entity[0] + "." + split_sub_entity[1] + if (split_sub_entity.length > 2) + { + self.sub_entity_attribute = split_sub_entity[2] + } + // Check if the entity should be created by monitoring an attribute of the sub_entity + if ("sub_entity_to_entity_attribute" in parameters && !("entity" in self)) + { + self.entity = self.sub_entity + self.entity_attribute = parameters.sub_entity_to_entity_attribute + } + } + + if ("entity" in self) + { + monitored_entities.push({"entity": self.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate}) + } + if ("sub_entity" in self) + { + monitored_entities.push({"entity": self.sub_entity, "initial": self.OnSubStateAvailable, "update": self.OnSubStateUpdate}) + } + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks); + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + set_value(self, state) + } + + function OnStateUpdate(self, state) + { + set_value(self, state) + } + + function OnSubStateAvailable(self, state) + { + set_sub_value(self, state) + } + + function OnSubStateUpdate(self, state) + { + set_sub_value(self, state) + } + + function set_value(self, state) + { + if ("entity_attribute" in self) { + value = state.attributes[self.entity_attribute] + } + else + { + value = state.state + } + + if (isNaN(value)) + { + self.set_field(self, "value_style", self.parameters.css.text_style); + self.set_field(self, "value", self.map_state(self, value)) + } + else + { + self.set_field(self, "value_style", self.parameters.css.value_style); + self.set_field(self, "value", self.format_number(self, value)); + self.set_field(self, "unit_style", self.parameters.css.unit_style); + if ("units" in self.parameters) + { + self.set_field(self, "unit", self.parameters.units) + } + else + { + self.set_field(self, "unit", state.attributes["unit_of_measurement"]) + } + } + } + + function set_sub_value(self, state) + { + if ("sub_entity_attribute" in self && self.sub_entity_attribute != "") + { + value = state.attributes[self.sub_entity_attribute] + } + else + { + value = state.state + } + + if ("sub_entity_map" in self.parameters) + { + self.set_field(self, "state_text", self.parameters.sub_entity_map[value]) + } + else + { + self.set_field(self, "state_text", value) + } + } +} + +function baselight(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Parameters may come in useful later on + + self.parameters = parameters + + // Parameter handling + + if ("monitored_entity" in self.parameters) + { + entity = self.parameters.monitored_entity + } + else + { + entity = self.parameters.entity + } + + if ("on_brightness" in self.parameters) + { + self.on_brightness = self.parameters.on_brightness + } + else + { + self.on_brightness = 127 + } + + // Define callbacks for on click events + // They are defined as functions below and can be any name as long as the + // 'self'variables match the callbacks array below + // We need to add them into the object for later reference + + self.OnButtonClick = OnButtonClick + self.OnRaiseLevelClick = OnRaiseLevelClick + self.OnLowerLevelClick = OnLowerLevelClick + + var callbacks = + [ + {"selector": '#' + widget_id + ' > span', "action": "click", "callback": self.OnButtonClick}, + {"selector": '#' + widget_id + ' #level-up', "action": "click", "callback": self.OnRaiseLevelClick}, + {"selector": '#' + widget_id + ' #level-down', "action": "click", "callback": self.OnLowerLevelClick}, + ] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + var monitored_entities = + [ + {"entity": entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + + function OnStateAvailable(self, state) + { + self.state = state.state; + if ("brightness" in state.attributes) + { + self.level = state.attributes.brightness + } + else + { + self.level = 0 + } + set_view(self, self.state, self.level) + } + + // The OnStateUpdate function will be called when the specific entity + // receives a state update - its new values will be available + // in self.state[] and returned in the state parameter + + function OnStateUpdate(self, state) + { + self.state = state.state; + if ("brightness" in state.attributes) + { + self.level = state.attributes.brightness + } + else + { + self.level = 0 + } + + set_view(self, self.state, self.level) + } + + function OnButtonClick(self) + { + if (self.state == "off") + { + args = self.parameters.post_service_active + if ("on_attributes" in self.parameters) + { + for (var attr in self.parameters.on_attributes) + { + args[attr] = self.parameters.on_attributes[attr] + } + } + } + else + { + args = self.parameters.post_service_inactive + } + self.call_service(self, args) + toggle(self) + } + + function OnRaiseLevelClick(self) + { + self.level = self.level + 255/10; + self.level = parseInt(self.level) + if (self.level > 255) + { + self.level = 255 + } + args = self.parameters.post_service_active + args["brightness"] = self.level + self.call_service(self, args) + } + + function OnLowerLevelClick(self) + { + self.level = self.level - 255/10; + if (self.level < 0) + { + self.level = 0; + } + self.level = parseInt(self.level) + if (self.level == 0) + { + args = self.parameters.post_service_inactive + } + else + { + args = self.parameters.post_service_active + args["brightness"] = self.level + } + self.call_service(self, args) + } + + function toggle(self) + { + if (self.state == "on") + { + self.state = "off"; + self.level = 0 + } + else + { + self.state = "on"; + } + set_view(self, self.state, self.level) + } + + // Set view is a helper function to set all aspects of the widget to its + // current state - it is called by widget code when an update occurs + // or some other event that requires a an update of the view + + function set_view(self, state, level) + { + + if (state == "on") + { + // Set Icon will set the style correctly for an icon + self.set_icon(self, "icon", self.icons.icon_on) + // Set view will set the view for the appropriate field + self.set_field(self, "icon_style", self.css.icon_style_active) + } + else + { + self.set_icon(self, "icon", self.icons.icon_off) + self.set_field(self, "icon_style", self.css.icon_style_inactive) + } + if (typeof level == 'undefined') + { + self.set_field(self, "level", 0) + } + else + { + self.set_field(self, "level", Math.ceil((level*100/255) / 10) * 10) + } + } +} + +function basegauge(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters + + var callbacks = [] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + + self.gauge = new JustGage({ + parentNode: $('#' + widget_id + ' > div')[0], + //id: "graph", + value: 0, + nogradient: true, + levelColors: [self.parameters.low_color, self.parameters.med_color, self.parameters.high_color], + labelFontColor: self.parameters.color, + valueFontColor: self.parameters.color, + levelColorsGradient: false, + gaugeColor: self.parameters.bgcolor, + symbol: self.parameters.units, + min: self.parameters.min, + max: self.parameters.max, + }); + + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + set_value(self, state) + } + + function OnStateUpdate(self, state) + { + set_value(self, state) + } + + function set_value(self, state) + { + self.gauge.refresh(state.state) + } +} +function basetemperature(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters + + var callbacks = [] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable + self.OnStateUpdate = OnStateUpdate + + if ("entity" in parameters) + { + var monitored_entities = + [ + {"entity": parameters.entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate} + ] + } + else + { + var monitored_entities = [] + } + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + + // Function Definitions + + // The StateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateAvailable(self, state) + { + drawChart(self, state) + set_value(self, state) + } + + function OnStateUpdate(self, state) + { + set_value(self, state) + } + + function set_value(self, state) + { + self.gauge.value = state.state + //self.gauge.update() + } + function drawChart(self, state) + { + self.gauge = new LinearGauge({ + renderTo: document.getElementById(self.widget_id).getElementsByClassName('gaugeclass')[0], + type: 'linear-gauge', + width: '120', + height: '120', + valueInt: 2, + valueDec: 1, + colorTitle: '#333', + minValue: 17, + maxValue: 25, + //majorTicks: [0, 5, 10, 15, 20, 25, 30, 35], + minorTicks: 2, + strokeTicks: true + }); + self.gauge.value = state.state + self.gauge.update(self.parameters.settings) + } +} +function baseweather(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this; + + // Initialization + + self.widget_id = widget_id; + + // Parameters may come in useful later on + + self.parameters = parameters; + + var callbacks = []; + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + self.OnStateAvailable = OnStateAvailable; + self.OnStateUpdate = OnStateUpdate; + + // Map will be used to know what field are we going to update from what sensor + self.entities_map = {} + + var monitored_entities = [] + + var entities = $.extend({}, parameters.entities, parameters.sensors); + for (var key in entities) + { + var entity = entities[key] + if (entity != '' && check_if_forecast_sensor(parameters.show_forecast, entity)) + { + monitored_entities.push({ + "entity": entity, "initial": self.OnStateAvailable, "update": self.OnStateUpdate + }) + self.entities_map[entity] = key + } + } + + // If forecast is disabled - don't monitor the forecast sensors + function check_if_forecast_sensor(show_forecast, entity) + { + if (show_forecast) + { + return true + } + else if(entity.substring(entity.length - 2) === "_1") + { + return false + } + else + { + return true + } + } + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks); + + // Function Definitions + + // The OnStateAvailable function will be called when + // self.state[] has valid information for the requested entity + // state is the initial state + // Methods + + function OnStateUpdate(self, state) + { + set_view(self, state) + } + + function OnStateAvailable(self, state) + { + field = self.entities_map[state.entity_id] + if (field == 'temperature') + { + self.set_field(self, "unit", state.attributes.unit_of_measurement) + } + else if (field == 'wind_speed') + { + self.set_field(self, "wind_unit", state.attributes.unit_of_measurement) + } + else if (field == 'pressure') + { + self.set_field(self, "pressure_unit", state.attributes.unit_of_measurement) + } + else if (field == 'precip_intensity') + { + self.set_field(self, "rain_unit", state.attributes.unit_of_measurement) + } + set_view(self, state) + } + + function set_view(self, state) + { + field = self.entities_map[state.entity_id] + if (field) + { + if (field == 'precip_type') + { + self.set_field(self, "precip_type_icon", self.parameters.icons[state.state]) + } + else if (field == 'forecast_precip_type') + { + self.set_field(self, "forecast_precip_type_icon", self.parameters.icons[state.state]) + } + else if (field == 'wind_bearing') + { + var counts = [45, 90, 135, 180, 225, 270, 315] + var goal = (parseInt(state.state) + 270) % 360 + var closest = counts.reduce(function(prev, curr) { + return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev); + }); + self.set_field(self, "bearing_icon", "mdi-rotate-" + closest) + } + self.set_field(self, field, state.state) + } + } +} + +function baseerror(widget_id, url, skin, parameters) +{ + // Will be using "self" throughout for the various flavors of "this" + // so for consistency ... + + self = this + + // Initialization + + self.widget_id = widget_id + + // Store on brightness or fallback to a default + + // Parameters may come in useful later on + + self.parameters = parameters + + var callbacks = [] + + // Define callbacks for entities - this model allows a widget to monitor multiple entities if needed + // Initial will be called when the dashboard loads and state has been gathered for the entity + // Update will be called every time an update occurs for that entity + + var monitored_entities = [] + + // Finally, call the parent constructor to get things moving + + WidgetBase.call(self, widget_id, url, skin, parameters, monitored_entities, callbacks) + +} diff --git a/appdaemon/compiled/javascript/default/hello_init.js b/appdaemon/compiled/javascript/default/hello_init.js new file mode 100644 index 0000000..56a360d --- /dev/null +++ b/appdaemon/compiled/javascript/default/hello_init.js @@ -0,0 +1,101 @@ +$(function(){ //DOM Ready + + function navigate(url) + { + window.location.href = url; + } + + $(document).attr("title", "Hello Panel"); + content_width = (120 + 5) * 8 + 5 + $('.gridster').width(content_width) + $(".gridster ul").gridster({ + widget_margins: [5, 5], + widget_base_dimensions: [120, 120], + avoid_overlapped_widgets: true, + max_rows: 15, + max_size_x: 8, + shift_widgets_up: false + }).data('gridster').disable(); + + // Add Widgets + + var gridster = $(".gridster ul").gridster().data('gridster'); + + gridster.add_widget('
  • ', 2, 2, 1, 1) + + + + var widgets = {} + // Initialize Widgets + + widgets["default-label"] = new basedisplay("default-label", "", "default", {'widget_type': 'basedisplay', 'fields': {'title': '', 'title2': '', 'value': 'Hello World', 'unit': '', 'state_text': ''}, 'static_css': {'title_style': 'color: #fff;', 'title2_style': 'color: #fff;', 'unit_style': '', 'value_style': 'color: #fff;', 'state_text_style': 'color: #fff;', 'widget_style': 'background-color: #444;', 'container_style': ''}, 'css': {}, 'icons': [], 'static_icons': [], 'namespace': 'default'}) + + + // Setup click handler to cancel timeout navigations + + $( ".gridster" ).click(function(){ + clearTimeout(myTimeout); + if (myTimeoutSticky) { + myTimeout = setTimeout(function() { navigate(myTimeoutUrl); }, myTimeoutDelay); + } + }); + + // Set up timeout + + var myTimeout; + var myTimeoutUrl; + var myTimeoutDelay; + var myTimeoutSticky = 0; + + if (location.search != "") + { + var query = location.search.substr(1); + var result = {}; + query.split("&").forEach(function(part) { + var item = part.split("="); + result[item[0]] = decodeURIComponent(item[1]); + }); + + if ("timeout" in result && "return" in result) + { + url = result.return + argcount = 0 + for (arg in result) + { + if (arg != "timeout" && arg != "return" && arg != "sticky") + { + if (argcount == 0) + { + url += "?"; + } + else + { + url += "?"; + } + argcount ++; + url += arg + "=" + result[arg] + } + } + if ("sticky" in result) + { + myTimeoutSticky = (result.sticky == "1"); + } + myTimeoutUrl = url; + myTimeoutDelay = result.timeout * 1000; + myTimeout = setTimeout(function() { navigate(url); }, result.timeout * 1000); + } + } + + // Start listening for HA Events + if (location.protocol == 'https:') + { + wsprot = "wss:" + } + else + { + wsprot = "ws:" + } + var stream_url = wsprot + '//' + location.host + '/stream' + ha_status(stream_url, "Hello Panel", widgets); + +}); \ No newline at end of file diff --git a/appdaemon/dashboards/Hello.dash b/appdaemon/dashboards/Hello.dash new file mode 100755 index 0000000..cead237 --- /dev/null +++ b/appdaemon/dashboards/Hello.dash @@ -0,0 +1,14 @@ +# +# Main arguments, all optional +# +title: Hello Panel +widget_dimensions: [120, 120] +widget_margins: [5, 5] +columns: 8 + +label: + widget_type: label + text: Hello World + +layout: + - label(2x2) diff --git a/appdaemon/secrets-example.yaml b/appdaemon/secrets-example.yaml new file mode 100644 index 0000000..ebcbedd --- /dev/null +++ b/appdaemon/secrets-example.yaml @@ -0,0 +1,2 @@ +# General Config +ha_token: "A very big string here" diff --git a/docker/run-appdaemon.sh b/docker/run-appdaemon.sh new file mode 100755 index 0000000..5acab20 --- /dev/null +++ b/docker/run-appdaemon.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# First run +#mkdir /opt/appdaemon && cd /opt/appdaemon +#git clone https://github.com/home-assistant/appdaemon.git . + +# All subsequent runs: update to latest app daemon, build, run +cd /opt/appdaemon +git checkout master +git pull +LATEST=`git tag | sort -r | grep -v b | head -n1` +git checkout ${LATEST} +docker build -t appdaemon:latest -t appdaemon:${LATEST} . + +docker container stop home-assistant-dashboard +docker container rm home-assistant-dashboard + +docker run -it --name home-assistant-dashboard \ + --restart unless-stopped \ + --network docker-private \ + -e DEBUG=1 \ + -e HA_URL="http://home-assistant:8123" \ + -l traefik.frontend.rule=Host:home-automation-dashboard.domain.tld \ + -l traefik.frontend.passHostHeader=true \ + -l traefik.port=5050 \ + -v /etc/localtime:/etc/localtime:ro \ + -v /var/home-assistant/appdaemon:/conf \ + appdaemon:latest diff --git a/docker/run-home-assistant.sh b/docker/run-home-assistant.sh index 0cebf40..1d2fd72 100755 --- a/docker/run-home-assistant.sh +++ b/docker/run-home-assistant.sh @@ -16,6 +16,8 @@ else REPO="homeassistant/raspberrypi3-homeassistant" fi +docker pull ${REPO} + touch /var/home-assistant/known_devices.yaml docker container stop home-assistant