From 6ba37caa0305c0f5a709a7d6598b3b2872ce61fa Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Thu, 10 Dec 2015 22:50:15 -0600 Subject: [PATCH 1/3] Added fingerprints/support for new version of multi/moisture/motion sensors Partially resolves: https://smartthings.atlassian.net/browse/DVCSMP-1305 --- .../smartsense-moisture-sensor.groovy | 2 +- .../smartsense-motion-sensor.groovy | 3 ++- .../smartsense-multi-sensor.groovy | 27 ++++++++++++------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy index 0abd266..0d04bce 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -27,7 +27,7 @@ metadata { fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor" - + fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor" } simulator { diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index bb0b72b..0012cf7 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -29,6 +29,7 @@ metadata { fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326" + fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor" } simulator { @@ -352,4 +353,4 @@ private byte[] reverseArray(byte[] array) { i++; } return array -} \ No newline at end of file +} diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index 4e66dd6..97c7bb5 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -30,6 +30,7 @@ fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor" + fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor" attribute "status", "string" } @@ -374,26 +375,26 @@ def getTemperature(value) { /* sensitivity - default value (8) */ - "zcl mfg-code 0x104E", "delay 200", + "zcl mfg-code ${manufacturerCode}", "delay 200", "zcl global write 0xFC02 0 0x20 {02}", "delay 200", "send 0x${device.deviceNetworkId} 1 1", "delay 400", "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", "st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200", - "zcl mfg-code 0x104E", "delay 200", + "zcl mfg-code ${manufacturerCode}", "delay 200", "zcl global read 0xFC02 0x0010", "send 0x${device.deviceNetworkId} 1 1","delay 400", - "zcl mfg-code 0x104E", "delay 200", + "zcl mfg-code ${manufacturerCode}", "delay 200", "zcl global read 0xFC02 0x0012", "send 0x${device.deviceNetworkId} 1 1","delay 400", - "zcl mfg-code 0x104E", "delay 200", + "zcl mfg-code ${manufacturerCode}", "delay 200", "zcl global read 0xFC02 0x0013", "send 0x${device.deviceNetworkId} 1 1","delay 400", - "zcl mfg-code 0x104E", "delay 200", + "zcl mfg-code ${manufacturerCode}", "delay 200", "zcl global read 0xFC02 0x0014", "send 0x${device.deviceNetworkId} 1 1", "delay 400" ] @@ -420,19 +421,19 @@ def getTemperature(value) { "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200", - "zcl mfg-code 0x104E", + "zcl mfg-code ${manufacturerCode}", "zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - "zcl mfg-code 0x104E", + "zcl mfg-code ${manufacturerCode}", "zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - "zcl mfg-code 0x104E", + "zcl mfg-code ${manufacturerCode}", "zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - "zcl mfg-code 0x104E", + "zcl mfg-code ${manufacturerCode}", "zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" @@ -530,6 +531,14 @@ private Map getXyzResult(results, description) { ] } +private getManufacturerCode() { + if (device.getDataValue("manufacturer") == "SmartThings") { + return "0x110A" + } else { + return "0x104E" + } +} + private hexToInt(value) { new BigInteger(value, 16) } From fe4a2ed3d0ec2469a963b88c44d9ef348258a84b Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Wed, 9 Dec 2015 23:56:51 -0600 Subject: [PATCH 2/3] multi: Fix invalid accelerometer readings during refresh The code can only correctly handle the accelerometer readings when they are all received in the same message. Individual reads of the attributes results in individual read attribute responses which led to events with incorrect values. The most straightforward fix is to not read the attributes in the first place and rely on attribute reporting to report the values at the appropriate time. Resolves: https://smartthings.atlassian.net/browse/DVCSMP-1315 --- .../smartsense-multi-sensor.groovy | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index 97c7bb5..ae18c1c 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -384,20 +384,8 @@ def getTemperature(value) { "zcl mfg-code ${manufacturerCode}", "delay 200", "zcl global read 0xFC02 0x0010", - "send 0x${device.deviceNetworkId} 1 1","delay 400", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global read 0xFC02 0x0012", - "send 0x${device.deviceNetworkId} 1 1","delay 400", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global read 0xFC02 0x0013", - "send 0x${device.deviceNetworkId} 1 1","delay 400", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global read 0xFC02 0x0014", - "send 0x${device.deviceNetworkId} 1 1", "delay 400" - ] + "send 0x${device.deviceNetworkId} 1 1","delay 400" + ] return refreshCmds + enrollResponse() } From d1504e9a3c9ef0aa55ef38aca67e9f1e2bc09856 Mon Sep 17 00:00:00 2001 From: vlaminck Date: Mon, 14 Dec 2015 13:27:41 -0600 Subject: [PATCH 3/3] =?UTF-8?q?Updated=20the=20ever=20element=20app=20to?= =?UTF-8?q?=20better=20represent=20what=20each=20element=20ca=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../every-element.src/every-element.groovy | 796 +++++++++++------- 1 file changed, 501 insertions(+), 295 deletions(-) diff --git a/smartapps/smartthings/examples/every-element.src/every-element.groovy b/smartapps/smartthings/examples/every-element.src/every-element.groovy index 82ff69a..417ae1c 100644 --- a/smartapps/smartthings/examples/every-element.src/every-element.groovy +++ b/smartapps/smartthings/examples/every-element.src/every-element.groovy @@ -1,7 +1,7 @@ /** * Every Element * - * Copyright 2014 SmartThings + * Copyright 2015 SmartThings * * 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: @@ -14,349 +14,555 @@ * */ definition( - name: "Every Element", - namespace: "smartthings/examples", - author: "SmartThings", - description: "Every element demonstration app", - category: "SmartThings Internal", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png" + name: "Every Element", + namespace: "smartthings/examples", + author: "SmartThings", + description: "Every element demonstration app", + category: "SmartThings Internal", + iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", + iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png" ) preferences { - page(name: "firstPage") - page(name: "inputPage") - page(name: "appPage") - page(name: "labelPage") - page(name: "modePage") - page(name: "paragraphPage") - page(name: "iconPage") - page(name: "hrefPage") - page(name: "buttonsPage") - page(name: "imagePage") - page(name: "videoPage") - page(name: "deadEnd", title: "Nothing to see here, move along.", content: "foo") - page(name: "flattenedPage") + // landing page + page(name: "firstPage") + + // PageKit + page(name: "buttonsPage") + page(name: "imagePage") + page(name: "inputPage") + page(name: "inputBooleanPage") + page(name: "inputIconPage") + page(name: "inputImagePage") + page(name: "inputDevicePage") + page(name: "inputCapabilityPage") + page(name: "inputRoomPage") + page(name: "inputModePage") + page(name: "inputSelectionPage") + page(name: "inputHubPage") + page(name: "inputContactBookPage") + page(name: "inputTextPage") + page(name: "inputTimePage") + page(name: "appPage") + page(name: "hrefPage") + page(name: "paragraphPage") + page(name: "videoPage") + page(name: "labelPage") + page(name: "modePage") + + // Every element helper pages + page(name: "deadEnd", title: "Nothing to see here, move along.", content: "foo") + page(name: "flattenedPage") } def firstPage() { - dynamicPage(name: "firstPage", title: "Where to first?", install: true, uninstall: true) { - section() { - href(page: "inputPage", title: "Element: 'input'") - href(page: "appPage", title: "Element: 'app'") - href(page: "labelPage", title: "Element: 'label'") - href(page: "modePage", title: "Element: 'mode'") - href(page: "paragraphPage", title: "Element: 'paragraph'") - href(page: "iconPage", title: "Element: 'icon'") - href(page: "hrefPage", title: "Element: 'href'") - href(page: "buttonsPage", title: "Element: 'buttons'") - href(page: "imagePage", title: "Element: 'image'") - href(page: "videoPage", title: "Element: 'video'") - } - section() { - href(page: "flattenedPage", title: "All of the above elements on a single page") - } - } + dynamicPage(name: "firstPage", title: "Where to first?", install: true, uninstall: true) { + section { + href(page: "appPage", title: "Element: 'app'") + href(page: "buttonsPage", title: "Element: 'buttons'") + href(page: "hrefPage", title: "Element: 'href'") + href(page: "imagePage", title: "Element: 'image'") + href(page: "inputPage", title: "Element: 'input'") + href(page: "labelPage", title: "Element: 'label'") + href(page: "modePage", title: "Element: 'mode'") + href(page: "paragraphPage", title: "Element: 'paragraph'") + href(page: "videoPage", title: "Element: 'video'") + } + section { + href(page: "flattenedPage", title: "All of the above elements on a single page") + } + } } def inputPage() { - dynamicPage(name: "inputPage", title: "Every 'input' type") { - section("enum") { - input(type: "enum", name: "enumRefresh", title: "submitOnChange:true", required: false, multiple: true, options: ["one", "two", "three"], submitOnChange: true) - if (enumRefresh) { - paragraph "${enumRefresh}" - } - input(type: "enum", name: "enumSegmented", title: "style:segmented", required: false, multiple: true, options: ["one", "two", "three"], style: "segmented") - input(type: "enum", name: "enum", title: "required:false, multiple:false", required: false, multiple: false, options: ["one", "two", "three"]) - input(type: "enum", name: "enumRequired", title: "required:true", required: true, multiple: false, options: ["one", "two", "three"]) - input(type: "enum", name: "enumMultiple", title: "multiple:true", required: false, multiple: true, options: ["one", "two", "three"]) - input(type: "enum", name: "enumWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, options: ["one", "two", "three"], image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - input(type: "enum", name: "enumWithGroupedOptions", title: "groupedOptions", description: "This enum has grouped options", required: false, multiple: true, groupedOptions: [ - [ - title : "the group title that is displayed", - order : 0, // the order of the group; 0-based - image : "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", // not yet supported - values: [ - [ - key : "the value that will be placed in SmartApp settings.", // such as a device id - value: "the title of the selectable option that is displayed", // such as a device name - order: 0 // the order of the option - ] - ] - ], - [ - title : "the second group title that is displayed", - order : 1, // the order of the group; 0-based - image : null, // not yet supported - values: [ - [ - key : "some_device_id", - value: "some_device_name", - order: 1 // the order of the option. This option will appear second in the list even though it is the first option defined in this map - ], - [ - key : "some_other_device_id", - value: "some_other_device_name", - order: 0 // the order of the option. This option will appear first in the list even though it is not the first option defined in this map - ] - ] - ] - ]) - } - section("text") { - input(type: "text", name: "text", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "text", name: "textRequired", title: "required:true", required: true, multiple: false) - input(type: "text", name: "textWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("number") { - input(type: "number", name: "number", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "number", name: "numberRequired", title: "required:true", required: true, multiple: false) - input(type: "number", name: "numberWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("boolean") { - input(type: "boolean", name: "boolean", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "boolean", name: "booleanWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("password") { - input(type: "password", name: "password", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "password", name: "passwordRequired", title: "required:true", required: true, multiple: false) - input(type: "password", name: "passwordWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("phone") { - input(type: "phone", name: "phone", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "phone", name: "phoneRequired", title: "required:true", required: true, multiple: false) - input(type: "phone", name: "phoneWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("email") { - input(type: "email", name: "email", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "email", name: "emailRequired", title: "required:true", required: true, multiple: false) - input(type: "email", name: "emailWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("decimal") { - input(type: "decimal", name: "decimal", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "decimal", name: "decimalRequired", title: "required:true", required: true, multiple: false) - input(type: "decimal", name: "decimalWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("mode") { - input(type: "mode", name: "mode", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "mode", name: "modeRequired", title: "required:true", required: true, multiple: false) - input(type: "mode", name: "modeMultiple", title: "multiple:true", required: false, multiple: true) - input(type: "mode", name: "iconWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("icon") { - input(type: "icon", name: "icon", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "icon", name: "iconRequired", title: "required:true", required: true, multiple: false) - input(type: "icon", name: "iconWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("capability") { - input(type: "capability.switch", name: "capability", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "capability.switch", name: "capabilityRequired", title: "required:true", required: true, multiple: false) - input(type: "capability.switch", name: "capabilityMultiple", title: "multiple:true", required: false, multiple: true) - input(type: "capability.switch", name: "capabilityWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("hub") { - input(type: "hub", name: "hub", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "hub", name: "hubRequired", title: "required:true", required: true, multiple: false) - input(type: "hub", name: "hubMultiple", title: "multiple:true", required: false, multiple: true) - input(type: "hub", name: "hubWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("device") { - input(type: "device.switch", name: "device", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "device.switch", name: "deviceRequired", title: "required:true", required: true, multiple: false) - input(type: "device.switch", name: "deviceMultiple", title: "multiple:true", required: false, multiple: true) - input(type: "device.switch", name: "deviceWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("time") { - input(type: "time", name: "time", title: "required:false, multiple:false", required: false, multiple: false) - input(type: "time", name: "timeRequired", title: "required:true", required: true, multiple: false) - input(type: "time", name: "timeWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - section("contact-book") { - input("recipients", "contact", title: "Notify", description: "Send notifications to") { - input(type: "phone", name: "phone", title: "Send text message to", required: false, multiple: false) - input(type: "boolean", name: "boolean", title: "Send push notification", required: false, multiple: false) - } - } - } + dynamicPage(name: "inputPage", title: "Links to every 'input' element") { + section { + href(page: "inputBooleanPage", title: "to boolean page") + href(page: "inputIconPage", title: "to icon page") + href(page: "inputImagePage", title: "to image page") + href(page: "inputSelectionPage", title: "to selection page") + href(page: "inputTextPage", title: "to text page") + href(page: "inputTimePage", title: "to time page") + } + section("subsets of selection input") { + href(page: "inputDevicePage", title: "to device selection page") + href(page: "inputCapabilityPage", title: "to capability selection page") + href(page: "inputRoomPage", title: "to room selection page") + href(page: "inputModePage", title: "to mode selection page") + href(page: "inputHubPage", title: "to hub selection page") + href(page: "inputContactBookPage", title: "to contact-book selection page") + } + } +} + +def inputBooleanPage() { + dynamicPage(name: "inputBooleanPage") { + section { + paragraph "The `required` and `multiple` attributes have no effect because the value will always be either `true` or `false`" + } + section { + input(type: "boolean", name: "booleanWithoutDescription", title: "without description", description: null) + input(type: "boolean", name: "booleanWithDescription", title: "with description", description: "This has a description") + } + section("defaultValue: 'true'") { + input(type: "boolean", name: "booleanWithDefaultValue", title: "", description: "", defaultValue: "true") + } + section("with image") { + input(type: "boolean", name: "booleanWithoutDescriptionWithImage", title: "without description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", description: null) + input(type: "boolean", name: "booleanWithDescriptionWithImage", title: "with description", description: "This has a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputIconPage() { + dynamicPage(name: "inputIconPage") { + section { + paragraph "`description` is not displayed for icon elements" + paragraph "`multiple` has no effect because you can only choose a single icon" + } + section("required: true") { + input(type: "icon", name: "iconRequired", title: "without description", required: true) + input(type: "icon", name: "iconRequiredWithDescription", title: "with description", description: "this is a description", required: true) + } + section("with image") { + paragraph "The image specified will be replaced after an icon is selected" + input(type: "icon", name: "iconwithImage", title: "without description", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputImagePage() { + dynamicPage(name: "inputImagePage") { + section { + paragraph "This only exists in DeviceTypes. Someone should do something about that. (glares at MikeDave)" + paragraph "Go to the device preferences of a Mobile Presence device to see it in action" + paragraph "If you try to set the value of this, it will not behave as it would in Device Preferences" + input(type: "image", title: "This is kind of what it looks like", required: false) + } + } +} + + +def optionsGroup(List groups, String title) { + def group = [values:[], order: groups.size()] + group.title = title ?: "" + groups << group + return groups +} +def addValues(List groups, String key, String value) { + def lastGroup = groups[-1] + lastGroup["values"] << [ + key: key, + value: value, + order: lastGroup["values"].size() + ] + return groups +} +def listToMap(List original) { + original.inject([:]) { result, v -> + result[v] = v + return result + } +} +def addGroup(List groups, String title, values) { + if (values instanceof List) { + values = listToMap(values) + } + + values.inject(optionsGroup(groups, title)) { result, k, v -> + return addValues(result, k, v) + } + return groups +} +def addGroup(values) { + addGroup([], null, values) +} +/* Example usage of options builder + +// Creating grouped options + def newGroups = [] + addGroup(newGroups, "first group", ["foo", "bar", "baz"]) + addGroup(newGroups, "second group", [zero: "zero", one: "uno", two: "dos", three: "tres"]) + +// simple list + addGroup(["a", "b", "c"]) + +// simple map + addGroup(["a": "yes", "b": "no", "c": "maybe"])​​​​ +*/ + + +def inputSelectionPage() { + + def englishOptions = ["One", "Two", "Three"] + def spanishOptions = ["Uno", "Dos", "Tres"] + def groupedOptions = [] + addGroup(groupedOptions, "English", englishOptions) + addGroup(groupedOptions, "Spanish", spanishOptions) + + dynamicPage(name: "inputSelectionPage") { + + section("options variations") { + paragraph "tap these elements and look at the differences when selecting an option" + input(type: "enum", name: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions)) + input(type: "enum", name: "selectionGrouped", title: "Grouped options", description: "separate groups of options with headers", groupedOptions: groupedOptions) + } + + section("list vs map") { + paragraph "These should be identical in UI, but are different in code and will produce different settings" + input(type: "enum", name: "selectionList", title: "Choose a device", description: "settings will be something like ['Device1 Label']", groupedOptions: addGroup(["Device1 Label", "Device2 Label"])) + input(type: "enum", name: "selectionMap", title: "Choose a device", description: "settings will be something like ['device1-id']", groupedOptions: addGroup(["device1-id": "Device1 Label", "device2-id": "Device2 Label"])) + } + + section("segmented") { + paragraph "segmented should only work if there are either 2 or 3 options to choose from" + input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", groupedOptions: addGroup(["One"])) + input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", groupedOptions: addGroup(["One", "Two", "Three", "Four"])) + + paragraph "multiple and required will have no effect on segmented selection elements. There will always be exactly 1 option selected" + input(type: "enum", name: "selectionSegmented2", style: "segmented", title: "2 options", options: ["One", "Two"]) + input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"]) + + paragraph "specifying defaultValue still works with segmented selection elements" + input(type: "enum", name: "selectionSegmentedWithDefault", title: "defaulted to 'two'", groupedOptions: addGroup(["One", "Two", "Three"]), defaultValue: "Two") + } + + section("required: true") { + input(type: "enum", name: "selectionRequired", title: "This is required", description: "It should look different when nothing is selected", groupedOptions: addGroup(["only option"]), required: true) + } + + section("multiple: true") { + input(type: "enum", name: "selectionMultiple", title: "This allows multiple selections", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true) + } + + section("with image") { + input(type: "enum", name: "selectionWithImage", title: "This has an image", description: "and a description", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputTextPage() { + dynamicPage(name: "inputTextPage", title: "Every 'text' variation") { + section("style and functional differences") { + input(type: "text", name: "textRequired", title: "required: true", description: "This should look different when nothing has been entered", required: true) + input(type: "text", name: "textWithImage", title: "with image", description: "This should look different when nothing has been entered", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", required: false) + } + section("text") { + input(type: "text", name: "text", title: "This has an alpha-numeric keyboard", description: "no special formatting", required: false) + } + section("password") { + input(type: "password", name: "password", title: "This has an alpha-numeric keyboard", description: "masks value", required: false) + } + section("email") { + input(type: "email", name: "email", title: "This has an email-specific keyboard", description: "no special formatting", required: false) + } + section("phone") { + input(type: "phone", name: "phone", title: "This has a numeric keyboard", description: "formatted for phone numbers", required: false) + } + section("decimal") { + input(type: "decimal", name: "decimal", title: "This has an numeric keyboard with decimal point", description: "no special formatting", required: false) + } + section("number") { + input(type: "number", name: "number", title: "This has an numeric keyboard without decimal point", description: "no special formatting", required: false) + } + + section("specified ranges") { + paragraph "You can limit number and decimal inputs to a specific range." + input(range: "50..150", type: "decimal", name: "decimalRange50..150", title: "only values between 50 and 150 will pass validation", description: "no special formatting", required: false) + paragraph "Negative limits will add a negative symbol to the keyboard." + input(range: "-50..50", type: "number", name: "numberRange-50..50", title: "only values between -50 and 50 will pass validation", description: "no special formatting", required: false) + paragraph "Specify * to not limit one side or the other." + input(range: "*..0", type: "decimal", name: "decimalRange*..0", title: "only negative values will pass validation", description: "no special formatting", required: false) + input(range: "*..*", type: "number", name: "numberRange*..*", title: "only positive values will pass validation", description: "no special formatting", required: false) + paragraph "If you don't specify a range, it defaults to 0..*" + } + } +} +def inputTimePage() { + dynamicPage(name: "inputTimePage") { + section { + input(type: "time", name: "timeWithDescription", title: "a time picker", description: "with a description", required: false) + input(type: "time", name: "timeWithoutDescription", title: "without a description", description: null, required: false) + input(type: "time", name: "timeRequired", title: "required: true", required: true) + } + } +} + +/// selection subsets +def inputDevicePage() { + + dynamicPage(name: "inputDevicePage") { + + section("required: true") { + input(type: "device.switch", name: "deviceRequired", title: "This is required", description: "It should look different when nothing is selected") + } + + section("multiple: true") { + input(type: "device.switch", name: "deviceMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true) + } + + section("with image") { + input(type: "device.switch", name: "deviceRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputCapabilityPage() { + + dynamicPage(name: "inputCapabilityPage") { + + section("required: true") { + input(type: "capability.switch", name: "capabilityRequired", title: "This is required", description: "It should look different when nothing is selected") + } + + section("multiple: true") { + input(type: "capability.switch", name: "capabilityMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true) + } + + section("with image") { + input(type: "capability.switch", name: "capabilityRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputRoomPage() { + + dynamicPage(name: "inputRoomPage") { + + section("required: true") { + input(type: "room", name: "roomRequired", title: "This is required", description: "It should look different when nothing is selected") + } + + section("multiple: true") { + input(type: "room", name: "roomMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true) + } + + section("with image") { + input(type: "room", name: "roomRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputModePage() { + + dynamicPage(name: "inputModePage") { + + section("required: true") { + input(type: "mode", name: "modeRequired", title: "This is required", description: "It should look different when nothing is selected") + } + + section("multiple: true") { + input(type: "mode", name: "modeMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true) + } + + section("with image") { + input(type: "mode", name: "modeRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputHubPage() { + + dynamicPage(name: "inputHubPage") { + + section("required: true") { + input(type: "hub", name: "hubRequired", title: "This is required", description: "It should look different when nothing is selected") + } + + section("multiple: true") { + input(type: "hub", name: "hubMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true) + } + + section("with image") { + input(type: "hub", name: "hubRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } +} +def inputContactBookPage() { + + dynamicPage(name: "inputContactBookPage") { + + section("required: true") { + input(type: "contact", name: "contactRequired", title: "This is required", description: "It should look different when nothing is selected") + } + + section("multiple: true") { + input(type: "contact", name: "contactMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true) + } + + section("with image") { + input(type: "contact", name: "contactRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } } def appPage() { - dynamicPage(name: "appPage", title: "Every 'app' type") { - section { - paragraph "These won't work unless you create a child SmartApp to link to... Sorry." - } - section("app") { - app( - name: "app", - title: "required:false, multiple:false", - required: false, - multiple: false, - namespace: "Steve", - appName: "Child SmartApp" - ) - app(name: "appRequired", title: "required:true", required: true, multiple: false, namespace: "Steve", appName: "Child SmartApp") - app(name: "appComplete", title: "state:complete", required: false, multiple: false, namespace: "Steve", appName: "Child SmartApp", state: "complete") - app(name: "appWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp") - } - section("multiple:true") { - app(name: "appMultiple", title: "multiple:true", required: false, multiple: true, namespace: "Steve", appName: "Child SmartApp") - } - section("multiple:true with image") { - app(name: "appMultipleWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp") - } - } + dynamicPage(name: "appPage", title: "Every 'app' type") { + section { + paragraph "These won't work unless you create a child SmartApp to link to... Sorry." + } + section("app") { + app( + name: "app", + title: "required:false, multiple:false", + required: false, + multiple: false, + namespace: "Steve", + appName: "Child SmartApp" + ) + app(name: "appRequired", title: "required:true", required: true, multiple: false, namespace: "Steve", appName: "Child SmartApp") + app(name: "appComplete", title: "state:complete", required: false, multiple: false, namespace: "Steve", appName: "Child SmartApp", state: "complete") + app(name: "appWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp") + } + section("multiple:true") { + app(name: "appMultiple", title: "multiple:true", required: false, multiple: true, namespace: "Steve", appName: "Child SmartApp") + } + section("multiple:true with image") { + app(name: "appMultipleWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp") + } + } } def labelPage() { - dynamicPage(name: "labelPage", title: "Every 'Label' type") { - section("label") { - label(name: "label", title: "required:false, multiple:false", required: false, multiple: false) - label(name: "labelRequired", title: "required:true", required: true, multiple: false) - label(name: "labelWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - } + dynamicPage(name: "labelPage", title: "Every 'Label' type") { + section("label") { + paragraph "The difference between a label element and a text input element is that the label element will effect the SmartApp directly by setting the label. An input element will place the set value in the SmartApp's settings." + paragraph "There are 3 here as an example. Never use more than 1 label element on a page." + label(name: "label", title: "required:false, multiple:false", required: false, multiple: false) + label(name: "labelRequired", title: "required:true", required: true, multiple: false) + label(name: "labelWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } } def modePage() { - dynamicPage(name: "modePage", title: "Every 'mode' type") { // TODO: finish this - section("mode") { - mode(name: "mode", title: "required:false, multiple:false", required: false, multiple: false) - mode(name: "modeRequired", title: "required:true", required: true, multiple: false) - mode(name: "modeMultiple", title: "multiple:true", required: false, multiple: true) - mode(name: "modeWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - } + dynamicPage(name: "modePage", title: "Every 'mode' type") { // TODO: finish this + section("mode") { + paragraph "The difference between a mode element and a mode input element is that the mode element will effect the SmartApp directly by setting the modes it executes in. A mode input element will place the set value in the SmartApp's settings." + paragraph "Another difference is that you can select 'All Modes' when choosing which mode the SmartApp should execute in. This is the same as selecting no modes. When a SmartApp does not have modes specified, it will execute in all modes." + paragraph "There are 4 here as an example. Never use more than 1 mode element on a page." + mode(name: "mode", title: "required:false, multiple:false", required: false, multiple: false) + mode(name: "modeRequired", title: "required:true", required: true, multiple: false) + mode(name: "modeMultiple", title: "multiple:true", required: false, multiple: true) + mode(name: "modeWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + } + } } def paragraphPage() { - dynamicPage(name: "paragraphPage", title: "Every 'paragraph' type") { - section("paragraph") { - paragraph "This us how you should make a paragraph element" - paragraph image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "This is a long description, blah, blah, blah." - } - } -} - -def iconPage() { - dynamicPage(name: "iconPage", title: "Every 'icon' type") { // TODO: finish this - section("icon") { - icon(name: "icon", title: "required:false, multiple:false", required: false, multiple: false) - icon(name: "iconRequired", title: "required:true", required: true, multiple: false) - icon(name: "iconWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - } + dynamicPage(name: "paragraphPage", title: "Every 'paragraph' type") { + section("paragraph") { + paragraph "This is how you should make a paragraph element" + paragraph image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "This is a long description, blah, blah, blah." + } + } } def hrefPage() { - dynamicPage(name: "hrefPage", title: "Every 'href' type") { - section("page") { - href(name: "hrefPage", title: "required:false, multiple:false", required: false, multiple: false, page: "deadEnd") - href(name: "hrefPageRequired", title: "required:true", required: true, multiple: false, page: "deadEnd", description: "Don't make hrefs required") - href(name: "hrefPageComplete", title: "state:complete", required: false, multiple: false, page: "deadEnd", state: "complete") - href(name: "hrefPageWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", page: "deadEnd",) - } - section("external") { - href(name: "hrefExternal", title: "required:false, multiple:false", required: false, multiple: false, style: "external", url: "http://smartthings.com/") - href(name: "hrefExternalRequired", title: "required:true", required: true, multiple: false, style: "external", url: "http://smartthings.com/", description: "Don't make hrefs required") - href(name: "hrefExternalComplete", title: "state:complete", required: false, multiple: true, style: "external", url: "http://smartthings.com/", state: "complete") - href(name: "hrefExternalWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", url: "http://smartthings.com/") - } - section("embedded") { - href(name: "hrefEmbedded", title: "required:false, multiple:false", required: false, multiple: false, style: "embedded", url: "http://smartthings.com/") - href(name: "hrefEmbeddedRequired", title: "required:true", required: true, multiple: false, style: "embedded", url: "http://smartthings.com/", description: "Don't make hrefs required") - href(name: "hrefEmbeddedComplete", title: "state:complete", required: false, multiple: true, style: "embedded", url: "http://smartthings.com/", state: "complete") - href(name: "hrefEmbeddedWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", url: "http://smartthings.com/") - } - } + dynamicPage(name: "hrefPage", title: "Every 'href' variation") { + section("stylistic differences") { + href(page: "deadEnd", title: "state: 'complete'", description: "gives the appearance of an input that has been filled out", state: "complete") + href(page: "deadEnd", title: "with image", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") + href(page: "deadEnd", title: "with image and description", description: "and state: 'complete'", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", state: "complete") + } + section("functional differences") { + href(page: "deadEnd", title: "to a page within the app") + href(url: "http://www.google.com", title: "to a url using all defaults") + href(url: "http://www.google.com", title: "external: true", description: "takes you outside the app", external: true) + } + } } def buttonsPage() { - dynamicPage(name: "buttonsPage", title: "Every 'button' type") { - section("buttons") { - buttons(name: "buttons", title: "required:false, multiple:false", required: false, multiple: false, buttons: [ - [label: "foo", action: "foo"], - [label: "bar", action: "bar"] - ]) - buttons(name: "buttonsRequired", title: "required:true", required: true, multiple: false, buttons: [ - [label: "foo", action: "foo"], - [label: "bar", action: "bar"] - ]) - buttons(name: "buttonsWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", buttons: [ - [label: "foo", action: "foo"], - [label: "bar", action: "bar"] - ]) - } - section("Colored Buttons") { - buttons(name: "buttonsColoredSpecial", title: "special strings", description: "SmartThings highly recommends using these colors", buttons: [ - [label: "complete", action: "bar", backgroundColor: "complete"], - [label: "required", action: "bar", backgroundColor: "required"] - ]) - buttons(name: "buttonsColoredHex", title: "hex values work", buttons: [ - [label: "bg: #000dff", action: "foo", backgroundColor: "#000dff"], - [label: "fg: #ffac00", action: "foo", color: "#ffac00"], - [label: "both fg and bg", action: "foo", color: "#ffac00", backgroundColor: "#000dff"] - ]) - buttons(name: "buttonsColoredString", title: "strings work too", buttons: [ - [label: "green", action: "foo", backgroundColor: "green"], - [label: "red", action: "foo", backgroundColor: "red"], - [label: "both fg and bg", action: "foo", color: "red", backgroundColor: "green"] - ]) - } - } + dynamicPage(name: "buttonsPage", title: "Every 'button' type") { + section("Simple Buttons") { + paragraph "If there are an odd number of buttons, the last button will span the entire view area." + buttons(name: "buttons1", title: "1 button", buttons: [ + [label: "foo", action: "foo"] + ]) + buttons(name: "buttons2", title: "2 buttons", buttons: [ + [label: "foo", action: "foo"], + [label: "bar", action: "bar"] + ]) + buttons(name: "buttons3", title: "3 buttons", buttons: [ + [label: "foo", action: "foo"], + [label: "bar", action: "bar"], + [label: "baz", action: "baz"] + ]) + buttons(name: "buttonsWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", buttons: [ + [label: "foo", action: "foo"], + [label: "bar", action: "bar"] + ]) + } + section("Colored Buttons") { + buttons(name: "buttonsColoredSpecial", title: "special strings", description: "SmartThings highly recommends using these colors", buttons: [ + [label: "complete", action: "bar", backgroundColor: "complete"], + [label: "required", action: "bar", backgroundColor: "required"] + ]) + buttons(name: "buttonsColoredHex", title: "hex values work", buttons: [ + [label: "bg: #000dff", action: "foo", backgroundColor: "#000dff"], + [label: "fg: #ffac00", action: "foo", color: "#ffac00"], + [label: "both fg and bg", action: "foo", color: "#ffac00", backgroundColor: "#000dff"] + ]) + buttons(name: "buttonsColoredString", title: "strings work too", buttons: [ + [label: "green", action: "foo", backgroundColor: "green"], + [label: "red", action: "foo", backgroundColor: "red"], + [label: "both fg and bg", action: "foo", color: "red", backgroundColor: "green"] + ]) + } + } } def imagePage() { - dynamicPage(name: "imagePage", title: "Every 'image' type") { // TODO: finish thise - section("image") { - image "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg" - image(name: "imageWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png") - } - } + dynamicPage(name: "imagePage", title: "Every 'image' type") { // TODO: finish thise + section("image") { + image "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg" + image(name: "imageWithMultipleImages", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, images: ["https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg"]) + } + } } def videoPage() { - dynamicPage(name: "imagePage", title: "Every 'image' type") { // TODO: finish this - section("video") { - // TODO: update this when there is a videoElement method - element(name: "videoElement", element: "video", type: "video", title: "this is a video!", description: "I am setting long title and descriptions to test the offset", required: false, image: "http://ec2-54-161-144-215.compute-1.amazonaws.com:8081/jesse/cam1/54aafcd1c198347511c26321.jpg", video: "http://ec2-54-161-144-215.compute-1.amazonaws.com:8081/jesse/cam1/54aafcd1c198347511c2631f.mp4") - } - } + dynamicPage(name: "videoPage", title: "Every 'video' type") { // TODO: finish this + section("video") { + // TODO: update this when there is a videoElement method + element(name: "videoElement", element: "video", type: "video", title: "this is a video!", description: "I am setting long title and descriptions to test the offset", required: false, image: "http://f.cl.ly/items/0w0D1p0K2D0d190F3H3N/Image%202015-12-14%20at%207.57.27%20AM.jpg", video: "http://f.cl.ly/items/3O2L03471l2K3E3l3K1r/Zombie%20Kid%20Likes%20Turtles.mp4") + } + } } def flattenedPage() { - def allSections = [] - firstPage().sections.each { section -> - section.body.each { hrefElement -> - if (hrefElement.page != "flattenedPage") { - allSections += "${hrefElement.page}"().sections - } - } - } - def flattenedPage = dynamicPage(name: "flattenedPage", title: "All elements in one page!") {} - flattenedPage.sections = allSections - return flattenedPage + def allSections = [] + firstPage().sections[0].body.each { hrefElement -> + if (hrefElement.name != "inputPage") { + // inputPage is a bunch of hrefs + allSections += "${hrefElement.page}"().sections + } + } + // collect the input elements + inputPage().sections.each { section -> + section.body.each { hrefElement -> + allSections += "${hrefElement.page}"().sections + } + } + def flattenedPage = dynamicPage(name: "flattenedPage", title: "All elements in one page!") {} + flattenedPage.sections = allSections + return flattenedPage } def foo() { - dynamicPage(name: "deadEnd") { - - } + dynamicPage(name: "deadEnd") { + section { } + } } def installed() { - log.debug "Installed with settings: ${settings}" + log.debug "Installed with settings: ${settings}" - initialize() + initialize() } def updated() { - log.debug "Updated with settings: ${settings}" + log.debug "Updated with settings: ${settings}" - unsubscribe() - initialize() + unsubscribe() + initialize() } def initialize() { - // TODO: subscribe to attributes, devices, locations, etc. + // TODO: subscribe to attributes, devices, locations, etc. }