jschlackman / AirNow

SmartThings virtual device type for retrieving air quality data from AirNow.gov
MIT License
1 stars 7 forks source link

Updated Code to Pull PM10 Data #1

Open edfrahm opened 4 years ago

edfrahm commented 4 years ago

James-

Thanks so much for building this DTH. I'm new to SmartThings and deployed this to supplement an in-home sensor I have that only monitors PM2.5 and VOCs. I wanted to pull AirNow data to track PM10 and turn my HVAC fan on under certain conditions using WebCore.

The code you deployed didn't report PM10, but I noticed it was a part of the AirNow API. I'm not a coder, but I copied your syntax and changed the 2.5 references to 10. It appears to be working, but I'd appreciate your more skilled eyes to check it over. Here's the updated code:

/**

// Parse events into attributes. This will never be called but needs to be present in the DTH code. def parse(String description) { log.debug("AirNow: Parsing '${description}'") }

def installed() { runEvery1Hour(poll) poll() }

def updated() { poll() }

def uninstalled() { unschedule() }

// handle commands def poll() { log.debug("Polling AirNow for air quality data, location: ${location.name}")

if(airNowKey) {

    def airZip = null
    def airDistance = 0

    // Use hub zipcode if user has not defined their own
    if(zipCode) {
        airZip = zipCode
    } else {
        airZip = location.zipCode
    }

    // Set the user's requested observation distance, or use a default of 25 miles
    if(distance) {
        airDistance = distance
    } else {
        airDistance = 25
    }

    // Set up the AirNow API query
    def params = [
        uri: 'http://www.airnowapi.org/aq/',
        path: 'observation/zipCode/current/',
        contentType: 'application/json',
        query: [format:'application/json', zipCode: airZip, distance: airDistance, API_KEY: airNowKey]
    ]

    try {
    // Send query to the AirNow API
        httpGet(params) {resp ->
            def newCombined = -1
            def newCombinedCategory = -1
            def newCombinedCategoryName = ''

            // Parse the observation data array
            resp.data.each {observation ->

                // Only parse this data if the AQI figure is in a sensible range (accounts for occasional API bugs)
                if ((observation.AQI >= 0) && (observation.AQI <= 2000)) {

                    if (observation.ParameterName == "O3") {
                        send(name: "O3", value: observation.AQI)
                        send(name: "O3Category", value: observation.Category.Number)
                        send(name: "O3CategoryName", value: observation.Category.Name)
                    }
                    else if (observation.ParameterName == "PM2.5") {
                        send(name: "Pm25", value: observation.AQI)
                        send(name: "Pm25Category", value: observation.Category.Number)
                        send(name: "Pm25CategoryName", value: observation.Category.Name)
                    }   
                    else if (observation.ParameterName == "PM10") {
                        send(name: "Pm10", value: observation.AQI)
                        send(name: "Pm10Category", value: observation.Category.Number)
                        send(name: "Pm10CategoryName", value: observation.Category.Name)
                    }   

                    // Check if the observation currently being parsed has the highest AQI and should therefore be the combined AQI
                    if ((observation.AQI > newCombined) || (observation.Category.Number > newCombinedCategory)) {
                        newCombined = observation.AQI
                        newCombinedCategory = observation.Category.Number
                        newCombinedCategoryName = observation.Category.Name
                    }
                } else {
                    log.error("AirNow returned an AQI of ${observation.AQI} for ${observation.ParameterName}. Ignoring as this is probably invalid.")
                }
            }

            // If we got valid data for at least one observation, send the combined AQI and reporting data
            if (newCombined > -1) {

                // Send the combined AQI
                send(name: "combined", value: newCombined)
                send(name: "combinedCategory", value: newCombinedCategory)
                send(name: "combinedCategoryName", value: newCombinedCategoryName)

                // Send the first reporting area (it will be the same for both observations)
                send(name: "reportingLocation", value: "${resp.data[0].ReportingArea} ${resp.data[0].StateCode}")
                send(name: "latitude", value: resp.data[0].Latitude)
                send(name: "longitude", value: resp.data[0].Longitude)
                send(name: "dateObserved", value: resp.data[0].DateObserved)
                send(name: "hourObserved", value: resp.data[0].HourObserved)

                log.debug("Successfully retrieved air quality data from AirNow.")
            } else {
                log.debug("Failed to retrieve valid air quality data from AirNow.")
            }
        }

    }
    catch (SocketTimeoutException e) {
        log.error("Connection to AirNow API timed out.")
        send(name: "reportingLocation", value: "Connection timed out while retrieving data from AirNow")
    }
    catch (e) {
        log.error("Could not retrieve AirNow data: $e")
        send(name: "reportingLocation", value: "Could not retrieve data: check API key in device settings")
    }

}

else {
    log.warn "No AirNow API key specified."
    send(name: "reportingLocation", value: "No AirNow API key specified in device settings")
}

}

def refresh() { poll() }

def configure() { poll() }

private send(map) { //log.debug "AirNow: event: $map" sendEvent(map) }

jschlackman commented 4 years ago

@edfrahm, you were pretty much spot on here, the only changes I've made from your suggestions were to update some comments and use subscript for PM₁₀

The only reason I missed this originally is that PM₁₀ data isn't published in my region, so I never saw example data for it. I've committed v1.2, so please go ahead and test. Unfortunately it will only be visible in the SmartThings Classic app which is being sunset very soon, but data should still be visible in WebCore until SmartThings drop support for custom DTHs as well.