[UPDATED] - Weather Switch - Turn on a switch in response to weather

[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

@Cobra Do you need anything else?

Justin
Iā€™m sorry but I really havenā€™t had the time to look at this yet

Andy

1 Like

No worries. Just want to give you everything you need.

1 Like

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 :slight_smile:

Andy

1 Like

Truly appreciate if you did add this. :slight_smile:

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 :slight_smile: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.

1 Like

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.