definition( name: "Hubigraphs", namespace: "tchoward", author: "Thomas Howard", description: "Hubitat Graph Creator Parent App", category: "My Apps", installOnOpen: true, iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") //V 1.0 Ordering, Color and Common API Update //V 1.8 Smoother sliders, bug fixes //V 2.0 Support for Time Graphs //V 2.1 Support for Heatmaps //V 3.3 Radar Tiles //V 0.1 Beta - Weather Tiles 2 preferences { // The parent app preferences are pretty simple: just use the app input for the child app. page(name: "mainPage", title: "Graph Creator", install: true, uninstall: true, submitOnChange: true) page(name: "setupOpenWeather", title: "Setup Open Weather", nextPage: "mainPage") page(name: "setupLongTermStorage", title: "Setup Long Term Storage") } def call(Closure code) { code.setResolveStrategy(Closure.DELEGATE_ONLY); code.setDelegate(this); code.call(); } def getLongTermStorage() { lts = getChildAppByLabel("Hubigraph Long Term Storage") } def getEvents(Map map){ sensor = map.sensor; attribute = map.attribute; then = map.start_time; respEvents = sensor.statesSince(attribute, then, [max: 2000]).collect{[ date: it.date, value: it.value]} respEvents = respEvents.flatten(); respEvents = respEvents.reverse(); return respEvents; } def getData(sensor, attribute, lts, time){ return_data = []; then = time; if (lts) { ltsApp = getChildAppByLabel("Hubigraph Long Term Storage"); return_data = ltsApp.getFileData(sensor, attribute); if (return_data.size()> 0) then = return_data[return_data.size()-1].date; } events = getEvents(sensor: sensor, attribute: attribute, start_time: then); return_data = (return_data << events).flatten(); return return_data; } def ltsAvailable(sensor, attribute){ lts = getChildAppByLabel("Hubigraph Long Term Storage"); if (lts!=null){ return lts.isStorage(sensor, attribute); } else { return false; } } def mainPage(){ if (latitude && longitude && apikey) { childDevice = getChildDevice("OPEN_WEATHER${app.id}"); log.debug(childDevice); if (!childDevice) { device_name="Open Weather Child Device"; log.debug("Creating Device $device_name"); childDevice = addChildDevice("tchoward", "OpenWeather Hubigraph Driver", "OPEN_WEATHER${app.id}", null,[completedSetup: true, label: device_name]) log.info "Successfully Created Child" } if (childDevice) { childDevice.setupDevice(latitude: latitude, longitude: longitude, apiKey: apikey, pollInterval: open_weather_refresh_rate); childDevice.pollData(); } } dynamicPage(name: "mainPage"){ section { app(name: "hubiStorage", appName: "Hubigraph Long Term Storage", namespace: "tchoward", title: "Setup Long Term Storage", multiple: true) } section { app(name: "hubiWidget", appName: "Hubigraph Bubble Widget", namespace: "tchoward", title: "Create New Bubble Widget", multiple: true) app(name: "hubiBarGraph", appName: "Hubigraph Bar Graph", namespace: "tchoward", title: "Create New Bar Graph", multiple: true) app(name: "hubiRangeBar", appName: "Hubigraph Range Bar", namespace: "tchoward", title: "Create New Range Bar", multiple: true) app(name: "hubiGraphTime", appName: "Hubigraph Time Line", namespace: "tchoward", title: "Create New Time Line", multiple: true) app(name: "hubiGauge", appName: "Hubigraph Gauge", namespace: "tchoward", title: "Create New Gauge", multiple: true) app(name: "hubiTimeGraph", appName: "Hubigraph Time Graph", namespace: "tchoward", title: "Create New Time Graph", multiple: true) app(name: "hubiHeatMap", appName: "Hubigraph Heat Map", namespace: "tchoward", title: "Create New Heat Map", multiple: true) app(name: "hubiWeather", appName: "Hubigraph Weather Tile", namespace: "tchoward", title: "Create New Weather Tile", multiple: true) app(name: "hubiForecast", appName: "Hubigraph Forecast Tile", namespace: "tchoward", title: "Create New Forecast Tile", multiple: true) app(name: "hubiWeather2", appName: "Hubigraph Weather Tile 2", namespace: "tchoward", title: "Create New Weather Tile 2", multiple: true) app(name: "hubiRadar", appName: "Hubigraph Radar Tile", namespace: "tchoward", title: "Create New Radar Tile", multiple: true) } section { href name: "setupOpenWeather", title: "Setup Up Open Weather for Weather Tile", description: "", page: "setupOpenWeather" } } } def setupOpenWeather(){ def updateEnum = ['Manual Poll Only','1 Minute','5 Minutes', '10 Minutes', '15 Minutes', '30 Minutes', '1 Hour', '3 Hours']; def location = getLocation(); dynamicPage(name: "setupOpenWeather"){ section{ input( type: "enum", name: "open_weather_refresh_rate", title: "Select OpenWeather Update Rate", multiple: false, required: false, options: updateEnum, defaultValue: "5 Minutes"); input( type: "text", name: "latitude", title:"Latitude (Default = Hub Location)", defaultValue: location.latitude); input( type: "text", name: "longitude", title:"Longitude (Default = Hub Location)", defaultValue: location.longitude); input( type: "text", name: "apikey", title: "OpenWeather Key", defaultValue:"", submitOnChange: true); } } } def getOpenWeatherData(){ childDevice = getChildDevice("OPEN_WEATHER${app.id}"); if (!childDevice){ log.debug("Error: No Child Found"); return null; } return(childDevice.getWeatherData()); } def installed() { log.debug "Installed with settings: ${settings}" initialize() } def updated() { log.debug "Updated with settings: ${settings}" unsubscribe() initialize() } def initialize() { // nothing needed here, since the child apps will handle preferences/subscriptions // this just logs some messages for demo/information purposes log.debug "there are ${childApps.size()} child smartapps" childApps.each {child -> log.debug "child app: ${child.label}" } } /******************************************************************************************************************************** ********************************************************************************************************************************* ****************************************** NEW FORM FUNCTIONS******************************************************************** ********************************************************************************************************************************* *********************************************************************************************************************************/ def hubiForm_container(child, containers, numPerRow=1){ if (numPerRow == 0){ style = """style="margin: 0 !important; padding: 0 !important;""" numPerRow = 1 } else { style = ""; } child.call(){ def html_ = """
""" containers.each{container-> html_ += """
""" html_ += container; html_ += """
""" } html_ += """
""" paragraph (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_subcontainer(Map map, child){ child.call(){ def containers = map.objects; def breakdown = map.breakdown; def html_ = """
""" count = 0; containers.each{container-> def sz_12 = 12*breakdown[count]; def sz_8 = 8*breakdown[count]; def sz_4 = 4*breakdown[count]; html_ += """
""" html_ += container; html_ += """
""" count++; } html_ += """
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_table(Map map, child){ child.call(){ def header = map.header; def rows = map.rows; def footer = map.footer; def html_ = """ """ header.each{cell-> html_ += """""" } html+= """""" count = 0; rows.each{row-> html_ += """"""; row.each{cell-> html_ += """"""; } html_ += """"""; } //rows html_ += """"""; footer.each{cell-> html_ += """"""; } html_ += """"""; html_ += """
${cell}
${cell}
${cell}
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_text(child, text, link=null){ child.call(){ def html_ = ""; if (link != null){ html_ = """${text}"""; } else { html_ = """${text}"""; } return html_ } } def hubiForm_text_format(Map map, child){ child.call(){ def text = map.text; def halign = map.horizontal_align ? "text-align: ${map.horizontal_align};" : ""; def valign = map.vertical_align ? "vertical-align: ${map.vertical_align}; " : ""; def size = map.size ? "font-size: ${map.size}px;" : ""; def html_ = """

$text

"""; return html_ } } def hubiForm_page_button(child, title, page, width, icon=""){ def html_; child.call(){ html_ = """ """.replace('\t', '').replace('\n', '').replace(' ', '') } return html_; } def hubiForm_section(child, title, pos, icon="", Closure code) { child.call(){ def id = title.replace(' ', '_').replace('(', '').replace(')',''); def title_ = title.replace("'", "").replace("`", ""); def titleHTML = """
${title_}
"""; def modContent = """ """; modContent = modContent.replace('\t', '').replace('\n', '').replace(' ', ''); section(modContent, code); } } def hubiForm_enum(Map map, child){ child.call(){ def title = map.title; def var = map.name; def list = map.list; def defaultVal = map.default; def submit_on_change = map.submit_on_change; if (settings[var] == null){ app.updateSetting ("${var}", defaultVal); } def actualVal = settings[var] != null ? "${settings[var]}" : defaultVal; def submitOnChange = submit_on_change ? "submitOnChange" : ""; def html_ = """
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_switch(Map map, child){ child.call(){ def title = map.title; def var = map.name; def defaultVal = map.default; def submit_on_change = map.submit_on_change; def actualVal = settings[var] != null ? settings[var] : defaultVal; def submitOnChange = submit_on_change ? "submitOnChange" : ""; def html_ = """
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_text_input(child, title, var, defaultVal, submitOnChange){ child.call(){ settings[var] = settings[var] != null ? settings[var] : defaultVal; def html_ = """
""" return html_.replace('\t', '').replace('\n', '').replace(' ', ''); } } def hubiForm_font_size(Map map, child){ child.call(){ def title = map.title; def varname = map.name; def default_ = map.default; def min = map.min; def max = map.max; def submit_on_change = map.submit_on_change; def baseId = varname; def varFontSize = "${varname}_font" settings[varFontSize] = settings[varFontSize] ? settings[varFontSize] : default_; submitOnChange = submit_on_change ? "submitOnChange" : ""; def html_ = """
Font Size: ${settings[varFontSize]}
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_fontvx_size(Map map, child){ child.call(){ def title = map.title; def varname = map.name; def default_ = map.default; def min = map.min; def max = map.max; def submit_on_change = map.submit_on_change; def baseId = varname; def weight = map.weight ? "font-weight: ${map.weight} !important;" : ""; def icon = null; def varFontSize = "${varname}_font" def icon_size = settings[varFontSize] ? 10*settings[varFontSize] : default_*10; def jq = ""; if (map.icon){ icon = """ cloud """; jq = """jQuery('.test').css('font-size', 10*val+"px");""" } else { jq = """ jQuery('#${baseId}_font_size_val').css("font-size", 0.5*val+"em"); jQuery('#${baseId}_font_size_val').text(text); """ } settings[varFontSize] = settings[varFontSize] ? settings[varFontSize] : default_; submitOnChange = submit_on_change ? "submitOnChange" : ""; def html_ = """
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_line_size(Map map, child){ child.call(){ def title = map.title; def varname = map.name; def default_ = map.default; def min = map.min; def max = map.max; def submit_on_change = map.submit_on_change; def baseId = varname; def varLineSize = "${varname}_line_size" settings[varLineSize] = settings[varLineSize] ? settings[varLineSize] : default_; submitOnChange = submit_on_change ? "submitOnChange" : ""; def html_ = """
Width: ${settings[varLineSize]}
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_slider(Map map, child){ child.call(){ def title = map.title; def varname = map.name; def default_ = map.default; def min = map.min; def max = map.max; def units = map.units; def submit_on_change = map.submit_on_change; def fontSize; def varSize = "${varname}" def baseId = "${varname}"; settings[varSize] = settings[varSize] ? settings[varSize] : default_; submitOnChange = submit_on_change ? "submitOnChange" : ""; def html_ = """
${settings[varSize]}${units}
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_color(child, title, varname, defaultColorValue, defaultTransparentValue, submit = false){ child.call(){ def varnameColor = "${varname}_color"; def varnameTransparent = "${varname}_color_transparent" def colorTitle = "${title} Color" def notTransparentTitle = "Transparent"; def transparentTitle = "${title}: Transparent" settings[varnameColor] = settings[varnameColor] ? settings[varnameColor]: defaultColorValue; settings[varnameTransparent] = settings[varnameTransparent] ? settings[varnameTransparent]: defaultTransparentValue; def isTransparent = settings[varnameTransparent]; def html_ = """
${!isTransparent ? """""" : """"""}
${!isTransparent ? """
""" : ""}
"""; return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_graph_preview(child){ child.call(){ if (!state.count_) state.count_ = 7; def html_ = """
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_sub_section(child, myText=""){ child.call(){ def id = myText.replaceAll("[^a-zA-Z0-9]", ""); def newText = myText.replaceAll("'", "").replaceAll("`", "") def html_ = """

${newText}

""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_cell(child, containers, numPerRow){ child.call(){ def html_ = """
""" containers.each{container-> html_ += """
""" html_ += container; html_ += """
""" } html_ += """
""" return (html_.replace('\t', '').replace('\n', '').replace(' ', '')); } } def hubiForm_list_reorder(child, var, var_color, solid_background="") { child.call(){ def count_ = 0; if (settings["${var}"] != null){ list_ = parent.hubiTools_get_order(settings["${var}"]); //Check List result_ = true; count_ = 0; //check for addition/changes sensors.each { sensor -> id = sensor.id; attributes = settings["attributes_${id}"]; attributes.each { attribute -> count_ ++; inner_result = false; for (i=0; i attributes = settings["attributes_${sensor.id}"]; attributes.each { attribute -> settings["${var}"] += /"attribute_${sensor.id}_${attribute}",/ if (settings["attribute_${sensor.id}_${attribute}_${var_color}_color"] == null){ if (solid_background== ""){ settings["attribute_${sensor.id}_${attribute}_${var_color}_color"] = parent.hubiTools_rotating_colors(count_); } else { settings["attribute_${sensor.id}_${attribute}_${var_color}_color"] = solid_background; } } count_++; } } settings["${var}"] = settings["${var}"].substring(0, settings["${var}"].length() - 1); settings["${var}"] += "]"; } count_ = 0; order_ = parent.hubiTools_get_order(settings["${var}"]); order_.each { device_-> deviceName_ = parent.hubiTools_get_name_from_id(device_.id, sensors); title_ = """${deviceName_}

${device_.attribute}

"""; title_.replace("'", "").replace("`", ""); list_data << [title: title_, var: "attribute_${device_.id}_${device_.attribute}"]; } /**********************************************/ def var_val_ = settings["${var}"].replace('"', '"'); def html_ = """
""" list_data.each{data-> color_ = settings["${data.var}_${var_color}_color"]; id_ = "${data.var}" html_ += """
fiber_manual_record """ html_ += data.title; html_ += """
""" } html_ += """
""" html_ = html_.replace('\t', '').replace('\n', '').replace(' ', ''); paragraph (html_); } } /******************************************************************************************************************************** ********************************************************************************************************************************* ****************************************** TOOLS ******************************************************************************** ********************************************************************************************************************************* *********************************************************************************************************************************/ def hubiTool_create_tile(child, location="graph") { child.call(){ log.info "Creating HubiGraph Child Device" def childDevice = getChildDevice("HUBIGRAPH_${app.id}"); if (!childDevice) { if (!device_name) device_name="Dummy Device"; log.debug("Creating Device $device_name"); childDevice = addChildDevice("tchoward", "Hubigraph Tile Device", "HUBIGRAPH_${app.id}", null,[completedSetup: true, label: device_name]) log.info "Created HTTP Switch [${childDevice}]" //Send the html automatically childDevice.setGraph("${state.localEndpointURL}${location}/?access_token=${state.endpointSecret}"); log.info "Sent setGraph: ${state.localEndpointURL}${location}/?access_token=${state.endpointSecret}" } else { childDevice.label = device_name; log.info "Label Updated to [${device_name}]" //Send the html automatically childDevice.setGraph("${state.localEndpointURL}${location}/?access_token=${state.endpointSecret}"); log.info "Sent setGraph: ${state.localEndpointURL}${location}/?access_token=${state.endpointSecret}" } } } def hubiTools_validate_order(child, all) { child.call(){ def order = []; sensors.eachWithIndex {sensor, idx -> order << settings["displayOrder_${sensor.id}"]; } //if we are initialized and need to check if(state.lastOrder && state.lastOrder[0]) { def remains = all.findAll { !order.contains(it) } def dupes = []; order.each {ord -> if(order.count(ord) > 1) dupes << ord; } sensors.eachWithIndex {sensor, idx -> if(state.lastOrder[idx] == order[idx] && dupes.contains(settings["displayOrder_${sensor.id}"])) { settings["displayOrder_${sensor.id}"] = remains[0]; app.updateSetting("displayOrder_${sensor.id}", [value: remains[0], type: "enum"]); remains.removeAt(0); } } } //reconstruct order order = []; sensors.eachWithIndex {sensor, idx -> order << settings["displayOrder_${sensor.id}"]; } state.lastOrder = order; } } def hubiTools_rotating_colors(c){ ret = "#FFFFFF" color = c % 13; switch (color){ case 0: return hubiTools_get_color_code("RED"); break; case 1: return hubiTools_get_color_code("GREEN"); break; case 2: return hubiTools_get_color_code("BLUE"); break; case 3: return hubiTools_get_color_code("MAROON"); break; case 4: return hubiTools_get_color_code("YELLOW"); break; case 5: return hubiTools_get_color_code("OLIVE"); break; case 6: return hubiTools_get_color_code("AQUA"); break; case 7: return hubiTools_get_color_code("LIME"); break; case 8: return hubiTools_get_color_code("NAVY"); break; case 9: return hubiTools_get_color_code("FUCHSIA"); break; case 10: return hubiTools_get_color_code("PURPLE"); break; case 11: return hubiTools_get_color_code("TEAL"); break; case 12: return hubiTools_get_color_code("ORANGE"); break; } return ret; } def hubiTools_get_color_code(input_color){ new_color = input_color.toUpperCase(); switch (new_color){ case "WHITE" : return "#FFFFFF"; break; case "SILVER" : return "#C0C0C0"; break; case "GRAY" : return "#808080"; break; case "BLACK" : return "#000000"; break; case "RED" : return "#FF0000"; break; case "MAROON" : return "#800000"; break; case "YELLOW" : return "#FFFF00"; break; case "OLIVE" : return "#808000"; break; case "LIME" : return "#00FF00"; break; case "GREEN" : return "#008000"; break; case "AQUA" : return "#00FFFF"; break; case "TEAL" : return "#008080"; break; case "BLUE" : return "#0000FF"; break; case "NAVY" : return "#000080"; break; case "FUCHSIA" :return "#FF00FF"; break; case "PURPLE" : return "#800080"; break; } } def hubiTools_get_name_from_id(id, sensors){ def return_val = "Error" sensors.each { sensor -> if (id == sensor.id) { return_val = sensor.displayName; } } return return_val; } def hubiTools_get_order(order){ split_ = order.replace('"', '').replace('[', '').replace(']', '').replace("attribute_", "").split(','); list_ = []; split_.each{device-> sub_ = device.split('_'); list_ << [id: sub_[0], attribute:sub_[1]]; } return list_; } def hubiTools_check_list(child, sensors, list_){ result = true; count_ = 0; //check for addition/changes sensors.each { sensor -> id = sensor.id; attributes = settings["attributes_${sensor.id}"]; attributes.each { attribute -> count_ ++; inner_result = false; for (i=0; i