Air Gradient integration with Maker API

Air Gradient Direct Integration with HE

This integration will allow you to run a virtual driver in HE that can be upgraded by the Air Gradient DIY Air quality Sensor.

What will you need to complete this:

  1. A computer with a usb cable and the Adruino IDE to flash updates to the Air Gradient Kit
  2. The Air Gradient virtual Driver driver from the provided link
  3. A instance of Maker API that can share the device.

To start with install the virtual driver, Create a virtual device with that driver, and then share it through your instance of Maker API.

Collect your Maker API instance API Token from the Maker API configuration page. Retrieve your Device ID number by browsing into the device and documenting it's number that is displayed in your browsers address bar. Also copy the "Send Device Command" Url provided in the lower part of the screen

For my purposes I am using the Air Gradient DIY Pro Kit Presoldered version. This integration may need to be slightly modified depending on weather you get the basic kit or the Pro kit. You may also need to tweak this if you add sensors to your setup.

The brains of the Air Quality Monitor is a Wemos D1 Min. This device can be tweaked from the Adriuno IDE. At this point you should reference the appropriate documentation from Air Gradient to download and install the Adruino IDE and be prepared to flash your DIY Kit. The directions from Air Gradient can be found here. You will want to go through the setup of the IDE and get prepped to flash your Wemos d1 Mini through the USB port. When you are prepared you will want to make the following adjustments to you adruino sketch.

Near the top of the sketch provided by Air Gradient there is a configuration section. Update the endpoint configuration to point to your Maker API instance that your sensor will work from. The easiest way to do this would be to past in the URL you copied out earlier for the "Send Device Command", then delete everything following /device/. The last slash needs to be there. Now add right below that the vlaues for the API Token and the Device id.

It should look something like this:
image

Now scroll down towards the bottom until you find the section for "sendToServer"

void sendToServer() {
   if (currentMillis - previoussendToServer >= sendToServerInterval) {
     previoussendToServer += sendToServerInterval;
      String values = String(Co2)
      + "," + String(pm25)
      + "," + String(TVOC)
      + "," + String(temp)
      + "," + String(hum);

      if(WiFi.status()== WL_CONNECTED){
//        Serial.println(payload);
        String POSTURL = APIROOT + DEVID + "/update/" + values + "?access_token=" + APITOKEN;
        Serial.println(POSTURL);
        WiFiClient client;
        HTTPClient http;
        http.begin(client,POSTURL);
//        http.addHeader("content-type", "application/json");
        int httpCode = http.GET();
        String response = http.getString();
        Serial.println(httpCode);
        Serial.println(response);
        http.end();
      }
      else {
        Serial.println("WiFi Disconnected");
      }
   }
} 

Once this is done save the sketch and then upload it to the device.

Once it comes back up you will want to go through the setup on the Air Gradient and associate the sensor to the same network your Hub is on. If you have more then one Sensor all you need to do is adjust the device id the next time you flash

2 Likes

I have updated the Sketch to only publish updates to Hubitat once a minute. the default setting in the sketch is every 10 seconds and this can be a bit much. Both are still published on Github.

The whole sketch has also been updated to use the latest code from Air Gradient as there have been many stability improvements in the sketch.

@mavrrick58 Thank you for posting and sharing that, it has helped me a lot.

I've used a simplified version of your driver and elements from the Air Gradient sketch to pull data from an ESP32 wired directly to a Plantower PMS7003.
It's a low-tech variant that now captures PM1.0, PM2.5 and PM10 readings and sends them to the hub every 30-seconds.

1 Like

Any plans to update the driver to the newest V9 AirGradient ONE?

It is still a work in progress, and needs to have CO2 calibration and LED indicator controls implemented.

1 Like

My suggestion would be to simply take the DIY Driver and to add in what you need. The driver is rather generic on the Hubitat side.

I don't have the new unit to create a driver and validate the device talk properly.

If anyone make progress on an AirGradient V9 integration, would like to participate.

1 Like

There were some big changes between the version I coded for before and the new v9 one. One of the big things is that it looks like the new version supports MQTT. If that is true then it shouldn't be to hard at all to build a driver. That said I don't have one of the newer ones to develop to.

Edit
@richards.roger
I dug into their site a bit more and found a few things about the new version.

  1. it looks like for MQTT to be used you need a broker setup not just a client. So i am not sure a driver will work.
  2. There is also a option for Polling. Can you try the below URL and see what you get back

http://airgradient_ecda3b1eaaaf.local/measures/current

“ecda3b1eaaaf” being the serial number of your monitor. You may also be able to replace the airgradient_ecda3b1eaaaf.local with the devices IP address.

What I am expecting is a return that is probably in json format.

@richards.roger

If you are still interested, I can get this done fairly quickly I think. I just updated the firmware on my Air Gradient Open Air sensor. That is the external one, and I hadn't thought about it for a while, but then realized it supported the new firmware and I could get this going. I don't have all the sensor values you will but I have found good documentation for it so it shouldn't be to hard to do. I already updated the driver for the Open Air sensor that I have. I just have some testing and cleanup to do in the driver

It may even be possible to send configuration changes from Hubitat.

@richards.roger

This is very ruff but take a look at this..

/**
 *  Air Gradient Universal Driver with Polling
 *
 *  Copyright 2016 
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
import groovy.json.JsonSlurper
metadata
{
    definition (name: "Air Gradient Universal Driver", namespace: "mavrrick", author: "Mavrrick")
    {
		capability "TemperatureMeasurement"
		capability "RelativeHumidityMeasurement"
        capability "CarbonDioxideMeasurement"
        capability "Polling"
        capability "Initialize"
        
        attribute "rssi", "number"
        attribute "pm01", "number"		
		attribute "pm25", "number"
        attribute "pm10", "number"
        attribute "pmCount", "number"
        attribute "tvoc", "number"
        attribute "nox", "number"
                          
	}

	preferences
	{	
		section
		{
            input("pollRate", "number", title: "Polling Rate (seconds)\nDefault:300", defaultValue:300, submitOnChange: true, width:4)
            input("ip", "text", title: "IP Address", description: "IP address of your Govee light", required: false)
			input "cOrF", "bool", title: "Turn on to show value in Farenhite", defaultValue: false, required: false		
            input "enableDebug", "bool", title: "Enables debug logging for 30 minutes", defaultValue: false, required: false
		}
	}
}


def installed()
{
	log.info "Air Gradient is loaded. Waiting for updates"
	settingsInitialize()
}


def updated()
{
	log.info "Air Gradient is loaded. Waiting for updates"
	settingsInitialize()
}


def settingsInitialize()
{
	if (enableDebug)
	{
		log.info "Verbose logging has been enabled for the next 30 minutes."
		runIn(1800, logsOff)
	}
}

//	runs when HUB boots, starting the device refresh cycle, if any
void initialize()
	{
	log.info "Air Gradient Driver is initializing"
    if (pollRate > 0) {
        pollRateInt = pollRate.toInteger()
        runIn(pollRateInt,poll)
    }        
}

/*
    Retrieve data states from device
*/
def poll() {
    def params = [
        uri   : "http://"+ip,
        path  : '/measures/current',
        contentType: "application/json"   
    ]  
    httpGet(params) { resp ->
        if (debugLog) {log.debug "poll(): Response Data is "+resp.data}
        if (resp.status == 200) {
            log.debug "Poll(): succesful poll parsing data to apply to device"
            resp.data.each {
                if (debugLog) {log.debug "poll(): Respone Data part "+it.key}
                if (debugLog) {log.debug "poll(): Respone Data part "+it.value}
                if (it.key == "pm01") {
                    sendEvent(name: "pm01", value: it.value)
                } else if (it.key == "pm02") {
                    sendEvent(name: "pm25", value: it.value)
                } else if (it.key == "pm10") {
                    sendEvent(name: "pm10", value: it.value)
                }  else if (it.key == "pm003Count") {
                    sendEvent(name: "pmCount", value: it.value)
                } else if (it.key == "rhumCompensated") {
                    sendEvent(name: "humidity", value: it.value)
                } else if (it.key == "wifi") {
                    sendEvent(name: "rssi", value: it.value)
                } else if (it.key == "rco2") {
                    sendEvent(name: "carbonDioxide", value: it.value)
                } else if (it.key == "tvocIndex") {
                    sendEvent(name: "tvoc", value: it.value)
                }  else if (it.key == "noxIndex") {
                    sendEvent(name: "nox", value: it.value)
                } else if (it.key == "atmpCompensated") {
                    float temp = it.value
                    if (cOrF) { temperature=((temp*9/5)+32).round(1) 
                        sendEvent(name: "temperature", value: temperature)
                    } else sendEvent(name: "temperature", value: temperature)
                } else {
                    if (debugLog) {log.debug "Unknown parm to parse ${it.key} = ${it.value}"}
                }
            }
        } 
    }
    if (pollRate > 0) {
        pollRateInt = pollRate.toInteger()
        runIn(pollRateInt,poll)
    }
}    


/*
	logsOff
    
	Disables debug logging.
*/
def logsOff()
{
    log.warn "debug logging disabled..."
	device.updateSetting("enableDebug", [value:"false",type:"bool"])
}

I know it says outdoor driver, but I added stuff that may cover all of the things you are looking for for the internal one as well. Let me know how this goes for you

Thank you! Will test it tomorrow and give you feedback.

Actually thank you for letting me know.

I actually just updated the code snippet above as I found something with the polling scheduling I missed.

This new driver just polls the device and parses the return data to post updates to the device. It should be fairly quick to poll and is actually a much simpler way to integrate the device. No more need for Maker API. The downside is that on some older devices it currently requires Beta firmware. My old v3.3 PCB DIY Pro kit has to use the beta firmware, but it seems to work fine, and the polling functions as expected.

One thing that seems to have been dropped in more recent firmware is providing a AQI value. I suspect that it wasn't very useful since calculating AQI varies by region and country and what sensors a device has. I may bring the old code in, but want to do a bit more research on how it is calculated before i bring that forward in the driver.

I have further enhanced the driver. I have the code posted on my github at raw.githubusercontent.com/Mavrrick/Hubitat-by-Mavrrick/main/AirGradient/AirGradientUniversalDriver.Groovy

I added several of the configuration options that are allowed with the new API. There are a few more left to implement, but they are mainly for triggering actions instead of simply setting config values. I will go ahead and get this in HPM when I can to make this easier.

It is in HPM now and I have added the two last configuration commands to do a Co2 Calibration and test the LED strip. I look forward to hearing how it goes when you start to test this as what I can do is fairly limited. I have converted both of my sensors though to this driver.

Finally tried it out - works great! Thank You!
One question: when I put the v device on a dashboard, the only sensor data shown is Temp, CO2, and Humidity. Any way to get other sensor data such as the PMs?

Depends on what dashboard type you are using. The items you mention that work are included by device capability while the PM, NOx, and tVOC values are custom create attributes.

The EZ dashboards are driven by know capabilities to identify expected tile layout and data. The more advanced older dashboard have some ability for custom attributes. I am not heavy into dashboards, so not 100% sure on how to do it, but seems it should be possible with the older dashboard type.