Hello - i'm starting a new thread from my original NWS sensor given this sensor uses an entirely different API. I am posting my code here for my outside temperature and humidity sensor driver. This pulls data from the open meteo API based on the coordinates specified on the hub.
- Can report in both F / C
- Can use the actual temperature or the 'feels like' temperature
- You can specify a refresh rate and a temperature differential to trigger a change
Hope this is of use to someone.
/*
* Outside Temperature Sensor (Open-Meteo)
*
* Hubitat driver that creates a temperature sensor from Open-Meteo's current
* weather data for the hub's configured latitude and longitude.
*
* Author: Jon Wallace
* Copyright 2025 Jon Wallace
* License: MIT
*
* Notes:
* - Fahrenheit is the default reporting unit.
* - Actual air temperature is reported by default. Apparent temperature can
* be selected in preferences.
* - Relative humidity is reported when Open-Meteo provides it.
* - Requires the hub location coordinates to be configured in Hubitat.
*/
metadata {
definition (
name: "Outside Temperature Sensor (Open-Meteo)",
namespace: "jonw",
author: "Jon Wallace"
) {
capability "TemperatureMeasurement"
capability "RelativeHumidityMeasurement"
capability "Sensor"
capability "Refresh"
}
preferences {
def lat = location?.latitude
def lon = location?.longitude
def hubLocationDescription = (lat != null && lon != null)
? "Using hub coordinates: ${lat}, ${lon}"
: "Hub coordinates are not configured. Set the hub location in Hubitat before this driver can retrieve outside temperature."
input name: "hubLocationStatus", type: "paragraph", title: hubLocationDescription, displayDuringSetup: true
input name: "temperatureUnit", type: "enum", title: "Temperature unit", description: "Unit used for temperature events.", options: ["Fahrenheit", "Celsius"], defaultValue: "Fahrenheit"
input name: "temperatureSource", type: "enum", title: "Reported temperature", description: "Choose whether the temperature attribute uses measured air temperature or the feels-like temperature.", options: ["Actual temperature", "Feels-like temperature"], defaultValue: "Actual temperature"
input name: "updateFrequency", type: "number", title: "Auto-refresh interval (minutes)", description: "How often the driver refreshes the current outside temperature.", defaultValue: 30, range: "1..1440"
input name: "minChange", type: "number", title: "Minimum temperature change to report", description: "Suppresses events until the change is at least this amount in the selected unit. Use 0 to report every refresh.", defaultValue: 0.1, range: "0..100"
input name: "logEnable", type: "bool", title: "Enable debug logging", description: "Log API requests and temperature update decisions.", defaultValue: false
}
}
// ===== Lifecycle =====
def installed() {
log.info "Installed Open-Meteo Temperature Sensor"
initialize()
}
def updated() {
log.info "Updated settings"
initialize()
}
def initialize() {
unschedule()
scheduleAutoRefresh()
getOutsideTemperature()
}
// ===== Commands =====
def refresh() {
getOutsideTemperature()
}
def scheduleAutoRefresh() {
def minutes = settings?.updateFrequency ?: 30
minutes = Math.max(1, Math.min(1440, minutes))
if (logEnable) log.debug "Scheduling auto-refresh every ${minutes} minutes"
if (minutes == 60) {
runEvery1Hour(getOutsideTemperature)
} else if (minutes > 60) {
runIn(minutes * 60, getOutsideTemperature)
} else {
schedule("0 */${minutes} * ? * *", getOutsideTemperature)
}
}
def getOutsideTemperature() {
def coords = getHubCoordinates()
if (!coords) {
log.error "Hub coordinates are not configured. Set the hub location in Hubitat before retrieving outside temperature."
scheduleNextRefresh()
return
}
def lat = coords.lat
def lon = coords.lon
def url = "https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,apparent_temperature,relative_humidity_2m"
if (logEnable) log.debug "Requesting temperature from: ${url}"
try {
httpGet([uri: url, contentType: "application/json"]) { resp ->
if (resp.status == 200) {
def data = resp.data
def tempC = data?.current?.temperature_2m
def apparentTempC = data?.current?.apparent_temperature
def humidity = data?.current?.relative_humidity_2m
def selectedSource = settings?.temperatureSource ?: "Actual temperature"
def sourceTempC = selectedSource == "Feels-like temperature" ? apparentTempC : tempC
def sourceLabel = selectedSource == "Feels-like temperature" ? "feels-like temperature" : "actual temperature"
if (sourceTempC != null) {
def selectedUnit = settings?.temperatureUnit ?: "Fahrenheit"
def unit = selectedUnit == "Celsius" ? "°C" : "°F"
def tempValue = (
selectedUnit == "Celsius"
? (sourceTempC as BigDecimal).setScale(1, BigDecimal.ROUND_HALF_UP)
: (((sourceTempC as BigDecimal) * 9 / 5) + 32).setScale(1, BigDecimal.ROUND_HALF_UP)
)
def currentVal = device.currentValue("temperature")
def currentTemp = currentVal != null ? new BigDecimal(currentVal.toString()) : null
def threshold = settings?.minChange != null ? (settings.minChange as BigDecimal) : new BigDecimal("0.1")
if (currentTemp == null || (tempValue - currentTemp).abs() >= threshold) {
sendEvent(name: "temperature", value: tempValue, unit: unit)
if (logEnable) log.debug "Temperature updated to ${tempValue}${unit} from ${sourceLabel}"
} else if (logEnable) {
log.debug "Temperature change (${tempValue}${unit}) within threshold (${threshold}${unit}); event not sent."
}
} else {
log.warn "${selectedSource} data missing in Open-Meteo response"
}
if (humidity != null) {
sendEvent(name: "humidity", value: humidity as BigDecimal, unit: "%")
if (logEnable) log.debug "Humidity updated to ${humidity}%"
} else {
log.warn "Humidity data missing in Open-Meteo response"
}
} else {
log.error "Failed to get temperature: ${resp.status}"
}
}
} catch (Exception e) {
log.error "Error fetching temperature: ${e.message}"
}
scheduleNextRefresh()
}
// ===== Helpers =====
def scheduleNextRefresh() {
def minutes = settings?.updateFrequency != null ? settings.updateFrequency as Integer : 30
minutes = Math.max(1, Math.min(1440, minutes))
if (minutes > 60) {
runIn(minutes * 60, getOutsideTemperature)
}
}
def getHubCoordinates() {
def lat = location?.latitude
def lon = location?.longitude
return (lat != null && lon != null) ? [lat: lat, lon: lon] : null
}
EDIT: Removed sections from preferences based on feedback from @thebearmay