4 * Author: todd@wackford.net
9 * Added device specific methods called from poll (versus in poll)
15 * Improved eggtray integration
16 * Added notifications to hello home
17 * Introduced Quirky Eggtray specific icons (Thanks to Dane)
18 * Added an Egg Report that outputs to hello home.
19 * Switched to Dan Lieberman's client and secret
20 * Still not browser flow (next update?)
23 * Added Browser Flow OAuth
27 * Added dynamic icon/tile updating to the nimbus. Changes the device icon from app.
30 * Stubbed out creation and choice of nimbus, eggtray and porkfolio per request.
33 * Renamed to 'Quirky (Connect)' and updated device names
35 * Update8:2014-04-08 (dlieberman)
38 * Update9:2014-04-08 (twackford)
39 * resubscribe to events on each poll
42 import java.text.DecimalFormat
45 private apiUrl() { "https://winkapi.quirky.com/" }
46 private getVendorName() { "Quirky Wink" }
47 private getVendorAuthPath() { "https://winkapi.quirky.com/oauth2/authorize?" }
48 private getVendorTokenPath(){ "https://winkapi.quirky.com/oauth2/token?" }
49 private getVendorIcon() { "https://s3.amazonaws.com/smartthings-device-icons/custom/quirky/quirky-device@2x.png" }
50 private getClientId() { appSettings.clientId }
51 private getClientSecret() { appSettings.clientSecret }
52 private getServerUrl() { appSettings.serverUrl }
55 name: "Quirky (Connect)",
56 namespace: "wackford",
57 author: "SmartThings",
58 description: "Connect your Quirky to SmartThings.",
59 category: "SmartThings Labs",
60 iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky.png",
61 iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky@2x.png",
65 appSetting "clientSecret"
66 appSetting "serverUrl"
70 page(name: "Credentials", title: "Fetch OAuth2 Credentials", content: "authPage", install: false)
71 page(name: "listDevices", title: "Quirky Devices", content: "listDevices", install: false)
75 path("/receivedToken") { action:[ POST: "receivedToken", GET: "receivedToken"] }
76 path("/receiveToken") { action:[ POST: "receiveToken", GET: "receiveToken"] }
77 path("/powerstripCallback") { action:[ POST: "powerstripEventHandler", GET: "subscriberIdentifyVerification"]}
78 path("/sensor_podCallback") { action:[ POST: "sensor_podEventHandler", GET: "subscriberIdentifyVerification"]}
79 path("/piggy_bankCallback") { action:[ POST: "piggy_bankEventHandler", GET: "subscriberIdentifyVerification"]}
80 path("/eggtrayCallback") { action:[ POST: "eggtrayEventHandler", GET: "subscriberIdentifyVerification"]}
81 path("/cloud_clockCallback"){ action:[ POST: "cloud_clockEventHandler", GET: "subscriberIdentifyVerification"]}
85 log.debug "In authPage"
86 if(canInstallLabs()) {
87 def description = null
89 if (state.vendorAccessToken == null) {
90 log.debug "About to create access token."
93 description = "Tap to enter Credentials."
95 def redirectUrl = oauthInitUrl()
98 return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: true, install:false) {
99 section { href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description }
102 description = "Tap 'Next' to proceed"
104 return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: true, install:false) {
105 section { href url: buildRedirectUrl("receivedToken"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description }
111 def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
113 To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
116 return dynamicPage(name:"Credentials", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
118 paragraph "$upgradeNeeded"
126 log.debug "In oauthInitUrl"
128 /* OAuth Step 1: Request access code with our client ID */
130 state.oauthInitState = UUID.randomUUID().toString()
132 def oauthParams = [ response_type: "code",
133 client_id: getClientId(),
134 state: state.oauthInitState,
135 redirect_uri: buildRedirectUrl("receiveToken") ]
137 return getVendorAuthPath() + toQueryString(oauthParams)
140 def buildRedirectUrl(endPoint) {
141 log.debug "In buildRedirectUrl"
143 return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}"
147 log.debug "In receiveToken"
149 def oauthParams = [ client_secret: getClientSecret(),
150 grant_type: "authorization_code",
153 def tokenUrl = getVendorTokenPath() + toQueryString(oauthParams)
158 /* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */
159 httpPost(params) { response ->
161 def data = response.data.data
163 state.vendorRefreshToken = data.refresh_token //these may need to be adjusted depending on depth of returned data
164 state.vendorAccessToken = data.access_token
167 if ( !state.vendorAccessToken ) { //We didn't get an access token, bail on install
171 /* OAuth Step 3: Use the access token to call into the vendor API throughout your code using state.vendorAccessToken. */
177 <meta name="viewport" content="width=50%,height=50%, user-scalable = yes">
178 <title>${getVendorName()} Connection</title>
179 <style type="text/css">
181 font-family: 'Swiss 721 W01 Thin';
182 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
183 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
184 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
185 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
186 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
191 font-family: 'Swiss 721 W01 Light';
192 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
193 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
194 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
195 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
196 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
203 /*background: #eee;*/
207 vertical-align: middle;
214 font-family: 'Swiss 721 W01 Thin';
226 font-family: 'Swiss 721 W01 Light';
231 <div class="container">
232 <img src=""" + getVendorIcon() + """ alt="Vendor icon" />
233 <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
234 <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
235 <p>We have located your """ + getVendorName() + """ account.</p>
236 <p>Tap 'Done' to process your credentials.</p>
241 render contentType: 'text/html', data: html
244 def receivedToken() {
245 log.debug "In receivedToken"
251 <meta name="viewport" content="width=50%,height=50%, user-scalable = yes">
252 <title>Withings Connection</title>
253 <style type="text/css">
255 font-family: 'Swiss 721 W01 Thin';
256 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
257 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
258 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
259 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
260 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
265 font-family: 'Swiss 721 W01 Light';
266 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
267 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
268 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
269 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
270 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
277 /*background: #eee;*/
281 vertical-align: middle;
288 font-family: 'Swiss 721 W01 Thin';
300 font-family: 'Swiss 721 W01 Light';
305 <div class="container">
306 <img src=""" + getVendorIcon() + """ alt="Vendor icon" />
307 <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
308 <img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
309 <p>Tap 'Done' to continue to Devices.</p>
314 render contentType: 'text/html', data: html
317 String toQueryString(Map m) {
318 return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
322 def subscriberIdentifyVerification()
324 log.debug "In subscriberIdentifyVerification"
326 def challengeToken = params.hub.challenge
328 render contentType: 'text/plain', data: challengeToken
333 log.debug "Initialized with settings: ${settings}"
335 //createAccessToken()
337 //state.oauthInitState = UUID.randomUUID().toString()
339 settings.devices.each {
342 state.deviceDataArr.each {
343 if ( it.id == deviceId ) {
347 log.debug "we have a Pivot Power Genius"
348 createPowerstripChildren(it.data) //has sub-devices, so we call out to create kids
349 createWinkSubscription( it.subsPath, it.subsSuff )
353 log.debug "we have a Spotter"
354 addChildDevice("wackford", "Quirky Wink Spotter", deviceId, null, [name: it.name, label: it.label, completedSetup: true])
355 createWinkSubscription( it.subsPath, it.subsSuff )
359 log.debug "we have a Piggy Bank"
360 addChildDevice("wackford", "Quirky Wink Porkfolio", deviceId, null, [name: it.name, label: it.label, completedSetup: true])
361 createWinkSubscription( it.subsPath, it.subsSuff )
365 log.debug "we have a Egg Minder"
366 addChildDevice("wackford", "Quirky Wink Eggtray", deviceId, null, [name: it.name, label: it.label, completedSetup: true])
367 createWinkSubscription( it.subsPath, it.subsSuff )
371 log.debug "we have a Nimbus"
372 createNimbusChildren(it.data) //has sub-devices, so we call out to create kids
373 createWinkSubscription( it.subsPath, it.subsSuff )
383 log.debug "In getDeviceList"
386 state.deviceDataArr = []
388 apiGet("/users/me/wink_devices") { response ->
389 response.data.data.each() {
390 if ( it.powerstrip_id ) {
391 deviceList["${it.powerstrip_id}"] = it.name
392 state.deviceDataArr.push(['name' : it.name,
393 'id' : it.powerstrip_id,
394 'type' : "powerstrip",
395 'serial' : it.serial,
397 'subsSuff': "/powerstripCallback",
398 'subsPath': "/powerstrips/${it.powerstrip_id}/subscriptions"
402 /* stubbing out these out for later release
403 if ( it.sensor_pod_id ) {
404 deviceList["${it.sensor_pod_id}"] = it.name
405 state.deviceDataArr.push(['name' : it.name,
406 'id' : it.sensor_pod_id,
407 'type' : "sensor_pod",
408 'serial' : it.serial,
410 'subsSuff': "/sensor_podCallback",
411 'subsPath': "/sensor_pods/${it.sensor_pod_id}/subscriptions"
416 if ( it.piggy_bank_id ) {
417 deviceList["${it.piggy_bank_id}"] = it.name
418 state.deviceDataArr.push(['name' : it.name,
419 'id' : it.piggy_bank_id,
420 'type' : "piggy_bank",
421 'serial' : it.serial,
423 'subsSuff': "/piggy_bankCallback",
424 'subsPath': "/piggy_banks/${it.piggy_bank_id}/subscriptions"
427 if ( it.cloud_clock_id ) {
428 deviceList["${it.cloud_clock_id}"] = it.name
429 state.deviceDataArr.push(['name' : it.name,
430 'id' : it.cloud_clock_id,
431 'type' : "cloud_clock",
432 'serial' : it.serial,
434 'subsSuff': "/cloud_clockCallback",
435 'subsPath': "/cloud_clocks/${it.cloud_clock_id}/subscriptions"
438 if ( it.eggtray_id ) {
439 deviceList["${it.eggtray_id}"] = it.name
440 state.deviceDataArr.push(['name' : it.name,
441 'id' : it.eggtray_id,
443 'serial' : it.serial,
445 'subsSuff': "/eggtrayCallback",
446 'subsPath': "/eggtrays/${it.eggtray_id}/subscriptions"
456 private removeChildDevices(delete)
458 log.debug "In removeChildDevices"
460 log.debug "deleting ${delete.size()} devices"
463 deleteChildDevice(it.deviceNetworkId)
469 log.debug "In uninstalled"
471 removeWinkSubscriptions()
473 removeChildDevices(getChildDevices())
476 def updateWinkSubscriptions()
477 { //since we don't know when wink subscription dies, we'll delete and recreate on every poll
478 log.debug "In updateWinkSubscriptions"
480 state.deviceDataArr.each() {
482 def path = it.subsPath
483 def suffix = it.subsSuff
484 apiGet(it.subsPath) { response ->
485 response.data.data.each {
486 if ( it.subscription_id ) {
487 deleteWinkSubscription(path + "/", it.subscription_id)
488 createWinkSubscription(path, suffix)
496 def createWinkSubscription(path, suffix)
498 log.debug "In createWinkSubscription"
500 def callbackUrl = buildCallbackUrl(suffix)
505 body: ['callback': callbackUrl],
506 headers : ['Authorization' : 'Bearer ' + state.vendorAccessToken]
509 log.debug "Created subscription ID ${response.data.data.subscription_id}"
513 def deleteWinkSubscription(path, subscriptionId)
515 log.debug "Deleting the wink subscription ${subscriptionId}"
519 path: path + subscriptionId,
520 headers : [ 'Authorization' : 'Bearer ' + state.vendorAccessToken ]
523 log.debug "Subscription ${subscriptionId} deleted"
528 def removeWinkSubscriptions()
530 log.debug "In removeSubscriptions"
533 state.deviceDataArr.each() {
535 def path = it.subsPath
536 apiGet(it.subsPath) { response ->
537 response.data.data.each {
538 if ( it.subscription_id ) {
539 deleteWinkSubscription(path + "/", it.subscription_id)
545 } catch (groovyx.net.http.HttpResponseException e) {
546 log.warn "Caught HttpResponseException: $e, with status: ${e.statusCode}"
550 def buildCallbackUrl(suffix)
552 log.debug "In buildRedirectUrl"
554 def serverUrl = getServerUrl()
555 return serverUrl + "/api/token/${state.accessToken}/smartapps/installations/${app.id}" + suffix
558 def createChildDevice(deviceFile, dni, name, label)
560 log.debug "In createChildDevice"
563 def existingDevice = getChildDevice(dni)
564 if(!existingDevice) {
565 log.debug "Creating child"
566 def childDevice = addChildDevice("wackford", deviceFile, dni, null, [name: name, label: label, completedSetup: true])
568 log.debug "Device $dni already exists"
571 log.error "Error creating device: ${e}"
578 log.debug "In listDevices"
582 def devices = getDeviceList()
583 log.debug "Device List = ${devices}"
585 dynamicPage(name: "listDevices", title: "Choose devices", install: true) {
587 input "devices", "enum", title: "Select Device(s)", required: false, multiple: true, options: devices
592 def apiGet(String path, Closure callback)
597 headers : [ 'Authorization' : 'Bearer ' + state.vendorAccessToken ]
601 callback.call(response)
605 def apiPut(String path, cmd, Closure callback)
611 headers : [ 'Authorization' : 'Bearer ' + state.vendorAccessToken ]
616 callback.call(response)
621 log.debug "Installed with settings: ${settings}"
628 log.debug "Updated with settings: ${settings}"
638 def poll(childDevice)
641 log.debug childDevice
645 def dni = childDevice.device.deviceNetworkId
649 def deviceType = null
651 state.deviceDataArr.each() {
657 log.debug "device type is: ${deviceType}"
659 switch(deviceType) { //outlets are polled in unique method not here
662 log.debug "Polling sensor_pod"
663 getSensorPodUpdate(childDevice)
664 log.debug "sensor pod status updated"
668 log.debug "Polling piggy_bank"
669 getPiggyBankUpdate(childDevice)
670 log.debug "piggy bank status updated"
674 log.debug "Polling eggtray"
675 getEggtrayUpdate(childDevice)
676 log.debug "eggtray status updated"
680 updateWinkSubscriptions()
684 return temp * 1.8 + 32
688 return (temp - 32) / 1.8
691 def dollarize(int money)
693 def value = money.toString()
695 if ( value.length() == 1 ) {
699 if ( value.length() == 2 ) {
703 def newval = value.substring(0, value.length() - 2) + "." + value.substring(value.length()-2, value.length())
706 def pattern = "\$0.00"
707 def moneyform = new DecimalFormat(pattern)
708 String output = moneyform.format(value.toBigDecimal())
713 def debugEvent(message, displayEvent) {
717 descriptionText: message,
718 displayed: displayEvent
720 log.debug "Generating AppDebug Event: ${results}"
725 /////////////////////////////////////////////////////////////////////////
726 // START NIMBUS SPECIFIC CODE HERE
727 /////////////////////////////////////////////////////////////////////////
728 def createNimbusChildren(deviceData)
730 log.debug "In createNimbusChildren"
732 def nimbusName = deviceData.name
733 def deviceFile = "Quirky-Wink-Nimbus"
735 deviceData.dials.each {
736 log.debug "creating dial device for ${it.dial_id}"
737 def dialName = "Dial ${index}"
738 def dialLabel = "${nimbusName} ${dialName}"
739 createChildDevice( deviceFile, it.dial_id, dialName, dialLabel )
744 def cloud_clockEventHandler()
746 log.debug "In Nimbus Event Handler..."
748 def json = request.JSON
749 def dials = json.dials
751 def html = """{"code":200,"message":"OK"}"""
752 render contentType: 'application/json', data: html
756 def childDevice = getChildDevice(it.dial_id)
757 childDevice?.sendEvent( name : "dial", value : it.label , unit : "" )
758 childDevice?.sendEvent( name : "info", value : it.name , unit : "" )
766 log.debug "In pollNimbus using dni # ${dni}"
772 apiGet("/users/me/wink_devices") { response ->
774 response.data.data.each() {
775 if (it.cloud_clock_id ) {
776 log.debug "Found Nimbus #" + it.cloud_clock_id
785 def childDevice = getChildDevice(it.dial_id)
787 childDevice?.sendEvent( name : "dial", value : it.label , unit : "" )
788 childDevice?.sendEvent( name : "info", value : it.name , unit : "" )
790 //Change the tile/icon to what info is being displayed
793 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-weather")
796 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-traffic")
799 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-time")
802 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-twitter")
805 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-calendar")
808 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-mail")
811 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-facebook")
814 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-instagram")
817 childDevice?.setIcon("dial", "dial", "st.quirky.nimbus.quirky-nimbus-fitbit")
820 childDevice?.setIcon("dial", "dial", "st.quirky.egg-minder.quirky-egg-device")
823 childDevice?.setIcon("dial", "dial", "st.quirky.porkfolio.quirky-porkfolio-side")
832 /////////////////////////////////////////////////////////////////////////
833 // START EGG TRAY SPECIFIC CODE HERE
834 /////////////////////////////////////////////////////////////////////////
835 def getEggtrayUpdate(childDevice)
837 log.debug "In getEggtrayUpdate"
839 apiGet("/eggtrays/" + childDevice.device.deviceNetworkId) { response ->
841 def data = response.data.data
842 def freshnessPeriod = data.freshness_period
843 def trayName = data.name
850 def nowUnixTime = now.getTime()/1000
852 data.eggs.each() { it ->
857 def eggArriveDate = it
858 def eggStaleDate = eggArriveDate + freshnessPeriod
859 if ( nowUnixTime > eggStaleDate ){
865 int freshEggs = totalEggs - oldEggs
868 childDevice?.sendEvent(name:"inventory",value:"haveBadEgg")
869 def msg = "${trayName} says: "
870 msg+= "Did you know that all it takes is one bad egg? "
871 msg+= "And it looks like I found one.\n\n"
872 msg+= "You should probably run an Egg Report before you use any eggs."
873 sendNotificationEvent(msg)
875 if ( totalEggs == 0 ) {
876 childDevice?.sendEvent(name:"inventory",value:"noEggs")
877 sendNotificationEvent("${trayName} says:\n'Oh no, I'm out of eggs!'")
878 sendNotificationEvent(msg)
880 if ( (freshEggs == totalEggs) && (totalEggs != 0) ) {
881 childDevice?.sendEvent(name:"inventory",value:"goodEggs")
883 childDevice?.sendEvent( name : "totalEggs", value : totalEggs , unit : "" )
884 childDevice?.sendEvent( name : "freshEggs", value : freshEggs , unit : "" )
885 childDevice?.sendEvent( name : "oldEggs", value : oldEggs , unit : "" )
889 def runEggReport(childDevice)
891 apiGet("/eggtrays/" + childDevice.device.deviceNetworkId) { response ->
893 def data = response.data.data
894 def trayName = data.name
895 def freshnessPeriod = data.freshness_period
897 def nowUnixTime = now.getTime()/1000
903 data.eggs.each() { it ->
905 def eggArriveDate = it
906 def eggStaleDate = eggArriveDate + freshnessPeriod
907 if ( nowUnixTime > eggStaleDate ){
908 eggArray.push("Bad ")
910 eggArray.push("Good ")
913 eggArray.push("Empty")
918 def msg = " Egg Report for ${trayName}\n\n"
919 msg+= "#7:${eggArray[6]} #14:${eggArray[13]}\n"
920 msg+= "#6:${eggArray[5]} #13:${eggArray[12]}\n"
921 msg+= "#5:${eggArray[4]} #12:${eggArray[11]}\n"
922 msg+= "#4:${eggArray[3]} #11:${eggArray[10]}\n"
923 msg+= "#3:${eggArray[2]} #10:${eggArray[9]}\n"
924 msg+= "#2:${eggArray[1]} #9:${eggArray[8]}\n"
925 msg+= "#1:${eggArray[0]} #8:${eggArray[7]}\n"
930 sendNotificationEvent(msg)
934 def eggtrayEventHandler()
936 log.debug "In eggtrayEventHandler..."
938 def json = request.JSON
939 def dni = getChildDevice(json.eggtray_id)
941 log.debug "event received from ${dni}"
943 poll(dni) //sometimes events are stale, poll for all latest states
946 def html = """{"code":200,"message":"OK"}"""
947 render contentType: 'application/json', data: html
950 /////////////////////////////////////////////////////////////////////////
951 // START PIGGY BANK SPECIFIC CODE HERE
952 /////////////////////////////////////////////////////////////////////////
953 def getPiggyBankUpdate(childDevice)
955 apiGet("/piggy_banks/" + childDevice.device.deviceNetworkId) { response ->
956 def status = response.data.data
957 def alertData = status.triggers
959 if (( alertData.enabled ) && ( state.lastCheckTime )) {
960 if ( alertData.triggered_at[0].toInteger() > state.lastCheckTime ) {
961 childDevice?.sendEvent(name:"acceleration",value:"active",unit:"")
963 childDevice?.sendEvent(name:"acceleration",value:"inactive",unit:"")
967 childDevice?.sendEvent(name:"goal",value:dollarize(status.savings_goal),unit:"")
969 childDevice?.sendEvent(name:"balance",value:dollarize(status.balance),unit:"")
972 def longTime = now.getTime()/1000
973 state.lastCheckTime = longTime.toInteger()
977 def piggy_bankEventHandler()
979 log.debug "In piggy_bankEventHandler..."
981 def json = request.JSON
982 def dni = getChildDevice(json.piggy_bank_id)
984 log.debug "event received from ${dni}"
986 poll(dni) //sometimes events are stale, poll for all latest states
989 def html = """{"code":200,"message":"OK"}"""
990 render contentType: 'application/json', data: html
993 /////////////////////////////////////////////////////////////////////////
994 // START SENSOR POD SPECIFIC CODE HERE
995 /////////////////////////////////////////////////////////////////////////
996 def getSensorPodUpdate(childDevice)
998 apiGet("/sensor_pods/" + childDevice.device.deviceNetworkId) { response ->
999 def status = response.data.data.last_reading
1001 status.loudness ? childDevice?.sendEvent(name:"sound",value:"active",unit:"") :
1002 childDevice?.sendEvent(name:"sound",value:"inactive",unit:"")
1004 status.brightness ? childDevice?.sendEvent(name:"light",value:"active",unit:"") :
1005 childDevice?.sendEvent(name:"light",value:"inactive",unit:"")
1007 status.vibration ? childDevice?.sendEvent(name:"acceleration",value:"active",unit:"") :
1008 childDevice?.sendEvent(name:"acceleration",value:"inactive",unit:"")
1010 status.external_power ? childDevice?.sendEvent(name:"powerSource",value:"powered",unit:"") :
1011 childDevice?.sendEvent(name:"powerSource",value:"battery",unit:"")
1013 childDevice?.sendEvent(name:"humidity",value:status.humidity,unit:"")
1015 childDevice?.sendEvent(name:"battery",value:(status.battery * 100).toInteger(),unit:"")
1017 childDevice?.sendEvent(name:"temperature",value:cToF(status.temperature),unit:"F")
1021 def sensor_podEventHandler()
1023 log.debug "In sensor_podEventHandler..."
1025 def json = request.JSON
1027 def dni = getChildDevice(json.sensor_pod_id)
1029 log.debug "event received from ${dni}"
1031 poll(dni) //sometimes events are stale, poll for all latest states
1034 def html = """{"code":200,"message":"OK"}"""
1035 render contentType: 'application/json', data: html
1038 /////////////////////////////////////////////////////////////////////////
1039 // START POWERSTRIP SPECIFIC CODE HERE
1040 /////////////////////////////////////////////////////////////////////////
1042 def powerstripEventHandler()
1044 log.debug "In Powerstrip Event Handler..."
1046 def json = request.JSON
1047 def outlets = json.outlets
1050 def dni = getChildDevice(it.outlet_id)
1051 pollOutlet(dni) //sometimes events are stale, poll for all latest states
1054 def html = """{"code":200,"message":"OK"}"""
1055 render contentType: 'application/json', data: html
1058 def pollOutlet(childDevice)
1060 log.debug "In pollOutlet"
1064 log.debug "Polling powerstrip"
1065 apiGet("/outlets/" + childDevice.device.deviceNetworkId) { response ->
1066 def data = response.data.data
1067 data.powered ? childDevice?.sendEvent(name:"switch",value:"on") :
1068 childDevice?.sendEvent(name:"switch",value:"off")
1076 apiPut("/outlets/" + childDevice.device.deviceNetworkId, [powered : true]) { response ->
1077 def data = response.data.data
1078 log.debug "Sending 'on' to device"
1082 def off(childDevice)
1086 apiPut("/outlets/" + childDevice.device.deviceNetworkId, [powered : false]) { response ->
1087 def data = response.data.data
1088 log.debug "Sending 'off' to device"
1092 def createPowerstripChildren(deviceData)
1094 log.debug "In createPowerstripChildren"
1096 def powerstripName = deviceData.name
1097 def deviceFile = "Quirky Wink Powerstrip"
1099 deviceData.outlets.each {
1100 createChildDevice( deviceFile, it.outlet_id, it.name, "$powerstripName ${it.name}" )
1104 private Boolean canInstallLabs()
1106 return hasAllHubsOver("000.011.00603")
1109 private Boolean hasAllHubsOver(String desiredFirmware)
1111 return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
1114 private List getRealHubFirmwareVersions()
1116 return location.hubs*.firmwareVersionString.findAll { it }