[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:24.765:debugUpdating Wind Module [time_utc:1535550049, WindStrength:19, WindAngle:41, GustStrength:24, GustAngle:37, max_wind_str:30, max_wind_angle:47, date_max_wind_str:1535547640]
[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:24.717:debugUpdating Rain Module [time_utc:1535550049, Rain:0, sum_rain_24:0.1, sum_rain_1:0]
[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:24.627:debugUpdating Outdoor Module [time_utc:1535550004, Temperature:30.3, Humidity:78, min_temp:24.8, max_temp:30.9, date_min_temp:1535531037, date_max_temp:1535547595, temp_trend:stable]
[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:24.614:debugUpdating Additional Module [time_utc:1535550010, Temperature:26.1, CO2:708, Humidity:55, min_temp:23.8, max_temp:26.1, date_min_temp:1535523252, date_max_temp:1535549446, temp_trend:up]
[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:24.550:debugUpdating Basestation [time_utc:1535550050, Temperature:25.7, CO2:749, Humidity:63, Noise:44, Pressure:1020.8, AbsolutePressure:1020.2, min_temp:23, max_temp:25.7, date_min_temp:1535523257, date_max_temp:1535550050, temp_trend:up, pressure_trend:stable]
[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:24.488:debugUpdating Additional Module [time_utc:1535550043, Temperature:25.1, CO2:706, Humidity:56, min_temp:22.9, max_temp:25.1, date_min_temp:1535523233, date_max_temp:1535549427, temp_trend:stable]
[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:22.974:debugRefreshing station data
[app:1734](http://192.168.7.201/logs#app1734)2018-08-29 09:44:22.971:debugPolling
I missed your last post. Here is the full connect app.
/**
* Netatmo Connect
*/
import java.text.DecimalFormat
import groovy.json.JsonSlurper
private getApiUrl() { "https://api.netatmo.com" }
private getVendorName() { "netatmo" }
private getVendorAuthPath() { "/oauth2/authorize" }
private getVendorTokenPath(){ "${apiUrl}/oauth2/token" }
private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" }
private getClientId() { settings.clientId }
private getClientSecret() { settings.clientSecret }
//private getClientId() { app.id }
//private getClientSecret() { state.accessToken }
private getCallbackUrl() { getServerUrl()+ "/oauth/callback?access_token=${state.accessToken}" }
private getBuildRedirectUrl() { getServerUrl() + "/oauth/initialize?access_token=${state.accessToken}" }
private getServerUrl() { return getFullApiServerUrl() }
// Automatically generated. Make future change here.
definition(
name: "Netatmo (Connect)",
namespace: "fuzzysb",
author: "Stuart Buchanan",
description: "Netatmo Integration",
category: "Weather",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
oauth: true,
singleInstance: true
)
preferences {
page(name: "Credentials", title: "Fetch OAuth2 Credentials", content: "authPage", install: false)
page(name: "listDevices", title: "Netatmo Devices", content: "listDevices", install: true)
}
mappings {
path("/oauth/callback") {action: [GET: "callback"]}
}
def authPage() {
log.debug "In authPage"
def description
def uninstallAllowed = false
def oauthTokenProvided = false
if (!state.accessToken) {
log.debug "About to create access token."
state.accessToken = createAccessToken()
log.debug "Access token is : ${state.accessToken}"
}
def redirectUrl = getBuildRedirectUrl()
// log.debug "Redirect url = ${redirectUrl}"
if (state.authToken) {
description = "Tap 'Next' to proceed"
uninstallAllowed = true
oauthTokenProvided = true
} else {
description = "Click to enter Credentials."
}
if (!oauthTokenProvided) {
log.debug "Showing the login page"
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
section("Enter Netatmo Application Details...") {
paragraph "you can get these details after creating a new application on https:\\developer.netatmo.com"
input(name: 'clientId', title: 'Client ID', type: 'text', required: true)
input(name: 'clientSecret', title: 'Client secret (click away from this box before pressing the button below)', type: 'text', required: true, submitOnChange: true )
}
section() {
paragraph "Tap below to log in to Netatmo and authorize Hubitat access."
href url:oauthInitUrl(), external:true, required:false, title:"Connect to ${getVendorName()}:", description:description
}
}
} else {
log.debug "Showing the devices page"
return dynamicPage(name: "Credentials", title: "Connected", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
section() {
input(name:"Devices", style:"embedded", required:false, title:"Netatmo is now connected to Hubitat!", description:description)
}
}
}
}
def oauthInitUrl() {
log.debug "In oauthInitUrl"
a
state.oauthInitState = UUID.randomUUID().toString()
log.debug "oAuthInitStateIs: ${state.oauthInitState}"
def oauthParams = [
response_type: "code",
client_id: getClientId(),
client_secret: getClientSecret(),
state: state.oauthInitState,
redirect_uri: getCallbackUrl(),
scope: "read_station"
]
def authMethod = [
'location': [
uri: getApiUrl(),
path: getVendorAuthPath(),
requestContentType: "application/json",
query: [toQueryString(oauthParams)]
]
]
def authRequest = authMethod.getAt(authMethod)
try{
log.debug "Executing 'SendCommand'"
if (authMethod == "location"){
log.debug "Executing 'SendAuthRequest'"
httpGet(authRequest) { authResp ->
parseAuthResponse(authResp)
}
}
}
catch(Exception e){
log.debug("___exception: " + e)
}
log.debug "REDIRECT URL: ${getApiUrl()}${getVendorAuthPath()}?${toQueryString(oauthParams)}"
return "${getApiUrl()}${getVendorAuthPath()}?${toQueryString(oauthParams)}"
}
private parseAuthResponse(resp) {
log.debug("Executing parseAuthResponse: "+resp.data)
log.debug("Output status: "+resp.status)
}
def callback() {
log.debug "callback()>> params: $params, params.code ${params.code}"
def code = params.code
def oauthState = params.state
if (oauthState == state.oauthInitState) {
def tokenParams = [
client_secret: getClientSecret(),
client_id : getClientId(),
grant_type: "authorization_code",
redirect_uri: getCallbackUrl(),
code: code,
scope: "read_station"
]
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
def tokenUrl = getVendorTokenPath()
def params = [
uri: tokenUrl,
contentType: 'application/x-www-form-urlencoded',
body: tokenParams
]
log.debug "PARAMS: ${params}"
httpPost(params) { resp ->
def slurper = new JsonSlurper()
resp.data.each { key, value ->
def data = slurper.parseText(key)
state.refreshToken = data.refresh_token
state.authToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
// log.debug "swapped token: $resp.data"
}
}
// Handle success and failure here, and render stuff accordingly
if (state.authToken) {
success()
} else {
fail()
}
} else {
log.error "callback() failed oauthState != state.oauthInitState"
}
}
def success() {
log.debug "OAuth flow succeeded"
def message = """
<p>We have located your """ + getVendorName() + """ account.</p>
<p>Close this page and install the application again. you will not be prompted for credentials next time.</p>
"""
connectionStatus(message)
}
def fail() {
log.debug "OAuth flow failed"
def message = """
<p>The connection could not be established!</p>
<p>Close this page and attempt install the application again.</p>
"""
connectionStatus(message)
}
def connectionStatus(message, redirectUrl = null) {
def redirectHtml = ""
if (redirectUrl) {
redirectHtml = """
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${getVendorName()} Connection</title>
<style type="text/css">
* { box-sizing: border-box; }
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 100%;
padding: 40px;
/*background: #eee;*/
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 30px;
}
p {
font-size: 2.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
margin-bottom: 0;
}
/*
p:last-child {
margin-top: 0px;
}
*/
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
</head>
<body>
<div class="container">
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img src="https://cdn.shopify.com/s/files/1/2575/8806/t/20/assets/logo-image-file.png" alt="Hubitat logo" />
${message}
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
def refreshToken() {
log.debug "In refreshToken"
def oauthParams = [
client_secret: getClientSecret(),
client_id: getClientId(),
grant_type: "refresh_token",
refresh_token: state.refreshToken
]
def tokenUrl = getVendorTokenPath()
def params = [
uri: tokenUrl,
contentType: 'application/x-www-form-urlencoded',
body: oauthParams,
]
// OAuth Step 2: Request access token with our client Secret and OAuth "Code"
try {
httpPost(params) { response ->
def slurper = new JsonSlurper();
response.data.each {key, value ->
def data = slurper.parseText(key);
// log.debug "Data: $data"
state.refreshToken = data.refresh_token
state.accessToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
return true
}
}
} catch (Exception e) {
log.debug "Error: $e"
}
// We didn't get an access token
if ( !state.accessToken ) {
return false
}
}
String toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
unschedule()
initialize()
}
def initialize() {
log.debug "Initialized with settings: ${settings}"
// Pull the latest device info into state
getDeviceList();
settings.devices.each {
def deviceId = it
def detail = state?.deviceDetail[deviceId]
try {
switch(detail?.type) {
case 'NAMain':
log.debug "Creating Base station, DeviceID: ${deviceId} Device name: ${detail.module_name}"
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
break
case 'NAModule1':
log.debug "Creating Outdoor module, DeviceID: ${deviceId} Device name: ${detail.module_name}"
createChildDevice("Netatmo Outdoor Module", deviceId, "${detail.type}.${deviceId}", detail.module_name)
break
case 'NAModule3':
log.debug "Creating Rain Gauge, DeviceID: ${deviceId} Device name: ${detail.module_name}"
createChildDevice("Netatmo Rain", deviceId, "${detail.type}.${deviceId}", detail.module_name)
break
case 'NAModule4':
log.debug "Creating Additional module, DeviceID: ${deviceId} Device name: ${detail.module_name}"
createChildDevice("Netatmo Additional Module", deviceId, "${detail.type}.${deviceId}", detail.module_name)
break
case 'NAModule2':
log.debug "Creating Wind module, DeviceID: ${deviceId} Device name: ${detail.module_name}"
createChildDevice("Netatmo Wind", deviceId, "${detail.type}.${deviceId}", detail.module_name)
break
}
} catch (Exception e) {
log.error "Error creating device: ${e}"
}
}
// Cleanup any other devices that need to go away
def delete = getChildDevices().findAll { !settings.devices.contains(it.deviceNetworkId) }
log.debug "Delete: $delete"
delete.each { deleteChildDevice(it.deviceNetworkId) }
// check if user has set location
checkloc()
// Do the initial poll
poll()
// Schedule it to run every 5 minutes
runEvery5Minutes("poll")
}
def uninstalled() {
log.debug "In uninstalled"
removeChildDevices(getChildDevices())
}
def getDeviceList() {
log.debug "Refreshing station data"
def deviceList = [:]
def moduleName = null
state.deviceDetail = [:]
state.deviceState = [:]
apiGet("/api/getstationsdata",["get_favorites":true]) { resp ->
state.response = resp.data.body
resp.data.body.devices.each { value ->
def key = value._id
if (value.module_name != null) {
deviceList[key] = "${value.station_name}: ${value.module_name}"
state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data
}
value.modules.each { value2 ->
def key2 = value2._id
if (value2.module_name != null) {
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
state.deviceDetail[key2] = value2
state.deviceState[key2] = value2.dashboard_data
}
else {
switch(value2.type) {
case "NAModule1":
moduleName = "Outdoor ${value.station_name}"
break
case "NAModule2":
moduleName = "Wind ${value.station_name}"
break
case "NAModule3":
moduleName = "Rain ${value.station_name}"
break
case "NAModule4":
moduleName = "Additional ${value.station_name}"
break
}
deviceList[key2] = "${value.station_name}: ${moduleName}"
state.deviceDetail[key2] = value2 << ["module_name" : moduleName]
state.deviceState[key2] = value2.dashboard_data
}
}
}
}
return deviceList.sort() { it.value.toLowerCase() }
}
private removeChildDevices(delete) {
log.debug "In removeChildDevices"
log.debug "deleting ${delete.size()} devices"
delete.each {
deleteChildDevice(it.deviceNetworkId)
}
}
def createChildDevice(deviceFile, dni, name, label) {
log.debug "In createChildDevice"
try {
def existingDevice = getChildDevice(dni)
if(!existingDevice) {
log.debug "Creating child"
def childDevice = addChildDevice("fuzzysb", deviceFile, dni, null, [name: name, label: label, completedSetup: true])
} else {
log.debug "Device $dni already exists"
}
} catch (e) {
log.error "Error creating device: ${e}"
}
}
def listDevices() {
log.debug "Listing devices $devices "
def devices = getDeviceList()
dynamicPage(name: "listDevices", title: "Choose devices", install: true) {
section("Devices") {
input "devices", "enum", title: "Select Device(s)", required: false, multiple: true, options: devices
}
section("Preferences") {
input "rainUnits", "enum", title: "Rain Units", description: "Please select rain units", required: true, options: [mm:'Millimeters', in:'Inches']
input "pressUnits", "enum", title: "Pressure Units", description: "Please select pressure units", required: true, options: [mbar:'mbar', inhg:'inhg']
input "windUnits", "enum", title: "Wind Units", description: "Please select wind units", required: true, options: [kph:'kph', ms:'ms', mph:'mph', kts:'kts']
input "time", "enum", title: "Time Format", description: "Please select time format", required: true, options: [12:'12 Hour', 24:'24 Hour']
input "sound", "number", title: "Sound Sensor: \nEnter the value when sound will be marked as detected", description: "Please enter number", required: false
}
}
}
def apiGet(String path, Map query, Closure callback) {
if(now() >= state.tokenExpires) {
refreshToken();
}
query['access_token'] = state.accessToken
def params = [
uri: getApiUrl(),
path: path,
'query': query
]
// log.debug "API Get: $params"
try {
httpGet(params) { response ->
callback.call(response)
}
} catch (Exception e) {
// This is most likely due to an invalid token. Try to refresh it and try again.
log.debug "apiGet: Call failed $e"
if(refreshToken()) {
log.debug "apiGet: Trying again after refreshing token"
httpGet(params) { response ->
callback.call(response)
}
}
}
}
def apiGet(String path, Closure callback) {
apiGet(path, [:], callback);
}
def poll() {
log.debug "Polling"
getDeviceList();
def children = getChildDevices()
//log.debug "State: ${state.deviceState}"
//log.debug "Time Zone: ${location.timeZone}"
settings.devices.each { deviceId ->
def detail = state?.deviceDetail[deviceId]
def data = state?.deviceState[deviceId]
def child = children?.find { it.deviceNetworkId == deviceId }
//log.debug "Update: $child";
switch(detail?.type) {
case 'NAMain':
log.debug "Updating Basestation $data"
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'carbonDioxide', value: data['CO2'], unit: "ppm")
child?.sendEvent(name: 'humidity', value: data['Humidity'], unit: "%")
child?.sendEvent(name: 'temp_trend', value: data['temp_trend'], unit: "")
child?.sendEvent(name: 'pressure', value: (pressToPref(data['Pressure'])).toDouble().trunc(2), unit: settings.pressUnits)
child?.sendEvent(name: 'soundPressureLevel', value: data['Noise'], unit: "db")
child?.sendEvent(name: 'sound', value: noiseTosound(data['Noise']))
child?.sendEvent(name: 'pressure_trend', value: data['pressure_trend'], unit: "")
child?.sendEvent(name: 'min_temp', value: cToPref(data['min_temp']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'max_temp', value: cToPref(data['max_temp']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'units', value: settings.pressUnits)
child?.sendEvent(name: 'lastupdate', value: lastUpdated(data['time_utc']), unit: "")
child?.sendEvent(name: 'date_min_temp', value: lastUpdated(data['date_min_temp']), unit: "")
child?.sendEvent(name: 'date_max_temp', value: lastUpdated(data['date_max_temp']), unit: "")
break;
case 'NAModule1':
log.debug "Updating Outdoor Module $data"
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'humidity', value: data['Humidity'], unit: "%")
child?.sendEvent(name: 'temp_trend', value: data['temp_trend'], unit: "")
child?.sendEvent(name: 'min_temp', value: cToPref(data['min_temp']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'max_temp', value: cToPref(data['max_temp']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'battery', value: detail['battery_percent'], unit: "%")
child?.sendEvent(name: 'lastupdate', value: lastUpdated(data['time_utc']), unit: "")
child?.sendEvent(name: 'date_min_temp', value: lastUpdated(data['date_min_temp']), unit: "")
child?.sendEvent(name: 'date_max_temp', value: lastUpdated(data['date_max_temp']), unit: "")
break;
case 'NAModule3':
log.debug "Updating Rain Module $data"
child?.sendEvent(name: 'rain', value: (rainToPref(data['Rain'])), unit: settings.rainUnits)
child?.sendEvent(name: 'rainSumHour', value: (rainToPref(data['sum_rain_1'])), unit: settings.rainUnits)
child?.sendEvent(name: 'rainSumDay', value: (rainToPref(data['sum_rain_24'])), unit: settings.rainUnits)
child?.sendEvent(name: 'units', value: settings.rainUnits)
child?.sendEvent(name: 'battery', value: detail['battery_percent'], unit: "%")
child?.sendEvent(name: 'lastupdate', value: lastUpdated(data['time_utc']), unit: "")
child?.sendEvent(name: 'rainUnits', value: rainToPrefUnits(data['Rain']), displayed: false)
child?.sendEvent(name: 'rainSumHourUnits', value: rainToPrefUnits(data['sum_rain_1']), displayed: false)
child?.sendEvent(name: 'rainSumDayUnits', value: rainToPrefUnits(data['sum_rain_24']), displayed: false)
break;
case 'NAModule4':
log.debug "Updating Additional Module $data"
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'carbonDioxide', value: data['CO2'], unit: "ppm")
child?.sendEvent(name: 'humidity', value: data['Humidity'], unit: "%")
child?.sendEvent(name: 'temp_trend', value: data['temp_trend'], unit: "")
child?.sendEvent(name: 'min_temp', value: cToPref(data['min_temp']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'max_temp', value: cToPref(data['max_temp']) as float, unit: getTemperatureScale())
child?.sendEvent(name: 'battery', value: detail['battery_percent'], unit: "%")
child?.sendEvent(name: 'lastupdate', value: lastUpdated(data['time_utc']), unit: "")
child?.sendEvent(name: 'date_min_temp', value: lastUpdated(data['date_min_temp']), unit: "")
child?.sendEvent(name: 'date_max_temp', value: lastUpdated(data['date_max_temp']), unit: "")
break;
case 'NAModule2':
log.debug "Updating Wind Module $data"
child?.sendEvent(name: 'WindAngle', value: data['WindAngle'], unit: "Ā°", displayed: false)
child?.sendEvent(name: 'GustAngle', value: data['GustAngle'], unit: "Ā°", displayed: false)
child?.sendEvent(name: 'battery', value: detail['battery_percent'], unit: "%")
child?.sendEvent(name: 'WindStrength', value: (windToPref(data['WindStrength'])).toDouble().trunc(1), unit: settings.windUnits)
child?.sendEvent(name: 'GustStrength', value: (windToPref(data['GustStrength'])).toDouble().trunc(1), unit: settings.windUnits)
child?.sendEvent(name: 'max_wind_str', value: (windToPref(data['max_wind_str'])).toDouble().trunc(1), unit: settings.windUnits)
child?.sendEvent(name: 'units', value: settings.windUnits)
child?.sendEvent(name: 'lastupdate', value: lastUpdated(data['time_utc']), unit: "")
child?.sendEvent(name: 'date_max_wind_str', value: lastUpdated(data['date_max_wind_str']), unit: "")
child?.sendEvent(name: 'WindDirection', value: windTotext(data['WindAngle']))
child?.sendEvent(name: 'GustDirection', value: gustTotext(data['GustAngle']))
child?.sendEvent(name: 'WindStrengthUnits', value: windToPrefUnits(data['WindStrength']), displayed: false)
child?.sendEvent(name: 'GustStrengthUnits', value: windToPrefUnits(data['GustStrength']), displayed: false)
child?.sendEvent(name: 'max_wind_strUnits', value: windToPrefUnits(data['max_wind_str']), displayed: false)
break;
}
}
}
def cToPref(temp) {
if(getTemperatureScale() == 'C') {
return temp
} else {
return temp * 1.8 + 32
}
}
def rainToPref(rain) {
if(settings.rainUnits == 'mm') {
return rain.toDouble().trunc(1)
} else {
return (rain * 0.039370).toDouble().trunc(3)
}
}
def rainToPrefUnits(rain) {
if(settings.rainUnits == 'mm') {
return rain.toDouble().trunc(1) + " mm"
} else {
return (rain * 0.039370).toDouble().trunc(3) + " in"
}
}
def pressToPref(Pressure) {
if(settings.pressUnits == 'mbar') {
return Pressure
} else {
return Pressure * 0.029530
}
}
def windToPref(Wind) {
if(settings.windUnits == 'kph') {
return Wind
} else if (settings.windUnits == 'ms') {
return Wind * 0.277778
} else if (settings.windUnits == 'mph') {
return Wind * 0.621371192
} else if (settings.windUnits == 'kts') {
return Wind * 0.539956803
}
}
def windToPrefUnits(Wind) {
if(settings.windUnits == 'kph') {
return Wind
} else if (settings.windUnits == 'ms') {
return (Wind * 0.277778).toDouble().trunc(1) +" ms"
} else if (settings.windUnits == 'mph') {
return (Wind * 0.621371192).toDouble().trunc(1) +" mph"
} else if (settings.windUnits == 'kts') {
return (Wind * 0.539956803).toDouble().trunc(1) +" kts"
}
}
def lastUpdated(time) {
if(location.timeZone == null) {
log.warn "Time Zone is not set, time will be in UTC. Go to your ST app and set your hub location to get local time!"
def updtTime = new Date(time*1000L).format("HH:mm")
state.lastUpdated = updtTime
return updtTime + " UTC"
} else if(settings.time == '24') {
def updtTime = new Date(time*1000L).format("HH:mm", location.timeZone)
state.lastUpdated = updtTime
return updtTime
} else if(settings.time == '12') {
def updtTime = new Date(time*1000L).format("h:mm aa", location.timeZone)
state.lastUpdated = updtTime
return updtTime
}
}
def windTotext(WindAngle) {
if(WindAngle < 23) {
return WindAngle + "Ā° North"
} else if (WindAngle < 68) {
return WindAngle + "Ā° NorthEast"
} else if (WindAngle < 113) {
return WindAngle + "Ā° East"
} else if (WindAngle < 158) {
return WindAngle + "Ā° SouthEast"
} else if (WindAngle < 203) {
return WindAngle + "Ā° South"
} else if (WindAngle < 248) {
return WindAngle + "Ā° SouthWest"
} else if (WindAngle < 293) {
return WindAngle + "Ā° West"
} else if (WindAngle < 338) {
return WindAngle + "Ā° NorthWest"
} else if (WindAngle < 361) {
return WindAngle + "Ā° North"
}
}
def gustTotext(GustAngle) {
if(GustAngle < 23) {
return GustAngle + "Ā° North"
} else if (GustAngle < 68) {
return GustAngle + "Ā° NEast"
} else if (GustAngle < 113) {
return GustAngle + "Ā° East"
} else if (GustAngle < 158) {
return GustAngle + "Ā° SEast"
} else if (GustAngle < 203) {
return GustAngle + "Ā° South"
} else if (GustAngle < 248) {
return GustAngle + "Ā° SWest"
} else if (GustAngle < 293) {
return GustAngle + "Ā° West"
} else if (GustAngle < 338) {
return GustAngle + "Ā° NWest"
} else if (GustAngle < 361) {
return GustAngle + "Ā° North"
}
}
def noiseTosound(Noise) {
if(Noise > settings.sound) {
return "detected"
} else {
return "not detected"
}
}
def checkloc() {
if(location.timeZone == null)
sendPush("Netatmo: Time Zone is not set, time will be in UTC. Go to your ST app and set your hub location to get local time!")
}
def debugEvent(message, displayEvent) {
def results = [
name: "appdebug",
descriptionText: message,
displayed: displayEvent
]
log.debug "Generating AppDebug Event: ${results}"
sendEvent (results)
}
New Hubitat user here trying to install your WU app.
I saved the parent and the child under Apps code. No problem
Did a reboot of HE
Go to create a new virtual device
Stuck in the Network ID box. What to put here?
Thanks
Ok
Apps are different to devices.
If you installed the code for weather switch app then after installing the code you āloadā the parent app then save it.
After this you can create multiple child apps to switch dependant upon responses from a weather device.
By your question it seems that you are creating a new virtual device (using the custom weather underground driver??)
In answer to your question you can put almost anything in the network id providing it is unique.
I usually put the same as the device name but without spaces so.. for example
If I had a device called āThe Front Doorā
I personally would use āthefrontdoorā as the network id
Some people use numbers and letters.. it really doenāt matter providing you haven't used that network id before.
Andy
Thanks Andy
Justin
Iām sorry but I really havenāt had the time to look at this yet
Andy
No worries. Just want to give you everything you need.
So this is a really cool app. I like what you can do with it. Is there a way to activate TTS with severe weather alerts?
This is something Iāve been thinking about.
I would need to match a word from the āalertā attribute sent froma weather device (like a Weather Underground device)
Then throw the switch.
Message Central could take care of the tts using a momentary switch as the trigger.
Something for the feature list
Andy
Truly appreciate if you did add this.
After last night's ST outage I am now aggressively moving everything over to HE. I use BT2 but it isn't working for weather and SONOS with HE currently. Really like the weather alerts currently.
'Alert' now added as a trigger.
You can match a word or a phrase
Andy
Awesome news! So should I wait for message central update for weather alerts or is the plan to still use weather switch to activate a weather alert in Message Central?
Message Central has a few other bugs I want to iron out at the moment so it might be a while before I release a new version.
Once I have sorted those out (mostly with playing mp3s) then I'll add the 'alert' variable so it will speak the whole alert (rather than matching just a word or phrase and turning on a switch)
Andy
Is the issue with MP3s surrounding Sonos devices? If so that probably isn't you but rather the Sonos driver. I have been following a couple other threads surrounding duplication of announcements and also character limitations imposed on the input txt.
Unfortunately no There are some coding issues within MC that need to be sorted.
It's just taking a while
Andy
You are doing a great job btw and I truly appreciate your work on this application.
Saw an update?? Might it included Netatmo?
Sorry, I came home at 3am this morning and have not got a lot done.
No need to apologize. I should apologize to you. Your apps are quite awesome and you have worked hard to make HE users quite happy with the HE.