NAME
Weather::PurpleAir::API -- Client interface to Purple Air air quality API
SYNOPSIS
use Weather::PurpleAir::API;
use Data::Dumper;
my $api = Weather::PurpleAir::API->new();
my @sensors = qw(38887 22961 26363);
my $report = $api->report(\@sensors);
for my $sensor_name (keys %$report) {
print join("\t", ($sensor_name, @{$report->{$sensor_name}})), "\n";
}
DESCRIPTION
Weather::PurpleAir::API
provides a convenient interface to the Purple Air air quality API. It will pull down data for specified sensors, transform them as desired (for instance, converting from raw PM2.5 concentration to USA EPA AQI number, averaging results from dual-sensor nodes, etc) and provide a concise report by sensor name.
The Purple Air map of sensors is located at https://www.purpleair.com/map.
This module is very much a work in progress and 0.x releases might not be suitable for use.
For a simple commandline script wrapping this module, see bin/aqi
in this package.
Please do not poll sensors too frequently or the Purple Air server might block your IP.
PACKAGE ATTRIBUTES
OBJECT ATTRIBUTES
sensor_hr
(hashref)-
Keys on sensor ID numbers, maps to sensor names.
It is empty until the
report()
method is called. name_hr
(hashref)-
Keys on sensor names, maps to sensor ID numbers.
It is empty until the
report()
method is called. ok
(string)-
Indicates status of most recent operation: "OK", "WARNING" or "ERROR".
* "OK" means everything completed as expected.
* "WARNING" means everything completed and possibly-useful data was returned, but something suspicious happened.
* "ERROR" means something went horribly wrong and the operation was unable to complete.
err
(string or arrayref)-
Set by WARNING and ERROR conditions, describes details of what went wrong.
n_err
(integer)-
Incremented every time an error occurs.
n_warn
(integer)-
Incremented every time a warning occurs.
ex
(exception or exception string)-
Set to $@ when an exception is caught (for instance, when a JSON string does not decode).
js_or
(JSON::MaybeXS
object reference)-
Contains a reference to the JSON decoder object used internally. May be overridden by the user when different behavior is desired. The default instance has parameters
ascii =
1, allow_nonref => 1, space_after => 1>.
METHODS
All functionality is available via a Weather::PurpleAir::API
object. Start with new
and use the resulting object to generate reports. Additional functionality (like finding sensors by name, or finding sensors near locations) will come in future releases.
new(%options)
Instantiates and returns a Weather::PurpleAir::API
object. Options passed here may be overridden by passing options to methods of the object.
All options have hopefully-sane defaults:
api_url
= string-
Override the URL at which the API is queried. Useful for testing, or if you have your own server.
Default is "https://www.purpleair.com/json?show=", to which the sensor ID is appended by the
sensor_url
method. average
= 0 or 1-
Averages AQI metrics from a node's sensors instead of returning the AQI metrics from each sensor in a node.
Default is 0 (do not average).
d
= 0 or 1-
Activate debugging logic, which will print horribly confusing things to STDOUT. Default is 0 (off).
g
= 0 or 1-
Indicate that reports should include a "GUESSING" entry, providing a pruned average of all results from all sensors. In future releases it might incorporate other heuristics (like using older cached values when bad metrics are detected).
The format of this entry is a little different from the sensor entries. Instead of $report->{GUESSING} referring to an array of AQI metrics, it refers to an array containing the guessed gestalt AQI metric and its standard deviation (a measure of statistical skew). The higher the standard deviation, the lower the confidence you should have in the guessed metric.
For example:
{ GUESSING => [123.45, 1.09]} # AQI is 123.45, with high confidence { GUESSING => [123.45, 10.3]} # AQI is 123.45, with somewhat less confidence { GUESSING => [123.45, 67.5]} # AQI is 123.45, with very low confidence!
Default is 0 (do not guess).
http_or
= HTTP::Tiny object-
Provide the
HTTP::Tiny
instance used to query the API. This is useful when you need to customize the query timeout, set a user agent string or specify an https proxy.Default is undef (an
HTTP::Tiny
object will be instantiated internally). no_errors
= 0 or 1-
Set this to suppress writing error messages to stderr. Default is 0 (errors will be displayed).
no_warnings
= 0 or 1-
Set this to suppress writing warning messages to stderr. Default is 0 (warnings will be displayed).
now
= 0 or 1-
Normally reports will use the ten-minute average AQI from each sensor. Specify
now
to use the current AQI instead.Default is 0 (report will use ten-minute average AQI metrics).
prune_threshold
= fraction between 0 and 1-
When the
g
(GUESSING) option is set, the guessing heuristic may prune more then one high and one low outlier from the sensor data, if doing so will leave sufficient data left over for meaningful averaging. The prune threshold determines how close to an outlier other data points must be, as a fraction of the outlier value, for them to be pruned as well.For instance, if
prune_threshold
= 0.1 (10%) and the high outlier is 90, then 90 * 0.1 = 9, so data points which are 81 or higher might also be pruned. Ifprune_threshold
= 0.05 (5%) and the high outlier is 90, 90 * 0.05 = 4.5, so data points which are 85.5 or higher might be pruned, etc.Default is 0.1 (10%).
q
= 0 or 1-
"
q
" is for "quiet". Setting this is equivalent to settingno_errors
andno_warnings
.Default is 0.
raw
= 0 or 1-
When set,
report
will not convert the API's raw concentration numbers to USA EPA PM2.5 AQI scores (the metric displayed on the Purple Air website map). Part-per-million concentrations of 2.5 micrometer diameter particles will be provided instead.Default is 0 (concentrations will be converted to USA EPA PM2.5 AQI scores).
sensor
= ID-numbersensor
= [ID-number, ID-number, ...]sensor
= "ID-number ID-number ..."-
Normally sensor IDs are passed to the
report
method, but a default sensor or sensors can also be given tonew
at object instantiation time.Right now
Weather::PurpleAir::API
can only work with numeric sensor IDs and there isn't a really good way to find the IDs of sensors. If you point a browser at the Purple Air map, some browsers will expose the IDs of specific sensors and others will not.If you download the all-sensors blob (either from the API at https://www.purpleair.com/json or the cached blob at http://ciar.org/h/sensors.all.json) you can pick through the list and find IDs of sensors of interest, which is a pain in the ass.
Future releases of
Weather::PurpleAir::API
will support functions for finding sensors near a location, but this one doesn't, which is one of the reasons it's a 0.x release.Some example sensors and their IDs:
25407 Gravenstein School 38887 Litchfield 22961 City Of Santa Rosa Laguna Treatment Plant 26363 Geek Orchard
The default is 25407 (Gravenstein School, in Sonoma County, California)
stash_path
= path string-
When a
stash_path
is provided,report
will store a copy of each sensor's JSON blob in the specified directory, under the nameaqi.$sensor_id.json
.For example, if
stash_path
= "/tmp" andsensor
= 25407, the JSON blob will be stored in file "/tmp/aqi.25407.json".The default is undef (not set, will not stash).
v
= 0 or 1-
"
v
" is for verbosity. When set, the reported-upon sensors (and gestalt guess, wheng
parameter is set) will be printed to stdout. This is normally set by thebin/aqi
utility.Future releases might implement higher verbosity levels.
The default is 0 (do not print to stdout).
report()
report([sensor-id, sensor-id, ...])
report([sensor-id, sensor-id, ...], {option =
value, ...})>
The report
method retrieves current data from each specified sensor and returns a hashref with sensor name keys and arrayref values. The values represent the air quality at the sensor's location (where higher values = more gunk in the air). Higher than 50 is bad, lower than 20 is good.
If no list of sensor IDs is used, and no list was provided to new(sensor =
...)> a default of 25407 (Gravenstein School) will be used, which probably is not very useful to you.
See the notes under sensor
in the section for new
regarding sensor IDs and how to find them.
report
optionally accepts a hashref options, which are the same as those documented for new
.
Returns undef
on error and sets the object's ok
and err
attributes.
Returns a hashref as described on success and sets the object's ok
and err
attributes to "OK" and "" respectively.
AUTHOR
TTK Ciar, <ttk[at]ciar[dot]org>
COPYRIGHT AND LICENSE
Copyright 2020 by TTK Ciar
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
ABOUT RELIABILITY
The sensor readings are not always reliable. A car starting or a person smoking near the sensor can produce a false high reading. Right now the workaround is to specify multiple sensors and use the -g option. Future releases will provide alternative remedies.
TO DO
Write more documentation. Some methods are undocumented.
Write more unit tests.
Save some state and perform time averaging, perhaps throw out dramatic changes and reuse last known value.
Improve upon the conversion of concentration metric US EPA PM2.5 AQI. It kind of sucks, especially at the low end.
Pull down and cache the all-sensors blob, implement relevant operations:
* find sensors by name
* find sensors nearest a latitude/longitude, or near another sensor, maybe do some light GIS-fu
HISTORY
This was originally a throw-away script that just yanked JSON from the API, parsed out the sensor name and first reading's PM2.5 concentration, and printed to stdout.
It was quickly apparent that this left something to be desired, so the script was refactored into this module and associated wrapper utility.
My friend Matt using the throw-away script made me feel embarrassed at its shortcomings, which provided some of the motivation to do better.
SEE ALSO
The PurpleAir API FAQ (stored in more convenient form by purpleairpy project)
Python API Client by ReagentX, providing a different approach to the interface.
Air Concentration to USA EPA PM2.5 AQI Calculator an unfortunately crude tool corresponding PM2.5 concentration to the AQI metric.