Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PurpleAir is discontinuing the API this plugin uses #14

Open
nashnix opened this issue Apr 21, 2022 · 8 comments
Open

PurpleAir is discontinuing the API this plugin uses #14

nashnix opened this issue Apr 21, 2022 · 8 comments

Comments

@nashnix
Copy link

nashnix commented Apr 21, 2022

PurpleAir has made the change and this plugin will no longer work. There is a workaround here.


As per their Using PurpleAir data document, PurpleAir is migrating all users to a new API at https://api.purpleair.com/, and will soon be blocking all access to purpleair.com/data.json and purpleair.com/json.

Users will also need to contact PurpleAir by email to get a key to access the new API, which would need this plugin to provide a place to provide that key.


Next day edit:
There may be a workaround; the sensor has a webserver open on port 80, and from that the user can find a string of data that gets reported out to PurpleAir (it can be found at http://192.xxx.xxx.xxx/json). I'm not sure how to go about doing this (my java-fu is very weak, and so far I've only got an error log a mile long to show for it), but if this string could be accessed and the readings extracted, then that would eliminate the problem of the API access altogether. There's also the option to expose the PM1.0 reading, dewpoint, and air pressure, and make these available to the user.

There'd be a bit of calculation involved though to make the values match what the PurpleAir map shows; there's the humidity and temperature divergence issues that have already been raised, but also there are two particulate sensors in the device, and this method exposes both of them as-is, so that would need to be taken care of as well (but I'm pretty sure they just average them, unless one of the sensors dies).

@NathanBlais
Copy link

Looks like Purple Air has implemented the change. 😦

@nashnix
Copy link
Author

nashnix commented May 27, 2022

Please use the code from Schweigi below - don't use my awful kludge here.


@NathanBlais Good news! If you get HTTP Advanced Accessory, you can use the sensor's webserver to extract the air quality readings without needing the API.

The annoying news is that you'll have to code it directly into the JSON configuration yourself, and I haven't worked out any way to combine the A and B sensors into a single averaged reading the way PurpleAir does (it should be simple; just add this to that and divide by two, but I can't work out how to make that happen). So you'll get two air quality sensors out of this. Also, HTTP-AA really doesn't like extracting the temperature values for reasons I can't figure out.

That said, to save you a world of grief, my code to do this is below, refined from a lot of painful trial-and-error. It works so long as the sensor is connected to the home network, even if the internet is not available. I've used the more hardline Tasmanian standards for when to cut off the ratings like "excellent" (that's the code of "if(value >= 0 && value <= 9){value = 1}" - adjust them as needed).

What you need to do to make this code work:

There are two things you have to do to make this code work for you; first replace every instance of XXX.XXX.XXX.XXX with the IP address of your PurpleAir sensor (no login or key is needed), and second assign the sensor a static IP in your router, or you'll have to do that every time it changes IP address.

Otherwise, you should just be able to copy and paste this into the appropriate place. Note this code will pull the live values.

The code for the JSON:

{
"accessories": [
	{
		"accessory": "HttpAdvancedAccessory",
		"name": "PurpleAir Channel A",
		"service": "AirQualitySensor",
		"debug": false,
		"forceRefreshDelay": 20,
		"uriCallsDelay": 1000,
		"optionCharacteristic": [
			"PM2.5Density",
			"PM10Density"
		],
		"urls": {
			"getAirQuality": {
				"url": "http://XXX.XXX.XXX.XXX/json",
				"mappers": [
					{
						"type": "jpath",
						"parameters": {
							"jpath": "$.pm2_5_atm"
						}
					},
					{
						"type": "eval",
						"parameters": {
							"expression": "value = parseFloat(value); if(value >= 0 && value <= 9){value = 1} else if (value > 9 && value <= 24) {value = 2} else if (value > 24 && value <= 35) {value = 3} else if (value > 35 && value <= 50) {value = 4} else if (value > 50 ) {value = 5} else {value = 0}"
						}
					}
				]
			},
			"getPM2.5Density": {
				"url": "http://XXX.XXX.XXX.XXX/json",
				"mappers": [
					{
						"type": "jpath",
						"parameters": {
							"jpath": "$.pm2_5_atm"
						}
					},
					{
						"type": "eval",
						"parameters": {
							"expression": "value = parseFloat(value)"
						}
					}
				]
			},
			"getPM10Density": {
				"url": "http://XXX.XXX.XXX.XXX/json",
				"mappers": [
					{
						"type": "jpath",
						"parameters": {
							"jpath": "$.pm10_0_atm"
						}
					},
					{
						"type": "eval",
						"parameters": {
							"expression": "value = parseFloat(value)"
						}
					}
				]
			}
		}
	},
	{
		"accessory": "HttpAdvancedAccessory",
		"name": "PurpleAir Channel B",
		"service": "AirQualitySensor",
		"debug": false,
		"forceRefreshDelay": 20,
		"uriCallsDelay": 1000,
		"optionCharacteristic": [
			"PM2.5Density",
			"PM10Density"
		],
		"urls": {
			"getAirQuality": {
				"url": "http://XXX.XXX.XXX.XXX/json",
				"mappers": [
					{
						"type": "jpath",
						"parameters": {
							"jpath": "$.pm2_5_atm_b"
						}
					},
					{
						"type": "eval",
						"parameters": {
							"expression": "value = parseFloat(value); if(value >= 0 && value <= 9){value = 1} else if (value > 9 && value <= 24) {value = 2} else if (value > 24 && value <= 35) {value = 3} else if (value > 35 && value <= 50) {value = 4} else if (value > 50 ) {value = 5} else {value = 0}"
						}
					}
				]
			},
			"getPM2.5Density": {
				"url": "http://XXX.XXX.XXX.XXX/json",
				"mappers": [
					{
						"type": "jpath",
						"parameters": {
							"jpath": "$.pm2_5_atm_b"
						}
					},
					{
						"type": "eval",
						"parameters": {
							"expression": "value = parseFloat(value)"
						}
					}
				]
			},
			"getPM10Density": {
				"url": "http://XXX.XXX.XXX.XXX/json",
				"mappers": [
					{
						"type": "jpath",
						"parameters": {
							"jpath": "$.pm10_0_atm_b"
						}
					},
					{
						"type": "eval",
						"parameters": {
							"expression": "value = parseFloat(value)"
						}
					}
				]
			}
		}
	}
],
"platforms": []
}

@Schweigi
Copy link

Schweigi commented Jun 4, 2022

@nashnix - Instead of using the jpath mapper, best to only use the eval mapper and parse the JSON response from PurpleAir into a JS object. That way you can easily do the average calculation between both channels in JS code inside the eval mapper. See sample JSON config below for HTTP Advanced Accessory plugin.

{
    "accessory": "HttpAdvancedAccessory",
    "name": "PurpleAir",
    "service": "AirQualitySensor",
    "debug": false,
    "forceRefreshDelay": 60,
    "uriCallsDelay": 1000,
    "optionCharacteristic": [
        "PM2.5Density",
        "PM10Density"
    ],
    "urls": {
        "getAirQuality": {
            "url": "http://XXX.XXX.XXX.XXX/json",
            "mappers": [
                {
                    "type": "eval",
                    "parameters": {
                        "expression": "response = JSON.parse(value); value = (response['pm2.5_aqi'] + response['pm2.5_aqi_b'])/2"
                    }
                },
                {
                    "type": "eval",
                    "parameters": {
                        "expression": "if (value >= 0 && value <= 50) {value = 1} else if (value <= 100) {value = 2} else if (value <= 150) {value = 3} else if (value <= 200) {value = 4} else if (value > 200) {value = 5} else {value = 0}"
                    }
                }
            ]
        },
        "getPM2.5Density": {
            "url": "http://XXX.XXX.XXX.XXX/json",
            "mappers": [
                {
                    "type": "eval",
                    "parameters": {
                        "expression": "response = JSON.parse(value); value = (response['pm2_5_atm'] + response['pm2_5_atm_b'])/2"
                    }
                }
            ]
        },
        "getPM10Density": {
            "url": "http://XXX.XXX.XXX.XXX/json",
            "mappers": [
                {
                    "type": "eval",
                    "parameters": {
                        "expression": "response = JSON.parse(value); value = (response['pm10_0_atm'] + response['pm10_0_atm_b'])/2"
                    }
                }
            ]
        }
    }
}

@nashnix
Copy link
Author

nashnix commented Jun 4, 2022

@Schweigi Thank you very much for that! I've replaced my horror mess with your code, and it looks like it's working wonderfully (unfortunately the air here is really clear right now - so I can't tell if the values are actually 0, or if something went wrong and it's just saying that).

You can probably tell that I was pretty much just throwing myself at it (and trying to learn this coding language) until I found something that sort-of-worked. The result is the brutal kludge you see in my comment above.

@adresner
Copy link

adresner commented Jul 1, 2022

I tried to use the code above, edited the xxx IP for the local IP of my purple. I could not save the code.. it would just hang in its saving mode for a few hours ..

Any suggestions? thank you

@nashnix
Copy link
Author

nashnix commented Jul 1, 2022

@adresner Are you using my awful kludge at here, or @Schweigi's excellent code at here?

With my awful kludge, you can just copy and paste it and swap out the XXX.XXX.XXX.XXX. But Schweigi's needs a couple little changes made to it to make it play happily with the HTTP AA thing.

I've copied my (working) code directly from my config.json file in full; if you swap out the XXX.XXX.XXX.XXX in the code below with the local IP of your Purple, and just paste it in via the advanced configuration page, it should work:

{
    "accessories": [
        {
            "accessory": "HttpAdvancedAccessory",
            "name": "PurpleAir",
            "service": "AirQualitySensor",
            "debug": false,
            "forceRefreshDelay": 60,
            "uriCallsDelay": 1000,
            "optionCharacteristic": [
                "PM2.5Density",
                "PM10Density"
            ],
            "urls": {
                "getAirQuality": {
                    "url": "http://XXX.XXX.XXX.XXX/json",
                    "mappers": [
                        {
                            "type": "eval",
                            "parameters": {
                                "expression": "response = JSON.parse(value); value = (response['pm2.5_aqi'] + response['pm2.5_aqi_b'])/2"
                            }
                        },
                        {
                            "type": "eval",
                            "parameters": {
                                "expression": "value = parseFloat(value); if(value >= 0 && value <= 15){value = 1} else if (value > 15 && value <= 40) {value = 2} else if (value > 40 && value <= 80) {value = 3} else if (value > 80 && value <= 120) {value = 4} else if (value > 120 ) {value = 5} else {value = 0}"
                            }
                        }
                    ]
                },
                "getPM2.5Density": {
                    "url": "http://XXX.XXX.XXX.XXX/json",
                    "mappers": [
                        {
                            "type": "eval",
                            "parameters": {
                                "expression": "response = JSON.parse(value); value = (response['pm2_5_atm'] + response['pm2_5_atm_b'])/2"
                            }
                        }
                    ]
                },
                "getPM10Density": {
                    "url": "http://XXX.XXX.XXX.XXX/json",
                    "mappers": [
                        {
                            "type": "eval",
                            "parameters": {
                                "expression": "response = JSON.parse(value); value = (response['pm10_0_atm'] + response['pm10_0_atm_b'])/2"
                            }
                        }
                    ]
                }
            }
        }
    ],
    "platforms": []
}

One last note; I used the Tasmanian values for what is good and bad air, so it's a bit more sensitive to that than most others.

@adresner
Copy link

adresner commented Jul 6, 2022

Thank you for helping...

The code you shared allowed HOOBS to save the file under advanced. I changed the xxx to my local IP of my purple.

But now, nothing showing up in home.app I even deleted and re-added the bridge. My Hoobs is fully up to date as well... frustrating - any other ideas?

Cheers

@nashnix
Copy link
Author

nashnix commented Jul 6, 2022

Hey @adresner,

Sorry to hear it's not cooperating.

Here's a step-by-step of what I did to get mine working. If you follow this, it ought to behave:

  1. First I went to http://XXX.XXX.XXX.XXX - the IP of my PurpleAir - just to make sure I had the right one and that it was connected to my network. You should get an image like this (behind the blue square is the ID number of your PurpleAir, behind the black is the name of your WiFi network - the signal strength is to the right of it):

image

  1. Then I went to http://XXX.XXX.XXX.XXX/json to ensure that was loading. You should see a long string of text that starts {"SensorId": and is followed by the MAC address of your PurpleAir sensor.

If you're seeing this, great! The sensor is connected and the plug-in should be able to get to it. If you can't, then something is blocking access; check your wifi, your firewalls, make sure things are on the same subnet - all that sort of thing.

  1. Next, through the HOOBS Desktop app (not the web interface, their official app; you can get it here for free) go through the plugins interface and find "Http Advanced Accessory". In mine, the graphic looks like a little yellow set of tetris blocks, the author is "tasict", the version is 1.2.2, and the Last Publish is December 15th, 2021.

It is helpful if you have a second screen here; open the HOOBS interface in a browser like you normally would and set it to the log page so you can see what's happening. Do your configuration in the app, watch what happens via a browser.

  1. Install this plugin on its own child bridge.

  2. Once that's done, click on the gear symbol in the bottom left of the screen.

  3. Go to the option "advanced"

  4. Click on the name of the bridge you installed Http Advanced Accessory on.

  5. Delete everything in the box in the app, and then paste in the code in from my post here, exactly as it appears. If there's any errant brackets it just won't work.

  6. Replace all the XXX.XXX.XXX.XXX with your PurpleAir's IP address (there are three), and click save. Make sure it's not set to https.

  7. The screen will blank out - don't worry! This is normal while it configures itself. You should see a pile of activity in the log as it figures out what it's doing. The bridge and the plugin and the rest may take a few minutes to sort itself out and behave. If the log is throwing out errors like it can't find the IP address, make sure you got the right one - my first go I had an extra 1.

  8. Once the app comes back, you can go and add the bridge; click on the Bridges option on the left, and add the bridge. Make sure you tell Homekit you don't care it's not certified.

It should work now, but be warned that it'll be added to some random room. It could be "Default Room". The first time I did this it got added to the bedroom; the second time it was the kitchen. You may have to go on a hunt to find the thing (and if you have another air quality monitor, it could be hiding behind that one, because of course it does).

Hope it works!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants