import groovy.json.*;
/**
* Hubigraph Line Graph Child App
*
* Copyright 2020, but let's behonest, you'll copy it
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
// Hubigraph Line Graph Changelog
// *****ALPHA BUILD
// v0.1 Initial release
// v0.2 My son added webpage efficiencies, reduced load on hubitat by 75%.
// v0.3 Loading Update; Removed ALL processing from Hub, uses websocket endpoint
// v0.5 Multiple line support
// v0.51 Select ANY device
// v0.60 Select AXIS to graph on
// v0.70 A lot more options
// v0.80 Added Horizontal Axis Formatting
// ****BETA BUILD
// v0.1 Added Hubigraph Tile support with Auto-add Dashboard Tile
// v0.2 Added Custom Device/Attribute Labels
// v0.3 Added waiting screen for initial graph loading & sped up load times
// v0.32 Bug Fixes
// V 1.0 Released (not Beta) Cleanup and Preview Enabled
// v 1.2 Complete UI Refactor
// V 1.5 Ordering, Color and Common API Update
// V 1.8 Smoother sliders, bug fixes
// Credit to Alden Howard for optimizing the code.
def ignoredEvents() { return [ 'lastReceive' , 'reachable' ,
'buttonReleased' , 'buttonPressed', 'lastCheckinDate', 'lastCheckin', 'buttonHeld' ] }
def version() { return "v1.0" }
definition(
name: "Hubigraph Line Graph",
namespace: "tchoward",
author: "Thomas Howard",
description: "Hubigraph Line Graph",
category: "",
parent: "tchoward:Hubigraphs",
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",
)
preferences {
section ("test"){
page(name: "mainPage", install: true, uninstall: true)
page(name: "deviceSelectionPage", nextPage: "graphSetupPage")
page(name: "graphSetupPage", nextPage: "mainPage")
page(name: "enableAPIPage")
page(name: "disableAPIPage")
}
mappings {
path("/graph/") {
action: [
GET: "getGraph"
]
}
path("/getData/") {
action: [
GET: "getData"
]
}
path("/getOptions/") {
action: [
GET: "getOptions"
]
}
path("/getSubscriptions/") {
action: [
GET: "getSubscriptions"
]
}
}
}
def call(Closure code) {
code.setResolveStrategy(Closure.DELEGATE_ONLY);
code.setDelegate(this);
code.call();
}
/********************************************************************************************************************************
*********************************************************************************************************************************
****************************************** PAGES ********************************************************************************
*********************************************************************************************************************************
*********************************************************************************************************************************/
def graphSetupPage(){
def fontEnum = [["1":"1"], ["2":"2"], ["3":"3"], ["4":"4"], ["5":"5"], ["6":"6"], ["7":"7"], ["8":"8"], ["9":"9"], ["10":"10"],
["11":"11"], ["12":"12"], ["13":"13"], ["14":"14"], ["15":"15"], ["16":"16"], ["17":"17"], ["18":"18"], ["19":"19"], ["20":"20"]];
def updateEnum = [["-1":"Never"], ["0":"Real Time"], ["10":"10 Milliseconds"], ["1000":"1 Second"], ["5000":"5 Seconds"], ["60000":"1 Minute"],
["300000":"5 Minutes"], ["600000":"10 Minutes"], ["1800000":"Half Hour"], ["3600000":"1 Hour"]];
def timespanEnum = [["60000":"1 Minute"], ["3600000":"1 Hour"], ["43200000":"12 Hours"], ["86400000":"1 Day"], ["259200000":"3 Days"], ["604800000":"1 Week"]]
def timespanEnum2 = [["60000":"1 Minute"], ["120000":"2 Minutes"], ["300000":"5 Minutes"], ["600000":"10 Minutes"],
["2400000":"30 minutes"], ["3600000":"1 Hour"], ["43200000":"12 Hours"], ["86400000":"1 Day"], ["259200000":"3 Days"], ["604800000":"1 Week"]];
def supportedTypes = [
"alarm": ["start": "on",
"end": "off"],
"contact": ["start": "open",
"end": "closed"],
"switch": ["start": "on",
"end": "off"],
"motion": ["start": "active",
"end": "inactive"],
"mute": ["start": "muted",
"end": "unmuted"],
"presence": ["start":"present",
"end":"not present"],
"holdableButton": ["start":"true",
"end":"false"],
"carbonMonoxide": ["start":"detected",
"end":"clear"],
"playing": ["start":"playing",
"end":"stopped"],
"door": ["start": "open",
"end": "closed"],
"speed": ["start": "on",
"end": "off"],
"lock": ["start": "unlocked",
"end": "locked"],
"shock": ["start": "detected",
"end": "clear"],
"sleepSensor": ["start": "sleeping",
"end": "not sleeping"],
"smoke": ["start":"detected",
"end":"clear"],
"sound": ["start":"detected",
"end":"not detected"],
"tamper": ["start":"detected",
"end":"clear"],
"valve": ["start": "open",
"end": "closed"],
"camera": ["start": "on",
"end": "off"],
"water": ["start": "wet",
"end": "dry"],
"windowShade": ["start": "open",
"end": "closed"],
"acceleration": ["start": "inactive",
"end": "active"]
];
dynamicPage(name: "graphSetupPage") {
def non_numeric = false;
sensors.each { sensor ->
settings["attributes_${sensor.id}"].each { attribute ->
if (supportedTypes[attribute] != null) non_numeric = true;
}
}
if (non_numeric) {
app.updateSetting ("graph_max_points", 0);
}
parent.hubiForm_section(this,"General Options", 1)
{
input( type: "enum", name: "graph_type", title: "Graph Type", defaultValue: "Line Graph", options: ["Line Graph", "Area Graph", "Scatter Plot"], submitOnChange: true)
input( type: "enum", name: "graph_update_rate", title: "Select graph update rate", multiple: false, required: true, options: updateEnum, defaultValue: "0")
input( type: "enum", name: "graph_timespan", title: "Select Timespan to Graph", multiple: false, required: true, options: timespanEnum, defaultValue: "43200000")
container = [];
container << parent.hubiForm_color (this, "Graph Background", "graph_background", "#FFFFFF", false)
container << parent.hubiForm_switch(this, title: "Smooth Graph Points", name: "graph_smoothing", default: false);
container << parent.hubiForm_switch(this, title: "Flip Graph to Vertical?
(Rotate 90 degrees)", name: "graph_y_orientation", default: false);
container << parent.hubiForm_switch(this, title: "Reverse Data Order?
(Flip data left to Right)", name: "graph_z_orientation", default: false)
if (!non_numeric)
container << parent.hubiForm_slider (this, title: "Maximum number of Data Points?
(Zero for ALL)", name: "graph_max_points", default: 0, min: 0, max: 1000, units: " data points", submit_on_change: false);
parent.hubiForm_container(this, container, 1);
}
parent.hubiForm_section(this,"Graph Title", 1)
{
container = [];
container << parent.hubiForm_switch(this, title: "Show Title on Graph", name: "graph_show_title", default: false, submit_on_change: true);
if (graph_show_title==true) {
container << parent.hubiForm_text_input (this, "Graph Title", "graph_title", "Graph Title", false);
container << parent.hubiForm_font_size (this, title: "Title", name: "graph_title", default: 9, min: 2, max: 20);
container << parent.hubiForm_color (this, "Title", "graph_title", "#000000", false);
container << parent.hubiForm_switch (this, title: "Graph Title Inside Graph?", name: "graph_title_inside", default: false);
}
parent.hubiForm_container(this, container, 1);
}
parent.hubiForm_section(this, "Graph Size", 1){
container = [];
container << parent.hubiForm_switch (this, title: "Set size of Graph?
(False = Fill Window)", name: "graph_static_size", default: false, submit_on_change: true);
if (graph_static_size==true){
container << parent.hubiForm_slider (this, title: "Horizontal dimension of the graph", name: "graph_h_size", default_value: 800, min: 100, max: 3000, units: " pixels", submit_on_change: false);
container << parent.hubiForm_slider (this, title: "Vertical dimension of the graph", name: "graph_v_size", default_value: 600, min: 100, max: 3000, units: " pixels", submit_on_change: false);
}
parent.hubiForm_container(this, container, 1);
}
parent.hubiForm_section(this,"Horizontal Axis", 1)
{
//Axis
container = [];
container << parent.hubiForm_font_size (this, title: "Horizontal Axis", name: "graph_haxis", default: 9, min: 2, max: 20);
container << parent.hubiForm_color (this, "Horizonal Header", "graph_hh", "#C0C0C0", false);
container << parent.hubiForm_color (this, "Horizonal Axis", "graph_ha", "#C0C0C0", false);
container << parent.hubiForm_text_input (this, "Num Horizontal Gridlines
(Blank for auto)", "graph_h_num_grid", "", false);
container << parent.hubiForm_switch (this, title: "Show String Formatting Help", name: "dummy", default: false, submit_on_change: true);
if (dummy == true){
val = [];
val <<"Name"; val << "Format" ; val <<"Result";
val <<"Year"; val << "Y"; val << "2020";
val <<"Month Number"; val << "M"; val << "12";
val <<"Month Name "; val << "MMM"; val << "Feb";
val <<"Month Full Name"; val << "MMMM"; val << "February";
val <<"Day of Month"; val << "d"; val << "February";
val <<"Day of Week"; val << "EEE"; val << "Mon";
val <<"Day of Week"; val << "EEEE"; val << "Monday";
val <<"Period"; val << "a"; val << "AM/PM";
val <<"Hour (12)"; val << "h"; val << "1..12";
val <<"Hour (12)"; val << "hh"; val << "01..12";
val <<"Hour (24)"; val << "H"; val << "1..23";
val <<"Hour (24)"; val << "HH"; val << "01..23";
val <<"Minute"; val << "m"; val << "1..59";
val <<"Minute"; val << "mm"; val << "01..59";
val <<"Seconds"; val << "s"; val << "1..59";
val <<"Seconds"; val << "ss"; val << "01..59 "
container << parent.hubiForm_cell(this, val, 3);
container << parent.hubiForm_text(this, """Example: "EEEE, MMM d, Y hh:mm:ss a"
= "Monday, June 2, 2020 08:21:33 AM""")
}
container << parent.hubiForm_text_input (this, "Horizontal Axis Format", "graph_h_format", "", true);
parent.hubiForm_container(this, container, 1);
if (graph_h_format){
today = new Date();
paragraph "Horizontal Axis Sample: ${today.format(graph_h_format)}"
}
}
//Vertical Axis
parent.hubiForm_section(this,"Vertical Axis", 1)
{
container = [];
container << parent.hubiForm_font_size (this, title: "Vertical Axis", name: "graph_vaxis", default: 9, min: 2, max: 20);
container << parent.hubiForm_color (this, "Vertical Header", "graph_vh", "#000000", false);
container << parent.hubiForm_color (this, "Vertical Axis", "graph_va", "#C0C0C0", false);
parent.hubiForm_container(this, container, 1);
}
//Left Axis
parent.hubiForm_section(this,"Left Axis", 1, "arrow_back"){
container = [];
container << parent.hubiForm_text_input(this, "Minimum for left axis(Blank for auto)", "graph_vaxis_1_min", "", false);
container << parent.hubiForm_text_input(this, "Maximum for left axis(Blank for auto)", "graph_vaxis_1_max", "", false);
container << parent.hubiForm_text_input (this, "Num Vertical Gridlines
(Blank for auto)", "graph_vaxis_1_num_lines", "", false);
container << parent.hubiForm_switch (this, title: "Show Left Axis Label on Graph", name: "graph_show_left_label", default: false, submit_on_change: true);
if (graph_show_left_label==true){
container << parent.hubiForm_text_input (this, "Input Left Axis Label", "graph_left_label", "Left Axis Label", false);
container << parent.hubiForm_font_size (this, title: "Left Axis", name: "graph_left", default: 9, min: 2, max: 20);
container << parent.hubiForm_color (this, "Left Axis", "graph_left", "#FFFFFF", false);
}
parent.hubiForm_container(this, container, 1);
}
//Right Axis
parent.hubiForm_section(this,"Right Axis", 1, "arrow_forward"){
container = [];
container << parent.hubiForm_text_input(this, "Minimum for right axis(Blank for auto)", "graph_vaxis_2_min", "", false);
container << parent.hubiForm_text_input(this, "Maximum for right axis(Blank for auto)", "graph_vaxis_2_max", "", false);
container << parent.hubiForm_text_input (this, "Num Vertical Gridlines
(Blank for auto)", "graph_vaxis_2_num_lines", "", false);
container << parent.hubiForm_switch (this, title: "Show Right Axis Label on Graph", name: "graph_show_right_label", default: false, submit_on_change: true);
if (graph_show_right_label==true){
container << parent.hubiForm_text_input (this, "Input right Axis Label", "graph_right_label", "Right Axis Label", false);
container << parent.hubiForm_font_size (this, title: "Right Axis", name: "graph_right", default: 9, min: 2, max: 20);
container << parent.hubiForm_color (this, "Right Axis", "graph_right", "#FFFFFF", false);
}
parent.hubiForm_container(this, container, 1);
}
//Legend
parent.hubiForm_section(this,"Legend", 1){
container = [];
def legendPosition = [["top": "Top"], ["bottom":"Bottom"], ["in": "Inside Top"]];
def insidePosition = [["start": "Left"], ["center": "Center"], ["end": "Right"]];
container << parent.hubiForm_switch(this, title: "Show Legend on Graph", name: "graph_show_legend", default: false, submit_on_change: true);
if (graph_show_legend==true){
container << parent.hubiForm_font_size (this, title: "Legend", name: "graph_legend", default: 9, min: 2, max: 20);
container << parent.hubiForm_color (this, "Legend", "graph_legend", "#000000", false);
parent.hubiForm_container(this, container, 1);
input( type: "enum", name: "graph_legend_position", title: "Legend Position", defaultValue: "Bottom", options: legendPosition);
input( type: "enum", name: "graph_legend_in side_position", title: "Legend Justification", defaultValue: "center", options: insidePosition);
} else {
parent.hubiForm_container(this, container, 1);
}
}
state.num_devices = 0;
sensors.each { sensor ->
settings["attributes_${sensor.id}"].each { attribute ->
state.num_devices++;
}
}
def availableAxis = [["0" : "Left Axis"], ["1": "Right Axis"]];
if (state.num_devices == 1) {
availableAxis = [["0" : "Left Axis"], ["1": "Right Axis"], ["2": "Both Axes"]];
}
//Line
cnt = 1;
sensors.each { sensor ->
settings["attributes_${sensor.id}"].each { attribute ->
parent.hubiForm_section(this,"${sensor.displayName} - ${attribute}", 1){
container = [];
input( type: "enum", name: "graph_axis_number_${sensor.id}_${attribute}", title: "Graph Axis Side", defaultValue: "0", options: availableAxis);
container << parent.hubiForm_color(this, "Line",
"graph_line_${sensor.id}_${attribute}",
parent.hubiTools_rotating_colors(cnt),
false);
container << parent.hubiForm_line_size (this, title: "Line Thickness",
name: "attribute_${sensor.id}_${attribute}",
default: 2, min: 1, max: 20);
container << parent.hubiForm_text_input(this, "Override Device Name
Use %deviceName% for DEVICE and %attributeName% for ATTRIBUTE",
"graph_name_override_${sensor.id}_${attribute}",
"%deviceName%: %attributeName%", false);
startVal = supportedTypes[attribute] ? supportedTypes[attribute].start : "";
endVal = supportedTypes[attribute] ? supportedTypes[attribute].end : "";
if (graph_type == "Area Graph"){
container << parent.hubiForm_slider (this, title: "Opacity of the area below the line",
name: "attribute_${sensor.id}_${attribute}_opacity",
default_value: 30,
min: 0,
max: 100,
units: "%",
submit_on_change: false);
}
if (startVal != ""){
app.updateSetting ("attribute_${sensor.id}_${attribute}_non_number", true);
app.updateSetting ("attribute_${sensor.id}_${attribute}_startString", startVal);
app.updateSetting ("attribute_${sensor.id}_${attribute}_endString", endVal);
container << parent.hubiForm_text(this, "This Attribute ($attribute) is non-numerical, please choose values for the states below");
container << parent.hubiForm_text_input(this, "Value for $startVal",
"attribute_${sensor.id}_${attribute}_${startVal}",
"100", false);
container << parent.hubiForm_text_input(this, "Value for $endVal",
"attribute_${sensor.id}_${attribute}_${endVal}",
"0", false);
parent.hubiForm_container(this, container, 1);
} else {
container << parent.hubiForm_switch(this, title: "Display as a Drop Line", name: "attribute_${sensor.id}_${attribute}_drop_line", default: false, submit_on_change: true);
if (settings["attribute_${sensor.id}_${attribute}_drop_line"]==true){
container << parent.hubiForm_text_input(this,"Value to drop the Line",
"attribute_${sensor.id}_${attribute}_drop_value",
"0", false);
parent.hubiForm_container(this, container, 1);
input( type: "enum", name: "attribute_${sensor.id}_${attribute}_drop_time", title: "Drop Line Time", defaultValue: "5 Minutes", options: timespanEnum2 )
} else {
parent.hubiForm_container(this, container, 1);
}
}
cnt += 1;
}
}
}
}
}
def deviceSelectionPage() {
def supported_attrs;
dynamicPage(name: "deviceSelectionPage") {
parent.hubiForm_section(this,"Device Selection", 1){
input "sensors", "capability.*", title: "Sensors", multiple: true, required: true, submitOnChange: true
if (sensors){
sensors.each {
sensor_events = it.events([max:250]).name;
supported_attrs = sensor_events.unique(false);
container = [];
container << parent.hubiForm_sub_section(this, it.displayName);
parent.hubiForm_container(this, container, 1);
input( type: "enum", name: "attributes_${it.id}", title: "Attributes to graph", required: true, multiple: true, options: supported_attrs, defaultValue: "1")
}
}
}
}
}
def disableAPIPage() {
dynamicPage(name: "disableAPIPage") {
section() {
if (state.endpoint) {
revokeAccessToken();
state.endpoint = null
}
paragraph "Token revoked. Click done to continue."
}
}
}
def enableAPIPage() {
dynamicPage(name: "enableAPIPage", title: "") {
section() {
if(!state.endpoint) initializeAppEndpoint();
paragraph "Token created. Click done to continue."
}
}
}
def mainPage() {
dynamicPage(name: "mainPage") {
def container = [];
if (!state.endpoint) {
parent.hubiForm_section(this, "Please set up OAuth API", 1, "report"){
href name: "enableAPIPageLink", title: "Enable API", description: "", page: "enableAPIPage"
}
} else {
parent.hubiForm_section(this, "Graph Options", 1, "tune"){
container = [];
container << parent.hubiForm_page_button(this, "Select Device/Data", "deviceSelectionPage", "100%", "vibration");
container << parent.hubiForm_page_button(this, "Configure Graph", "graphSetupPage", "100%", "poll");
parent.hubiForm_container(this, container, 1);
}
parent.hubiForm_section(this, "Local Graph URL", 1, "link"){
container = [];
container << parent.hubiForm_text(this, "${state.localEndpointURL}graph/?access_token=${state.endpointSecret}");
parent.hubiForm_container(this, container, 1);
}
if (graph_timespan){
parent.hubiForm_section(this, "Preview", 10, "show_chart"){
container = [];
container << parent.hubiForm_graph_preview(this)
parent.hubiForm_container(this, container, 1);
} //graph_timespan
parent.hubiForm_section(this, "Hubigraph Tile Installation", 2, "apps"){
container = [];
container << parent.hubiForm_switch(this, title: "Install Hubigraph Tile Device?", name: "install_device", default: false, submit_on_change: true);
if (install_device==true){
container << parent.hubiForm_text_input(this, "Name for HubiGraph Tile Device", "device_name", "Hubigraph Tile", "false");
}
parent.hubiForm_container(this, container, 1);
}
}
if (state.endpoint){
parent.hubiForm_section(this, "Hubigraph Application", 1, "settings"){
container = [];
container << parent.hubiForm_sub_section(this, "Application Name");
container << parent.hubiForm_text_input(this, "Rename the Application?", "app_name", "Hubigraph Bar Graph", "false");
container << parent.hubiForm_sub_section(this, "Debugging");
container << parent.hubiForm_switch(this, title: "Enable Debug Logging?", name: "debug", default: false);
container << parent.hubiForm_sub_section(this, "Disable Oauth Authorization");
container << parent.hubiForm_page_button(this, "Disable API", "disableAPIPage", "100%", "cancel");
parent.hubiForm_container(this, container, 1);
}
}
} //else
} //dynamicPage
}
/********************************************************************************************************************************
*********************************************************************************************************************************
****************************************** END PAGES ********************************************************************************
*********************************************************************************************************************************
*********************************************************************************************************************************/
def installed() {
initialize()
}
def uninstalled() {
if (state.endpoint) {
try {
revokeAccessToken()
}
catch (e) {
log.warn "Unable to revoke API access token: $e"
}
}
removeChildDevices(getChildDevices());
}
private removeChildDevices(delete) {
delete.each {deleteChildDevice(it.deviceNetworkId)}
}
def updated() {
app.updateLabel(app_name);
if (install_device == true){
parent.hubiTool_create_tile(this);
}
}
def initialize() {
updated();
}
private getValue(id, attr, val){
def reg = ~/[a-z,A-Z]+/;
if (settings["attribute_${id}_${attr}_${val}"]!=null){
ret = Double.parseDouble(settings["attribute_${id}_${attr}_${val}"]);
} else {
ret = Double.parseDouble(val - reg )
}
return ret;
}
private buildData() {
def resp = [:]
def today = new Date();
def then = new Date();
use (groovy.time.TimeCategory) {
then -= Integer.parseInt(graph_timespan).milliseconds;
}
if(sensors) {
sensors.each { sensor ->
resp[sensor.id] = [:];
settings["attributes_${sensor.id}"].each { attribute ->
def respEvents = [];
respEvents << sensor.statesSince(attribute, then, [max: 50000]).collect{[ date: it.date.getTime(), value: getValue(sensor.id, attribute, it.value) ]}
respEvents = respEvents.flatten();
respEvents = respEvents.reverse();
//Add drop lines for non-numerical devices
if (settings["attribute_${sensor.id}_${attribute}_non_number"] && respEvents.size()>1){
start = settings["attribute_${sensor.id}_${attribute}_startString"];
end = settings["attribute_${sensor.id}_${attribute}_endString"];
startVal = Float.parseFloat(settings["attribute_${sensor.id}_${attribute}_${start}"]);
endVal = Float.parseFloat(settings["attribute_${sensor.id}_${attribute}_${end}"]);
tEvents = [];
//Add Start Event
currDate = then;
if (respEvents[0].value == startVal){
tEvents.push([date: currDate, value: endVal]);
} else {
tEvents.push([date: currDate, value: startVal]);
}
for (i=0; i 0) {
reduction = (int) Math.ceil((float)respEvents.size() / graph_max_points);
respEvents = respEvents.collate(reduction).collect{ group ->
group.inject([ date: 0, value: 0 ]){ col, it ->
col.date += it.date / group.size();
col.value += it.value / group.size();
return col;
}
};
}
//add drop line data
tEvents = [];
if (settings["attribute_${sensor.id}_${attribute}_drop_line"] && respEvents.size()>1){
def curr, prev, currDate, prevDate, drop_time, drop_value;
drop_time = settings["attribute_${sensor.id}_${attribute}_drop_time"];
drop_value = settings["attribute_${sensor.id}_${attribute}_drop_value"];
tEvents.push(respEvents[0]);
for (i=1; i Integer.parseInt(drop_time)){
//add first zero
tEvents.push([date: prevDate-1000, value: Float.parseFloat(drop_value)]);
tEvents.push([date: currDate+1000, value: Float.parseFloat(drop_value)]);
}
tEvents.push(curr);
}
respEvents = tEvents;
}
resp[sensor.id][attribute] = respEvents;
}
}
}
return resp;
}
def getChartOptions(){
def options = [
"graphReduction": graph_max_points,
"graphTimespan": Integer.parseInt(graph_timespan),
"graphUpdateRate": Integer.parseInt(graph_update_rate),
"graphOptions": [
"width": graph_static_size ? graph_h_size : "100%",
"height": graph_static_size ? graph_v_size: "100%",
"chartArea": [ "width": graph_static_size ? graph_h_size : "80%", "height": graph_static_size ? graph_v_size: "80%"],
"hAxis": ["textStyle": ["fontSize": graph_haxis_font,
"color": graph_hh_color_transparent ? "transparent" : graph_hh_color ],
"gridlines": ["color": graph_ha_color_transparent ? "transparent" : graph_ha_color,
"count": graph_h_num_grid != "" ? graph_h_num_grid : null
],
"format": graph_h_format==""?"":graph_h_format
],
"vAxis": ["textStyle": ["fontSize": graph_vaxis_font,
"color": graph_vh_color_transparent ? "transparent" : graph_vh_color],
"gridlines": ["color": graph_va_color_transparent ? "transparent" : graph_va_color],
],
"vAxes": [
0: ["title" : graph_show_left_label ? graph_left_label: null,
"titleTextStyle": ["color": graph_left_color_transparent ? "transparent" : graph_left_color, "fontSize": graph_left_font],
"viewWindow": ["min": graph_vaxis_1_min != "" ? graph_vaxis_1_min : null,
"max": graph_vaxis_1_max != "" ? graph_vaxis_1_max : null],
"gridlines": ["count" : graph_vaxis_1_num_lines != "" ? graph_vaxis_1_num_lines : null ],
"minorGridlines": ["count" : 0]
],
1: ["title": graph_show_right_label ? graph_right_label : null,
"titleTextStyle": ["color": graph_right_color_transparent ? "transparent" : graph_right_color, "fontSize": graph_right_font],
"viewWindow": ["min": graph_vaxis_2_min != "" ? graph_vaxis_2_min : null,
"max": graph_vaxis_2_max != "" ? graph_vaxis_2_max : null],
"gridlines": ["count" : graph_vaxis_2_num_lines != "" ? graph_vaxis_2_num_lines : null ],
"minorGridlines": ["count" : 0]
]
],
"legend": !graph_show_legend ? ["position": "none"] : ["position": graph_legend_position,
"alignment": graph_legend_inside_position,
"textStyle": ["fontSize": graph_legend_font,
"color": graph_legend_color_transparent ? "transparent" : graph_legend_color]],
"backgroundColor": graph_background_color_transparent ? "transparent" : graph_background_color,
"curveType": !graph_smoothing ? "" : "function",
"title": !graph_show_title ? "" : graph_title,
"titleTextStyle": !graph_show_title ? "" : ["fontSize": graph_title_font, "color": graph_title_color_transparent ? "transparent" : graph_title_color],
"titlePosition" : graph_title_inside ? "in" : "out",
"interpolateNulls": true, //for null vals on our chart
"orientation" : graph_y_orientation == true ? "vertical" : "horizontal",
"reverseCategories" : graph_x_orientation,
"series": [],
]
];
//add colors and thicknesses
sensors.each { sensor ->
settings["attributes_${sensor.id}"].each { attribute ->
def axis = Integer.parseInt(settings["graph_axis_number_${sensor.id}_${attribute}"]);
def text_color = settings["graph_line_${sensor.id}_${attribute}_color"];
def text_color_transparent = settings["graph_line_${sensor.id}_${attribute}_color_transparent"];
def line_thickness = settings["attribute_${sensor.id}_${attribute}_line_size"];
if (settings["attribute_${sensor.id}_${attribute}_opacity"]){
def opacity = settings["attribute_${sensor.id}_${attribute}_opacity"]/100.0;
}
def annotations = [
"targetAxisIndex": axis,
"color": text_color_transparent ? "transparent" : text_color,
"stroke": text_color_transparent ? "transparent" : "red",
"lineWidth": line_thickness,
"areaOpacity" : opacity,
];
options.graphOptions.series << annotations;
}
}
return options;
}
def getDrawType(){
switch (graph_type){
case "Line Graph": return "google.visualization.LineChart";
case "Area Graph": return "google.visualization.AreaChart";
case "Scatter Plot": return "google.visualization.ScatterChart";
}
}
void removeLastChar(str) {
str.subSequence(0, str.length() - 1)
str
}
def getLineGraph() {
def fullSizeStyle = "margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden";
def html = """
"""
return html;
}
// Events come in Date format
def getDateStringEvent(date) {
def dateObj = date
def yyyy = dateObj.getYear() + 1900
def MM = String.format("%02d", dateObj.getMonth()+1);
def dd = String.format("%02d", dateObj.getDate());
def HH = String.format("%02d", dateObj.getHours());
def mm = String.format("%02d", dateObj.getMinutes());
def ss = String.format("%02d", dateObj.getSeconds());
def dateString = /$yyyy-$MM-$dd $HH:$mm:$ss.000/;
dateString
}
def initializeAppEndpoint() {
if (!state.endpoint) {
try {
def accessToken = createAccessToken()
if (accessToken) {
state.endpoint = getApiServerUrl()
state.localEndpointURL = fullLocalApiServerUrl("")
state.remoteEndpointURL = fullApiServerUrl("")
state.endpointSecret = accessToken
}
}
catch(e) {
state.endpoint = null
}
}
return state.endpoint
}
//oauth endpoints
def getGraph() {
render(contentType: "text/html", data: getLineGraph());
}
def getDataMetrics() {
def data;
def then = new Date().getTime();
data = getData();
def now = new Date().getTime();
return data;
}
def getData() {
def timeline = buildData();
return render(contentType: "text/json", data: JsonOutput.toJson(timeline));
}
def getOptions() {
render(contentType: "text/json", data: JsonOutput.toJson(getChartOptions()));
}
def getSubscriptions() {
def ids = [];
def sensors_ = [:];
def attributes = [:];
def labels = [:];
def drop_ = [:];
def non_num_ = [:];
sensors.each {sensor->
ids << sensor.idAsLong;
//only take what we need
sensors_[sensor.id] = [ id: sensor.id, idAsLong: sensor.idAsLong, displayName: sensor.displayName ];
attributes[sensor.id] = settings["attributes_${sensor.id}"];
labels[sensor.id] = [:];
settings["attributes_${sensor.id}"].each { attr ->
labels[sensor.id][attr] = settings["graph_name_override_${sensor.id}_${attr}"];
}
drop_[sensor.id] = [:];
non_num_[sensor.id] = [:];
settings["attributes_${sensor.id}"].each { attr ->
if (settings["attribute_${sensor.id}_${attr}_non_number"]==true){
startString = settings["attribute_${sensor.id}_${attr}_startString"];
endString = settings["attribute_${sensor.id}_${attr}_endString"];
non_num_[sensor.id][attr] = [ valid: true,
start: startString,
startVal: settings["attribute_${sensor.id}_${attr}_${startString}"],
end: endString,
endVal: settings["attribute_${sensor.id}_${attr}_${endString}"]
];
} else {
non_num_[sensor.id][attr] = [ valid: false,
start: "",
end: ""];
}
drop_[sensor.id][attr] = [valid: settings["attribute_${sensor.id}_${attr}_drop_line"],
time: settings["attribute_${sensor.id}_${attr}_drop_time"],
value: settings["attribute_${sensor.id}_${attr}_drop_value"]];
}
}
def obj = [
ids: ids,
sensors: sensors_,
attributes: attributes,
labels : labels,
drop : drop_,
non_num: non_num_
]
def subscriptions = obj;
return render(contentType: "text/json", data: JsonOutput.toJson(subscriptions));
}
def getColorCode(code){
ret = "#FFFFFF"
switch (code){
case 7: ret = "#800000"; break;
case 1: ret = "#FF0000"; break;
case 6: ret = "#FFA500"; break;
case 8: ret = "#FFFF00"; break;
case 9: ret = "#808000"; break;
case 2: ret = "#008000"; break;
case 5: ret = "#800080"; break;
case 4: ret = "#FF00FF"; break;
case 10: ret = "#00FF00"; break;
case 11: ret = "#008080"; break;
case 12: ret = "#00FFFF"; break;
case 3: ret = "#0000FF"; break;
case 13: ret = "#000080"; break;
}
return ret;
}