├── README.md ├── devicetypes ├── domsim │ └── custom-keen-home-smart-vent-dth.src │ │ └── custom-keen-home-smart-vent-dth.groovy ├── iharyadi │ └── environment-sensor-ex.src │ │ └── environment-sensor-ex.groovy └── jscgs350 │ ├── fortrezz-water-valve.src │ └── fortrezz-water-valve.groovy │ ├── leviton-vizia-rf-button-scene-controller.src │ └── leviton-vizia-rf-button-scene-controller.groovy │ ├── my-aeon-home-energy-monitor-gen1.src │ ├── my-aeon-home-energy-monitor-gen1.groovy │ └── readme.md │ ├── my-aeon-home-energy-monitor-no-battery.src │ └── my-aeon-home-energy-monitor-no-battery.groovy │ ├── my-aeon-home-energy-monitor.src │ ├── my-aeon-home-energy-monitor.groovy │ └── readme.md │ ├── my-aeon-metering-switch.src │ ├── my-aeon-metering-switch.groovy │ └── readme.md │ ├── my-aeon-minimote.src │ └── my-aeon-minimote.groovy │ ├── my-centralite-keypad.src │ └── my-centralite-keypad.groovy │ ├── my-centralite-thermostat.src │ └── my-centralite-thermostat.groovy │ ├── my-dome-zooz-siren-and-chime.src │ └── my-dome-zooz-siren-and-chime.groovy │ ├── my-ecolink-doorbell-sensor.src │ └── my-ecolink-doorbell-sensor.groovy │ ├── my-ecolink-lock-sensor.src │ └── my-ecolink-lock-sensor.groovy │ ├── my-ecolink-mailbox-door-sensor.src │ └── my-ecolink-mailbox-door-sensor.groovy │ ├── my-enhanced-ge-dimmer.src │ └── my-enhanced-ge-dimmer.groovy │ ├── my-enhanced-leviton-dz6hd-dimmer.src │ └── my-enhanced-leviton-dz6hd-dimmer.groovy │ ├── my-fortrezz-flow-meter-interface.src │ └── my-fortrezz-flow-meter-interface.groovy │ ├── my-fortrezz-water-valve.src │ └── my-fortrezz-water-valve.groovy │ ├── my-ge-fan-control-switch.src │ └── my-ge-fan-control-switch.groovy │ ├── my-ge-link-bulb.src │ └── my-ge-link-bulb.groovy │ ├── my-iris-il071-motion-sensor.src │ └── my-iris-il071-motion-sensor.groovy │ ├── my-iris-lock-sensor.src │ └── my-iris-lock-sensor.groovy │ ├── my-iris-smart-button.src │ └── my-iris-smart-button.groovy │ ├── my-keen-home-smart-vent-v2.src │ └── my-keen-home-smart-vent-v2.groovy │ ├── my-keen-home-smart-vent.src │ └── my-keen-home-smart-vent.groovy │ ├── my-landroid-alerter.src │ └── my-landroid-alerter.groovy │ ├── my-leak-gopher-valve-controller.src │ └── my-leak-gopher-valve-controller.groovy │ ├── my-mimolite-garage-door-controller.src │ └── my-mimolite-garage-door-controller.groovy │ ├── my-mimolite-water-valve-controller.src │ └── my-mimolite-water-valve-controller.groovy │ ├── my-monoprice-motion-sensor.src │ └── my-monoprice-motion-sensor.groovy │ ├── my-philio-psm01-sensor.src │ └── my-philio-psm01-sensor.groovy │ ├── my-simulated-presence-sensor.src │ └── my-simulated-presence-sensor.groovy │ ├── my-smartpower-outlet.src │ └── my-smartpower-outlet.groovy │ ├── my-smartsense-doorbell-sensor.src │ └── my-smartsense-doorbell-sensor.groovy │ ├── my-smartsense-lock-sensor.src │ └── my-smartsense-lock-sensor.groovy │ ├── my-smartsense-motion-sensor.src │ └── my-smartsense-motion-sensor.groovy │ ├── my-smartsense-multi-no-contact.src │ └── my-smartsense-multi-no-contact.groovy │ ├── my-smartsense-temp-humidity-sensor.src │ └── my-smartsense-temp-humidity-sensor.groovy │ ├── my-smartweather-station-tile.src │ └── my-smartweather-station-tile.groovy │ ├── my-spruce-sensor.src │ └── my-spruce-sensor.groovy │ ├── my-utilitech-water-sensor.src │ └── my-utilitech-water-sensor.groovy │ ├── my-utilitech-zwave-siren.src │ └── my-utilitech-zwave-siren.groovy │ ├── my-virtual-temp-tile.src │ └── my-virtual-temp-tile.groovy │ ├── my-water-level-sensor.src │ └── my-water-level-sensor.groovy │ ├── my-zigbee-valve.src │ └── my-zigbee-valve.groovy │ ├── my-zooz-metering-switch.src │ └── my-zooz-metering-switch.groovy │ ├── my-zwave-door-sensor.src │ └── my-zwave-door-sensor.groovy │ ├── my-zwave-lock-sensor.src │ └── my-zwave-lock-sensor.groovy │ ├── my-zwave-reversed-contact-sensor.src │ └── my-zwave-reversed-contact-sensor.groovy │ ├── my-zwave-thermostat-old-slider-version.src │ └── my-zwave-thermostat-old-slider-version.groovy │ ├── my-zwave-thermostat.src │ ├── my-zwave-thermostat-OLD.groovy │ ├── my-zwave-thermostat.groovy │ └── readme.md │ ├── my-zwave-valve.src │ └── my-zwave-valve.groovy │ ├── readme.md │ ├── smartweather-station-tile.src │ └── smartweather-station-tile.groovy │ ├── zigbee-lock.src │ └── zigbee-lock.groovy │ └── zwave-lock.src │ └── zwave-lock.groovy ├── img ├── 24-hour-clockv2.png ├── 7day.png ├── Battery-Charge-icon.png ├── auto@2x.png ├── battery-icon-614x460.png ├── cool@2x.png ├── device-activity-tile@2x.png ├── fan-auto@2x.png ├── fan-on@2x.png ├── heat@2x.png ├── icon-garage1.png ├── mailbox.png ├── monthv2.png ├── nobattery.png ├── poutlet.png ├── readme ├── settings.png ├── settings_small.png ├── transportation12-icn@2x.png ├── warning.png ├── watervalve.png └── watervalve1.png └── smartapps └── jscgs350 ├── aeon-hem-v1-reset-manager.src └── aeon-hem-v1-reset-manager.groovy ├── dashboard-battery-monitor-child.src └── dashboard-battery-monitor-child.groovy ├── dashboard-battery-monitor-parent.src └── dashboard-battery-monitor-parent.groovy ├── dashboard-contact-sensors.src └── dashboard-contact-sensors.groovy ├── dashboard-device-check.src └── dashboard-device-check.groovy ├── dashboard-lights-switches-and-outlets-child.src └── dashboard-lights-switches-and-outlets-child.groovy ├── dashboard-lights-switches-and-outlets-parent.src └── dashboard-lights-switches-and-outlets-parent.groovy ├── dashboard-motion-sensors.src └── dashboard-motion-sensors.groovy ├── dashboard-power-meters.src └── dashboard-power-meters.groovy ├── dashboard-temperatures.src └── dashboard-temperatures.groovy ├── device-monitor.src └── device-monitor.groovy ├── do-these-things-when-all-specified-people-come-back.src └── do-these-things-when-all-specified-people-come-back.groovy ├── do-these-things-when-all-specified-people-leave.src └── do-these-things-when-all-specified-people-leave.groovy ├── fortrezz-flow-meter-reset-manager.src └── fortrezz-flow-meter-reset-manager.groovy ├── fortrezz-leak-detector-child.src └── fortrezz-leak-detector-child.groovy ├── fortrezz-leak-detector.src └── fortrezz-leak-detector.groovy ├── gear-watch.src └── gear-watch.groovy ├── hello-home.src └── hello-home.groovy ├── humidity-monitor-child-app.src └── humidity-monitor-child-app.groovy ├── humidity-monitor-parent-app.src └── humidity-monitor-parent-app.groovy ├── master-and-child-switches.src └── master-and-child-switches.groovy ├── my-button-controller-child.src └── my-button-controller-child.groovy ├── my-button-controller-parent.src └── my-button-controller-parent.groovy ├── my-button-controller.src └── my-button-controller.groovy ├── notify-me-when-a-switch-turns-off.src └── notify-me-when-a-switch-turns-off.groovy ├── notify-me-when-a-switch-turns-on.src └── notify-me-when-a-switch-turns-on.groovy ├── pollster.src └── pollster.groovy ├── power-meter-reset-manager.src └── power-meter-reset-manager.groovy ├── samsung-tv-connect.src └── samsung-tv-connect.groovy ├── smartweather-station-controller.src └── smartweather-station-controller.groovy ├── thermostat-manager-child.src └── thermostat-manager-child.groovy ├── thermostat-manager.src └── thermostat-manager.groovy ├── virtual-temp-device-child.src └── virtual-temp-device-child.groovy └── virtual-temp-device.src └── virtual-temp-device.groovy /README.md: -------------------------------------------------------------------------------- 1 | # jcdevhandlers 2 | My repo for smartthings device handlers and smartapps (although the directory name implies just device handlers) 3 | 4 | Many people have asked for my PayPal info, so I thought I'd post it here in case anyone would like it: 5 | 6 | http://paypal.me/JohnConstantelos 7 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/fortrezz-water-valve.src/fortrezz-water-valve.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | */ 14 | metadata { 15 | definition (name: "Fortrezz Water Valve", namespace: "jscgs350", author: "SmartThings", ocfDeviceType: "oic.d.watervalve", runLocally: false, minHubCoreVersion: '000.017.0012', executeCommandsLocally: false) { 16 | capability "Actuator" 17 | capability "Health Check" 18 | capability "Valve" 19 | capability "Refresh" 20 | capability "Sensor" 21 | capability "Switch" 22 | 23 | fingerprint deviceId: "0x1000", inClusters: "0x25,0x72,0x86,0x71,0x22,0x70" 24 | fingerprint mfr:"0084", prod:"0213", model:"0215", deviceJoinName: "FortrezZ Water Valve" 25 | } 26 | 27 | // simulator metadata 28 | simulator { 29 | status "close": "command: 2503, payload: FF" 30 | status "open": "command: 2503, payload: 00" 31 | 32 | // reply messages 33 | reply "2001FF": "command: 2503, payload: FF" 34 | reply "200100": "command: 2503, payload: 00" 35 | } 36 | 37 | // tile definitions 38 | tiles(scale: 2) { 39 | multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){ 40 | tileAttribute ("device.valve", key: "PRIMARY_CONTROL") { 41 | attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing" 42 | attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening" 43 | attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC" 44 | attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff" 45 | } 46 | } 47 | 48 | standardTile("refresh", "device.valve", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { 49 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 50 | } 51 | 52 | main "valve" 53 | details(["valve","refresh"]) 54 | } 55 | } 56 | 57 | def installed(){ 58 | // Device-Watch simply pings if no device events received for 32min(checkInterval) 59 | sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 60 | 61 | response(refresh()) 62 | } 63 | 64 | def updated(){ 65 | // Device-Watch simply pings if no device events received for 32min(checkInterval) 66 | sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 67 | } 68 | 69 | def parse(String description) { 70 | log.trace description 71 | def cmd = zwave.parse(description) 72 | if (cmd) { 73 | return zwaveEvent(cmd) 74 | } 75 | log.debug "Could not parse message" 76 | return null 77 | } 78 | 79 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { 80 | def value = cmd.value ? "closed" : "open" 81 | 82 | return createEventWithDebug([name: "valve", value: value, descriptionText: "$device.displayName valve is $value"]) 83 | } 84 | 85 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 86 | return createEvent([:]) // Handles all Z-Wave commands we aren't interested in 87 | } 88 | 89 | def open() { 90 | delayBetween([ 91 | zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format(), 92 | zwave.switchBinaryV1.switchBinaryGet().format() 93 | ], 500) 94 | } 95 | 96 | def off() { 97 | open() 98 | } 99 | 100 | def close() { 101 | delayBetween([ 102 | zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format(), 103 | zwave.switchBinaryV1.switchBinaryGet().format() 104 | ], 500) 105 | } 106 | 107 | def on() { 108 | close() 109 | } 110 | 111 | /** 112 | * PING is used by Device-Watch in attempt to reach the Device 113 | * */ 114 | def ping() { 115 | refresh() 116 | } 117 | 118 | def refresh() { 119 | zwave.switchBinaryV1.switchBinaryGet().format() 120 | } 121 | 122 | def createEventWithDebug(eventMap) { 123 | def event = createEvent(eventMap) 124 | log.debug "Event created with ${event?.name}:${event?.value} - ${event?.descriptionText}" 125 | return event 126 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-aeon-home-energy-monitor-gen1.src/readme.md: -------------------------------------------------------------------------------- 1 | Many people have asked for my PayPal info, so I thought I'd post it here in case anyone would like it: 2 | 3 | http://paypal.me/JohnConstantelos 4 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-aeon-home-energy-monitor-no-battery.src/my-aeon-home-energy-monitor-no-battery.groovy: -------------------------------------------------------------------------------- 1 | This has moved to: 2 | 3 | https://raw.githubusercontent.com/constjs/jcdevhandlers/master/devicetypes/jscgs350/my-aeon-home-energy-monitor-gen1.src/my-aeon-home-energy-monitor-gen1.groovy 4 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-aeon-home-energy-monitor.src/my-aeon-home-energy-monitor.groovy: -------------------------------------------------------------------------------- 1 | This has moved to: 2 | 3 | https://raw.githubusercontent.com/constjs/jcdevhandlers/master/devicetypes/jscgs350/my-aeon-home-energy-monitor-gen1.src/my-aeon-home-energy-monitor-gen1.groovy 4 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-aeon-home-energy-monitor.src/readme.md: -------------------------------------------------------------------------------- 1 | This has moved to: 2 | 3 | https://github.com/constjs/jcdevhandlers/tree/master/devicetypes/jscgs350/my-aeon-home-energy-monitor-gen1.src 4 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-aeon-metering-switch.src/readme.md: -------------------------------------------------------------------------------- 1 | Many people have asked for my PayPal info, so I thought I'd post it here in case anyone would like it: 2 | 3 | http://paypal.me/JohnConstantelos 4 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-fortrezz-flow-meter-interface.src/my-fortrezz-flow-meter-interface.groovy: -------------------------------------------------------------------------------- 1 | This has moved to here: 2 | https://github.com/jsconstantelos/SmartThings/blob/master/devicetypes/jsconstantelos/my-fortrezz-flow-meter-interface.src/my-fortrezz-flow-meter-interface.groovy 3 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-fortrezz-water-valve.src/my-fortrezz-water-valve.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | */ 14 | metadata { 15 | definition (name: "My Fortrezz Water Valve", namespace: "jscgs350", author: "SmartThings", ocfDeviceType: "oic.d.watervalve", runLocally: false, minHubCoreVersion: '000.017.0012', executeCommandsLocally: false) { 16 | capability "Actuator" 17 | capability "Health Check" 18 | capability "Valve" 19 | capability "Refresh" 20 | capability "Sensor" 21 | capability "Switch" 22 | 23 | fingerprint deviceId: "0x1000", inClusters: "0x25,0x72,0x86,0x71,0x22,0x70" 24 | fingerprint mfr:"0084", prod:"0213", model:"0215", deviceJoinName: "FortrezZ Water Valve" 25 | } 26 | 27 | // simulator metadata 28 | simulator { 29 | status "close": "command: 2503, payload: FF" 30 | status "open": "command: 2503, payload: 00" 31 | 32 | // reply messages 33 | reply "2001FF": "command: 2503, payload: FF" 34 | reply "200100": "command: 2503, payload: 00" 35 | } 36 | 37 | // tile definitions 38 | tiles(scale: 2) { 39 | multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){ 40 | tileAttribute ("device.valve", key: "PRIMARY_CONTROL") { 41 | attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing" 42 | attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening" 43 | attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC" 44 | attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff" 45 | } 46 | } 47 | 48 | standardTile("refresh", "device.valve", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { 49 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 50 | } 51 | 52 | main "valve" 53 | details(["valve","refresh"]) 54 | } 55 | } 56 | 57 | def installed(){ 58 | // Device-Watch simply pings if no device events received for 32min(checkInterval) 59 | sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 60 | 61 | response(refresh()) 62 | } 63 | 64 | def updated(){ 65 | // Device-Watch simply pings if no device events received for 32min(checkInterval) 66 | sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 67 | } 68 | 69 | def parse(String description) { 70 | log.trace description 71 | def cmd = zwave.parse(description) 72 | if (cmd) { 73 | return zwaveEvent(cmd) 74 | } 75 | log.debug "Could not parse message" 76 | return null 77 | } 78 | 79 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { 80 | def value = cmd.value ? "closed" : "open" 81 | 82 | return createEventWithDebug([name: "valve", value: value, descriptionText: "$device.displayName valve is $value"]) 83 | } 84 | 85 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 86 | return createEvent([:]) // Handles all Z-Wave commands we aren't interested in 87 | } 88 | 89 | def open() { 90 | delayBetween([ 91 | zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format(), 92 | zwave.switchBinaryV1.switchBinaryGet().format() 93 | ], 500) 94 | } 95 | 96 | def off() { 97 | delayBetween([ 98 | zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format(), 99 | zwave.switchBinaryV1.switchBinaryGet().format() 100 | ], 500) 101 | } 102 | 103 | def close() { 104 | delayBetween([ 105 | zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format(), 106 | zwave.switchBinaryV1.switchBinaryGet().format() 107 | ], 500) 108 | } 109 | 110 | def on() { 111 | delayBetween([ 112 | zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format(), 113 | zwave.switchBinaryV1.switchBinaryGet().format() 114 | ], 500) 115 | } 116 | 117 | /** 118 | * PING is used by Device-Watch in attempt to reach the Device 119 | * */ 120 | def ping() { 121 | refresh() 122 | } 123 | 124 | def refresh() { 125 | zwave.switchBinaryV1.switchBinaryGet().format() 126 | } 127 | 128 | def createEventWithDebug(eventMap) { 129 | def event = createEvent(eventMap) 130 | log.debug "Event created with ${event?.name}:${event?.value} - ${event?.descriptionText}" 131 | return event 132 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-iris-lock-sensor.src/my-iris-lock-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * SmartSense Open/Closed Sensor 3 | * 4 | * Copyright 2014 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | import physicalgraph.zigbee.clusters.iaszone.ZoneStatus 17 | 18 | metadata { 19 | definition(name: "My Iris Lock Sensor", namespace: "jscgs350", author: "SmartThings", mnmn: "SmartThings", vid:"generic-lock") { 20 | // definition(name: "ZigBee Lock", namespace: "jscgs350", author: "SmartThings", mnmn: "SmartThings", vid:"generic-lock") { 21 | capability "Battery" 22 | capability "Configuration" 23 | capability "Contact Sensor" 24 | capability "Refresh" 25 | capability "Temperature Measurement" 26 | capability "Health Check" 27 | capability "Lock" 28 | capability "Sensor" 29 | 30 | command "enrollResponse" 31 | 32 | } 33 | 34 | preferences { 35 | input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" 36 | input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false 37 | } 38 | 39 | tiles(scale: 2) { 40 | multiAttributeTile(name: "contact", type: "generic", width: 6, height: 4) { 41 | tileAttribute("device.contact", key: "PRIMARY_CONTROL") { 42 | attributeState "open", action:"refresh", label: 'Unlocked', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff" 43 | attributeState "closed", action:"refresh", label: 'Locked', icon:"st.locks.lock.locked", backgroundColor:"#00A0DC" 44 | } 45 | } 46 | 47 | valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) { 48 | state "temperature", label: '${currentValue}°', 49 | backgroundColors: [ 50 | [value: 31, color: "#153591"], 51 | [value: 44, color: "#1e9cbb"], 52 | [value: 59, color: "#90d2a7"], 53 | [value: 74, color: "#44b621"], 54 | [value: 84, color: "#f1d801"], 55 | [value: 95, color: "#d04e00"], 56 | [value: 96, color: "#bc2323"] 57 | ] 58 | } 59 | valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { 60 | state "battery", label: '${currentValue}% battery', unit: "" 61 | } 62 | standardTile("lock", "device.lock", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 63 | state "locked", label: 'Locked', icon:"st.locks.lock.locked", backgroundColor:"#00A0DC" 64 | state "unlocked", label: 'Unlocked', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff" 65 | } 66 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 67 | state "default", action: "refresh.refresh", icon: "st.secondary.refresh" 68 | } 69 | 70 | main(["contact", "temperature"]) 71 | details(["contact", "temperature", "battery", "refresh"]) 72 | } 73 | } 74 | 75 | def parse(String description) { 76 | log.debug "description: $description" 77 | 78 | Map map = zigbee.getEvent(description) 79 | if (!map) { 80 | if (description?.startsWith('zone status')) { 81 | map = parseIasMessage(description) 82 | } else { 83 | Map descMap = zigbee.parseDescriptionAsMap(description) 84 | if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { 85 | map = getBatteryResult(Integer.parseInt(descMap.value, 16)) 86 | } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { 87 | if (descMap.data[0] == "00") { 88 | log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" 89 | sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 90 | } else { 91 | log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" 92 | } 93 | } 94 | } 95 | } else if (map.name == "temperature") { 96 | if (tempOffset) { 97 | map.value = (int) map.value + (int) tempOffset 98 | } 99 | map.descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C' : '{{ device.displayName }} was {{ value }}°F' 100 | map.translatable = true 101 | } 102 | 103 | log.debug "Parse returned $map" 104 | def result = map ? createEvent(map) : [:] 105 | 106 | if (description?.startsWith('enroll request')) { 107 | List cmds = zigbee.enrollResponse() 108 | log.debug "enroll response: ${cmds}" 109 | result = cmds?.collect { new physicalgraph.device.HubAction(it) } 110 | } 111 | return result 112 | } 113 | 114 | 115 | private Map parseIasMessage(String description) { 116 | ZoneStatus zs = zigbee.parseZoneStatus(description) 117 | return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') 118 | } 119 | 120 | private Map getBatteryResult(rawValue) { 121 | log.debug 'Battery' 122 | def linkText = getLinkText(device) 123 | 124 | def result = [:] 125 | 126 | def volts = rawValue / 10 127 | if (!(rawValue == 0 || rawValue == 255)) { 128 | def minVolts = 2.1 129 | def maxVolts = 3.0 130 | def pct = (volts - minVolts) / (maxVolts - minVolts) 131 | def roundedPct = Math.round(pct * 100) 132 | if (roundedPct <= 0) 133 | roundedPct = 1 134 | result.value = Math.min(100, roundedPct) 135 | result.descriptionText = "${linkText} battery was ${result.value}%" 136 | result.name = 'battery' 137 | } 138 | 139 | return result 140 | } 141 | 142 | private Map getContactResult(value) { 143 | log.debug 'Contact Status is $value' 144 | def linkText = getLinkText(device) 145 | def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}" 146 | if (value == 'open') { 147 | sendEvent(name: "lock", value: "unlocked") 148 | } else { 149 | sendEvent(name: "lock", value: "locked") 150 | } 151 | return [ 152 | name : 'contact', 153 | value : value, 154 | descriptionText: descriptionText 155 | ] 156 | } 157 | 158 | /** 159 | * PING is used by Device-Watch in attempt to reach the Device 160 | * */ 161 | def ping() { 162 | return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level 163 | } 164 | 165 | def refresh() { 166 | log.debug "Refreshing Temperature and Battery" 167 | def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + 168 | zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) 169 | 170 | return refreshCmds + zigbee.enrollResponse() 171 | } 172 | 173 | def configure() { 174 | // Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time) 175 | // enrolls with default periodic reporting until newer 5 min interval is confirmed 176 | sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 177 | 178 | log.debug "Configuring Reporting, IAS CIE, and Bindings." 179 | 180 | // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity 181 | // battery minReport 30 seconds, maxReportTime 6 hrs by default 182 | return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config 183 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-leak-gopher-valve-controller.src/my-leak-gopher-valve-controller.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Leak Gopher Valve Controller 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | * Updates: 14 | * ------- 15 | * 01-05-2018 : Initial commit 16 | * 17 | */ 18 | metadata { 19 | // Automatically generated. Make future change here. 20 | definition (name: "My Leak Gopher Valve Controller", namespace: "jscgs350", author: "jscgs350") { 21 | capability "Alarm" 22 | capability "Polling" 23 | capability "Refresh" 24 | capability "Switch" 25 | capability "Valve" 26 | capability "Contact Sensor" 27 | capability "Configuration" 28 | capability "Actuator" 29 | capability "Sensor" 30 | capability "Health Check" 31 | 32 | fingerprint mfr:"0173", prod:"0003", model:"0002", deviceJoinName: "Leak Intelligence Leak Gopher Water Shutoff Valve" 33 | } 34 | 35 | // tile definitions 36 | tiles(scale: 2) { 37 | multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){ 38 | tileAttribute ("device.valve", key: "PRIMARY_CONTROL") { 39 | attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#51afdb", nextState:"closing" 40 | attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ff0000", nextState:"opening" 41 | attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#f0b823" 42 | attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#f0b823" 43 | } 44 | } 45 | 46 | standardTile("refresh", "device.valve", width: 6, height: 2, inactiveLabel: false, decoration: "flat") { 47 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 48 | } 49 | 50 | main "valve" 51 | details(["valve","refresh"]) 52 | } 53 | 54 | } 55 | 56 | def installed() { 57 | // Device-Watch simply pings if no device events received for 32min(checkInterval) 58 | sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 59 | response(refresh()) 60 | } 61 | 62 | def updated() { 63 | // Device-Watch simply pings if no device events received for 32min(checkInterval) 64 | sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 65 | response(refresh()) 66 | } 67 | 68 | def parse(String description) { 69 | log.trace "parse description : $description" 70 | def cmd = zwave.parse(description, [0x20: 1]) 71 | if (cmd) { 72 | return zwaveEvent(cmd) 73 | } 74 | log.debug "Could not parse message" 75 | return null 76 | } 77 | 78 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { 79 | def value = cmd.value == 0xFF ? "open" : cmd.value == 0x00 ? "closed" : "unknown" 80 | 81 | return [createEventWithDebug([name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]), 82 | createEventWithDebug([name: "valve", value: value, descriptionText: "$device.displayName valve is $value"])] 83 | } 84 | 85 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { //TODO should show MSR when device is discovered 86 | log.debug "manufacturerId: ${cmd.manufacturerId}" 87 | log.debug "manufacturerName: ${cmd.manufacturerName}" 88 | log.debug "productId: ${cmd.productId}" 89 | log.debug "productTypeId: ${cmd.productTypeId}" 90 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 91 | updateDataValue("MSR", msr) 92 | return createEventWithDebug([descriptionText: "$device.displayName MSR: $msr", isStateChange: false]) 93 | } 94 | 95 | def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) { 96 | return createEventWithDebug([descriptionText: cmd.toString(), isStateChange: true, displayed: true]) 97 | } 98 | 99 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { 100 | def value = cmd.value == 0xFF ? "open" : cmd.value == 0x00 ? "closed" : "unknown" 101 | 102 | return [createEventWithDebug([name: "contact", value: value, descriptionText: "$device.displayName valve is $value"]), 103 | createEventWithDebug([name: "valve", value: value, descriptionText: "$device.displayName valve is $value"])] 104 | } 105 | 106 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 107 | return createEvent([:]) // Handles all Z-Wave commands we aren't interested in 108 | } 109 | 110 | def on() { 111 | delayBetween([ 112 | zwave.basicV1.basicSet(value: 0xFF).format(), 113 | zwave.switchBinaryV1.switchBinaryGet().format() 114 | ]) 115 | } 116 | 117 | def off() { 118 | delayBetween([ 119 | zwave.basicV1.basicSet(value: 0x00).format(), 120 | zwave.switchBinaryV1.switchBinaryGet().format() 121 | ]) 122 | } 123 | 124 | // This is for when the the valve's ALARM capability is called 125 | def both() { 126 | log.debug "Closing valve due to an ALARM capability condition" 127 | delayBetween([ 128 | zwave.basicV1.basicSet(value: 0xFF).format(), 129 | zwave.switchBinaryV1.switchBinaryGet().format() 130 | ]) 131 | } 132 | 133 | def open() { 134 | delayBetween([ 135 | zwave.basicV1.basicSet(value: 0xFF).format(), 136 | zwave.switchBinaryV1.switchBinaryGet().format() 137 | ],10000) //wait for a water valve to be completely opened 138 | } 139 | 140 | def close() { 141 | delayBetween([ 142 | zwave.basicV1.basicSet(value: 0x00).format(), 143 | zwave.switchBinaryV1.switchBinaryGet().format() 144 | ],10000) //wait for a water valve to be completely closed 145 | } 146 | 147 | def poll() { 148 | zwave.switchBinaryV1.switchBinaryGet().format() 149 | } 150 | 151 | /** 152 | * PING is used by Device-Watch in attempt to reach the Device 153 | * */ 154 | def ping() { 155 | refresh() 156 | } 157 | 158 | def refresh() { 159 | log.debug "refresh() is called" 160 | def commands = [zwave.switchBinaryV1.switchBinaryGet().format()] 161 | if (getDataValue("MSR") == null) { 162 | commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() 163 | } 164 | delayBetween(commands,100) 165 | } 166 | 167 | def createEventWithDebug(eventMap) { 168 | def event = createEvent(eventMap) 169 | log.debug "Event created with ${event?.descriptionText}" 170 | return event 171 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-monoprice-motion-sensor.src/my-monoprice-motion-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Monoprice Motion Sensor 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | * Updates: 14 | * ------- 15 | * 02-18-2016 : Initial commit 16 | * 04-08-2016 : Added fingerprint info 17 | * 04-10-2016 : Added Refesh tile 18 | * 08-27-2016 : Modified the device handler for my liking, primarly for looks and feel. 19 | * 02-11-2017 : Put battery info into the main tile instead of a separate tile. 20 | * 21 | */ 22 | 23 | preferences { 24 | input description: "Number of minutes after movement is gone before its reported inactive by the sensor.", displayDuringSetup: false, type: "paragraph", element: "paragraph" 25 | input "inactivityTimeout", "number", title: "Inactivity Timeout", displayDuringSetup: true, default: 3 26 | 27 | input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" 28 | input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false 29 | 30 | input description: "This feature allows you to change the temperature Unit. If left blank or anything else is typed the default is F.", displayDuringSetup: false, type: "paragraph", element: "paragraph" 31 | input "tempUnit", "string", title: "Celsius or Fahrenheit", description: "Temperature Unit (Type C or F)", displayDuringSetup: false 32 | 33 | } 34 | 35 | metadata { 36 | definition (name:"My Monoprice Motion Sensor", namespace:"jscgs350", author: "jscgs350", ocfDeviceType: "x.com.st.d.sensor.motion", mnmn: "SmartThings", vid:"generic-motion-4") { 37 | capability "Battery" 38 | capability "Motion Sensor" 39 | capability "Temperature Measurement" 40 | capability "Sensor" 41 | capability "Polling" 42 | capability "Refresh" 43 | 44 | command "refresh" 45 | 46 | fingerprint deviceId: "0x2001", inClusters: "0x71,0x85,0x80,0x72,0x30,0x86,0x31,0x70,0x84" 47 | 48 | } 49 | 50 | tiles(scale: 2) { 51 | multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4){ 52 | tileAttribute ("device.motion", key: "PRIMARY_CONTROL") { 53 | attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0" 54 | attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff" 55 | } 56 | tileAttribute ("device.battery", key: "SECONDARY_CONTROL") { 57 | attributeState("default", label:'${currentValue}% battery', icon: "https://raw.githubusercontent.com/constjs/jcdevhandlers/master/img/battery-icon-614x460.png") 58 | } 59 | } 60 | valueTile("temperature", "device.temperature", width: 3, height: 2) { 61 | state("temperature", label:'${currentValue}°', unit:"F", 62 | backgroundColors:[ 63 | [value: 31, color: "#153591"], 64 | [value: 44, color: "#1e9cbb"], 65 | [value: 59, color: "#90d2a7"], 66 | [value: 74, color: "#44b621"], 67 | [value: 84, color: "#f1d801"], 68 | [value: 95, color: "#d04e00"], 69 | [value: 96, color: "#bc2323"] 70 | ] 71 | ) 72 | } 73 | standardTile("refresh", "device.motion", width: 3, height: 2, inactiveLabel: false, decoration: "flat") { 74 | state "default", label:'Refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon" 75 | } 76 | main(["motion", "temperature"]) 77 | details(["motion", "temperature", "refresh"]) 78 | } 79 | } 80 | 81 | def parse(String description) { 82 | log.trace "Parse Raw: ${description}" 83 | def result = [] 84 | // Using reference in: http://www.pepper1.net/zwavedb/device/197 85 | def cmd = zwave.parse(description, [0x20: 1, 0x80: 1, 0x31: 2, 0x84: 2, 0x71: 1, 0x30: 1]) 86 | if (cmd) { 87 | if (cmd instanceof physicalgraph.zwave.commands.wakeupv2.WakeUpNotification) { 88 | result.addAll(sendSettingsUpdate(cmd)) 89 | } 90 | result << createEvent(zwaveEvent(cmd)) 91 | if (cmd.CMD == "8407") { 92 | result << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format()) 93 | } 94 | } 95 | 96 | if (inactivityTimeout) { 97 | log.debug "Applying preferences for Monoprice Motion Sensor: ${inactivityTimeout}" 98 | zwave.configurationV1.configurationSet(configurationValue: [inactivityTimeout], parameterNumber: 1, size: 1).format() 99 | log.debug "zwaveEvent ConfigurationReport: '${cmd}'" 100 | } 101 | 102 | return result 103 | 104 | } 105 | 106 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) { 107 | //log.trace "Woke Up!" 108 | def map = [:] 109 | map.value = "" 110 | map.descriptionText = "${device.displayName} woke up." 111 | return map 112 | } 113 | 114 | def sendSettingsUpdate(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) { 115 | /* def inactivityTimeout = (settings.inactivityTimeout == null ? 116 | 1 : Integer.parseInt(settings.inactivityTimeout)) 117 | def inactivityTimeoutStr = Integer.toString(inactivityTimeout) */ 118 | def actions = [] 119 | def lastBatteryUpdate = state.lastBatteryUpdate == null ? 0 : state.lastBatteryUpdate 120 | if ((new Date().time - lastBatteryUpdate) > 1000 * 60 * 60 * 24) { 121 | actions.addAll([ 122 | response(zwave.batteryV1.batteryGet().format()), 123 | [ descriptionText: "Requested battery update from ${device.displayName}.", value: "" ], 124 | response("delay 600"), 125 | ]) 126 | } 127 | actions.addAll([ 128 | response(zwave.configurationV1.configurationSet( 129 | configurationValue: [inactivityTimeout], defaultValue: False, parameterNumber: 1, size: 1).format()), 130 | response("delay 600"), 131 | [ descriptionText: "${device.displayName} was sent inactivity timeout of ${inactivityTimeoutStr}.", value: "" ] 132 | ]) 133 | actions 134 | } 135 | 136 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { 137 | def map = [:] 138 | map.name = "motion" 139 | map.value = cmd.value ? "active" : "inactive" 140 | map.handlerName = map.value 141 | map.descriptionText = cmd.value ? "${device.displayName} detected motion" : "${device.displayName} motion has stopped." 142 | return map 143 | } 144 | 145 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) { 146 | def map = [:] 147 | if (cmd.sensorType == 1) { 148 | def cmdScale = cmd.scale == 1 ? "F" : "C" 149 | def preValue = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) 150 | def value = preValue as float 151 | map.unit = tempUnit 152 | map.name = "temperature" 153 | 154 | switch(tempUnit) { 155 | case ["C","c"]: 156 | if (tempOffset) { 157 | def offset = tempOffset as float 158 | map.value = value + offset as float 159 | } 160 | else { 161 | map.value = value as float 162 | } 163 | map.value = map.value.round() 164 | map.descriptionText = "${device.displayName} temperature is ${map.value}°C." 165 | break 166 | 167 | case ["F","f"]: 168 | if (tempOffset) { 169 | def offset = tempOffset as float 170 | map.value = value + offset as float 171 | } 172 | else { 173 | map.value = value as float 174 | } 175 | map.value = map.value.round() 176 | map.descriptionText = "${device.displayName} temperature is ${map.value}°F." 177 | break 178 | 179 | default: 180 | if (tempOffset) { 181 | def offset = tempOffset as float 182 | map.value = value + offset as float 183 | } 184 | else { 185 | map.value = value as float 186 | } 187 | map.value = map.value.round() 188 | map.descriptionText = "${device.displayName} temperature is ${map.value}°." 189 | break 190 | } 191 | } 192 | map 193 | } 194 | 195 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 196 | state.lastBatteryUpdate = new Date().time 197 | def map = [ name: "battery", unit: "%" ] 198 | if (cmd.batteryLevel == 0xFF || cmd.batteryLevel == 0 ) { 199 | map.value = 1 200 | map.descriptionText = "${device.displayName} battery is almost dead!" 201 | } else if (cmd.batteryLevel < 15 ) { 202 | map.value = cmd.batteryLevel 203 | map.descriptionText = "${device.displayName} battery is low!" 204 | } else { 205 | map.value = cmd.batteryLevel 206 | } 207 | map 208 | } 209 | 210 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 211 | // Catch-all handler. The sensor does return some alarm values, which 212 | // could be useful if handled correctly (tamper alarm, etc.) 213 | [descriptionText: "Unhandled: ${device.displayName}: ${cmd}", displayed: false] 214 | } 215 | 216 | def poll() { 217 | refresh() 218 | } 219 | 220 | def refresh() { 221 | log.info "Executing Refresh/Poll per user request" 222 | // sendEvent(name: "motion", value: $device.currentState('motionState')) testing 223 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-philio-psm01-sensor.src/my-philio-psm01-sensor.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Philio PSM01 3-in-1 Multi Sensor Device Type 3 | * based on SmartThings' Aeon Multi Sensor Reference Device Type 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 12 | * for the specific language governing permissions and limitations under the License. 13 | * 14 | * Updates: 15 | * ------- 16 | * 02-18-2016 : Initial commit 17 | * 03-04-2016 : Changed multiAttributeTile type to generic to remove secondary_control data from showing up since other tiles already show those values. 18 | * 03-11-2016 : Due to ST's v2.1.0 app totally hosing up SECONDARY_CONTROL, implemented a workaround to display that info in a separate tile. 19 | * 04-05-2016 : Added fingerprint for the PSM01 20 | * 04-09-2016 : Rewrote several sections of code, added additional config parameter (5) to ensure open/close not disabled 21 | * 04-13-2016 : Added icon for illuminance 22 | * 08-27-2016 : Modified the device handler for my liking, primarly for looks and feel. 23 | * 02-11-2017 : Put battery info into the main tile instead of a separate tile. 24 | * 03-24-2017 : Changed color schema to match ST's new format. 25 | * 07-21-2017 : Changed Primary tile for my liking. 26 | * 27 | */ 28 | metadata { 29 | 30 | definition (name: "My Philio PSM01 Sensor", namespace: "jscgs350", author: "SmartThings") { 31 | capability "Contact Sensor" 32 | capability "Temperature Measurement" 33 | capability "Illuminance Measurement" 34 | capability "Configuration" 35 | capability "Sensor" 36 | capability "Battery" 37 | capability "Refresh" 38 | capability "Polling" 39 | 40 | fingerprint deviceId: "0x2001", inClusters: "0x80,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0xEF,0x20" 41 | 42 | } 43 | 44 | tiles(scale: 2) { 45 | multiAttributeTile(name: "temperature", type: "generic", width: 6, height: 4, canChangeIcon: true) { 46 | tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { 47 | attributeState "temperature", action:"refresh", icon:"st.Weather.weather2", label: '${currentValue}°', 48 | backgroundColors: [ 49 | [value: 31, color: "#153591"], 50 | [value: 44, color: "#1e9cbb"], 51 | [value: 59, color: "#90d2a7"], 52 | [value: 74, color: "#44b621"], 53 | [value: 84, color: "#f1d801"], 54 | [value: 95, color: "#d04e00"], 55 | [value: 96, color: "#bc2323"] 56 | ] 57 | } 58 | tileAttribute ("device.battery", key: "SECONDARY_CONTROL") { 59 | attributeState("default", label:'${currentValue}% battery', icon: "https://raw.githubusercontent.com/constjs/jcdevhandlers/master/img/battery-icon-614x460.png") 60 | } 61 | } 62 | standardTile("contact", "device.contact", width: 2, height: 2) { 63 | state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13" 64 | state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC" 65 | } 66 | standardTile("illuminance", "device.illuminance", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 67 | state "luminosity", label:'${currentValue} ${unit}', unit:"lux", icon:"st.illuminance.illuminance.bright" 68 | } 69 | standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 70 | state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" 71 | } 72 | 73 | main(["temperature"]) 74 | details(["temperature", "contact", "illuminance", "configure"]) 75 | } 76 | } 77 | 78 | // Parse incoming device messages to generate events 79 | def parse(String description) 80 | { 81 | def result = [] 82 | def cmd = zwave.parse(description, [0x20: 1, 0x31: 2, 0x30: 2, 0x80: 1, 0x84: 2, 0x85: 2]) 83 | if (cmd) { 84 | if( cmd.CMD == "8407" ) { result << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format()) } 85 | result << createEvent(zwaveEvent(cmd)) 86 | } 87 | log.debug "Parse returned ${result}" 88 | return result 89 | } 90 | 91 | // Event Generation 92 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) 93 | { 94 | [descriptionText: "${device.displayName} woke up", isStateChange: false] 95 | } 96 | 97 | def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) 98 | { 99 | def map = [:] 100 | switch (cmd.sensorType) { 101 | case 1: 102 | // temperature 103 | def cmdScale = cmd.scale == 1 ? "F" : "C" 104 | map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) 105 | map.unit = getTemperatureScale() 106 | map.name = "temperature" 107 | break; 108 | case 3: 109 | // luminance 110 | map.value = cmd.scaledSensorValue.toInteger().toString() 111 | map.unit = "lux" 112 | map.name = "illuminance" 113 | break; 114 | case 5: 115 | // humidity 116 | map.value = cmd.scaledSensorValue.toInteger().toString() 117 | map.unit = "%" 118 | map.name = "humidity" 119 | break; 120 | } 121 | map 122 | } 123 | 124 | def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) { 125 | log.debug "PSM01: SensorBinaryReport ${cmd.toString()}}" 126 | def map = [:] 127 | switch (cmd.sensorType) { 128 | case 10: // contact sensor 129 | map.name = "contact" 130 | if (cmd.sensorValue) { 131 | map.value = "open" 132 | map.descriptionText = "$device.displayName is open" 133 | } else { 134 | map.value = "closed" 135 | map.descriptionText = "$device.displayName is closed" 136 | } 137 | break; 138 | } 139 | map 140 | } 141 | 142 | /*def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 143 | def map = [:] 144 | map.name = "battery" 145 | map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1 146 | map.unit = "%" 147 | map.displayed = false 148 | map 149 | }*/ 150 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 151 | def map = [ name: "battery", unit: "%" ] 152 | if (cmd.batteryLevel == 0xFF) { 153 | map.value = 1 154 | map.descriptionText = "${device.displayName} has a low battery" 155 | map.isStateChange = true 156 | } else { 157 | map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1 158 | } 159 | map 160 | } 161 | 162 | 163 | def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) { 164 | def map = [:] 165 | map.value = cmd.sensorValue ? "open" : "closed" 166 | map.name = "contact" 167 | if (map.value == "active") { 168 | map.descriptionText = "$device.displayName is open" 169 | } 170 | else { 171 | map.descriptionText = "$device.displayName is closed" 172 | } 173 | map 174 | } 175 | 176 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { 177 | def map = [:] 178 | map.value = cmd.value ? "open" : "closed" 179 | map.name = "contact" 180 | if (map.value == "active") { 181 | map.descriptionText = "$device.displayName is open" 182 | } 183 | else { 184 | map.descriptionText = "$device.displayName is closed" 185 | } 186 | map 187 | } 188 | 189 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 190 | log.debug "Catchall reached for cmd: ${cmd.toString()}}" 191 | [:] 192 | } 193 | 194 | def refresh() { 195 | log.debug "Executing Refresh per user request" 196 | delayBetween([ 197 | zwave.switchBinaryV1.switchBinaryGet().format(), 198 | zwave.sensorBinaryV1.sensorBinaryGet().format(), 199 | zwave.basicV1.basicGet().format(), 200 | zwave.alarmV1.alarmGet().format() 201 | ],100) 202 | } 203 | 204 | def configure() { 205 | log.debug "PSM01: configure() called" 206 | delayBetween([ 207 | //value of 1 = 30 minutes for reporting times, or 1 = 8 seconds for how long the light stays on. 208 | zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 0).format(), // Enable the door/window function, default 0 209 | // zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: 0).format(), // How long the light stays on, default ? 210 | zwave.configurationV1.configurationSet(parameterNumber: 10, size: 1, scaledConfigurationValue: 2).format(), // Auto report Battery time 1-127, default 12 211 | zwave.configurationV1.configurationSet(parameterNumber: 11, size: 1, scaledConfigurationValue: 2).format(), // Auto report Door/Window state time 1-127, default 12 212 | zwave.configurationV1.configurationSet(parameterNumber: 12, size: 1, scaledConfigurationValue: 2).format(), // Auto report Illumination time 1-127, default 12 213 | zwave.configurationV1.configurationSet(parameterNumber: 13, size: 1, scaledConfigurationValue: 2).format(), // Auto report Temperature time 1-127, default 12 214 | zwave.wakeUpV1.wakeUpIntervalSet(seconds: 1 * 3600, nodeid:zwaveHubNodeId).format(), // Wake up every hour 215 | ]) 216 | } 217 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-simulated-presence-sensor.src/my-simulated-presence-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 SmartThings 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at: 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 7 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 8 | * for the specific language governing permissions and limitations under the License. 9 | */ 10 | metadata { 11 | // Automatically generated. Make future change here. 12 | definition (name: "My Simulated Presence Sensor", namespace: "jscgs350", author: "jscgs350") { 13 | capability "Contact Sensor" 14 | capability "Switch" 15 | command "open" 16 | command "close" 17 | command "on" 18 | command "off" 19 | } 20 | tiles { 21 | standardTile("contact", "device.contact", width: 2, height: 2) { 22 | state("open", label:'Away', icon:"st.presence.tile.not-present", backgroundColor:"#ffffff", action: "close") 23 | state("closed", label:'Present', icon:"st.presence.tile.present", backgroundColor:"#00a0dc", action: "open") 24 | } 25 | standardTile("switch", "device.switch", width: 2, height: 2) { 26 | state("off", label:'Away', icon:"st.presence.tile.not-present", backgroundColor:"#ffffff", action: "on") 27 | state("on", label:'Present', icon:"st.presence.tile.present", backgroundColor:"#00a0dc", action: "off") 28 | } 29 | main "contact" 30 | details "contact" 31 | } 32 | } 33 | def parse(String description) { 34 | def pair = description.split(":") 35 | createEvent(name: pair[0].trim(), value: pair[1].trim()) 36 | } 37 | def on() { 38 | sendEvent(name: "switch", value: "on") 39 | sendEvent(name: "contact", value: "closed", displayed: false) 40 | } 41 | def off() { 42 | sendEvent(name: "switch", value: "off") 43 | sendEvent(name: "contact", value: "open", displayed: false) 44 | } 45 | def open() { 46 | sendEvent(name: "contact", value: "open", displayed: false) 47 | sendEvent(name: "switch", value: "off") 48 | } 49 | def close() { 50 | sendEvent(name: "contact", value: "closed", displayed: false) 51 | sendEvent(name: "switch", value: "on") 52 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-smartpower-outlet.src/my-smartpower-outlet.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | * Updates: 17 | * ------- 18 | * 03-24-2017 : Initial commit. 19 | * 03-25-2017 : Added features from @blebson's DTH (Energy in kWh), as well as from my Aeon DSC06 gen 1 DTH 20 | * 21 | */ 22 | metadata { 23 | // Automatically generated. Make future change here. 24 | definition(name: "My SmartPower Outlet", namespace: "jscgs350", author: "SmartThings") { 25 | capability "Actuator" 26 | capability "Switch" 27 | capability "Power Meter" 28 | capability "Energy Meter" 29 | capability "Configuration" 30 | capability "Refresh" 31 | capability "Sensor" 32 | capability "Health Check" 33 | capability "Light" 34 | capability "Outlet" 35 | 36 | fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet" 37 | fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet" 38 | fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet" 39 | fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019" 40 | } 41 | 42 | preferences { 43 | section { 44 | image(name: 'educationalcontent', multiple: true, images: [ 45 | "http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS1.jpg", 46 | "http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS2.jpg" 47 | ]) 48 | } 49 | section("Device customizations") { 50 | input "disableOnOff", "boolean", 51 | title: "Disable On/Off switch?", 52 | defaultValue: false, 53 | displayDuringSetup: true 54 | input "debugOutput", "boolean", 55 | title: "Enable debug logging?", 56 | defaultValue: false, 57 | displayDuringSetup: true 58 | input "displayEvents", "boolean", 59 | title: "Display Power (watts) events in the Recently tab and the device's event log?", 60 | defaultValue: false, 61 | required: false, 62 | displayDuringSetup: true 63 | input "kWhCost", "string", 64 | title: "Enter your cost per kWh (or just use the default, or use 0 to not calculate):", 65 | defaultValue: 0.16, 66 | required: false, 67 | displayDuringSetup: true 68 | } 69 | } 70 | 71 | // UI tile definitions 72 | tiles(scale: 2) { 73 | multiAttributeTile(name: "switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) { 74 | tileAttribute("device.switch", key: "PRIMARY_CONTROL") { 75 | attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC" 76 | attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff" 77 | } 78 | tileAttribute("power", key: "SECONDARY_CONTROL") { 79 | attributeState "power", label: 'Currently using ${currentValue}W', icon: "st.secondary.activity" 80 | } 81 | } 82 | standardTile("power", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 83 | state "power", label: '${currentValue} W', icon: "st.secondary.activity" 84 | } 85 | 86 | standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 6, height: 2) { 87 | state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh" 88 | } 89 | 90 | main "power" 91 | details(["switch", "energyDisplay", "energyCost", "refresh"]) 92 | } 93 | } 94 | 95 | def updated() { 96 | state.onOffDisabled = ("true" == disableOnOff) 97 | state.debug = ("true" == debugOutput) 98 | state.displayDisabled = ("true" == displayEvents) 99 | log.debug "updated(disableOnOff: ${disableOnOff}(${state.onOffDisabled}), debugOutput: ${debugOutput}(${state.debug})" 100 | response(configure()) 101 | } 102 | 103 | // Parse incoming device messages to generate events 104 | def parse(String description) { 105 | if (state.debug) log.debug "description is $description" 106 | 107 | def event = zigbee.getEvent(description) 108 | 109 | if (event) { 110 | if (event.name == "power") { 111 | def value = (event.value as Integer) / 10 112 | if (state.displayDisabled) { 113 | event = createEvent(name: event.name, value: value, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true, displayed: true) 114 | } else { 115 | event = createEvent(name: event.name, value: value, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true, displayed: false) 116 | } 117 | // return calculateAndShowEnergy() 118 | } else if (event.name == "switch") { 119 | def descriptionText = event.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' 120 | event = createEvent(name: event.name, value: event.value, descriptionText: descriptionText, translatable: true) 121 | } 122 | } else { 123 | def cluster = zigbee.parse(description) 124 | 125 | if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) { 126 | if (cluster.data[0] == 0x00) { 127 | if (state.debug) log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster 128 | event = createEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 129 | } else { 130 | if (state.debug) log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}" 131 | event = null 132 | } 133 | } else { 134 | if (state.debug) log.warn "DID NOT PARSE MESSAGE for description : $description" 135 | if (state.debug) log.debug "${cluster}" 136 | } 137 | } 138 | return event ? createEvent(event) : event 139 | } 140 | 141 | def off() { 142 | if (state.onOffDisabled) { 143 | if (state.debug) log.debug "On/Off disabled..." 144 | refresh() 145 | } 146 | else { 147 | zigbee.off() 148 | } 149 | } 150 | 151 | def on() { 152 | if (state.onOffDisabled) { 153 | if (state.debug) log.debug "On/Off disabled..." 154 | refresh() 155 | } 156 | else { 157 | zigbee.on() 158 | } 159 | } 160 | 161 | def calculateAndShowEnergy() 162 | { 163 | def recentEvents = device.statesSince("power", new Date()-1, [max: 2]).collect {[value: it.value as float, date: it.date]} 164 | def deltaT = (recentEvents[0].date.getTime() - recentEvents[1].date.getTime()) // time since last "power" event in milliseconds 165 | deltaT = deltaT / 3600000 // convert to hours 166 | 167 | def energyValue = device.currentValue("energy") 168 | if(energyValue != null) { 169 | energyValue += (recentEvents[1].value * deltaT) / 1000 // energy used since last "power" event in kWh 170 | } 171 | 172 | sendEvent(name: "energy", value: energyValue, displayed: false) 173 | sendEvent(name: "energyDisplay", value: String.format("%6.3f kWh",energyValue), displayed: false) 174 | 175 | } 176 | 177 | /** 178 | * PING is used by Device-Watch in attempt to reach the Device 179 | * */ 180 | def ping() { 181 | return zigbee.onOffRefresh() 182 | } 183 | 184 | def refresh() { 185 | zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh() 186 | } 187 | 188 | def configure() { 189 | log.debug "Configuring..." 190 | // Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time) 191 | // enrolls with default periodic reporting until newer 5 min interval is confirmed 192 | sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 193 | 194 | // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity 195 | refresh() + zigbee.onOffConfig(0, 300) + zigbee.electricMeasurementPowerConfig() 196 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-smartsense-lock-sensor.src/my-smartsense-lock-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * SmartSense Lock Sensor 3 | * 4 | * Copyright 2014 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | import physicalgraph.zigbee.clusters.iaszone.ZoneStatus 17 | 18 | metadata { 19 | definition(name: "My SmartSense Lock Sensor", namespace: "jscgs350", author: "SmartThings") { 20 | capability "Battery" 21 | capability "Lock" 22 | capability "Configuration" 23 | capability "Contact Sensor" 24 | capability "Refresh" 25 | capability "Temperature Measurement" 26 | capability "Health Check" 27 | capability "Sensor" 28 | 29 | command "enrollResponse" 30 | 31 | } 32 | 33 | simulator { 34 | 35 | } 36 | 37 | preferences { 38 | input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" 39 | input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false 40 | } 41 | 42 | tiles(scale: 2) { 43 | multiAttributeTile(name: "contact", type: "generic", width: 6, height: 4) { 44 | tileAttribute("device.contact", key: "PRIMARY_CONTROL") { 45 | attributeState "unlocked", label: '${name}', icon: "st.presence.house.unlocked", backgroundColor: "#e86d13" 46 | attributeState "locked", label: '${name}', icon: "st.presence.house.secured", backgroundColor: "#00A0DC" 47 | } 48 | } 49 | 50 | valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) { 51 | state "temperature", label: '${currentValue}°', 52 | backgroundColors: [ 53 | [value: 31, color: "#153591"], 54 | [value: 44, color: "#1e9cbb"], 55 | [value: 59, color: "#90d2a7"], 56 | [value: 74, color: "#44b621"], 57 | [value: 84, color: "#f1d801"], 58 | [value: 95, color: "#d04e00"], 59 | [value: 96, color: "#bc2323"] 60 | ] 61 | } 62 | valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { 63 | state "battery", label: '${currentValue}% battery', unit: "" 64 | } 65 | 66 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 67 | state "default", action: "refresh.refresh", icon: "st.secondary.refresh" 68 | } 69 | 70 | main(["contact", "temperature"]) 71 | details(["contact", "temperature", "battery", "refresh"]) 72 | } 73 | } 74 | 75 | def parse(String description) { 76 | log.debug "description: $description" 77 | 78 | Map map = zigbee.getEvent(description) 79 | if (!map) { 80 | if (description?.startsWith('zone status')) { 81 | map = parseIasMessage(description) 82 | } else { 83 | Map descMap = zigbee.parseDescriptionAsMap(description) 84 | if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { 85 | map = getBatteryResult(Integer.parseInt(descMap.value, 16)) 86 | } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { 87 | if (descMap.data[0] == "00") { 88 | log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" 89 | sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 90 | } else { 91 | log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" 92 | } 93 | } 94 | } 95 | } else if (map.name == "temperature") { 96 | if (tempOffset) { 97 | map.value = (int) map.value + (int) tempOffset 98 | } 99 | map.descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C' : '{{ device.displayName }} was {{ value }}°F' 100 | map.translatable = true 101 | } 102 | 103 | log.debug "Parse returned $map" 104 | def result = map ? createEvent(map) : [:] 105 | 106 | if (description?.startsWith('enroll request')) { 107 | List cmds = zigbee.enrollResponse() 108 | log.debug "enroll response: ${cmds}" 109 | result = cmds?.collect { new physicalgraph.device.HubAction(it) } 110 | } 111 | return result 112 | } 113 | 114 | 115 | private Map parseIasMessage(String description) { 116 | ZoneStatus zs = zigbee.parseZoneStatus(description) 117 | return zs.isAlarm1Set() ? getContactResult('unlocked') : getContactResult('locked') 118 | } 119 | 120 | private Map getBatteryResult(rawValue) { 121 | log.debug 'Battery' 122 | def linkText = getLinkText(device) 123 | 124 | def result = [:] 125 | 126 | def volts = rawValue / 10 127 | if (!(rawValue == 0 || rawValue == 255)) { 128 | def minVolts = 2.1 129 | def maxVolts = 3.0 130 | def pct = (volts - minVolts) / (maxVolts - minVolts) 131 | def roundedPct = Math.round(pct * 100) 132 | if (roundedPct <= 0) 133 | roundedPct = 1 134 | result.value = Math.min(100, roundedPct) 135 | result.descriptionText = "${linkText} battery was ${result.value}%" 136 | result.name = 'battery' 137 | } 138 | 139 | return result 140 | } 141 | 142 | private Map getContactResult(value) { 143 | log.debug 'Contact Status' 144 | def linkText = getLinkText(device) 145 | def descriptionText = "${linkText} was ${value == 'unlock' ? 'unlocked' : 'locked'}" 146 | return [ 147 | name : 'contact', 148 | value : value, 149 | descriptionText: descriptionText 150 | ] 151 | } 152 | 153 | /** 154 | * PING is used by Device-Watch in attempt to reach the Device 155 | * */ 156 | def ping() { 157 | return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level 158 | } 159 | 160 | def refresh() { 161 | log.debug "Refreshing Temperature and Battery" 162 | def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + 163 | zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) 164 | 165 | return refreshCmds + zigbee.enrollResponse() 166 | } 167 | 168 | def configure() { 169 | // Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time) 170 | // enrolls with default periodic reporting until newer 5 min interval is confirmed 171 | sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 172 | 173 | log.debug "Configuring Reporting, IAS CIE, and Bindings." 174 | 175 | // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity 176 | // battery minReport 30 seconds, maxReportTime 6 hrs by default 177 | return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config 178 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-smartsense-temp-humidity-sensor.src/my-smartsense-temp-humidity-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * SmartSense Temp/Humidity Sensor 3 | * 4 | * Copyright 2014 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | import physicalgraph.zigbee.zcl.DataType 17 | 18 | metadata { 19 | definition(name: "My SmartSense Temp/Humidity Sensor", namespace: "jscgs350", author: "SmartThings") { 20 | capability "Configuration" 21 | capability "Battery" 22 | capability "Refresh" 23 | capability "Temperature Measurement" 24 | capability "Relative Humidity Measurement" 25 | capability "Health Check" 26 | capability "Sensor" 27 | 28 | fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003" 29 | } 30 | 31 | simulator { 32 | status 'H 40': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 000021780F' 33 | status 'H 45': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 0000218911' 34 | status 'H 57': 'catchall: 0104 FC45 01 01 0140 00 4E55 00 04 C2DF 0A 01 0000211316' 35 | status 'H 53': 'catchall: 0104 FC45 01 01 0140 00 20CD 00 04 C2DF 0A 01 0000219814' 36 | status 'H 43': 'read attr - raw: BF7601FC450C00000021A410, dni: BF76, endpoint: 01, cluster: FC45, size: 0C, attrId: 0000, result: success, encoding: 21, value: 10a4' 37 | } 38 | 39 | preferences { 40 | input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" 41 | input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false 42 | } 43 | 44 | tiles(scale: 2) { 45 | multiAttributeTile(name: "humidity", type: "generic", width: 6, height: 4) { 46 | tileAttribute("device.humidity", key: "PRIMARY_CONTROL") { 47 | attributeState "humidity", label: '${currentValue}%', icon: "st.Weather.weather12", backgroundColor:"#4cbce6" 48 | } 49 | } 50 | valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) { 51 | state "temperature", label: '${currentValue}°', 52 | backgroundColors: [ 53 | [value: 31, color: "#153591"], 54 | [value: 44, color: "#1e9cbb"], 55 | [value: 59, color: "#90d2a7"], 56 | [value: 74, color: "#44b621"], 57 | [value: 84, color: "#f1d801"], 58 | [value: 95, color: "#d04e00"], 59 | [value: 96, color: "#bc2323"] 60 | ] 61 | } 62 | valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { 63 | state "battery", label: '${currentValue}% battery' 64 | } 65 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 66 | state "default", action: "refresh.refresh", icon: "st.secondary.refresh" 67 | } 68 | 69 | main "humidity" 70 | details(["humidity", "temperature", "battery", "refresh"]) 71 | } 72 | } 73 | 74 | def parse(String description) { 75 | log.debug "description: $description" 76 | 77 | // getEvent will handle temperature and humidity 78 | Map map = zigbee.getEvent(description) 79 | if (!map) { 80 | Map descMap = zigbee.parseDescriptionAsMap(description) 81 | if (descMap.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { 82 | map = getBatteryResult(Integer.parseInt(descMap.value, 16)) 83 | } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { 84 | if (descMap.data[0] == "00") { 85 | log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" 86 | sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 87 | } else { 88 | log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" 89 | } 90 | } 91 | } else if (map.name == "temperature") { 92 | if (tempOffset) { 93 | map.value = (int) map.value + (int) tempOffset 94 | } 95 | map.descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C' : '{{ device.displayName }} was {{ value }}°F' 96 | map.translatable = true 97 | } 98 | 99 | log.debug "Parse returned $map" 100 | return map ? createEvent(map) : [:] 101 | } 102 | 103 | private Map getBatteryResult(rawValue) { 104 | log.debug 'Battery' 105 | def linkText = getLinkText(device) 106 | 107 | def result = [:] 108 | 109 | def volts = rawValue / 10 110 | if (!(rawValue == 0 || rawValue == 255)) { 111 | def minVolts = 2.1 112 | def maxVolts = 3.0 113 | def pct = (volts - minVolts) / (maxVolts - minVolts) 114 | def roundedPct = Math.round(pct * 100) 115 | if (roundedPct <= 0) 116 | roundedPct = 1 117 | result.value = Math.min(100, roundedPct) 118 | result.descriptionText = "${linkText} battery was ${result.value}%" 119 | result.name = 'battery' 120 | 121 | } 122 | 123 | return result 124 | } 125 | 126 | /** 127 | * PING is used by Device-Watch in attempt to reach the Device 128 | * */ 129 | def ping() { 130 | return zigbee.readAttribute(0x0001, 0x0020) // Read the Battery Level 131 | } 132 | 133 | def refresh() { 134 | log.debug "refresh temperature, humidity, and battery" 135 | return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware 136 | zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware 137 | zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + 138 | zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) 139 | } 140 | 141 | def configure() { 142 | // Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time) 143 | // enrolls with default periodic reporting until newer 5 min interval is confirmed 144 | sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 145 | 146 | log.debug "Configuring Reporting and Bindings." 147 | 148 | // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity 149 | // battery minReport 30 seconds, maxReportTime 6 hrs by default 150 | return refresh() + 151 | zigbee.configureReporting(0xFC45, 0x0000, DataType.UINT16, 30, 3600, 100, ["mfgCode": 0x104E]) + // New firmware 152 | zigbee.configureReporting(0xFC45, 0x0000, DataType.UINT16, 30, 3600, 100, ["mfgCode": 0xC2DF]) + // Original firmware 153 | zigbee.batteryConfig() + 154 | zigbee.temperatureConfig(30, 300) 155 | 156 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-utilitech-zwave-siren.src/my-utilitech-zwave-siren.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilitech/Everspring Siren 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | * Updates: 14 | * ------- 15 | * 02-19-2016 : Initial commit 16 | * 03-11-2016 : Due to ST's v2.1.0 app totally hosing up SECONDARY_CONTROL, implemented a workaround to display that info in a separate tile. 17 | * 08-14-2016 : Reimplemented SECONDARY_CONTROL (looks like formatting issues were fixed. 18 | * 08-27-2016 : Modified the device handler for my liking, primarly for looks and feel. 19 | * 01-31-2017 : Modified the device handler for my liking, primarly for looks and feel. Cleaned up code a bit. 20 | * 03-11-2017 : Changed from valueTile to standardTile for a few tiles since ST's mobile app v2.3.x changed something between the two. 21 | * 02-03-2018 : Cleaned up code. 22 | * 23 | */ 24 | metadata { 25 | definition (name: "My Utilitech Z-Wave Siren", namespace: "jscgs350", author: "SmartThings") { 26 | capability "Actuator" 27 | capability "Alarm" 28 | capability "Battery" 29 | capability "Polling" 30 | capability "Refresh" 31 | capability "Sensor" 32 | capability "Switch" 33 | 34 | attribute "alarmState", "string" 35 | 36 | } 37 | 38 | tiles(scale: 2) { 39 | multiAttributeTile(name:"alarm", type: "generic", width: 6, height: 4, canChangeIcon: true){ 40 | tileAttribute ("device.alarm", key: "PRIMARY_CONTROL") { 41 | attributeState "both", label:'alarm!', action:'alarm.off', icon:"st.alarm.alarm.alarm", backgroundColor:"#e86d13" 42 | attributeState "off", label:'off', action:'alarm.strobe', icon:"st.security.alarm.clear", backgroundColor:"#ffffff" 43 | } 44 | tileAttribute ("device.battery", key: "SECONDARY_CONTROL") { 45 | attributeState("default", label:'${currentValue}% battery', icon: "https://raw.githubusercontent.com/constjs/jcdevhandlers/master/img/battery-icon-614x460.png") 46 | } 47 | } 48 | standardTile("refresh", "device.refresh", width: 6, height: 2, inactiveLabel: false, decoration: "flat") { 49 | state "default", label:'Refresh', action:"refresh.refresh", icon:"st.secondary.refresh-icon" 50 | } 51 | standardTile("blankTile", "statusText", inactiveLabel: false, decoration: "flat", width: 1, height: 1) { 52 | state "default", label:'', icon:"http://cdn.device-icons.smartthings.com/secondary/device-activity-tile@2x.png" 53 | } 54 | standardTile("statusText", "statusText", inactiveLabel: false, decoration: "flat", width: 5, height: 1) { 55 | state "statusText", label:'${currentValue}', backgroundColor:"#ffffff" 56 | } 57 | main "alarm" 58 | details(["alarm", "blankTile","statusText","refresh"]) 59 | } 60 | } 61 | 62 | def parse(String description) { 63 | log.debug "parse($description)" 64 | def result = null 65 | def cmd = zwave.parse(description, [0x20: 1]) 66 | if (cmd) { 67 | result = createEvents(cmd) 68 | } 69 | 70 | def statusTextmsg = "" 71 | statusTextmsg = "Siren is ${device.currentState('alarmState').value} (tap to toggle on/off)." 72 | sendEvent("name":"statusText", "value":statusTextmsg) 73 | // log.debug statusTextmsg 74 | 75 | log.debug "Parse returned ${result?.descriptionText}" 76 | return result 77 | } 78 | 79 | def createEvents(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 80 | def map = [ name: "battery", unit: "%" ] 81 | if (cmd.batteryLevel == 0xFF) { 82 | map.value = 1 83 | map.descriptionText = "$device.displayName has a low battery" 84 | } else { 85 | map.value = cmd.batteryLevel 86 | } 87 | state.lastbatt = new Date().time 88 | createEvent(map) 89 | } 90 | 91 | def poll() { 92 | if (secondsPast(state.lastbatt, 36*60*60)) { 93 | return zwave.batteryV1.batteryGet().format() 94 | } else { 95 | return null 96 | } 97 | } 98 | 99 | private Boolean secondsPast(timestamp, seconds) { 100 | if (!(timestamp instanceof Number)) { 101 | if (timestamp instanceof Date) { 102 | timestamp = timestamp.time 103 | } else if ((timestamp instanceof String) && timestamp.isNumber()) { 104 | timestamp = timestamp.toLong() 105 | } else { 106 | return true 107 | } 108 | } 109 | return (new Date().time - timestamp) > (seconds * 1000) 110 | } 111 | 112 | def on() { 113 | log.debug "sending on" 114 | [ 115 | zwave.basicV1.basicSet(value: 0xFF).format(), 116 | zwave.basicV1.basicGet().format() 117 | ] 118 | } 119 | 120 | def off() { 121 | log.debug "sending off" 122 | [ 123 | zwave.basicV1.basicSet(value: 0x00).format(), 124 | zwave.basicV1.basicGet().format() 125 | ] 126 | } 127 | 128 | def strobe() { 129 | log.debug "sending stobe/on command" 130 | [ 131 | zwave.basicV1.basicSet(value: 0xFF).format(), 132 | zwave.basicV1.basicGet().format() 133 | ] 134 | } 135 | 136 | def both() { 137 | log.debug "Sending ON command to ${device.displayName} even though BOTH was called" 138 | [ 139 | zwave.basicV1.basicSet(value: 0xFF).format(), 140 | zwave.basicV1.basicGet().format() 141 | ] 142 | } 143 | 144 | def refresh() { 145 | log.debug "sending battery refresh command" 146 | zwave.batteryV1.batteryGet().format() 147 | } 148 | 149 | def createEvents(physicalgraph.zwave.commands.basicv1.BasicReport cmd) 150 | { 151 | def switchValue = cmd.value ? "on" : "off" 152 | def alarmValue 153 | if (cmd.value == 0) { 154 | alarmValue = "off" 155 | sendEvent(name: "alarmState", value: "standing by") 156 | } 157 | else if (cmd.value <= 33) { 158 | alarmValue = "strobe" 159 | sendEvent(name: "alarmState", value: "ON - strobe only!") 160 | } 161 | else if (cmd.value <= 66) { 162 | alarmValue = "siren" 163 | sendEvent(name: "alarmState", value: "ON - siren only!") 164 | } 165 | else { 166 | alarmValue = "both" 167 | sendEvent(name: "alarmState", value: "ON - strobe and siren!") 168 | } 169 | [ 170 | createEvent([name: "switch", value: switchValue, type: "digital", displayed: false]), 171 | createEvent([name: "alarm", value: alarmValue, type: "digital"]) 172 | ] 173 | } 174 | 175 | def createEvents(physicalgraph.zwave.Command cmd) { 176 | log.warn "UNEXPECTED COMMAND: $cmd" 177 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-virtual-temp-tile.src/my-virtual-temp-tile.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Virtual Temperature Tile 3 | * 4 | * Copyright (c) 2014 Statusbits.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. You may obtain a 8 | * copy of the License at: 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | * License for the specific language governing permissions and limitations 16 | * under the License. 17 | * 18 | * 2014-08-28 V1.1.0 parse takes 'temperature:' as an argument 19 | * 2014-08-10 V1.0.0 Initial release 20 | * 08-27-2016 : Modified the device handler for my liking, primarly for looks and feel. 21 | * 22 | */ 23 | 24 | metadata { 25 | definition (name:"My Virtual Temp Tile", namespace:"jscgs350", author:"geko@statusbits.com") { 26 | capability "Temperature Measurement" 27 | capability "Sensor" 28 | 29 | // custom commands 30 | command "parse" // (String "temperature:") 31 | } 32 | tiles(scale: 2) { 33 | multiAttributeTile(name:"temperature", type: "generic", width: 6, height: 4){ 34 | tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") { 35 | attributeState("temperature", icon:"st.tesla.tesla-hvac", label:'${currentValue}°', unit:"F", 36 | backgroundColors:[ 37 | [value: 31, color: "#153591"], 38 | [value: 44, color: "#1e9cbb"], 39 | [value: 59, color: "#90d2a7"], 40 | [value: 74, color: "#44b621"], 41 | [value: 84, color: "#f1d801"], 42 | [value: 95, color: "#d04e00"], 43 | [value: 96, color: "#bc2323"] 44 | ] 45 | ) 46 | } 47 | } 48 | main(["temperature"]) 49 | details(["temperature"]) 50 | } 51 | 52 | simulator { 53 | for (int i = 20; i <= 110; i += 10) { 54 | status "Temperature ${i}°": "temperature:${i}" 55 | } 56 | status "Invalid message" : "foobar:100.0" 57 | } 58 | } 59 | 60 | def parse(String message) { 61 | TRACE("parse(${message})") 62 | 63 | Map msg = stringToMap(message) 64 | if (!msg.containsKey("temperature")) { 65 | log.error "Invalid message: ${message}" 66 | return null 67 | } 68 | 69 | Float temp = msg.temperature.toFloat() 70 | def event = [ 71 | name : "temperature", 72 | value : temp.round(1), 73 | unit : tempScale, 74 | ] 75 | 76 | TRACE("event: (${event})") 77 | sendEvent(event) 78 | } 79 | 80 | private def TRACE(message) { 81 | //log.debug message 82 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-zigbee-valve.src/my-zigbee-valve.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | */ 14 | import physicalgraph.zigbee.zcl.DataType 15 | 16 | metadata { 17 | definition (name: "My ZigBee Valve", namespace: "jscgs350", author: "SmartThings", runLocally: false, minHubCoreVersion: '000.017.0012', executeCommandsLocally: false) { 18 | capability "Actuator" 19 | capability "Battery" 20 | capability "Configuration" 21 | capability "Power Source" 22 | capability "Health Check" 23 | capability "Refresh" 24 | capability "Valve" 25 | capability "Switch" 26 | capability "Contact Sensor" 27 | 28 | fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0006, 0020, 0B02, FC02", outClusters: "0019", manufacturer: "WAXMAN", model: "leakSMART Water Valve v2.10", deviceJoinName: "leakSMART Valve" 29 | fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0008, 000F, 0020, 0B02", outClusters: "0003, 0019", manufacturer: "WAXMAN", model: "House Water Valve - MDL-TBD", deviceJoinName: "Waxman House Water Valve" 30 | } 31 | 32 | tiles(scale: 2) { 33 | multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){ 34 | tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { 35 | attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing" 36 | attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ff0000", nextState:"opening" 37 | attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#f0b823", nextState:"closing" 38 | attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#f0b823", nextState:"opening" 39 | } 40 | tileAttribute ("powerSource", key: "SECONDARY_CONTROL") { 41 | attributeState "powerSource", label:'Power Source: ${currentValue}' 42 | } 43 | } 44 | 45 | valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:3, height:2) { 46 | state "battery", action:"configuration.configure", label:'${currentValue}% battery', unit:"" 47 | } 48 | 49 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 3, height: 2) { 50 | state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" 51 | } 52 | 53 | main(["contact"]) 54 | details(["contact", "battery", "refresh"]) 55 | } 56 | } 57 | 58 | private getCLUSTER_BASIC() { 0x0000 } 59 | private getBASIC_ATTR_POWER_SOURCE() { 0x0007 } 60 | private getCLUSTER_POWER() { 0x0001 } 61 | private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 } 62 | 63 | // Parse incoming device messages to generate events 64 | def parse(String description) { 65 | log.debug "description is $description" 66 | def event = zigbee.getEvent(description) 67 | if (event) { 68 | if(event.name == "switch") { 69 | event.name = "contact" //0006 cluster in valve is tied to contact 70 | if(event.value == "on") { 71 | event.value = "open" 72 | } 73 | else if(event.value == "off") { 74 | event.value = "closed" 75 | } 76 | } else if (event.name == "powerSource") { 77 | event.value = event.value.toLowerCase() 78 | } 79 | sendEvent(event) 80 | //handle valve attribute 81 | event.name = "valve" 82 | sendEvent(event) 83 | } 84 | else { 85 | def descMap = zigbee.parseDescriptionAsMap(description) 86 | if (descMap.clusterInt == CLUSTER_BASIC && descMap.attrInt == BASIC_ATTR_POWER_SOURCE){ 87 | def value = descMap.value 88 | if (value == "01" || value == "02") { 89 | sendEvent(name: "powerSource", value: "AC Power") 90 | } 91 | else if (value == "03") { 92 | sendEvent(name: "powerSource", value: "Backup Battery") 93 | } 94 | else if (value == "04") { 95 | sendEvent(name: "powerSource", value: "dc") 96 | } 97 | else { 98 | sendEvent(name: "powerSource", value: "unknown") 99 | } 100 | } 101 | else if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) { 102 | event.name = "battery" 103 | event.value = Math.round(Integer.parseInt(descMap.value, 16) / 2) 104 | sendEvent(event) 105 | } 106 | else { 107 | log.warn "DID NOT PARSE MESSAGE for description : $description" 108 | log.debug descMap 109 | } 110 | } 111 | } 112 | 113 | def open() { 114 | zigbee.on() 115 | } 116 | 117 | def on() { 118 | zigbee.on() 119 | } 120 | 121 | def close() { 122 | zigbee.off() 123 | } 124 | 125 | def off() { 126 | zigbee.off() 127 | } 128 | 129 | def refresh() { 130 | log.debug "refresh called" 131 | zigbee.onOffRefresh() + 132 | zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) + 133 | zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) + 134 | zigbee.onOffConfig() + 135 | zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, DataType.UINT8, 600, 21600, 1) + 136 | zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, DataType.ENUM8, 5, 21600, 1) 137 | } 138 | 139 | def configure() { 140 | log.debug "Configuring Reporting and Bindings." 141 | sendEvent(name: "checkInterval", value: 2*15* 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 142 | refresh() 143 | } 144 | 145 | def installed() { 146 | sendEvent(name: "checkInterval", value: 2*15* 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) 147 | } 148 | 149 | def ping() { 150 | zigbee.onOffRefresh() 151 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-zwave-door-sensor.src/my-zwave-door-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | * My Z-Wave Door Sensor Version: v2 14 | * 15 | * Updates: 16 | * ------- 17 | * 03-18-2016 : Initial commit 18 | * 19 | */ 20 | 21 | metadata { 22 | definition (name: "My Z-Wave Door Sensor", namespace: "jscgs350", author: "SmartThings") { 23 | capability "Contact Sensor" 24 | capability "Sensor" 25 | capability "Battery" 26 | capability "Configuration" 27 | 28 | } 29 | 30 | // UI tile definitions 31 | tiles(scale: 2) { 32 | multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){ 33 | tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { 34 | attributeState "closed", label: 'closed', icon: "st.nest.nest-home", backgroundColor: "#79b821" 35 | attributeState "open", label: 'open', icon: "st.nest.nest-away", backgroundColor: "#ffa81e" 36 | } 37 | } 38 | valueTile("battery", "device.battery", width: 6, height: 2, inactiveLabel: false, decoration: "flat") { 39 | state "battery", label:'${currentValue}% battery', unit:"" 40 | } 41 | 42 | main "contact" 43 | details(["contact", "battery"]) 44 | } 45 | } 46 | 47 | def parse(String description) { 48 | def result = null 49 | if (description.startsWith("Err 106")) { 50 | if (state.sec) { 51 | log.debug description 52 | } else { 53 | result = createEvent( 54 | descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.", 55 | eventType: "ALERT", 56 | name: "secureInclusion", 57 | value: "failed", 58 | isStateChange: true, 59 | ) 60 | } 61 | } else if (description != "updated") { 62 | def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1]) 63 | if (cmd) { 64 | result = zwaveEvent(cmd) 65 | } 66 | } 67 | log.debug "parsed '$description' to $result" 68 | return result 69 | } 70 | 71 | def updated() { 72 | def cmds = [] 73 | if (!state.MSR) { 74 | cmds = [ 75 | zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(), 76 | "delay 1200", 77 | zwave.wakeUpV1.wakeUpNoMoreInformation().format() 78 | ] 79 | } else if (!state.lastbat) { 80 | cmds = [] 81 | } else { 82 | cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation().format()] 83 | } 84 | response(cmds) 85 | } 86 | 87 | def configure() { 88 | delayBetween([ 89 | zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(), 90 | batteryGetCommand() 91 | ], 6000) 92 | } 93 | 94 | def sensorValueEvent(value) { 95 | if (value) { 96 | createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open") 97 | } else { 98 | createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed") 99 | } 100 | } 101 | 102 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) 103 | { 104 | sensorValueEvent(cmd.value) 105 | } 106 | 107 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) 108 | { 109 | sensorValueEvent(cmd.value) 110 | } 111 | 112 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) 113 | { 114 | sensorValueEvent(cmd.value) 115 | } 116 | 117 | def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) 118 | { 119 | sensorValueEvent(cmd.sensorValue) 120 | } 121 | 122 | def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) 123 | { 124 | sensorValueEvent(cmd.sensorState) 125 | } 126 | 127 | def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) 128 | { 129 | def result = [] 130 | if (cmd.notificationType == 0x06 && cmd.event == 0x16) { 131 | result << sensorValueEvent(1) 132 | } else if (cmd.notificationType == 0x06 && cmd.event == 0x17) { 133 | result << sensorValueEvent(0) 134 | } else if (cmd.notificationType == 0x07) { 135 | if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice door/window sensors 136 | result << sensorValueEvent(cmd.v1AlarmLevel) 137 | } else if (cmd.event == 0x01 || cmd.event == 0x02) { 138 | result << sensorValueEvent(1) 139 | } else if (cmd.event == 0x03) { 140 | result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true) 141 | result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId)) 142 | if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 143 | } else if (cmd.event == 0x05 || cmd.event == 0x06) { 144 | result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true) 145 | } else if (cmd.event == 0x07) { 146 | if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 147 | result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion") 148 | } 149 | } else if (cmd.notificationType) { 150 | def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}" 151 | result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false) 152 | } else { 153 | def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive" 154 | result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false) 155 | } 156 | result 157 | } 158 | 159 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) 160 | { 161 | def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false) 162 | def cmds = [] 163 | if (!state.MSR) { 164 | cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format() 165 | cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format() 166 | cmds << "delay 1200" 167 | } 168 | if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) { 169 | cmds << batteryGetCommand() 170 | } else { 171 | cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format() 172 | } 173 | [event, response(cmds)] 174 | } 175 | 176 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 177 | def map = [ name: "battery", unit: "%" ] 178 | if (cmd.batteryLevel == 0xFF) { 179 | map.value = 1 180 | map.descriptionText = "${device.displayName} has a low battery" 181 | map.isStateChange = true 182 | } else { 183 | map.value = cmd.batteryLevel 184 | } 185 | state.lastbat = now() 186 | [createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())] 187 | } 188 | 189 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 190 | def result = [] 191 | 192 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 193 | log.debug "msr: $msr" 194 | updateDataValue("MSR", msr) 195 | 196 | retypeBasedOnMSR() 197 | 198 | result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) 199 | 200 | if (msr == "011A-0601-0901") { // Enerwave motion doesn't always get the associationSet that the hub sends on join 201 | result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)) 202 | } else if (!device.currentState("battery")) { 203 | if (msr == "0086-0102-0059") { 204 | result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format()) 205 | } else { 206 | result << response(batteryGetCommand()) 207 | } 208 | } 209 | 210 | result 211 | } 212 | 213 | def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { 214 | def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1]) 215 | // log.debug "encapsulated: $encapsulatedCommand" 216 | if (encapsulatedCommand) { 217 | state.sec = 1 218 | zwaveEvent(encapsulatedCommand) 219 | } 220 | } 221 | 222 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 223 | createEvent(descriptionText: "$device.displayName: $cmd", displayed: false) 224 | } 225 | 226 | def batteryGetCommand() { 227 | def cmd = zwave.batteryV1.batteryGet() 228 | if (state.sec) { 229 | cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd) 230 | } 231 | cmd.format() 232 | } 233 | 234 | def retypeBasedOnMSR() { 235 | switch (state.MSR) { 236 | case "0086-0002-002D": 237 | log.debug("Changing device type to Z-Wave Water Sensor") 238 | setDeviceType("Z-Wave Water Sensor") 239 | break 240 | case "011F-0001-0001": // Schlage motion 241 | case "014A-0001-0001": // Ecolink motion 242 | case "0060-0001-0002": // Everspring SP814 243 | case "0060-0001-0003": // Everspring HSP02 244 | case "011A-0601-0901": // Enerwave ZWN-BPC 245 | log.debug("Changing device type to Z-Wave Motion Sensor") 246 | setDeviceType("Z-Wave Motion Sensor") 247 | break 248 | 249 | } 250 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-zwave-reversed-contact-sensor.src/my-zwave-reversed-contact-sensor.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | * Z-Wave Reversed Contact Sensor. Does the opposite of the regular door/window sensor. Built specifically for the 14 | * Aeotec Aeon Dry Contact Sensor for @Boatman (John Lee). 15 | * 16 | * Author: SmartThings 17 | * Date: 2013-11-3 18 | * Version: v2 19 | * 20 | * Updates: 21 | * ------- 22 | * 04-17-2016 : Initial commit 23 | * 24 | */ 25 | 26 | metadata { 27 | definition (name: "My Z-Wave Reversed Contact Sensor", namespace: "jscgs350", author: "SmartThings") { 28 | capability "Contact Sensor" 29 | capability "Sensor" 30 | capability "Battery" 31 | capability "Configuration" 32 | 33 | // fingerprint deviceId: "0x2001", inClusters: "0x30,0x80,0x84,0x85,0x86,0x72" 34 | // fingerprint deviceId: "0x07", inClusters: "0x30" 35 | // fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82" 36 | } 37 | 38 | // UI tile definitions 39 | tiles { 40 | standardTile("contact", "device.contact", width: 2, height: 2) { 41 | state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e" 42 | state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821" 43 | } 44 | valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") { 45 | state "battery", label:'${currentValue}% battery', unit:"" 46 | } 47 | 48 | main "contact" 49 | details(["contact", "battery"]) 50 | } 51 | } 52 | 53 | def parse(String description) { 54 | def result = null 55 | if (description.startsWith("Err 106")) { 56 | if (state.sec) { 57 | log.debug description 58 | } else { 59 | result = createEvent( 60 | descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.", 61 | eventType: "ALERT", 62 | name: "secureInclusion", 63 | value: "failed", 64 | isStateChange: true, 65 | ) 66 | } 67 | } else if (description != "updated") { 68 | def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1]) 69 | if (cmd) { 70 | result = zwaveEvent(cmd) 71 | } 72 | } 73 | log.debug "parsed '$description' to $result" 74 | return result 75 | } 76 | 77 | def updated() { 78 | def cmds = [] 79 | if (!state.MSR) { 80 | cmds = [ 81 | zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(), 82 | "delay 1200", 83 | zwave.wakeUpV1.wakeUpNoMoreInformation().format() 84 | ] 85 | } else if (!state.lastbat) { 86 | cmds = [] 87 | } else { 88 | cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation().format()] 89 | } 90 | response(cmds) 91 | } 92 | 93 | def configure() { 94 | delayBetween([ 95 | zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(), 96 | batteryGetCommand() 97 | ], 6000) 98 | } 99 | 100 | def sensorValueEvent(value) { 101 | if (value) { 102 | createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed") 103 | } else { 104 | createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open") 105 | } 106 | } 107 | 108 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) 109 | { 110 | sensorValueEvent(cmd.value) 111 | } 112 | 113 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) 114 | { 115 | sensorValueEvent(cmd.value) 116 | } 117 | 118 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) 119 | { 120 | sensorValueEvent(cmd.value) 121 | } 122 | 123 | def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) 124 | { 125 | sensorValueEvent(cmd.sensorValue) 126 | } 127 | 128 | def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) 129 | { 130 | sensorValueEvent(cmd.sensorState) 131 | } 132 | 133 | def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) 134 | { 135 | def result = [] 136 | if (cmd.notificationType == 0x06 && cmd.event == 0x16) { 137 | result << sensorValueEvent(1) 138 | } else if (cmd.notificationType == 0x06 && cmd.event == 0x17) { 139 | result << sensorValueEvent(0) 140 | } else if (cmd.notificationType == 0x07) { 141 | if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice door/window sensors 142 | result << sensorValueEvent(cmd.v1AlarmLevel) 143 | } else if (cmd.event == 0x01 || cmd.event == 0x02) { 144 | result << sensorValueEvent(1) 145 | } else if (cmd.event == 0x03) { 146 | result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true) 147 | result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId)) 148 | if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 149 | } else if (cmd.event == 0x05 || cmd.event == 0x06) { 150 | result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true) 151 | } else if (cmd.event == 0x07) { 152 | if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) 153 | result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion") 154 | } 155 | } else if (cmd.notificationType) { 156 | def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}" 157 | result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false) 158 | } else { 159 | def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive" 160 | result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false) 161 | } 162 | result 163 | } 164 | 165 | def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) 166 | { 167 | def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false) 168 | def cmds = [] 169 | if (!state.MSR) { 170 | cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format() 171 | cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format() 172 | cmds << "delay 1200" 173 | } 174 | if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) { 175 | cmds << batteryGetCommand() 176 | } else { 177 | cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format() 178 | } 179 | [event, response(cmds)] 180 | } 181 | 182 | def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { 183 | def map = [ name: "battery", unit: "%" ] 184 | if (cmd.batteryLevel == 0xFF) { 185 | map.value = 1 186 | map.descriptionText = "${device.displayName} has a low battery" 187 | map.isStateChange = true 188 | } else { 189 | map.value = cmd.batteryLevel 190 | } 191 | state.lastbat = now() 192 | [createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())] 193 | } 194 | 195 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { 196 | def result = [] 197 | 198 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 199 | log.debug "msr: $msr" 200 | updateDataValue("MSR", msr) 201 | 202 | retypeBasedOnMSR() 203 | 204 | result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) 205 | 206 | if (msr == "011A-0601-0901") { // Enerwave motion doesn't always get the associationSet that the hub sends on join 207 | result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)) 208 | } else if (!device.currentState("battery")) { 209 | if (msr == "0086-0102-0059") { 210 | result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format()) 211 | } else { 212 | result << response(batteryGetCommand()) 213 | } 214 | } 215 | 216 | result 217 | } 218 | 219 | def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { 220 | def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1]) 221 | // log.debug "encapsulated: $encapsulatedCommand" 222 | if (encapsulatedCommand) { 223 | state.sec = 1 224 | zwaveEvent(encapsulatedCommand) 225 | } 226 | } 227 | 228 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 229 | createEvent(descriptionText: "$device.displayName: $cmd", displayed: false) 230 | } 231 | 232 | def batteryGetCommand() { 233 | def cmd = zwave.batteryV1.batteryGet() 234 | if (state.sec) { 235 | cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd) 236 | } 237 | cmd.format() 238 | } 239 | 240 | def retypeBasedOnMSR() { 241 | switch (state.MSR) { 242 | case "0086-0002-002D": 243 | log.debug("Changing device type to Z-Wave Water Sensor") 244 | setDeviceType("Z-Wave Water Sensor") 245 | break 246 | case "011F-0001-0001": // Schlage motion 247 | case "014A-0001-0001": // Ecolink motion 248 | case "014A-0004-0001": // Ecolink motion + 249 | case "0060-0001-0002": // Everspring SP814 250 | case "0060-0001-0003": // Everspring HSP02 251 | case "011A-0601-0901": // Enerwave ZWN-BPC 252 | log.debug("Changing device type to Z-Wave Motion Sensor") 253 | setDeviceType("Z-Wave Motion Sensor") 254 | break 255 | 256 | } 257 | } -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-zwave-thermostat.src/readme.md: -------------------------------------------------------------------------------- 1 | Many people have asked for my PayPal info, so I thought I'd post it here in case anyone would like it: 2 | 3 | http://paypal.me/JohnConstantelos 4 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/my-zwave-valve.src/my-zwave-valve.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 SmartThings 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at: 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 10 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing permissions and limitations under the License. 12 | * 13 | */ 14 | metadata { 15 | definition (name: "My Zwave Valve", namespace: "jscgs350", author: "SmartThings") { 16 | capability "Actuator" 17 | capability "Valve" 18 | capability "Polling" 19 | capability "Refresh" 20 | capability "Sensor" 21 | capability "Switch" 22 | 23 | fingerprint deviceId: "0x1006", inClusters: "0x25" 24 | } 25 | 26 | // simulator metadata 27 | simulator { 28 | status "open": "command: 2503, payload: FF" 29 | status "close": "command: 2503, payload: 00" 30 | 31 | // reply messages 32 | reply "2001FF,delay 100,2502": "command: 2503, payload: FF" 33 | reply "200100,delay 100,2502": "command: 2503, payload: 00" 34 | } 35 | 36 | // tile definitions 37 | tiles(scale: 2) { 38 | multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){ 39 | tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { 40 | attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing" 41 | attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening" 42 | attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#ffe71e" 43 | attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffe71e" 44 | } 45 | } 46 | 47 | standardTile("refresh", "device.contact", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { 48 | state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" 49 | } 50 | 51 | main "valve" 52 | details(["valve","refresh"]) 53 | } 54 | 55 | } 56 | 57 | def updated() { 58 | response(refresh()) 59 | } 60 | 61 | def parse(String description) { 62 | log.trace "parse description : $description" 63 | def result = null 64 | def cmd = zwave.parse(description, [0x20: 1]) 65 | if (cmd) { 66 | result = createEvent(zwaveEvent(cmd)) 67 | } 68 | log.debug "Parse returned ${result?.descriptionText}" 69 | return result 70 | } 71 | 72 | def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { 73 | def value = cmd.value == 0xFF ? "open" : cmd.value == 0x00 ? "closed" : "unknown" 74 | [name: "contact", value: value, descriptionText: "$device.displayName valve is $value"] 75 | } 76 | 77 | def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { //TODO should show MSR when device is discovered 78 | log.debug "manufacturerId: ${cmd.manufacturerId}" 79 | log.debug "manufacturerName: ${cmd.manufacturerName}" 80 | log.debug "productId: ${cmd.productId}" 81 | log.debug "productTypeId: ${cmd.productTypeId}" 82 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) 83 | updateDataValue("MSR", msr) 84 | [descriptionText: "$device.displayName MSR: $msr", isStateChange: false] 85 | } 86 | 87 | def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) { 88 | [descriptionText: cmd.toString(), isStateChange: true, displayed: true] 89 | } 90 | 91 | def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { 92 | def value = cmd.value == 0xFF ? "open" : cmd.value == 0x00 ? "closed" : "unknown" 93 | [name: "contact", value: value, descriptionText: "$device.displayName valve is $value"] 94 | } 95 | 96 | def zwaveEvent(physicalgraph.zwave.Command cmd) { 97 | [:] // Handles all Z-Wave commands we aren't interested in 98 | } 99 | 100 | def open() { 101 | delayBetween([ 102 | zwave.basicV1.basicSet(value: 0xFF).format(), 103 | zwave.switchBinaryV1.switchBinaryGet().format() 104 | ],10000) //wait for a water valve to be completely opened 105 | } 106 | 107 | def off() { 108 | delayBetween([ 109 | zwave.basicV1.basicSet(value: 0xFF).format(), 110 | zwave.switchBinaryV1.switchBinaryGet().format() 111 | ],10000) //wait for a water valve to be completely opened 112 | } 113 | 114 | def close() { 115 | delayBetween([ 116 | zwave.basicV1.basicSet(value: 0x00).format(), 117 | zwave.switchBinaryV1.switchBinaryGet().format() 118 | ],10000) //wait for a water valve to be completely closed 119 | } 120 | 121 | def on() { 122 | delayBetween([ 123 | zwave.basicV1.basicSet(value: 0x00).format(), 124 | zwave.switchBinaryV1.switchBinaryGet().format() 125 | ],10000) //wait for a water valve to be completely closed 126 | } 127 | 128 | def poll() { 129 | zwave.switchBinaryV1.switchBinaryGet().format() 130 | } 131 | 132 | def refresh() { 133 | log.debug "refresh() is called" 134 | def commands = [zwave.switchBinaryV1.switchBinaryGet().format()] 135 | if (getDataValue("MSR") == null) { 136 | commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format() 137 | } 138 | delayBetween(commands,100) 139 | } 140 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/readme.md: -------------------------------------------------------------------------------- 1 | Welcome to my list of device type handlers for SmartThings! 2 | 3 | Many people have asked for my PayPal info, so I thought I'd post it here in case anyone would like it: 4 | 5 | http://paypal.me/JohnConstantelos 6 | -------------------------------------------------------------------------------- /devicetypes/jscgs350/zigbee-lock.src/zigbee-lock.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * SmartSense Open/Closed Sensor 3 | * 4 | * Copyright 2014 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | import physicalgraph.zigbee.clusters.iaszone.ZoneStatus 17 | 18 | metadata { 19 | // definition(name: "My Iris Lock Sensor", namespace: "jscgs350", author: "SmartThings", mnmn: "SmartThings", vid:"generic-lock") { 20 | definition(name: "ZigBee Lock", namespace: "jscgs350", author: "SmartThings", mnmn: "SmartThings", vid:"generic-lock") { 21 | capability "Battery" 22 | capability "Configuration" 23 | capability "Contact Sensor" 24 | capability "Refresh" 25 | capability "Temperature Measurement" 26 | capability "Health Check" 27 | capability "Lock" 28 | capability "Sensor" 29 | 30 | command "enrollResponse" 31 | 32 | } 33 | 34 | preferences { 35 | input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" 36 | input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false 37 | } 38 | 39 | tiles(scale: 2) { 40 | multiAttributeTile(name: "contact", type: "generic", width: 6, height: 4) { 41 | tileAttribute("device.contact", key: "PRIMARY_CONTROL") { 42 | attributeState "open", action:"refresh", label: 'Unlocked', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff" 43 | attributeState "closed", action:"refresh", label: 'Locked', icon:"st.locks.lock.locked", backgroundColor:"#00A0DC" 44 | } 45 | } 46 | 47 | valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) { 48 | state "temperature", label: '${currentValue}°', 49 | backgroundColors: [ 50 | [value: 31, color: "#153591"], 51 | [value: 44, color: "#1e9cbb"], 52 | [value: 59, color: "#90d2a7"], 53 | [value: 74, color: "#44b621"], 54 | [value: 84, color: "#f1d801"], 55 | [value: 95, color: "#d04e00"], 56 | [value: 96, color: "#bc2323"] 57 | ] 58 | } 59 | valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { 60 | state "battery", label: '${currentValue}% battery', unit: "" 61 | } 62 | standardTile("lock", "device.lock", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 63 | state "locked", label: 'Locked', icon:"st.locks.lock.locked", backgroundColor:"#00A0DC" 64 | state "unlocked", label: 'Unlocked', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff" 65 | } 66 | standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { 67 | state "default", action: "refresh.refresh", icon: "st.secondary.refresh" 68 | } 69 | 70 | main(["contact", "temperature"]) 71 | details(["contact", "temperature", "battery", "refresh"]) 72 | } 73 | } 74 | 75 | def parse(String description) { 76 | log.debug "description: $description" 77 | 78 | Map map = zigbee.getEvent(description) 79 | if (!map) { 80 | if (description?.startsWith('zone status')) { 81 | map = parseIasMessage(description) 82 | } else { 83 | Map descMap = zigbee.parseDescriptionAsMap(description) 84 | if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { 85 | map = getBatteryResult(Integer.parseInt(descMap.value, 16)) 86 | } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { 87 | if (descMap.data[0] == "00") { 88 | log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" 89 | sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 90 | } else { 91 | log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" 92 | } 93 | } 94 | } 95 | } else if (map.name == "temperature") { 96 | if (tempOffset) { 97 | map.value = (int) map.value + (int) tempOffset 98 | } 99 | map.descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C' : '{{ device.displayName }} was {{ value }}°F' 100 | map.translatable = true 101 | } 102 | 103 | log.debug "Parse returned $map" 104 | def result = map ? createEvent(map) : [:] 105 | 106 | if (description?.startsWith('enroll request')) { 107 | List cmds = zigbee.enrollResponse() 108 | log.debug "enroll response: ${cmds}" 109 | result = cmds?.collect { new physicalgraph.device.HubAction(it) } 110 | } 111 | return result 112 | } 113 | 114 | 115 | private Map parseIasMessage(String description) { 116 | ZoneStatus zs = zigbee.parseZoneStatus(description) 117 | return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') 118 | } 119 | 120 | private Map getBatteryResult(rawValue) { 121 | log.debug 'Battery' 122 | def linkText = getLinkText(device) 123 | 124 | def result = [:] 125 | 126 | def volts = rawValue / 10 127 | if (!(rawValue == 0 || rawValue == 255)) { 128 | def minVolts = 2.1 129 | def maxVolts = 3.0 130 | def pct = (volts - minVolts) / (maxVolts - minVolts) 131 | def roundedPct = Math.round(pct * 100) 132 | if (roundedPct <= 0) 133 | roundedPct = 1 134 | result.value = Math.min(100, roundedPct) 135 | result.descriptionText = "${linkText} battery was ${result.value}%" 136 | result.name = 'battery' 137 | } 138 | 139 | return result 140 | } 141 | 142 | private Map getContactResult(value) { 143 | log.debug 'Contact Status is $value' 144 | def linkText = getLinkText(device) 145 | def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}" 146 | if (value == 'open') { 147 | sendEvent(name: "lock", value: "unlocked") 148 | } else { 149 | sendEvent(name: "lock", value: "locked") 150 | } 151 | return [ 152 | name : 'contact', 153 | value : value, 154 | descriptionText: descriptionText 155 | ] 156 | } 157 | 158 | /** 159 | * PING is used by Device-Watch in attempt to reach the Device 160 | * */ 161 | def ping() { 162 | return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level 163 | } 164 | 165 | def refresh() { 166 | log.debug "Refreshing Temperature and Battery" 167 | def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + 168 | zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) 169 | 170 | return refreshCmds + zigbee.enrollResponse() 171 | } 172 | 173 | def configure() { 174 | // Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time) 175 | // enrolls with default periodic reporting until newer 5 min interval is confirmed 176 | sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) 177 | 178 | log.debug "Configuring Reporting, IAS CIE, and Bindings." 179 | 180 | // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity 181 | // battery minReport 30 seconds, maxReportTime 6 hrs by default 182 | return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config 183 | } -------------------------------------------------------------------------------- /img/24-hour-clockv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/24-hour-clockv2.png -------------------------------------------------------------------------------- /img/7day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/7day.png -------------------------------------------------------------------------------- /img/Battery-Charge-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/Battery-Charge-icon.png -------------------------------------------------------------------------------- /img/auto@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/auto@2x.png -------------------------------------------------------------------------------- /img/battery-icon-614x460.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/battery-icon-614x460.png -------------------------------------------------------------------------------- /img/cool@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/cool@2x.png -------------------------------------------------------------------------------- /img/device-activity-tile@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/device-activity-tile@2x.png -------------------------------------------------------------------------------- /img/fan-auto@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/fan-auto@2x.png -------------------------------------------------------------------------------- /img/fan-on@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/fan-on@2x.png -------------------------------------------------------------------------------- /img/heat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/heat@2x.png -------------------------------------------------------------------------------- /img/icon-garage1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/icon-garage1.png -------------------------------------------------------------------------------- /img/mailbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/mailbox.png -------------------------------------------------------------------------------- /img/monthv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/monthv2.png -------------------------------------------------------------------------------- /img/nobattery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/nobattery.png -------------------------------------------------------------------------------- /img/poutlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/poutlet.png -------------------------------------------------------------------------------- /img/readme: -------------------------------------------------------------------------------- 1 | Location for image files 2 | -------------------------------------------------------------------------------- /img/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/settings.png -------------------------------------------------------------------------------- /img/settings_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/settings_small.png -------------------------------------------------------------------------------- /img/transportation12-icn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/transportation12-icn@2x.png -------------------------------------------------------------------------------- /img/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/warning.png -------------------------------------------------------------------------------- /img/watervalve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/watervalve.png -------------------------------------------------------------------------------- /img/watervalve1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsconstantelos/jcdevhandlers/5c8fe6488f8a033ef7883d05d6cc021ee6ce88b9/img/watervalve1.png -------------------------------------------------------------------------------- /smartapps/jscgs350/aeon-hem-v1-reset-manager.src/aeon-hem-v1-reset-manager.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Aeon HEMv1 Reset Manager 3 | * 4 | * Copyright 2016 jscgs350 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Overview 16 | * ---------------- 17 | * This SmartApp resets the Aeon HEM v1 on a user specified day every month at a time you specify. 18 | * NOTE: This has been tested and only works with my DH for the Aeon HEM v1, which can be found here: 19 | * https://github.com/constjs/jcdevhandlers/tree/master/devicetypes/jscgs350 20 | * 21 | * Revision History 22 | * ---------------- 23 | * 11-22-2016 : Initial release 24 | * 02-16-2017 : Fixed scheduling issue and improved handling when the app is initially installed and when it's updated. 25 | * 26 | */ 27 | 28 | definition( 29 | name: "Aeon HEM v1 Reset Manager", 30 | namespace: "jscgs350", 31 | author: "jscgs350", 32 | description: "Resets the HEM on a specified day/time of every month", 33 | category: "My Apps", 34 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 35 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 36 | 37 | preferences { 38 | section("Choose an Aeon HEM v1 to reset:") { 39 | input(name: "meter", type: "capability.energyMeter", title: "Which Aeon HEM v1? (tap here)", description: null, required: true, submitOnChange: true) 40 | } 41 | section("Reset Time of Day") { 42 | input "time", "time", title: "At this time of day" 43 | } 44 | section("Reset Day of Month") { 45 | input "day", "number", title: "On this day of the month" 46 | } 47 | } 48 | 49 | def installed() { 50 | log.debug "Aeon HEM v1 Reset Manager SmartApp installed, now preparing to schedule the first reset." 51 | } 52 | 53 | def updated() { 54 | log.debug "Aeon HEM v1 Reset Manager SmartApp updated, so update the user defined schedule and schedule another check for the next day." 55 | unschedule() 56 | def scheduleTime = timeToday(time, location.timeZone) 57 | def timeNow = now() 58 | log.debug "Current time is ${(new Date(timeNow)).format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 59 | log.debug "Scheduling meter reset check at ${scheduleTime.format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 60 | schedule(scheduleTime, resetTheMeter) 61 | } 62 | 63 | def initialize() { 64 | unschedule() 65 | def scheduleTime = timeToday(time, location.timeZone) 66 | def timeNow = now() 67 | log.debug "Current time is ${(new Date(timeNow)).format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 68 | scheduleTime = scheduleTime + 1 // Next day schedule 69 | log.debug "Scheduling next meter reset check at ${scheduleTime.format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 70 | schedule(scheduleTime, resetTheMeter) 71 | } 72 | 73 | def resetTheMeter() { 74 | Calendar localCalendar = Calendar.getInstance(TimeZone.getDefault()); 75 | def currentDayOfMonth = localCalendar.get(Calendar.DAY_OF_MONTH); 76 | log.debug "Aeon HEM v1 meter reset schedule triggered..." 77 | log.debug "...checking for the day of month requested by the user" 78 | log.debug "...the day of the month right now is ${currentDayOfMonth}" 79 | log.debug "...the day the user requested a reset is ${day}" 80 | if (currentDayOfMonth == day) { 81 | log.debug "...resetting the meter because it's when the user requested it." 82 | meter.resetMeter() 83 | } else { 84 | log.debug "...meter reset not scheduled for today because it's not when the user requested it." 85 | } 86 | log.debug "Process completed, now schedule the reset to check on the next day." 87 | initialize() 88 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-battery-monitor-child.src/dashboard-battery-monitor-child.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Dashboard - Battery Monitor Child SmartApp for SmartThings 3 | * 4 | * Copyright (c) 2014 Brandon Gordon (https://github.com/notoriousbdg) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | 17 | definition( 18 | name: "Dashboard - Battery Monitor Child", 19 | namespace: "jscgs350", 20 | author: "Brandon Gordon", 21 | description: "SmartApp to monitor battery levels.", 22 | category: "My Apps", 23 | parent: "jscgs350:Dashboard - Battery Monitor Parent", 24 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 25 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 26 | 27 | preferences { 28 | page name:"pageStatus" 29 | page name:"pageConfigure" 30 | } 31 | 32 | // Show Status page 33 | def pageStatus() { 34 | def pageProperties = [ 35 | name: "pageStatus", 36 | title: "BatteryMonitor Status", 37 | nextPage: null, 38 | install: true, 39 | uninstall: true 40 | ] 41 | 42 | if (settings.devices == null) { 43 | return pageConfigure() 44 | } 45 | 46 | def listLevel0 = "" 47 | def listLevel1 = "" 48 | def listLevel2 = "" 49 | def listLevel3 = "" 50 | def listLevel4 = "" 51 | 52 | if (settings.level1 == null) { settings.level1 = 33 } 53 | if (settings.level3 == null) { settings.level3 = 67 } 54 | if (settings.pushMessage) { settings.pushMessage = true } 55 | 56 | return dynamicPage(pageProperties) { 57 | settings.devices.each() { 58 | try { 59 | if (it.currentBattery == null) { 60 | listLevel0 += "$it.displayName\n" 61 | } else if (it.currentBattery >= 0 && it.currentBattery < settings.level1.toInteger()) { 62 | listLevel1 += "$it.currentBattery $it.displayName\n" 63 | } else if (it.currentBattery >= settings.level1.toInteger() && it.currentBattery <= settings.level3.toInteger()) { 64 | listLevel2 += "$it.currentBattery $it.displayName\n" 65 | } else if (it.currentBattery > settings.level3.toInteger() && it.currentBattery < 100) { 66 | listLevel3 += "$it.currentBattery $it.displayName\n" 67 | } else if (it.currentBattery == 100) { 68 | listLevel4 += "$it.displayName\n" 69 | } else { 70 | listLevel0 += "$it.currentBattery $it.displayName\n" 71 | } 72 | } catch (e) { 73 | log.trace "Caught error checking battery status." 74 | log.trace e 75 | listLevel0 += "$it.displayName\n" 76 | } 77 | } 78 | 79 | if (listLevel0) { 80 | section("Batteries with errors or no status") { 81 | paragraph listLevel0.trim() 82 | } 83 | } 84 | 85 | if (listLevel1) { 86 | section("Batteries needing attention right now (less than $settings.level1)") { 87 | paragraph listLevel1.trim() 88 | } 89 | } 90 | 91 | if (listLevel2) { 92 | section("Batteries to monitor charge (between $settings.level1 and $settings.level3)") { 93 | paragraph listLevel2.trim() 94 | } 95 | } 96 | 97 | if (listLevel3) { 98 | section("Batteries with acceptable charge (more than $settings.level3)") { 99 | paragraph listLevel3.trim() 100 | } 101 | } 102 | 103 | if (listLevel4) { 104 | section("Batteries with full charge") { 105 | paragraph listLevel4.trim() 106 | } 107 | } 108 | 109 | section("Menu") { 110 | href "pageStatus", title:"Refresh", description:"Tap to refresh" 111 | href "pageConfigure", title:"Configure", description:"Tap to open" 112 | } 113 | } 114 | } 115 | 116 | // Show Configure Page 117 | def pageConfigure() { 118 | def helpPage = 119 | "Select devices with batteries that you wish to monitor." 120 | 121 | def inputBattery = [ 122 | name: "devices", 123 | type: "capability.battery", 124 | title: "Which devices with batteries?", 125 | multiple: true, 126 | required: true 127 | ] 128 | 129 | def inputLevel1 = [ 130 | name: "level1", 131 | type: "number", 132 | title: "Low battery threshold?", 133 | defaultValue: "33", 134 | required: true 135 | ] 136 | 137 | def inputLevel3 = [ 138 | name: "level3", 139 | type: "number", 140 | title: "Medium battery threshold?", 141 | defaultValue: "67", 142 | required: true 143 | ] 144 | 145 | def pageProperties = [ 146 | name: "pageConfigure", 147 | title: "BatteryMonitor Configuration", 148 | nextPage: "pageStatus", 149 | uninstall: true 150 | ] 151 | 152 | return dynamicPage(pageProperties) { 153 | section("About") { 154 | paragraph helpPage 155 | } 156 | 157 | section("Devices") { 158 | input inputBattery 159 | } 160 | 161 | section("Settings") { 162 | input inputLevel1 163 | input inputLevel3 164 | } 165 | 166 | section("Notification") { 167 | input("recipients", "contact", title: "Send notifications to") { 168 | input(name: "sms", type: "phone", title: "Send A Text To", description: null, required: false) 169 | input(name: "pushNotification", type: "bool", title: "Send a push notification", description: null, defaultValue: false) 170 | } 171 | } 172 | 173 | section("Minimum time between messages (optional)") { 174 | input "frequency", "decimal", title: "Minutes", required: false 175 | } 176 | 177 | section([title:"Options", mobileOnly:true]) { 178 | label title:"Assign a name", required:false 179 | } 180 | } 181 | } 182 | 183 | def installed() { 184 | initialize() 185 | } 186 | 187 | def updated() { 188 | unschedule() 189 | unsubscribe() 190 | initialize() 191 | } 192 | 193 | def initialize() { 194 | subscribe(devices, "battery", batteryHandler) 195 | state.lowBattNoticeSent = [:] 196 | 197 | runIn(60, updateBatteryStatus) 198 | } 199 | 200 | def send(msg) { 201 | /* if (frequency) { 202 | def lastTime = state[frequencyKey(evt)] 203 | // def lastTime = state[evt.deviceId] 204 | if (lastTime == null || now() - lastTime >= frequency * 60000) { 205 | if (location.contactBookEnabled) { 206 | sendNotificationToContacts(msg, recipients) 207 | } 208 | else { 209 | if (sms) { 210 | sendSms(sms, msg) 211 | } 212 | if (pushNotification) { 213 | sendPush(msg) 214 | } 215 | } 216 | } 217 | } 218 | else { 219 | if (location.contactBookEnabled) { 220 | sendNotificationToContacts(msg, recipients) 221 | } 222 | else { 223 | if (sms) { 224 | sendSms(sms, msg) 225 | } 226 | if (pushNotification) { 227 | sendPush(msg) 228 | } 229 | } 230 | }*/ 231 | } 232 | 233 | def updateBatteryStatus() { 234 | settings.devices.each() { 235 | try { 236 | if (it.currentBattery == null) { 237 | if (!state.lowBattNoticeSent.containsKey(it.id)) { 238 | send("${it.displayName} battery is not reporting.") 239 | state.lowBattNoticeSent[(it.id)] = true 240 | } 241 | } else if (it.currentBattery > 100) { 242 | if (!state.lowBattNoticeSent.containsKey(it.id)) { 243 | send("${it.displayName} battery is ${it.currentBattery}, which is over 100.") 244 | state.lowBattNoticeSent[(it.id)] = true 245 | } 246 | } else if (it.currentBattery < settings.level1) { 247 | if (!state.lowBattNoticeSent.containsKey(it.id)) { 248 | send("${it.displayName} battery is ${it.currentBattery} (threshold ${settings.level1}.)") 249 | state.lowBattNoticeSent[(it.id)] = true 250 | } 251 | } else { 252 | if (state.lowBattNoticeSent.containsKey(it.id)) { 253 | state.lowBattNoticeSent.remove(it.id) 254 | } 255 | } 256 | } catch (e) { 257 | log.trace "Caught error checking battery status." 258 | log.trace e 259 | if (!state.lowBattNoticeSent.containsKey(it.id)) { 260 | send("${it.displayName} battery reported a non-integer level.") 261 | state.lowBattNoticeSent[(it.id)] = true 262 | } 263 | } 264 | } 265 | } 266 | 267 | 268 | def batteryHandler(evt) { 269 | updateBatteryStatus() 270 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-battery-monitor-parent.src/dashboard-battery-monitor-parent.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "Dashboard - Battery Monitor Parent", 3 | singleInstance: true, 4 | namespace: "jscgs350", 5 | author: "jscgs350", 6 | description: "Dashboard - Battery Monitor Parent", 7 | category: "My Apps", 8 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 9 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 10 | 11 | preferences { 12 | page(name: "mainPage", title: "Dashboard - Battery Monitor Parent", install: true, uninstall: true,submitOnChange: true) { 13 | section { 14 | app(name: "childRules", appName: "Dashboard - Battery Monitor Child", namespace: "jscgs350", title: "Create Battery Monitor...", multiple: true) 15 | } 16 | } 17 | } 18 | 19 | def installed() { 20 | initialize() 21 | } 22 | 23 | def updated() { 24 | unsubscribe() 25 | initialize() 26 | } 27 | 28 | def initialize() { 29 | childApps.each {child -> 30 | log.info "Installed Monitors: ${child.label}" 31 | } 32 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-contact-sensors.src/dashboard-contact-sensors.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Dashboard - Contact Sensors SmartApp for SmartThings 3 | * 4 | * Copyright 2016 J.Constantelos 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Revision History 16 | * ---------------- 17 | * 10-09-2016 : v1.0.0 Initial release (parent/child app) 18 | * 19 | */ 20 | 21 | definition( 22 | name: "Dashboard - Contact Sensors", 23 | namespace: "jscgs350", 24 | author: "jscgs350", 25 | description: "SmartApp to report contact sensor statuses in a single view.", 26 | category: "My Apps", 27 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 28 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 29 | 30 | preferences { 31 | page name:"pageStatus" 32 | page name:"pageConfigure" 33 | } 34 | 35 | //*************************** 36 | //Show Status page 37 | //*************************** 38 | def pageStatus() { 39 | def pageProperties = [ 40 | name: "pageStatus", 41 | title: "Devices Dashboard", 42 | nextPage: null, 43 | install: true, 44 | uninstall: true 45 | ] 46 | 47 | if (settings.contactdevices == null) { 48 | return pageConfigure() 49 | } 50 | 51 | def openlist = "" 52 | def closedlist = "" 53 | def badlist = "" 54 | def errorlist = "" 55 | 56 | return dynamicPage(pageProperties) { 57 | def rightNow = new Date() 58 | settings.contactdevices.each() { 59 | def lastcontact = it.currentValue('contact') 60 | try { 61 | if (lastcontact) { 62 | if (lastcontact == "open") { 63 | openlist += "$it.displayName\n"} 64 | if (lastcontact == "closed") { 65 | closedlist += "$it.displayName\n"} 66 | } else { 67 | badlist += "$it.displayName\n" 68 | } 69 | 70 | } catch (e) { 71 | log.trace "Caught error checking a device." 72 | log.trace e 73 | errorlist += "$it.displayName\n" 74 | } 75 | } 76 | 77 | if (openlist) { 78 | section("OPEN - Contact Sensors") { 79 | paragraph openlist.trim() 80 | } 81 | } 82 | 83 | if (closedlist) { 84 | section("CLOSED - Contact Sensors") { 85 | paragraph closedlist.trim() 86 | } 87 | } 88 | 89 | if (badlist) { 90 | section("Devices NOT Reporting States") { 91 | paragraph badlist.trim() 92 | } 93 | } 94 | 95 | if (errorlist) { 96 | section("Devices with Errors") { 97 | paragraph errorlist.trim() 98 | } 99 | } 100 | 101 | section("Menu") { 102 | href "pageStatus", title:"Refresh", description:"Tap to refresh the status of devices" 103 | href "pageConfigure", title:"Configure", description:"Tap to manage your list of devices" 104 | } 105 | } 106 | } 107 | 108 | //*************************** 109 | //Show Configure Page 110 | //*************************** 111 | def pageConfigure() { 112 | def helpPage = "Select devices that you wish to check when you open this SmartApp." 113 | 114 | def inputContactDevices = [name:"contactdevices",type:"capability.contactSensor",title:"Which open/close/contact devices?",multiple:true,required:true] 115 | 116 | def pageProperties = [name:"pageConfigure", 117 | title: "Dashboard Configurator", 118 | nextPage: "pageStatus", 119 | uninstall: true 120 | ] 121 | 122 | return dynamicPage(pageProperties) { 123 | section("About This App") { 124 | paragraph helpPage 125 | } 126 | 127 | section("Devices To Check") { 128 | input inputContactDevices 129 | } 130 | 131 | section([title:"Available Options", mobileOnly:true]) { 132 | label title:"Assign a name for your app (optional)", required:false 133 | } 134 | } 135 | } 136 | 137 | def installed() { 138 | initialize() 139 | } 140 | 141 | def updated() { 142 | initialize() 143 | } 144 | 145 | def initialize() { 146 | log.trace "Devices Dashboard" 147 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-lights-switches-and-outlets-child.src/dashboard-lights-switches-and-outlets-child.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Dashboard - Lights, Switches, and Outlets Child SmartApp for SmartThings 3 | * 4 | * Copyright 2016 J.Constantelos 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Revision History 16 | * ---------------- 17 | * 09-17-2016 : v1.0.0 Initial release (parent/child app) 18 | * 19 | */ 20 | 21 | definition( 22 | name: "Dashboard - Lights, Switches, and Outlets Child", 23 | namespace: "jscgs350", 24 | author: "jscgs350", 25 | description: "SmartApp to report Lights, Switches, and Outlets status in a single view.", 26 | category: "My Apps", 27 | parent: "jscgs350:Dashboard - Lights, Switches, and Outlets Parent", 28 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 29 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 30 | 31 | preferences { 32 | page name:"pageStatus" 33 | page name:"pageConfigure" 34 | } 35 | 36 | //*************************** 37 | //Show Status page 38 | //*************************** 39 | def pageStatus() { 40 | def pageProperties = [ 41 | name: "pageStatus", 42 | title: "Devices Dashboard", 43 | nextPage: null, 44 | install: true, 45 | uninstall: true 46 | ] 47 | if (settings.switchdevices == null) { 48 | return pageConfigure() 49 | } 50 | def onlist = "" 51 | def offlist = "" 52 | def badlist = "" 53 | def errorlist = "" 54 | return dynamicPage(pageProperties) { 55 | def rightNow = new Date() 56 | settings.switchdevices.each() { 57 | def lastSwitch = it.currentValue('switch') 58 | try { 59 | if (lastSwitch) { 60 | if (lastSwitch == 'on') { 61 | onlist += "$it.displayName\n"} 62 | if (lastSwitch == 'off') { 63 | offlist += "$it.displayName\n"} 64 | } else { 65 | badlist += "$it.displayName\n" 66 | } 67 | } catch (e) { 68 | log.trace "Caught error checking a device." 69 | log.trace e 70 | errorlist += "$it.displayName\n" 71 | } 72 | } 73 | if (onlist) { 74 | section("ON - Lights, Switches, or Outlets") { 75 | paragraph onlist.trim() 76 | } 77 | } 78 | if (offlist) { 79 | section("OFF - Lights, Switches, or Outlets") { 80 | paragraph offlist.trim() 81 | } 82 | } 83 | if (badlist) { 84 | section("Devices NOT Reporting States") { 85 | paragraph badlist.trim() 86 | } 87 | } 88 | if (errorlist) { 89 | section("Devices with Errors") { 90 | paragraph errorlist.trim() 91 | } 92 | } 93 | section("Menu") { 94 | href "pageStatus", title:"Refresh", description:"Tap to refresh the status of devices" 95 | href "pageConfigure", title:"Configure", description:"Tap to manage your list of devices" 96 | } 97 | } 98 | } 99 | 100 | //*************************** 101 | //Show Configure Page 102 | //*************************** 103 | def pageConfigure() { 104 | def helpPage = "Select devices that you wish to check when you open this SmartApp." 105 | 106 | def inputSwitchDevices = [name:"switchdevices",type:"capability.switch",title:"Which switch devices?",multiple:true,required:true] 107 | 108 | def pageProperties = [name:"pageConfigure", 109 | title: "Dashboard Configurator", 110 | nextPage: "pageStatus", 111 | uninstall: true 112 | ] 113 | return dynamicPage(pageProperties) { 114 | section("About This App") { 115 | paragraph helpPage 116 | } 117 | section("Devices To Check") { 118 | input inputSwitchDevices 119 | } 120 | section([title:"Available Options", mobileOnly:true]) { 121 | label title:"Assign a name for your app (optional)", required:false 122 | } 123 | } 124 | } 125 | 126 | def installed() { 127 | initialize() 128 | } 129 | 130 | def updated() { 131 | initialize() 132 | } 133 | 134 | def initialize() { 135 | log.trace "Devices Dashboard" 136 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-lights-switches-and-outlets-parent.src/dashboard-lights-switches-and-outlets-parent.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "Dashboard - Lights, Switches, and Outlets Parent", 3 | singleInstance: true, 4 | namespace: "jscgs350", 5 | author: "jscgs350", 6 | description: "Dashboard - Lights, Switches, and Outlets Parent", 7 | category: "My Apps", 8 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 9 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 10 | 11 | preferences { 12 | page(name: "mainPage", title: "Dashboard - Lights, Switches, and Outlets Parent", install: true, uninstall: true,submitOnChange: true) { 13 | section { 14 | app(name: "childRules", appName: "Dashboard - Lights, Switches, and Outlets Child", namespace: "jscgs350", title: "Create a new dashboard...", multiple: true) 15 | } 16 | } 17 | } 18 | 19 | def installed() { 20 | initialize() 21 | } 22 | 23 | def updated() { 24 | unsubscribe() 25 | initialize() 26 | } 27 | 28 | def initialize() { 29 | childApps.each {child -> 30 | log.info "Installed Dashboards: ${child.label}" 31 | } 32 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-motion-sensors.src/dashboard-motion-sensors.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Motion Sensor Dashboard SmartApp for SmartThings 3 | * 4 | * Copyright 2015 J.Constantelos 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Contributors 16 | * ---------------- 17 | * Based off of code from Brandon Gordon (https://github.com/notoriousbdg) - BatteryMonitor SmartApp (https://github.com/notoriousbdg/SmartThings.BatteryMonitor) 18 | * 19 | * Overview 20 | * ---------------- 21 | * This SmartApp helps you see all your motion sensors in a single view for the devices you selected. 22 | * 23 | * Revision History 24 | * ---------------- 25 | * 02-14-2016 : v1.0.0 Initial release 26 | * 02-29-2016 : Faormatting and variable name changes 27 | * 10-11-2016 : Changed the app to show motion and inactive in separate lists vs. being all in one. 28 | * 29 | */ 30 | 31 | definition( 32 | name: "Dashboard - Motion Sensors", 33 | namespace: "jscgs350", 34 | author: "jscgs350", 35 | description: "SmartApp to report motion sensor statuses in a single view.", 36 | category: "My Apps", 37 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 38 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 39 | 40 | preferences { 41 | page name:"pageStatus" 42 | page name:"pageConfigure" 43 | } 44 | 45 | //*************************** 46 | //Show Status page 47 | //*************************** 48 | def pageStatus() { 49 | def pageProperties = [ 50 | name: "pageStatus", 51 | title: "Devices Dashboard", 52 | nextPage: null, 53 | install: true, 54 | uninstall: true 55 | ] 56 | 57 | if (settings.motiondevices == null) { 58 | return pageConfigure() 59 | } 60 | 61 | def motionlist = "" 62 | def nomotionlist = "" 63 | def badlist = "" 64 | def errorlist = "" 65 | 66 | return dynamicPage(pageProperties) { 67 | def rightNow = new Date() 68 | settings.motiondevices.each() { 69 | def lastmotion = it.currentValue('motion') 70 | try { 71 | if (lastmotion) { 72 | if (lastmotion == "active") { 73 | motionlist += "$it.displayName\n"} 74 | if (lastmotion == "inactive") { 75 | nomotionlist += "$it.displayName\n"} 76 | } else { 77 | badlist += "$it.displayName\n" 78 | } 79 | 80 | } catch (e) { 81 | log.trace "Caught error checking a device." 82 | log.trace e 83 | errorlist += "$it.displayName\n" 84 | } 85 | } 86 | 87 | if (motionlist) { 88 | section("ACTIVE - Motion Sensors") { 89 | paragraph motionlist.trim() 90 | } 91 | } 92 | 93 | if (nomotionlist) { 94 | section("INACTIVE - Motion Sensors") { 95 | paragraph nomotionlist.trim() 96 | } 97 | } 98 | 99 | if (badlist) { 100 | section("Devices NOT Reporting States") { 101 | paragraph badlist.trim() 102 | } 103 | } 104 | 105 | if (errorlist) { 106 | section("Devices with Errors") { 107 | paragraph errorlist.trim() 108 | } 109 | } 110 | 111 | section("Menu") { 112 | href "pageStatus", title:"Refresh", description:"Tap to refresh the status of devices" 113 | href "pageConfigure", title:"Configure", description:"Tap to manage your list of devices" 114 | } 115 | } 116 | } 117 | 118 | //*************************** 119 | //Show Configure Page 120 | //*************************** 121 | def pageConfigure() { 122 | def helpPage = "Select devices that you wish to check when you open this SmartApp." 123 | 124 | def inputMotionDevices = [name:"motiondevices",type:"capability.motionSensor",title:"Which motion sensors?",multiple:true,required:true] 125 | 126 | def pageProperties = [name:"pageConfigure", 127 | title: "Dashboard Configurator", 128 | nextPage: "pageStatus", 129 | uninstall: true 130 | ] 131 | 132 | return dynamicPage(pageProperties) { 133 | section("About This App") { 134 | paragraph helpPage 135 | } 136 | 137 | section("Devices To Check") { 138 | input inputMotionDevices 139 | } 140 | 141 | section([title:"Available Options", mobileOnly:true]) { 142 | label title:"Assign a name for your app (optional)", required:false 143 | } 144 | } 145 | } 146 | 147 | def installed() { 148 | initialize() 149 | } 150 | 151 | def updated() { 152 | initialize() 153 | } 154 | 155 | def initialize() { 156 | log.trace "Devices Dashboard" 157 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-power-meters.src/dashboard-power-meters.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Power Meter Dashboard SmartApp 3 | * 4 | * Copyright 2017 J.Constantelos 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Overview 16 | * ---------------- 17 | * This SmartApp helps you see all your power readings (at that moment) in a single view for the devices you selected. 18 | * 19 | * Revision History 20 | * ---------------- 21 | * 09-09-2017 : Initial release 22 | * 23 | */ 24 | 25 | definition( 26 | name: "Dashboard - Power Meters", 27 | namespace: "jscgs350", 28 | author: "jscgs350", 29 | description: "SmartApp to report power meter readings in a single view.", 30 | category: "My Apps", 31 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 32 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 33 | 34 | 35 | preferences { 36 | page name:"pageStatus" 37 | page name:"pageConfigure" 38 | } 39 | 40 | //*************************** 41 | //Show Status page 42 | //*************************** 43 | def pageStatus() { 44 | def pageProperties = [ 45 | name: "pageStatus", 46 | title: "Power Meters Dashboard", 47 | nextPage: null, 48 | install: true, 49 | uninstall: true 50 | ] 51 | 52 | if (settings.powerdevices == null) { 53 | return pageConfigure() 54 | } 55 | 56 | def goodlist = "" 57 | def badlist = "" 58 | def errorlist = "" 59 | 60 | return dynamicPage(pageProperties) { 61 | def rightNow = new Date() 62 | settings.powerdevices.each() { 63 | def lastPower = it.currentValue('power') 64 | try { 65 | goodlist += "$lastPower Watts : $it.displayName\n" 66 | } catch (e) { 67 | log.trace "Caught error checking a device." 68 | log.trace e 69 | errorlist += "$it.displayName\n" 70 | } 71 | } 72 | 73 | if (goodlist) { 74 | section("Devices and Current Power Values") { 75 | paragraph goodlist.trim() 76 | } 77 | } 78 | 79 | if (badlist) { 80 | section("Devices NOT Reporting Power") { 81 | paragraph badlist.trim() 82 | } 83 | } 84 | 85 | if (errorlist) { 86 | section("Devices with Errors") { 87 | paragraph errorlist.trim() 88 | } 89 | } 90 | 91 | section("Menu") { 92 | href "pageStatus", title:"Refresh", description:"Tap to refresh the status of devices" 93 | href "pageConfigure", title:"Configure", description:"Tap to manage your list of devices" 94 | } 95 | } 96 | } 97 | 98 | //*************************** 99 | //Show Configure Page 100 | //*************************** 101 | def pageConfigure() { 102 | def helpPage = "Select devices that you wish to check when you open this SmartApp." 103 | 104 | def inputPowerDevices = [name:"powerdevices",type:"capability.powerMeter",title:"Which power meter devices?",multiple:true,required:true] 105 | 106 | def pageProperties = [name:"pageConfigure", 107 | title: "Dashboard Configurator", 108 | nextPage: "pageStatus", 109 | uninstall: true 110 | ] 111 | 112 | return dynamicPage(pageProperties) { 113 | section("About This App") { 114 | paragraph helpPage 115 | } 116 | 117 | section("Devices To Check") { 118 | input inputPowerDevices 119 | } 120 | 121 | section([title:"Available Options", mobileOnly:true]) { 122 | label title:"Assign a name for your app (optional)", required:false 123 | } 124 | } 125 | } 126 | 127 | def installed() { 128 | initialize() 129 | } 130 | 131 | def updated() { 132 | initialize() 133 | } 134 | 135 | def initialize() { 136 | log.trace "Launching Power Meter Dashboard" 137 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/dashboard-temperatures.src/dashboard-temperatures.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Temperatures Dashboard SmartApp for SmartThings 3 | * 4 | * Copyright 2015 J.Constantelos 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Contributors 16 | * ---------------- 17 | * Based off of code from Brandon Gordon (https://github.com/notoriousbdg) - BatteryMonitor SmartApp (https://github.com/notoriousbdg/SmartThings.BatteryMonitor) 18 | * 19 | * Overview 20 | * ---------------- 21 | * This SmartApp helps you see all your temperatures in a single view for the devices you selected capable of reporting temperature readings. 22 | * 23 | * Revision History 24 | * ---------------- 25 | * 2015-11-12 v1.0.0 Initial release 26 | * 27 | */ 28 | 29 | definition( 30 | name: "Dashboard - Temperatures", 31 | namespace: "jscgs350", 32 | author: "jscgs350", 33 | description: "SmartApp to report temperature readings in a single view.", 34 | category: "My Apps", 35 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 36 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 37 | 38 | 39 | preferences { 40 | page name:"pageStatus" 41 | page name:"pageConfigure" 42 | } 43 | 44 | //*************************** 45 | //Show Status page 46 | //*************************** 47 | def pageStatus() { 48 | def pageProperties = [ 49 | name: "pageStatus", 50 | title: "Temperatures Dashboard", 51 | nextPage: null, 52 | install: true, 53 | uninstall: true 54 | ] 55 | 56 | if (settings.tempdevices == null) { 57 | return pageConfigure() 58 | } 59 | 60 | def goodlist = "" 61 | def badlist = "" 62 | def errorlist = "" 63 | 64 | return dynamicPage(pageProperties) { 65 | def rightNow = new Date() 66 | settings.tempdevices.each() { 67 | def lastTemp = it.currentValue('temperature') 68 | try { 69 | if (lastTemp) { 70 | goodlist += "$lastTemp° : $it.displayName\n" 71 | } else { 72 | badlist += "$it.displayName\n" 73 | } 74 | 75 | } catch (e) { 76 | log.trace "Caught error checking a device." 77 | log.trace e 78 | errorlist += "$it.displayName\n" 79 | } 80 | } 81 | 82 | if (goodlist) { 83 | section("Devices and Current Temps") { 84 | paragraph goodlist.trim() 85 | } 86 | } 87 | 88 | if (badlist) { 89 | section("Devices NOT Reporting Temps") { 90 | paragraph badlist.trim() 91 | } 92 | } 93 | 94 | if (errorlist) { 95 | section("Devices with Errors") { 96 | paragraph errorlist.trim() 97 | } 98 | } 99 | 100 | section("Menu") { 101 | href "pageStatus", title:"Refresh", description:"Tap to refresh the status of devices" 102 | href "pageConfigure", title:"Configure", description:"Tap to manage your list of devices" 103 | } 104 | } 105 | } 106 | 107 | //*************************** 108 | //Show Configure Page 109 | //*************************** 110 | def pageConfigure() { 111 | def helpPage = "Select devices that you wish to check when you open this SmartApp." 112 | 113 | def inputTempDevices = [name:"tempdevices",type:"capability.temperatureMeasurement",title:"Which temperature devices?",multiple:true,required:true] 114 | 115 | def pageProperties = [name:"pageConfigure", 116 | title: "Dashboard Configurator", 117 | nextPage: "pageStatus", 118 | uninstall: true 119 | ] 120 | 121 | return dynamicPage(pageProperties) { 122 | section("About This App") { 123 | paragraph helpPage 124 | } 125 | 126 | section("Devices To Check") { 127 | input inputTempDevices 128 | } 129 | 130 | section([title:"Available Options", mobileOnly:true]) { 131 | label title:"Assign a name for your app (optional)", required:false 132 | } 133 | } 134 | } 135 | 136 | def installed() { 137 | initialize() 138 | } 139 | 140 | def updated() { 141 | initialize() 142 | } 143 | 144 | def initialize() { 145 | log.trace "Launching Temperatures Dashboard" 146 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/do-these-things-when-all-specified-people-come-back.src/do-these-things-when-all-specified-people-come-back.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Do these things when all specified people come back 3 | * 4 | */ 5 | 6 | // Automatically generated. Make future change here. 7 | definition( 8 | name: "Do these things when all specified people come back", 9 | namespace: "jscgs350", 10 | author: "jscgs350", 11 | description: "Do these things when all specified people come back", 12 | category: "My Apps", 13 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 14 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png" 15 | ) 16 | 17 | preferences { 18 | 19 | section("When all of these people come back") { 20 | input "people", "capability.presenceSensor", multiple: true, required: true 21 | } 22 | 23 | section("Change these thermostats") { 24 | input "thermostat", "capability.thermostat", title: "Which?", multiple:true 25 | } 26 | 27 | section("To this Fan setting") { 28 | input "fanSetpoint", "enum", title: "Which setting?", multiple:false, 29 | metadata:[values:["On","Auto","Circulate"]] 30 | } 31 | 32 | section("Turn OFF all of these switches") { 33 | input "switches", "capability.switch", multiple: true, required: false 34 | } 35 | 36 | /* section { 37 | input("recipients", "contact", title: "Send notifications to") { 38 | input(name: "sms", type: "phone", title: "Send A Text To", description: null, required: false) 39 | input(name: "pushNotification", type: "bool", title: "Send a push notification", description: null, defaultValue: true) 40 | } 41 | }*/ 42 | } 43 | 44 | def installed() { 45 | log.debug "Installed with settings: ${settings}" 46 | subscribe(people, "presence", presence) 47 | } 48 | 49 | def updated() { 50 | log.debug "Updated with settings: ${settings}" 51 | unsubscribe() 52 | subscribe(people, "presence", presence) 53 | } 54 | 55 | def presence(evt) { 56 | log.debug "evt.name: $evt.value" 57 | if (evt.value == "present") { 58 | log.debug "checking if everyone is back" 59 | if (everyoneIsBack()) { 60 | log.debug "people are now back so going to change things..." 61 | changeThermo () 62 | changeSwitch () 63 | send "$thermostat fan mode set to '${fanSetpoint}', and turning $switches OFF because people have come back" 64 | } 65 | } 66 | } 67 | 68 | def changeThermo() { 69 | log.debug "Setting $thermostat fan mode to $fanSetpoint because people are back" 70 | if (fanSetpoint == "On"){ 71 | thermostat.fanon() 72 | } 73 | if (fanSetpoint == "Auto"){ 74 | thermostat.fanauto() 75 | } 76 | if (fanSetpoint == "Circulate"){ 77 | thermostat.fancir() 78 | } 79 | } 80 | 81 | def changeSwitch() { 82 | log.debug "Turning $switches OFF" 83 | switches.off() 84 | } 85 | 86 | private everyoneIsBack() { 87 | def result = true 88 | for (person in people) { 89 | if (person.currentPresence != "present") { 90 | result = false 91 | break 92 | } 93 | } 94 | return result 95 | } 96 | 97 | def send(msg) { 98 | sendPush(msg) 99 | } 100 | /* 101 | def send(msg) { 102 | if (location.contactBookEnabled) { 103 | sendNotificationToContacts(msg, recipients) 104 | } 105 | else { 106 | if (sms) { 107 | sendSms(sms, msg) 108 | } 109 | if (pushNotification) { 110 | sendPush(msg) 111 | } 112 | } 113 | } 114 | */ -------------------------------------------------------------------------------- /smartapps/jscgs350/do-these-things-when-all-specified-people-leave.src/do-these-things-when-all-specified-people-leave.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Do these things when all specified people leave 3 | * 4 | */ 5 | 6 | // Automatically generated. Make future change here. 7 | definition( 8 | name: "Do these things when all specified people leave", 9 | namespace: "jscgs350", 10 | author: "jscgs350", 11 | description: "Do these things when all specified people leave", 12 | category: "My Apps", 13 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 14 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png" 15 | ) 16 | 17 | preferences { 18 | 19 | section("When all of these people leave home") { 20 | input "people", "capability.presenceSensor", multiple: true, required: true 21 | } 22 | 23 | section("Change these thermostats") { 24 | input "thermostat", "capability.thermostat", title: "Which?", multiple:true 25 | } 26 | 27 | section("To this Fan setting") { 28 | input "fanSetpoint", "enum", title: "Which setting?", multiple:false, 29 | metadata:[values:["On","Auto","Circulate"]] 30 | } 31 | 32 | section("Turn ON all of these switches") { 33 | input "switches", "capability.switch", multiple: true, required: false 34 | } 35 | 36 | /* section { 37 | input("recipients", "contact", title: "Send notifications to") { 38 | input(name: "sms", type: "phone", title: "Send A Text To", description: null, required: false) 39 | input(name: "pushNotification", type: "bool", title: "Send a push notification", description: null, defaultValue: true) 40 | } 41 | }*/ 42 | } 43 | 44 | def installed() { 45 | log.debug "Installed with settings: ${settings}" 46 | subscribe(people, "presence", presence) 47 | } 48 | 49 | def updated() { 50 | log.debug "Updated with settings: ${settings}" 51 | unsubscribe() 52 | subscribe(people, "presence", presence) 53 | } 54 | 55 | def presence(evt) { 56 | log.debug "evt.name: $evt.value" 57 | if (evt.value == "not present") { 58 | log.debug "checking if everyone is away" 59 | if (everyoneIsAway()) { 60 | log.debug "people are now away so going to change things..." 61 | changeThermo () 62 | changeSwitch () 63 | send "$thermostat fan mode set to '${fanSetpoint}', and turning $switches ON because people have left" 64 | } 65 | } 66 | } 67 | 68 | def changeThermo() { 69 | log.debug "Setting $thermostat fan mode to $fanSetpoint because people have left" 70 | if (fanSetpoint == "On"){ 71 | thermostat.fanon() 72 | } 73 | if (fanSetpoint == "Auto"){ 74 | thermostat.fanauto() 75 | } 76 | if (fanSetpoint == "Circulate"){ 77 | thermostat.fancir() 78 | } 79 | } 80 | 81 | def changeSwitch() { 82 | log.debug "Turning $switches ON" 83 | switches.on() 84 | } 85 | 86 | private everyoneIsAway() { 87 | def result = true 88 | for (person in people) { 89 | if (person.currentPresence == "present") { 90 | result = false 91 | break 92 | } 93 | } 94 | return result 95 | } 96 | 97 | def send(msg) { 98 | sendPush(msg) 99 | } 100 | /* 101 | def send(msg) { 102 | if (location.contactBookEnabled) { 103 | sendNotificationToContacts(msg, recipients) 104 | } 105 | else { 106 | if (sms) { 107 | sendSms(sms, msg) 108 | } 109 | if (pushNotification) { 110 | sendPush(msg) 111 | } 112 | } 113 | } 114 | */ -------------------------------------------------------------------------------- /smartapps/jscgs350/fortrezz-flow-meter-reset-manager.src/fortrezz-flow-meter-reset-manager.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * FortrezZ Flow Meter Reset Manager 3 | * 4 | * Copyright 2016 jscgs350 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Overview 16 | * ---------------- 17 | * This SmartApp resets the FortrezZ Flow Meter Interface at the 1st of every month at a time you specify. 18 | * NOTE: This has been tested and works with my DH for the flow meter, which can be found here: 19 | * https://raw.githubusercontent.com/constjs/jcdevhandlers/master/devicetypes/jscgs350/fortrezz-flow-meter-interface.src/fortrezz-flow-meter-interface.groovy 20 | * 21 | * Revision History 22 | * ---------------- 23 | * 09-12-2016 : Initial release 24 | * 25 | */ 26 | 27 | definition( 28 | name: "FortrezZ Flow Meter Reset Manager", 29 | namespace: "jscgs350", 30 | author: "jscgs350", 31 | description: "Resets meter at the 1st day of every month", 32 | category: "My Apps", 33 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 34 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 35 | 36 | preferences { 37 | section("Choose a FortrezZ water meter to reset monthly:") { 38 | input(name: "meter", type: "capability.energyMeter", title: "Water Meter", description: null, required: true, submitOnChange: true) 39 | } 40 | section("Reset Time") { 41 | input "time", "time", title: "At this time" 42 | } 43 | } 44 | 45 | def installed() { 46 | log.debug "Flow Meter Reset Manager SmartApp installed, check for 1st day of month and schedule another check for the next day" 47 | resetTheMeter() 48 | } 49 | 50 | def updated() { 51 | } 52 | 53 | def initialize() { 54 | unschedule() 55 | def scheduleTime = timeToday(time, location.timeZone) 56 | def timeNow = now() + (2*1000) // ST platform has resolution of 1 minutes, so be safe and check for 2 minutes) 57 | log.debug "Current time is ${(new Date(timeNow)).format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 58 | scheduleTime = scheduleTime + 1 // Next day schedule 59 | log.debug "Scheduling next meter reset check at ${scheduleTime.format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 60 | schedule(scheduleTime, resetTheMeter) 61 | } 62 | 63 | def resetTheMeter() { 64 | Calendar localCalendar = Calendar.getInstance(TimeZone.getDefault()); 65 | def currentDayOfMonth = localCalendar.get(Calendar.DAY_OF_MONTH); 66 | log.debug "Check for 1st day of month..." 67 | log.debug "...day of the month today is ${currentDayOfMonth}" 68 | if (currentDayOfMonth == 1) { 69 | log.debug "...Resetting flow meter because it's the first day of the month." 70 | meter.resetMeter() 71 | } else { 72 | log.debug "...Flow meter reset not scheduled for today because it's not the first day of the month." 73 | } 74 | log.debug "Check complete, now schedule the SmartApp to check the next day." 75 | initialize() 76 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/gear-watch.src/gear-watch.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Shanes Webservice Test 3 | * Copyright 2017 Shane 4 | */ 5 | 6 | //Apollo Spock, we have lift off... wait what? 7 | definition( 8 | name: "Gear Watch", 9 | namespace: "jscgs350", 10 | author: "Shane", 11 | description: "Gear Watch", 12 | category: "", 13 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 14 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", 15 | iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", 16 | oauth: true 17 | ) 18 | 19 | preferences(oauthPage: "pageOne") { 20 | page(name: "pageOne", title: "Gear Devices:", nextPage: "selectActions", install: false, uninstall: true) { 21 | section("Choose devices to control with watch") { 22 | input "switches", "capability.switch", title: "Which Switches?", required: false, multiple: true 23 | input "locks", "capability.lock", title: "Which Locks?", required: false, multiple: true 24 | //input "lights", "capability.light", title: "Which Lights?", required: false, multiple: true 25 | //input "levels", "capability.switchLevel", title: "Which level switches?", required: false, multiple: true 26 | //input "motion", "capability.motionSensor", title: "Which Motion Sensors?", required: false, multiple: true 27 | } 28 | } 29 | page(name: "selectActions") 30 | } 31 | 32 | def selectActions() { 33 | dynamicPage(name: "selectActions", title: "Gear Actions/Routines", install: true, uninstall: true) { 34 | // get the available actions 35 | def actions = location.helloHome?.getPhrases()*.label 36 | if (actions) { 37 | // sort them alphabetically 38 | actions.sort() 39 | section("Choose routines to execute with watch:") { 40 | log.trace actions 41 | input "action", "enum", title: "Select an routines to execute", options: actions, required: false, multiple: true 42 | } 43 | } 44 | } 45 | } 46 | 47 | 48 | def installed() {} 49 | def updated() {} 50 | 51 | mappings { 52 | 53 | //Switches 54 | path("/switches") { 55 | action: [ 56 | GET: "listSwitches" 57 | ] 58 | } 59 | path("/switches/:id") { 60 | action: [ 61 | GET: "showSwitch" 62 | ] 63 | } 64 | path("/switches/:id/:command") { 65 | action: [ 66 | GET: "updateSwitch" 67 | ] 68 | } 69 | path("/switches/:id/:command/:value") { 70 | action: [ 71 | GET: "setSwitchLevel" 72 | ] 73 | } 74 | 75 | //Locks 76 | path("/locks") { 77 | action: [ 78 | GET: "listLocks" 79 | ] 80 | } 81 | path("/locks/:id") { 82 | action: [ 83 | GET: "showLock" 84 | ] 85 | } 86 | path("/locks/:id/:command") { 87 | action: [ 88 | GET: "updateLock" 89 | ] 90 | } 91 | 92 | //Routines 93 | path("/routines/:name") { 94 | action: [ 95 | GET: "executeRoutine" 96 | ] 97 | } 98 | path("/routines") { 99 | action: [ 100 | GET: "listRoutines" 101 | ] 102 | } 103 | 104 | path("/version") { 105 | action: [ 106 | GET: "smartAppVersion" 107 | ] 108 | } 109 | } 110 | 111 | //version 112 | def smartAppVersion(){ 113 | [version: "1.0.1"] 114 | } 115 | 116 | //switches 117 | def listSwitches() { 118 | switches.collect{device(it,"switch")} 119 | } 120 | 121 | def showSwitch() { 122 | show(switches, "switch") 123 | } 124 | 125 | def updateSwitch() { 126 | update(switches) 127 | } 128 | 129 | def setSwitchLevel(){ 130 | setlevel(switches) 131 | } 132 | 133 | //Locks 134 | def listLocks() { 135 | locks.collect{device(it,"lock")} 136 | } 137 | def showLock() { 138 | show(locks, "lock") 139 | } 140 | def updateLock() { 141 | update(locks) 142 | } 143 | 144 | //Routines 145 | def listRoutines() { 146 | def actions = location.helloHome?.getPhrases()*.label 147 | return actions 148 | } 149 | def executeRoutine(){ 150 | def name = params.name 151 | location.helloHome?.execute(name) 152 | def resp = [] 153 | resp << [ok: "executed"] 154 | return resp 155 | } 156 | 157 | //in the future... 158 | def deviceHandler(evt) {} 159 | 160 | private void setlevel(devices){ 161 | def command = params.command 162 | 163 | if(command) 164 | { 165 | def device = devices.find { it.id == params.id } 166 | if(!device) { 167 | httpError(404, "Device not found") 168 | } else { 169 | if(command == "level") 170 | { 171 | device.setLevel(params.value as int) 172 | } 173 | } 174 | } 175 | } 176 | 177 | private update(devices) { 178 | def command = params.command 179 | 180 | if (command){ 181 | def device = devices.find { it.id == params.id } 182 | if (!device) { 183 | httpError(404, "Device not found") 184 | } else { 185 | if(command == "toggle"){ 186 | if(device.currentValue('switch') == "on"){ 187 | device.off(); 188 | [status: "off"]} 189 | else{ 190 | device.on(); 191 | [status: "on"]} 192 | }else{ 193 | device."$command"() 194 | if(command == "on" || command == "off") 195 | { 196 | [status: command] 197 | } 198 | else if(command == "lock" || command == "unlock") 199 | { 200 | [status: command + "ed"] 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | private show(devices, type) { 208 | def device = devices.find { it.id == params.id } 209 | if (!device) { 210 | httpError(404, "Device not found") 211 | } else { 212 | def attributeName = type == "motionSensor" ? "motion" : type 213 | def s = device.currentState(attributeName) 214 | [id: device.id, label: device.displayName, level: it.currentValue("level"), value: s?.value, unitTime: s?.date?.time, type: type] 215 | } 216 | } 217 | 218 | private device(it, type) { 219 | def attributeName = type == "motionSensor" ? "motion" : type 220 | def s = it.currentState(attributeName) 221 | it ? [id: it.id, label: it.label, level: it.currentValue("level"), value: s?.value, type: type] : null 222 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/hello-home.src/hello-home.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "Hello Home", 3 | namespace: "jscgs350", 4 | author: "SmartThings", 5 | description: "Hi", 6 | category: "", 7 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 8 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", 9 | iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") -------------------------------------------------------------------------------- /smartapps/jscgs350/humidity-monitor-child-app.src/humidity-monitor-child-app.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Humidity Monitor Child App 3 | * 4 | * Based on Its too cold code by SmartThings and Brian Critchlow 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 12 | * for the specific language governing permissions and limitations under the License. 13 | * 14 | */ 15 | definition( 16 | name: "Humidity Monitor Child App", 17 | namespace: "jscgs350", 18 | author: "jscgs350", 19 | description: "Turn on a switch when humidity rises above the threshold, and off when it falls below the threshold.", 20 | category: "My Apps", 21 | parent: "jscgs350:Humidity Monitor Parent App", 22 | iconUrl: "https://graph.api.smartthings.com/api/devices/icons/st.Weather.weather9-icn", 23 | iconX2Url: "https://graph.api.smartthings.com/api/devices/icons/st.Weather.weather9-icn?displaySize=2x" 24 | ) 25 | 26 | preferences { 27 | section("Monitor the humidity of:") { 28 | input "humiditySensor1", "capability.relativeHumidityMeasurement" 29 | } 30 | section("Humidity threshold:") { 31 | input "humidity1", "number", title: "Percentage ?" 32 | } 33 | section("Control this switch:") { 34 | input "switch1", "capability.switch", required: false 35 | } 36 | } 37 | 38 | def installed() { 39 | subscribe(humiditySensor1, "humidity", humidityHandler) 40 | } 41 | 42 | def updated() { 43 | unsubscribe() 44 | subscribe(humiditySensor1, "humidity", humidityHandler) 45 | } 46 | 47 | def humidityHandler(evt) { 48 | def currentHumidity = Double.parseDouble(evt.value.replace("%", "")) 49 | def tooHumid = humidity1 50 | def mySwitch = settings.switch1 51 | if (currentHumidity > tooHumid) { 52 | switch1?.on() 53 | } else { 54 | switch1?.off() 55 | } 56 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/humidity-monitor-parent-app.src/humidity-monitor-parent-app.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "Humidity Monitor Parent App", 3 | singleInstance: true, 4 | namespace: "jscgs350", 5 | author: "jscgs350", 6 | description: "Humidity Monitor Parent App", 7 | category: "My Apps", 8 | iconUrl: "https://graph.api.smartthings.com/api/devices/icons/st.Weather.weather9-icn", 9 | iconX2Url: "https://graph.api.smartthings.com/api/devices/icons/st.Weather.weather9-icn?displaySize=2x") 10 | 11 | preferences { 12 | page(name: "mainPage", title: "Humidity Monitor Parent App", install: true, uninstall: true,submitOnChange: true) { 13 | section { 14 | app(name: "childRules", appName: "Humidity Monitor Child App", namespace: "jscgs350", title: "Create Humidity Monitor...", multiple: true) 15 | } 16 | } 17 | } 18 | 19 | def installed() { 20 | initialize() 21 | } 22 | 23 | def updated() { 24 | unsubscribe() 25 | initialize() 26 | } 27 | 28 | def initialize() { 29 | childApps.each {child -> 30 | log.info "Installed Monitors: ${child.label}" 31 | } 32 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/master-and-child-switches.src/master-and-child-switches.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Master and Child Switches 3 | */ 4 | 5 | definition( 6 | name: "Master and Child Switches", 7 | namespace: "jscgs350", 8 | author: "SmartThings", 9 | description: "Pick a master switch to control other switches. Kind of like a traditional switched outlet. Can also be used with a virtual master switch.", 10 | category: "My Apps", 11 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 12 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png") 13 | 14 | 15 | preferences { 16 | section("When this master switch is turned on or off...") { 17 | input name: "master", title: "Which Switch?", type: "capability.switch", required: true 18 | } 19 | section("Turn on or off all of these switches as well") { 20 | input "switches", "capability.switch", multiple: true, required: false 21 | } 22 | section("Turn off but not on all of these switches") { 23 | input "offSwitches", "capability.switch", multiple: true, required: false 24 | } 25 | section("And turn on but not off all of these switches") { 26 | input "onSwitches", "capability.switch", multiple: true, required: false 27 | } 28 | } 29 | 30 | def installed() { 31 | subscribeToEvents() 32 | } 33 | 34 | def updated() { 35 | unsubscribe() 36 | subscribeToEvents() 37 | } 38 | 39 | def subscribeToEvents() { 40 | subscribe(master, "switch.on", onHandler, [filterEvents: false]) 41 | subscribe(master, "switch.off", offHandler, [filterEvents: false]) 42 | } 43 | 44 | def onHandler(evt) { 45 | log.debug evt.value 46 | log.debug onSwitches() 47 | onSwitches()?.on() 48 | } 49 | 50 | def offHandler(evt) { 51 | log.debug evt.value 52 | log.debug offSwitches() 53 | offSwitches()?.off() 54 | } 55 | 56 | private onSwitches() { 57 | if(switches && onSwitches) { switches + onSwitches } 58 | else if(switches) { switches } 59 | else { onSwitches } 60 | } 61 | 62 | private offSwitches() { 63 | if(switches && offSwitches) { switches + offSwitches } 64 | else if(switches) { switches } 65 | else { offSwitches } 66 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/my-button-controller-parent.src/my-button-controller-parent.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "My Button Controller Parent", 3 | singleInstance: true, 4 | namespace: "jscgs350", 5 | author: "jscgs350", 6 | description: "My Button Controller Parent", 7 | category: "My Apps", 8 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 9 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 10 | 11 | preferences { 12 | page(name: "mainPage", title: "My Button Controller", install: true, uninstall: true,submitOnChange: true) { 13 | section { 14 | app(name: "childRules", appName: "My Button Controller Child", namespace: "jscgs350", title: "Create a button controller...", multiple: true) 15 | } 16 | } 17 | } 18 | 19 | def installed() { 20 | initialize() 21 | } 22 | 23 | def updated() { 24 | unsubscribe() 25 | initialize() 26 | } 27 | 28 | def initialize() { 29 | childApps.each {child -> 30 | log.info "Installed My Button Controller Child: ${child.label}" 31 | } 32 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/my-button-controller.src/my-button-controller.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "My Button Controller", 3 | singleInstance: true, 4 | namespace: "jscgs350", 5 | author: "SmartThings", 6 | description: "My Button Controller", 7 | category: "My Apps", 8 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 9 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 10 | 11 | preferences { 12 | page(name: "mainPage", title: "My Button Controller", install: true, uninstall: true,submitOnChange: true) { 13 | section { 14 | app(name: "childRules", appName: "My Button Controller Child", namespace: "jscgs350", title: "Create a button controller...", multiple: true) 15 | } 16 | } 17 | } 18 | 19 | def installed() { 20 | initialize() 21 | } 22 | 23 | def updated() { 24 | unsubscribe() 25 | initialize() 26 | } 27 | 28 | def initialize() { 29 | childApps.each {child -> 30 | log.info "Installed My Button Controller Child: ${child.label}" 31 | } 32 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/notify-me-when-a-switch-turns-off.src/notify-me-when-a-switch-turns-off.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify Me When A Switch Turns Off 3 | * 4 | * Author: SmartThings 5 | */ 6 | definition( 7 | name: "Notify Me When A Switch Turns Off", 8 | namespace: "jscgs350", 9 | author: "SmartThings", 10 | description: "Get a text message sent to your phone when a switch is turned off.", 11 | category: "My Apps", 12 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 13 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png" 14 | ) 15 | 16 | preferences { 17 | def inputSwitchDevices = [name:"switchdevices",type:"capability.switch",title:"Which switches to monitor?",multiple:true,required:true] 18 | section("Switches to monitor:") { 19 | input "switches1", "capability.switch", title: "Switches?", multiple: true 20 | } 21 | section("Phone number to text?"){ 22 | input "phone1", "phone", title: "Phone number?" 23 | } 24 | } 25 | 26 | def installed() 27 | { 28 | subscribe(switches1, "switch.off", switchOffHandler) 29 | } 30 | 31 | def updated() 32 | { 33 | unsubscribe() 34 | subscribe(switches1, "switch.off", switchOffHandler) 35 | } 36 | 37 | def switchOffHandler(evt) { 38 | log.trace "$evt.value: $evt, $settings" 39 | settings.switches1.each() { 40 | def lastSwitch = it.currentValue('switch') 41 | try { 42 | if (lastSwitch) { 43 | if (lastSwitch == 'off') { 44 | sendSms(phone1, "Your $it.displayName was turned OFF!")} 45 | } 46 | } catch (e) { 47 | log.trace "Caught error checking a device." 48 | log.trace e 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/notify-me-when-a-switch-turns-on.src/notify-me-when-a-switch-turns-on.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Notify Me When A Switch Turns On 3 | * 4 | * Author: SmartThings 5 | */ 6 | definition( 7 | name: "Notify Me When A Switch Turns On", 8 | namespace: "jscgs350", 9 | author: "SmartThings", 10 | description: "Get a text message sent to your phone when a switch is turned off.", 11 | category: "My Apps", 12 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 13 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png" 14 | ) 15 | 16 | preferences { 17 | def inputSwitchDevices = [name:"switchdevices",type:"capability.switch",title:"Which switches to monitor?",multiple:true,required:true] 18 | section("Switches to monitor:") { 19 | input "switches1", "capability.switch", title: "Switches?", multiple: true 20 | } 21 | section("Phone number to text?"){ 22 | input "phone1", "phone", title: "Phone number?" 23 | } 24 | } 25 | 26 | def installed() 27 | { 28 | subscribe(switches1, "switch.on", switchOnHandler) 29 | } 30 | 31 | def updated() 32 | { 33 | unsubscribe() 34 | subscribe(switches1, "switch.on", switchOnHandler) 35 | } 36 | 37 | def switchOnHandler(evt) { 38 | log.trace "$evt.value: $evt, $settings" 39 | settings.switches1.each() { 40 | def lastSwitch = it.currentValue('switch') 41 | try { 42 | if (lastSwitch) { 43 | if (lastSwitch == 'off') { 44 | sendSms(phone1, "Your $it.displayName was turned ON!")} 45 | } 46 | } catch (e) { 47 | log.trace "Caught error checking a device." 48 | log.trace e 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/pollster.src/pollster.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Pollster - The SmartThings Polling Daemon. 3 | * 4 | * Pollster works behind the scenes and periodically calls 'poll' or 5 | * 'refresh' commands for selected devices. Devices can be arranged into 6 | * three polling groups with configurable polling intervals down to 1 minute. 7 | * 8 | * Please visit [https://github.com/statusbits/smartthings] for more 9 | * information. 10 | * 11 | * -------------------------------------------------------------------------- 12 | * Copyright © 2014 Statusbits.com 13 | * 14 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 15 | * not use this file except in compliance with the License. You may obtain a 16 | * copy of the License at: 17 | * 18 | * http://www.apache.org/licenses/LICENSE-2.0 19 | * 20 | * Unless required by applicable law or agreed to in writing, software 21 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 22 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 23 | * License for the specific language governing permissions and limitations 24 | * under the License. 25 | * -------------------------------------------------------------------------- 26 | * 27 | * Version 1.5.0 (02/08/2016) 28 | */ 29 | 30 | definition( 31 | name: "Pollster", 32 | namespace: "jscgs350", 33 | author: "geko@statusbits.com", 34 | description: "Poll or refresh device status periodically.", 35 | category: "My Apps", 36 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 37 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 38 | 39 | preferences { 40 | section("About") { 41 | def hrefAbout = [ 42 | url: "http://statusbits.github.io/smartthings/", 43 | style: "embedded", 44 | title: "Tap for more information...", 45 | description:"http://statusbits.github.io/smartthings/", 46 | required: false 47 | ] 48 | 49 | paragraph about() 50 | //href hrefAbout 51 | } 52 | 53 | (1..4).each() { n -> 54 | section("Scheduled Polling Group ${n}") { 55 | input "group_${n}", "capability.polling", title:"Select devices to be polled", multiple:true, required:false 56 | input "refresh_${n}", "capability.refresh", title:"Select devices to be refreshed", multiple:true, required:false 57 | input "interval_${n}", "number", title:"Set polling interval (in minutes)", defaultValue:5 58 | } 59 | } 60 | 61 | section("REST API Polling Group") { 62 | paragraph "Poll these devices via REST API endpoint." 63 | input "enableRestApi", "bool", title:"Enable REST endpoint", defaultValue:false 64 | input "restPoll", "capability.polling", title:"Select devices to be polled", multiple:true, required:false 65 | input "restRefresh", "capability.refresh", title:"Select devices to be refreshed", multiple:true, required:false 66 | } 67 | } 68 | 69 | mappings { 70 | path("/poll") { 71 | action: [ GET: "apiPoll" ] 72 | } 73 | } 74 | 75 | def installed() { 76 | initialize() 77 | } 78 | 79 | def updated() { 80 | initialize() 81 | } 82 | 83 | def onAppTouch(event) { 84 | LOG("onAppTouch(${event.value})") 85 | 86 | watchdog() 87 | pollingTask1() 88 | pollingTask2() 89 | pollingTask3() 90 | pollingTask4() 91 | 92 | if (settings.restPoll) { 93 | settings.restPoll*.poll() 94 | } 95 | 96 | if (settings.restRefresh) { 97 | settings.restRefresh*.refresh() 98 | } 99 | } 100 | 101 | def onLocation(event) { 102 | LOG("onLocation(${event.value})") 103 | 104 | watchdog() 105 | } 106 | 107 | def pollingTask1() { 108 | LOG("pollingTask1()") 109 | 110 | state.trun1 = now() 111 | 112 | if (settings.group_1) { 113 | settings.group_1*.poll() 114 | } 115 | 116 | if (settings.refresh_1) { 117 | settings.refresh_1*.refresh() 118 | } 119 | } 120 | 121 | def pollingTask2() { 122 | LOG("pollingTask2()") 123 | 124 | state.trun2 = now() 125 | 126 | if (settings.group_2) { 127 | settings.group_2*.poll() 128 | } 129 | 130 | if (settings.refresh_2) { 131 | settings.refresh_2*.refresh() 132 | } 133 | } 134 | 135 | def pollingTask3() { 136 | LOG("pollingTask3()") 137 | 138 | state.trun3 = now() 139 | 140 | if (settings.group_3) { 141 | settings.group_3*.poll() 142 | } 143 | 144 | if (settings.refresh_3) { 145 | settings.refresh_3*.refresh() 146 | } 147 | } 148 | 149 | def pollingTask4() { 150 | LOG("pollingTask4()") 151 | 152 | state.trun4 = now() 153 | 154 | if (settings.group_4) { 155 | settings.group_4*.poll() 156 | } 157 | 158 | if (settings.refresh_4) { 159 | settings.refresh_4*.refresh() 160 | } 161 | } 162 | 163 | // Handle '.../poll' REST endpoint 164 | def apiPoll() { 165 | LOG("apiPoll()") 166 | 167 | if (settings.restPoll) { 168 | settings.restPoll*.poll() 169 | } 170 | 171 | if (settings.restRefresh) { 172 | settings.restRefresh*.refresh() 173 | } 174 | 175 | watchdog() 176 | 177 | return [status:"ok"] 178 | } 179 | 180 | def watchdog() { 181 | LOG("watchdog()") 182 | 183 | (1..4).each() { n -> 184 | def interval = settings."interval_${n}".toInteger() 185 | def trun = state."trun${n}" 186 | 187 | if (interval && trun && ((now() - trun) > ((interval + 10) * 60000))) { 188 | log.warn "Polling task #${n} stalled. Restarting..." 189 | restart() 190 | return 191 | } 192 | } 193 | } 194 | 195 | private def initialize() { 196 | log.info "Pollster. Version ${version()}. ${copyright()}" 197 | LOG("initialize() with settings: ${settings}") 198 | 199 | if (settings.enableRestApi && state.accessToken == null) { 200 | initAccessToken() 201 | } 202 | 203 | state.trun1 = 0 204 | state.trun2 = 0 205 | state.trun3 = 0 206 | state.trun4 = 0 207 | 208 | Random rand = new Random(now()) 209 | def numTasks = 0 210 | (1..4).each() { n -> 211 | def minutes = settings."interval_${n}".toInteger() 212 | def seconds = rand.nextInt(60) 213 | def size1 = settings["group_${n}"]?.size() ?: 0 214 | def size2 = settings["refresh_${n}"]?.size() ?: 0 215 | 216 | safeUnschedule("pollingTask${n}") 217 | 218 | if (minutes > 0 && (size1 + size2) > 0) { 219 | LOG("Scheduling polling task ${n} to run every ${minutes} minutes.") 220 | def sched = "${seconds} 0/${minutes} * * * ?" 221 | schedule(sched, "pollingTask${n}") 222 | numTasks++ 223 | } 224 | } 225 | 226 | if (numTasks) { 227 | subscribe(app, onAppTouch) 228 | subscribe(location, onLocation) 229 | subscribe(location, "position", onLocation) 230 | subscribe(location, "sunrise", onLocation) 231 | subscribe(location, "sunset", onLocation) 232 | } 233 | 234 | LOG("state: ${state}") 235 | } 236 | 237 | private def initAccessToken() { 238 | LOG("initAccessToken()") 239 | 240 | try { 241 | def token = createAccessToken() 242 | log.info "Created access token: ${token})" 243 | state.url = "https://graph.api.smartthings.com/api/smartapps/installations/${app.id}/poll?access_token=${token}" 244 | } catch (e) { 245 | log.error "Cannot create access token. ${e}" 246 | state.url = null 247 | return false 248 | } 249 | 250 | return true 251 | } 252 | 253 | private def safeUnschedule() { 254 | try { 255 | unschedule() 256 | } 257 | 258 | catch(e) { 259 | log.error ${e} 260 | } 261 | } 262 | 263 | private def safeUnschedule(handler) { 264 | try { 265 | unschedule(handler) 266 | } 267 | 268 | catch(e) { 269 | log.error ${e} 270 | } 271 | } 272 | 273 | private def restart() { 274 | //sendNotification("Pollster stalled. Restarting...") 275 | updated() 276 | } 277 | 278 | private def about() { 279 | def text = 280 | "Version ${version()}\n${copyright()}\n\n" + 281 | "You can contribute to the development of this app by making a " + 282 | "PayPal donation to geko@statusbits.com. We appreciate your support." 283 | } 284 | 285 | private def version() { 286 | return "Version 1.5.0" 287 | } 288 | 289 | private def copyright() { 290 | return "Copyright © 2014 Statusbits.com" 291 | } 292 | 293 | private def LOG(message) { 294 | log.trace message 295 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/power-meter-reset-manager.src/power-meter-reset-manager.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Power Meter Reset Manager for devices using my DTH. This was previously just for the Aeon HEM v1. 3 | * 4 | * Copyright 2017 jscgs350 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Revision History 16 | * ---------------- 17 | * 11-22-2016 : Initial release 18 | * 02-16-2017 : Fixed scheduling issue and improved handling when the app is initially installed and when it's updated. 19 | * 10-03-2018 : Added reset ability for devices using stock DTH's (reset command) 20 | * 12-18-2018 : Removed resetMeter in favor of just using reset since ST's stock DTH's use reset, and not resetMeter. My respective custom DTH's have also been updated. Also fixed scheduling issues. 21 | * 22 | */ 23 | 24 | definition( 25 | name: "Power Meter Reset Manager", 26 | namespace: "jscgs350", 27 | author: "jscgs350", 28 | description: "Resets power metering device using my DTH's on a specified day/time of every month", 29 | category: "My Apps", 30 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 31 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 32 | 33 | preferences { 34 | section("Choose an power metering device to reset:") { 35 | input "meters", "capability.energyMeter", title:"Select devices to be reset", multiple:true, required:true 36 | } 37 | section("Reset Time of Day") { 38 | input "time", "time", title: "At this time of day" 39 | } 40 | section("Reset Day of Month") { 41 | input "day", "number", title: "On this day of the month" 42 | } 43 | } 44 | 45 | def installed() { 46 | log.debug "Power Meter Reset Manager SmartApp installed, now preparing to schedule the first reset." 47 | resetTheMeter() 48 | } 49 | 50 | def updated() { 51 | log.debug "Power Meter Reset Manager SmartApp updated, and/or initialized due to a first time install, so update the user defined schedule." 52 | /* unschedule() 53 | def scheduleTime = timeToday(time, location.timeZone) 54 | def timeNow = now() 55 | log.debug "Current time is ${(new Date(timeNow)).format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 56 | log.debug "Scheduling meter reset check at ${scheduleTime.format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 57 | schedule(scheduleTime, resetTheMeter)*/ 58 | resetTheMeter() 59 | } 60 | 61 | def initialize() { 62 | unschedule() 63 | def scheduleTime = timeToday(time, location.timeZone) 64 | def timeNow = now() 65 | log.debug "Current time is ${(new Date(timeNow)).format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 66 | scheduleTime = scheduleTime + 1 // Next day schedule 67 | log.debug "Scheduling next meter reset check at ${scheduleTime.format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 68 | schedule(scheduleTime, resetTheMeter) 69 | } 70 | 71 | def resetTheMeter() { 72 | Calendar localCalendar = Calendar.getInstance(TimeZone.getDefault()); 73 | def currentDayOfMonth = localCalendar.get(Calendar.DAY_OF_MONTH); 74 | log.debug "Power Meter reset schedule triggered..." 75 | log.debug "...checking for the day of month requested by the user" 76 | log.debug "...the day of the month right now is ${currentDayOfMonth}" 77 | log.debug "...the day the user requested a reset is ${day}" 78 | if (currentDayOfMonth == day) { 79 | log.debug "...resetting the meter because it's when the user requested it." 80 | // settings.meters*.resetMeter() 81 | settings.meters*.reset() 82 | } else { 83 | log.debug "...meter reset not scheduled for today because it's not when the user requested it." 84 | } 85 | log.debug "Process completed, now schedule the reset to check on the next day." 86 | initialize() 87 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/smartweather-station-controller.src/smartweather-station-controller.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Weather Station Controller 3 | * 4 | * Copyright 2014 SmartThings 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | */ 16 | 17 | definition( 18 | name: "SmartWeather Station Controller", 19 | namespace: "jscgs350", 20 | author: "SmartThings", 21 | description: "Updates SmartWeather Station Tile devices every hour.", 22 | category: "My Apps", 23 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 24 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png" 25 | ) 26 | 27 | preferences { 28 | section { 29 | input "weatherDevices", "capability.thermostat" 30 | // input "weatherDevices", "device.smartweatherStationTile" 31 | } 32 | } 33 | 34 | def installed() { 35 | log.debug "Installed with settings: ${settings}" 36 | 37 | initialize() 38 | } 39 | 40 | def updated() { 41 | log.debug "Updated with settings: ${settings}" 42 | 43 | unschedule() 44 | initialize() 45 | } 46 | 47 | def initialize() { 48 | scheduledEvent() 49 | } 50 | 51 | def scheduledEvent() { 52 | // log.info "SmartWeather Station Controller / scheduledEvent terminated due to deprecation" // device handles this itself now -- Bob 53 | 54 | log.trace "scheduledEvent()" 55 | 56 | def delayTimeSecs = 60 * 60 // reschedule every 60 minutes 57 | def runAgainWindowMS = 58 * 60 * 1000 // can run at most every 58 minutes 58 | def timeSinceLastRunMS = state.lastRunTime ? now() - state.lastRunTime : null //how long since it last ran? 59 | 60 | if(!timeSinceLastRunMS || timeSinceLastRunMS > runAgainWindowMS){ 61 | runIn(delayTimeSecs, scheduledEvent, [overwrite: false]) 62 | state.lastRunTime = now() 63 | weatherDevices.refresh() 64 | } else { 65 | log.trace "Trying to run smartweather-station-controller too soon. Has only been ${timeSinceLastRunMS} ms but needs to be at least ${runAgainWindowMS} ms" 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/thermostat-manager-child.src/thermostat-manager-child.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Thermostat Manager 3 | * Allows you to set single/multiple thermostats to different temp during different days of the week unlimited number of times (one app instance for each change) 4 | * 5 | * Taken from : Samer Theodossy. modified by jscgs350 to add fan mode changes. 6 | * Update - 2014-11-20 7 | */ 8 | 9 | // Automatically generated. Make future change here. 10 | definition( 11 | name: "Thermostat Manager Child", 12 | namespace: "jscgs350", 13 | author: "jscgs350", 14 | description: "Program thermostat(s) from within SmartThings", 15 | category: "My Apps", 16 | parent: "jscgs350:Thermostat Manager", 17 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 18 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 19 | 20 | preferences { 21 | section("Set these thermostats") { 22 | input "thermostat", "capability.thermostat", title: "Which?", multiple:true 23 | 24 | } 25 | 26 | section("To these temperatures") { 27 | input "heatingSetpoint", "decimal", title: "When Heating" 28 | input "coolingSetpoint", "decimal", title: "When Cooling" 29 | } 30 | 31 | section("To this Fan setting") { 32 | input "fanSetpoint", "enum", title: "Which setting?", multiple:false, 33 | metadata:[values:["On","Auto","Circulate"]] 34 | } 35 | 36 | section("Configuration") { 37 | input "dayOfWeek", "enum", 38 | title: "Which day of the week?", 39 | multiple: false, 40 | metadata: [ 41 | values: [ 42 | 'All Week', 43 | 'Monday to Friday', 44 | 'Saturday & Sunday', 45 | 'Monday', 46 | 'Tuesday', 47 | 'Wednesday', 48 | 'Thursday', 49 | 'Friday', 50 | 'Saturday', 51 | 'Sunday' 52 | ] 53 | ] 54 | input "time", "time", title: "At this time" 55 | } 56 | /* 57 | section { 58 | input("recipients", "contact", title: "Send notifications to") { 59 | input(name: "sms", type: "phone", title: "Send A Text To", description: null, required: false) 60 | input(name: "pushNotification", type: "bool", title: "Send a push notification", description: null, required: false) 61 | } 62 | } 63 | */ 64 | } 65 | 66 | def installed() { 67 | // subscribe to these events 68 | initialize() 69 | } 70 | 71 | def updated() { 72 | // we have had an update 73 | // remove everything and reinstall 74 | initialize() 75 | } 76 | 77 | def initialize() { 78 | unschedule() 79 | def scheduleTime = timeToday(time, location.timeZone) 80 | def timeNow = now() + (2*1000) // ST platform has resolution of 1 minutes, so be safe and check for 2 minutes) 81 | log.debug "Current time is ${(new Date(timeNow)).format("EEE MMM dd yyyy HH:mm z", location.timeZone)}, scheduled check time is ${scheduleTime.format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 82 | if (scheduleTime.time < timeNow) { // If we have passed current time we're scheduled for next day 83 | log.debug "Current scheduling check time $scheduleTime has passed, scheduling check for tomorrow" 84 | scheduleTime = scheduleTime + 1 // Next day schedule 85 | } 86 | log.debug "Temp change schedule set for $dayOfWeek at time ${scheduleTime.format("HH:mm z", location.timeZone)} to $heatingSetpoint in heat and $coolingSetpoint in cool and the fan to $fanSetpoint" 87 | log.debug "Scheduling next temp check at ${scheduleTime.format("EEE MMM dd yyyy HH:mm z", location.timeZone)}" 88 | schedule(scheduleTime, setTheTemp) 89 | } 90 | 91 | def setTheTemp() { 92 | def doChange = false 93 | Calendar localCalendar = Calendar.getInstance(TimeZone.getDefault()); 94 | int currentDayOfWeek = localCalendar.get(Calendar.DAY_OF_WEEK); 95 | 96 | // some debugging in order to make sure things are working correclty 97 | log.debug "Calendar DOW: " + currentDayOfWeek 98 | log.debug "SET DOW: " + dayOfWeek 99 | 100 | // Check the condition under which we want this to run now 101 | // This set allows the most flexibility. 102 | if(dayOfWeek == 'All Week'){ 103 | doChange = true 104 | } 105 | else if((dayOfWeek == 'Monday' || dayOfWeek == 'Monday to Friday') && currentDayOfWeek == Calendar.instance.MONDAY){ 106 | doChange = true 107 | } 108 | 109 | else if((dayOfWeek == 'Tuesday' || dayOfWeek == 'Monday to Friday') && currentDayOfWeek == Calendar.instance.TUESDAY){ 110 | doChange = true 111 | } 112 | 113 | else if((dayOfWeek == 'Wednesday' || dayOfWeek == 'Monday to Friday') && currentDayOfWeek == Calendar.instance.WEDNESDAY){ 114 | doChange = true 115 | } 116 | 117 | else if((dayOfWeek == 'Thursday' || dayOfWeek == 'Monday to Friday') && currentDayOfWeek == Calendar.instance.THURSDAY){ 118 | doChange = true 119 | } 120 | 121 | else if((dayOfWeek == 'Friday' || dayOfWeek == 'Monday to Friday') && currentDayOfWeek == Calendar.instance.FRIDAY){ 122 | doChange = true 123 | } 124 | 125 | else if((dayOfWeek == 'Saturday' || dayOfWeek == 'Saturday & Sunday') && currentDayOfWeek == Calendar.instance.SATURDAY){ 126 | doChange = true 127 | } 128 | 129 | else if((dayOfWeek == 'Sunday' || dayOfWeek == 'Saturday & Sunday') && currentDayOfWeek == Calendar.instance.SUNDAY){ 130 | doChange = true 131 | } 132 | 133 | // If we have hit the condition to schedule this then lets do it 134 | if(doChange == true){ 135 | log.debug "Setting temperature in $thermostat to $heatingSetpoint in heat and $coolingSetpoint in cool and the fan to $fanSetpoint" 136 | thermostat.setHeatingSetpoint(heatingSetpoint) 137 | thermostat.setCoolingSetpoint(coolingSetpoint) 138 | if (fanSetpoint == "On"){ 139 | thermostat.fanOn() 140 | } 141 | if (fanSetpoint == "Auto"){ 142 | thermostat.fanAuto() 143 | } 144 | if (fanSetpoint == "Circulate"){ 145 | thermostat.fanCirculate() 146 | } 147 | sendMessage "$thermostat heat set to '${heatingSetpoint}' and cool to '${coolingSetpoint}' and the fan to '${fanSetpoint}'" 148 | } 149 | else { 150 | log.debug "Temp change not scheduled for today." 151 | } 152 | 153 | log.debug "Scheduling next check" 154 | 155 | initialize() // Setup the next check schedule 156 | } 157 | 158 | def sendMessage(msg) { 159 | /* if (location.contactBookEnabled) { 160 | sendNotificationToContacts(msg, recipients) 161 | } 162 | else { 163 | if (sms) { 164 | sendSms(sms, msg) 165 | } 166 | if (pushNotification) { 167 | sendPush(msg) 168 | } 169 | }*/ 170 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/thermostat-manager.src/thermostat-manager.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "Thermostat Manager", 3 | singleInstance: true, 4 | namespace: "jscgs350", 5 | author: "jscgs350", 6 | description: "Thermostat Manager", 7 | category: "My Apps", 8 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 9 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 10 | 11 | preferences { 12 | page(name: "mainPage", title: "Thermostat Manager", install: true, uninstall: true,submitOnChange: true) { 13 | section { 14 | app(name: "childRules", appName: "Thermostat Manager Child", namespace: "jscgs350", title: "Create a Thermostat Manager...", multiple: true) 15 | } 16 | } 17 | } 18 | 19 | def installed() { 20 | initialize() 21 | } 22 | 23 | def updated() { 24 | unsubscribe() 25 | initialize() 26 | } 27 | 28 | def initialize() { 29 | childApps.each {child -> 30 | log.info "Installed Thermostat Managers: ${child.label}" 31 | } 32 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/virtual-temp-device-child.src/virtual-temp-device-child.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at: 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 9 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 10 | * for the specific language governing permissions and limitations under the License. 11 | * 12 | */ 13 | definition( 14 | name: "Virtual Temp Device Child", 15 | namespace: "jscgs350", 16 | author: "jscgs350", 17 | description: "Virtual Temp Device Child", 18 | category: "My Apps", 19 | parent: "jscgs350:Virtual Temp Device", 20 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 21 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 22 | 23 | preferences { 24 | section("Choose a target virtual temperature device... "){ 25 | input "target", "capability.temperatureMeasurement", title: "Tile" 26 | } 27 | section("Choose one or more source temperature sensors... "){ 28 | input "sensors", "capability.temperatureMeasurement", title: "Sensors", multiple: true 29 | } 30 | } 31 | 32 | def installed() { 33 | initialize() 34 | } 35 | 36 | def updated() { 37 | unsubscribe() 38 | initialize() 39 | } 40 | 41 | def initialize() { 42 | subscribe(sensors, "temperature", temperatureHandler) 43 | } 44 | 45 | def temperatureHandler(evt) { 46 | def sum = 0 47 | def count = 0 48 | def average = 0 49 | 50 | for (sensor in settings.sensors) { 51 | count += 1 52 | sum += sensor.currentTemperature 53 | } 54 | 55 | average = sum/count 56 | log.debug "average: $average" 57 | 58 | settings.target.parse("temperature: ${average}") 59 | } -------------------------------------------------------------------------------- /smartapps/jscgs350/virtual-temp-device.src/virtual-temp-device.groovy: -------------------------------------------------------------------------------- 1 | definition( 2 | name: "Virtual Temp Device", 3 | singleInstance: true, 4 | namespace: "jscgs350", 5 | author: "jscgs350", 6 | description: "Virtual Temp Device", 7 | category: "My Apps", 8 | iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", 9 | iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png") 10 | 11 | preferences { 12 | page(name: "mainPage", title: "Virtual Temp Devices", install: true, uninstall: true,submitOnChange: true) { 13 | section { 14 | app(name: "childRules", appName: "Virtual Temp Device Child", namespace: "jscgs350", title: "Create Virtual Temp Device...", multiple: true) 15 | } 16 | } 17 | } 18 | 19 | def installed() { 20 | initialize() 21 | } 22 | 23 | def updated() { 24 | unsubscribe() 25 | initialize() 26 | } 27 | 28 | def initialize() { 29 | childApps.each {child -> 30 | log.info "Installed Virtual Devices: ${child.label}" 31 | } 32 | } --------------------------------------------------------------------------------