Modifying 'LaMetric Time device handler and smart apps'

This commit is contained in:
Nazar Bilous
2016-06-24 11:18:02 -05:00
parent ab61db3699
commit 6e32676fe5
2 changed files with 392 additions and 432 deletions

View File

@@ -15,22 +15,20 @@
* *
*/ */
import groovy.json.JsonOutput
definition( definition(
name: "LaMetric (Connect)", name: "LaMetric (Connect)",
namespace: "com.lametric", namespace: "com.lametric",
author: "Mykola Kirichuk", author: "Mykola Kirichuk",
description: "Lametric connect", description: "Control your LaMetric Time smart display",
category: "Fun & Social", category: "Family",
iconUrl: "https://developer.lametric.com/assets/smart_things/smart_things_60.png", iconUrl: "https://developer.lametric.com/assets/smart_things/smart_things_60.png",
iconX2Url: "https://developer.lametric.com/assets/smart_things/smart_things_120.png", iconX2Url: "https://developer.lametric.com/assets/smart_things/smart_things_120.png",
iconX3Url: "https://developer.lametric.com/assets/smart_things/smart_things_120.png", iconX3Url: "https://developer.lametric.com/assets/smart_things/smart_things_120.png",
singleInstance: true) singleInstance: true)
{ {
appSetting "clientId" appSetting "clientId"
appSetting "clientSecret" appSetting "clientSecret"
} }
preferences { preferences {
page(name: "auth", title: "LaMetric", nextPage:"", content:"authPage", uninstall: true, install:true) page(name: "auth", title: "LaMetric", nextPage:"", content:"authPage", uninstall: true, install:true)
@@ -42,6 +40,7 @@ mappings {
path("/oauth/callback") {action: [GET: "callback"]} path("/oauth/callback") {action: [GET: "callback"]}
} }
import groovy.json.JsonOutput
def getEventNameListOfUserDeviceParsed(){ "EventListOfUserRemoteDevicesParsed" } def getEventNameListOfUserDeviceParsed(){ "EventListOfUserRemoteDevicesParsed" }
def getEventNameTokenRefreshed(){ "EventAuthTokenRefreshed" } def getEventNameTokenRefreshed(){ "EventAuthTokenRefreshed" }
@@ -72,50 +71,50 @@ def initialize() {
} }
/** /**
* Get the name of the new device to instantiate in the user's smartapps * Get the name of the new device to instantiate in the user's smartapps
* This must be an app owned by the namespace (see #getNameSpace). * This must be an app owned by the namespace (see #getNameSpace).
* *
* @return name * @return name
*/ */
def getDeviceName() { def getDeviceName() {
return "LaMetric" return "LaMetric"
} }
/** /**
* Returns the namespace this app and siblings use * Returns the namespace this app and siblings use
* *
* @return namespace * @return namespace
*/ */
def getNameSpace() { def getNameSpace() {
return "com.lametric" return "com.lametric"
} }
/** /**
* Returns all discovered devices or an empty array if none * Returns all discovered devices or an empty array if none
* *
* @return array of devices * @return array of devices
*/ */
def getDevices() { def getDevices() {
state.remoteDevices = state.remoteDevices ?: [:] state.remoteDevices = state.remoteDevices ?: [:]
} }
/** /**
* Returns an array of devices which have been verified * Returns an array of devices which have been verified
* *
* @return array of verified devices * @return array of verified devices
*/ */
def getVerifiedDevices() { def getVerifiedDevices() {
getDevices().findAll{ it?.value?.verified == true } getDevices().findAll{ it?.value?.verified == true }
} }
/** /**
* Generates a Map object which can be used with a preference page * Generates a Map object which can be used with a preference page
* to represent a list of devices detected and verified. * to represent a list of devices detected and verified.
* *
* @return Map with zero or more devices * @return Map with zero or more devices
*/ */
Map getSelectableDevice() { Map getSelectableDevice() {
def devices = getVerifiedDevices() def devices = getVerifiedDevices()
def map = [:] def map = [:]
@@ -128,9 +127,9 @@ Map getSelectableDevice() {
} }
/** /**
* Starts the refresh loop, making sure to keep us up-to-date with changes * Starts the refresh loop, making sure to keep us up-to-date with changes
* *
*/ */
private refreshDevices(){ private refreshDevices(){
log.debug "refresh device list" log.debug "refresh device list"
listOfUserRemoteDevices() listOfUserRemoteDevices()
@@ -139,21 +138,21 @@ private refreshDevices(){
} }
/** /**
* The deviceDiscovery page used by preferences. Will automatically * The deviceDiscovery page used by preferences. Will automatically
* make calls to the underlying discovery mechanisms as well as update * make calls to the underlying discovery mechanisms as well as update
* whenever new devices are discovered AND verified. * whenever new devices are discovered AND verified.
* *
* @return a dynamicPage() object * @return a dynamicPage() object
*/ */
/****************************************************************************************************************** /******************************************************************************************************************
DEVICE DISCOVERY AND VALIDATION DEVICE DISCOVERY AND VALIDATION
******************************************************************************************************************/ ******************************************************************************************************************/
def deviceDiscovery() def deviceDiscovery()
{ {
// if(canInstallLabs()) // if(canInstallLabs())
if (1) if (1)
{ {
// userDeviceList(); // userDeviceList();
log.debug("deviceDiscovery") log.debug("deviceDiscovery")
def refreshInterval = 3 // Number of seconds between refresh def refreshInterval = 3 // Number of seconds between refresh
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
@@ -166,9 +165,9 @@ def deviceDiscovery()
subscribeNetworkEvents() subscribeNetworkEvents()
//device discovery request every 15s //device discovery request every 15s
// if((deviceRefreshCount % 15) == 0) { // if((deviceRefreshCount % 15) == 0) {
// discoverLaMetrics() // discoverLaMetrics()
// } // }
// Verify request every 3 seconds except on discoveries // Verify request every 3 seconds except on discoveries
if(((deviceRefreshCount % 5) == 0)) { if(((deviceRefreshCount % 5) == 0)) {
@@ -200,10 +199,10 @@ To update your Hub, access Location Settings in the Main Menu (tap the gear next
/** /**
/** /**
* Starts a subscription for network events * Starts a subscription for network events
* *
* @param force If true, will unsubscribe and subscribe if necessary (Optional, default false) * @param force If true, will unsubscribe and subscribe if necessary (Optional, default false)
*/ */
private subscribeNetworkEvents(force=false) { private subscribeNetworkEvents(force=false) {
if (force) { if (force) {
unsubscribe() unsubscribe()
@@ -212,7 +211,7 @@ private subscribeNetworkEvents(force=false) {
if(!state.subscribe) { if(!state.subscribe) {
log.debug("subscribe on network events") log.debug("subscribe on network events")
subscribe(location, null, locationHandler, [filterEvents:false]) subscribe(location, null, locationHandler, [filterEvents:false])
// subscribe(app, appHandler) // subscribe(app, appHandler)
state.subscribe = true state.subscribe = true
} }
} }
@@ -290,7 +289,7 @@ def locationHandler(evt)
if (parsedJsonBody) if (parsedJsonBody)
{ {
log.trace (parsedJsonBody) log.trace (parsedJsonBody)
log.debug("responce for device ${parsedJsonBody?.server_id}") log.debug("responce for device ${parsedJsonBody?.id}")
//put or post response //put or post response
if (parsedJsonBody.success) if (parsedJsonBody.success)
{ {
@@ -298,8 +297,8 @@ def locationHandler(evt)
} else { } else {
//poll response //poll response
log.debug "poll responce" log.debug "poll responce"
log.debug ("poll responce ${parsedJsonBody?.info}") log.debug ("poll responce ${parsedJsonBody}")
def deviceId = parsedJsonBody?.info?.server_id; def deviceId = parsedJsonBody?.id;
if (deviceId) if (deviceId)
{ {
def devices = getDevices(); def devices = getDevices();
@@ -328,10 +327,10 @@ def locationHandler(evt)
} }
/** /**
* Adds the child devices based on the user's selection * Adds the child devices based on the user's selection
* *
* Uses selecteddevice defined in the deviceDiscovery() page * Uses selecteddevice defined in the deviceDiscovery() page
*/ */
def addDevice() { def addDevice() {
def devices = getVerifiedDevices() def devices = getVerifiedDevices()
def devlist def devlist
@@ -430,27 +429,6 @@ def authPage() {
subscribeNetworkEvents() subscribeNetworkEvents()
listOfUserRemoteDevices() listOfUserRemoteDevices()
return deviceDiscovery(); return deviceDiscovery();
/*
return deviceDiscovery();
def stats = getEcobeeThermostats()
log.debug "thermostat list: $stats"
log.debug "sensor list: ${sensorsDiscovered()}"
return dynamicPage(name: "auth", title: "Select Your Thermostats", uninstall: true) {
section(""){
paragraph "Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats])
}
def options = sensorsDiscovered() ?: []
def numFound = options.size() ?: 0
if (numFound > 0) {
section(""){
paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
}
}
}*/
} }
} }
@@ -577,17 +555,17 @@ def oauthInitUrl() {
def success() { def success() {
def message = """ def message = """
<p>Your LaMetric Account is now connected to SmartThings!</p> <p>Your LaMetric Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p> <p>Click 'Done' to finish setup.</p>
""" """
connectionStatus(message) connectionStatus(message)
} }
def fail() { def fail() {
def message = """ def message = """
<p>The connection could not be established!</p> <p>The connection could not be established!</p>
<p>Click 'Done' to return to the menu.</p> <p>Click 'Done' to return to the menu.</p>
""" """
connectionStatus(message) connectionStatus(message)
} }
@@ -595,111 +573,111 @@ def connectionStatus(message, redirectUrl = null) {
def redirectHtml = "" def redirectHtml = ""
if (redirectUrl) { if (redirectUrl) {
redirectHtml = """ redirectHtml = """
<meta http-equiv="refresh" content="3; url=${redirectUrl}" /> <meta http-equiv="refresh" content="3; url=${redirectUrl}" />
""" """
} }
def html = """ def html = """
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"><head> <html lang="en"><head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="width=device-width" id="viewport" name="viewport"> <meta content="width=device-width" id="viewport" name="viewport">
<style> <style>
@font-face { @font-face {
font-family: 'latoRegular'; font-family: 'latoRegular';
src: url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.eot"); src: url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.eot");
src: url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.eot?#iefix") format("embedded-opentype"), src: url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.eot?#iefix") format("embedded-opentype"),
url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.woff") format("woff"), url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.woff") format("woff"),
url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.ttf") format("truetype"), url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.ttf") format("truetype"),
url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.svg#latoRegular") format("svg"); url("https://developer.lametric.com/assets/fonts/lato-regular-webfont.svg#latoRegular") format("svg");
font-style: normal; font-style: normal;
font-weight: normal; } font-weight: normal; }
.clearfix:after, .mobile .connect:after { .clearfix:after, .mobile .connect:after {
content: ""; content: "";
clear: both; clear: both;
display: table; } display: table; }
.transition { .transition {
transition: all .3s ease 0s; } transition: all .3s ease 0s; }
html, body { html, body {
height: 100%; height: 100%;
} }
body{ body{
margin: 0; margin: 0;
padding: 0; padding: 0;
background: #f0f0f0; background: #f0f0f0;
color: #5c5c5c; color: #5c5c5c;
min-width: 1149px; min-width: 1149px;
font-family: 'latoRegular', 'Lato'; font-family: 'latoRegular', 'Lato';
} }
.fixed-page #page { .fixed-page #page {
min-height: 100%; min-height: 100%;
background: url(https://developer.lametric.com/assets/smart_things/page-bg.png) 50% 0 repeat-y; background: url(https://developer.lametric.com/assets/smart_things/page-bg.png) 50% 0 repeat-y;
} }
.mobile { .mobile {
min-width: 100%; min-width: 100%;
color: #757575; } color: #757575; }
.mobile .wrap { .mobile .wrap {
margin: 0 auto; margin: 0 auto;
padding: 0; padding: 0;
max-width: 640px; max-width: 640px;
min-width: inherit; } min-width: inherit; }
.mobile .connect { .mobile .connect {
width: 100%; width: 100%;
padding-top: 230px; padding-top: 230px;
margin-bottom: 50px; margin-bottom: 50px;
text-align: center; } text-align: center; }
.mobile .connect img { .mobile .connect img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
margin-left: 2%; margin-left: 2%;
border-radius: 15px; } border-radius: 15px; }
.mobile .connect img:first-child { .mobile .connect img:first-child {
margin-left: 0; } margin-left: 0; }
.mobile .info { .mobile .info {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
margin-top: 50px; margin-top: 50px;
margin-bottom: 50px; } margin-bottom: 50px; }
.mobile .info p { .mobile .info p {
max-width: 80%; max-width: 80%;
margin: 0 auto; margin: 0 auto;
margin-top: 50px; margin-top: 50px;
font-size: 28px; font-size: 28px;
line-height: 50px; line-height: 50px;
text-align: center; } text-align: center; }
@media screen and (max-width: 639px) { @media screen and (max-width: 639px) {
.mobile .connect{ .mobile .connect{
padding-top: 100px; } padding-top: 100px; }
.mobile .wrap { .mobile .wrap {
margin: 0 20px; } margin: 0 20px; }
.mobile .connect img { .mobile .connect img {
width: 16%; } width: 16%; }
.mobile .connect img:first-child, .mobile .connect img:last-child { .mobile .connect img:first-child, .mobile .connect img:last-child {
width: 40%; } width: 40%; }
.mobile .info p{ .mobile .info p{
font-size: 18px; font-size: 18px;
line-height: 24px; line-height: 24px;
margin-top: 20px; } margin-top: 20px; }
} }
</style> </style>
</head> </head>
<body class="fixed-page mobile"> <body class="fixed-page mobile">
<div id="page"> <div id="page">
<div class="wrap"> <div class="wrap">
<div class="connect"> <div class="connect">
<img src="https://developer.lametric.com/assets/smart_things/product.png" width="190" height="190"><img src="https://developer.lametric.com/assets/smart_things/connected.png" width="87" height="19"><img src="https://developer.lametric.com/assets/smart_things/product-1.png" width="192" height="192"> <img src="https://developer.lametric.com/assets/smart_things/product.png" width="190" height="190"><img src="https://developer.lametric.com/assets/smart_things/connected.png" width="87" height="19"><img src="https://developer.lametric.com/assets/smart_things/product-1.png" width="192" height="192">
</div> </div>
<div class="info"> <div class="info">
${message} ${message}
</div> </div>
</div> </div>
</div> </div>
</body></html> </body></html>
""" """
@@ -713,8 +691,8 @@ def connectionStatus(message, redirectUrl = null) {
//****************************************************************************************************************** //******************************************************************************************************************
def getLocalApiDeviceInfoPath() { "/api/v2/info" } def getLocalApiDeviceInfoPath() { "/api/v2/info" }
def getLocalApiSendNotificationPath() { "/api/v2/notifications" } def getLocalApiSendNotificationPath() { "/api/v2/device/notifications" }
def getLocalApiIndexPath() { "/api/v2" } def getLocalApiIndexPath() { "/api/v2/device" }
def getLocalApiUser() { "dev" } def getLocalApiUser() { "dev" }
@@ -765,14 +743,15 @@ def getAllInfoFromDevice(localIp, apiKey)
log.debug "send something" log.debug "send something"
if (localIp && apiKey) if (localIp && apiKey)
{ {
sendHubCommand(new physicalgraph.device.HubAction([ def hubCommand = new physicalgraph.device.HubAction([
method: "GET", method: "GET",
path: localApiIndexPath, path: localApiIndexPath+"?fields=info,wifi,volume,bluetooth,id,name,mode,model,serial_number,os_version",
query:["fields": ["info","wifi", "volume", "bluetooth"]],
headers: [ headers: [
HOST: "${localIp}:8080", HOST: "${localIp}:8080",
Authorization: "Basic ${"${localApiUser}:${apiKey}".bytes.encodeBase64()}" Authorization: "Basic ${"${localApiUser}:${apiKey}".bytes.encodeBase64()}"
]])) ]])
log.debug "sending request ${hubCommand}"
sendHubCommand(hubCommand)
} }
} }
//****************************************************************************************************************** //******************************************************************************************************************
@@ -787,9 +766,9 @@ def resolveDNI2Device(dni)
def requestRefreshDeviceInfo (dni) def requestRefreshDeviceInfo (dni)
{ {
log.debug "device ${dni} request refresh"; log.debug "device ${dni} request refresh";
// def devices = getDevices(); // def devices = getDevices();
// def concreteDevice = devices[dni]; // def concreteDevice = devices[dni];
// requestDeviceInfo(conreteDevice); // requestDeviceInfo(conreteDevice);
} }
private poll(dni) { private poll(dni) {
@@ -837,13 +816,7 @@ void listOfUserRemoteDevices()
log.debug ("empty device info") log.debug ("empty device info")
} }
} }
// state.remoteDevices = remoteDevices;
verifyDevices(); verifyDevices();
// return
// def serializedData = new JsonOutput().toJson(notification);
// app.sendEvent(name: "EventListOfUserRemoteDevicesParsed", value: serializedData)
// app.sendEvent(name: "parsed", value: true)
// log.debug "Sending 'save new list' event ${result}"
} else { } else {
log.debug "http status: ${resp.status}" log.debug "http status: ${resp.status}"
} }

View File

@@ -20,8 +20,8 @@ definition(
name: "LaMetric Notifier", name: "LaMetric Notifier",
namespace: "com.lametric", namespace: "com.lametric",
author: "Mykola Kirichuk", author: "Mykola Kirichuk",
description: "Notify about changes with sound and message on your LaMetric", description: "Allows you to send notifications to your LaMetric Time when something happens in your home to notify the whole family.",
category: "Fun & Social", category: "Family",
iconUrl: "https://developer.lametric.com/assets/smart_things/weather_60.png", iconUrl: "https://developer.lametric.com/assets/smart_things/weather_60.png",
iconX2Url: "https://developer.lametric.com/assets/smart_things/weather_120.png", iconX2Url: "https://developer.lametric.com/assets/smart_things/weather_120.png",
iconX3Url: "https://developer.lametric.com/assets/smart_things/weather_120.png") iconX3Url: "https://developer.lametric.com/assets/smart_things/weather_120.png")
@@ -388,21 +388,8 @@ def eventHandler(evt) {
if (allOk) { if (allOk) {
log.trace "allOk" log.trace "allOk"
// def lastTime = state[frequencyKey(evt)]
// if (oncePerDayOk(lastTime)) {
/*
if (frequency) {
if (lastTime == null || now() - lastTime >= frequency * 60000) {
takeAction(evt) takeAction(evt)
} }
else {
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
}
/* }
else {*/
takeAction(evt)
// }
}
else { else {
log.debug "Not taking action because it was already taken today" log.debug "Not taking action because it was already taken today"
} }