upm/src/ppd42ns/ppd42ns.c
Noel Eck db3c73cbd1 utilities: Update time/r methods for LINUX
Default to MONOTONIC clock for timer methods to avoid falling victim to
clock corrections.  Changed signatures from accepting pointers since
this is not needed an complicates calls and Java/JS/Python bindings.

    * Switched from nanosleep to clock_nanosleep to allow developers to
      provide a clock for LINUX
    * Default upm_clock_init to CLOCK_MONOTONIC
    * Updated logic to calculating delay and elapsed to be more readable
    * Added ns flavors for completeness
    * Refactored all upm_* delay/timer methods
    * Added #else for preprocessor cases w/o an #else
    * Added test for AQI
    * Added test fixture with logic to identify a minimum delay time
      which is used as a metric for testing all delay methods
    * Much more lenient unit testing of delays to minimize false CI
      failures

Signed-off-by: Noel Eck <noel.eck@intel.com>
2018-07-31 14:20:30 -07:00

197 lines
5.9 KiB
C

/*
* Author: Jon Trulson <jtrulson@ics.com>
* Contributions: Rex Tsai <rex.cc.tsai@gmail.com>
* Abhishek Malik <abhishek.malik@intel.com>
* Copyright (c) 2016 Intel Corporation.
*
* Rewritten Based on original C++ driver written by:
* Author: Zion Orent <sorent@ics.com>
* Copyright (c) 2014 Intel Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <upm_math.h>
#include <upm_utilities.h>
#include "ppd42ns.h"
// Returns the amount of time it takes a pin to go from HIGH to LOW or
// from LOW to HIGH
static uint32_t ppd42ns_pulse_in(const ppd42ns_context dev,
bool high_low_value);
double pcs2ugm3 (double concentration_pcs);
ppd42ns_context ppd42ns_init(int pin)
{
ppd42ns_context dev =
(ppd42ns_context)malloc(sizeof(struct _ppd42ns_context));
if (!dev)
return NULL;
dev->gpio = NULL;
// make sure MRAA is initialized
int mraa_rv;
if ((mraa_rv = mraa_init()) != MRAA_SUCCESS)
{
printf("%s: mraa_init() failed (%d).\n", __FUNCTION__, mraa_rv);
ppd42ns_close(dev);
return NULL;
}
// MRAA contexts...
if ( !(dev->gpio = mraa_gpio_init(pin)) )
{
printf("%s: mraa_gpio_init() failed\n",
__FUNCTION__);
ppd42ns_close(dev);
return NULL;
}
mraa_gpio_dir(dev->gpio, MRAA_GPIO_IN);
return dev;
}
void ppd42ns_close(ppd42ns_context dev)
{
assert(dev != NULL);
if (dev->gpio)
mraa_gpio_close(dev->gpio);
free(dev);
}
ppd42ns_dust_data ppd42ns_get_data(const ppd42ns_context dev)
{
assert(dev != NULL);
ppd42ns_dust_data data;
// in ms, 30 seconds
const unsigned int pulse_check_time = 30000;
// loop timer
upm_clock_t max_loop_time;
unsigned int low_pulse_occupancy = 0;
max_loop_time = upm_clock_init();
do {
low_pulse_occupancy += ppd42ns_pulse_in(dev, 0);
} while (upm_elapsed_ms(&max_loop_time) < pulse_check_time);
// Store dust data
// Integer percentage 0=>100
double ratio = (float)low_pulse_occupancy
/ ((float)pulse_check_time * 10.0);
// using spec sheet curve
double concentration = (1.1 * pow(ratio,3)) - (3.8 * pow(ratio, 2))
+ (520 * ratio) + 0.62;
data.lowPulseOccupancy = low_pulse_occupancy;
data.ratio = ratio;
data.concentration = concentration;
data.ugm3 = pcs2ugm3(data.concentration);
data.aqi = upm_ugm3_to_aqi(data.ugm3);
return data;
}
// Mimicking Arduino's pulseIn function
// return how long it takes a pin to go from HIGH to LOW or LOW to HIGH
static uint32_t ppd42ns_pulse_in(const ppd42ns_context dev,
bool high_low_value)
{
assert(dev != NULL);
// we run for no more than 1 second at a time
upm_clock_t pulse_time;
uint32_t total_pulse_time = 0;
upm_clock_t max_time = upm_clock_init();
bool pin_level;
bool is_timing = false;
do {
pin_level = (bool)mraa_gpio_read(dev->gpio);
if (!is_timing && pin_level == high_low_value)
{
// level is desired level, but not currently timing
pulse_time = upm_clock_init();
is_timing = true;
}
else if (is_timing && pin_level != high_low_value)
{
// we started timing, but level changed
total_pulse_time += upm_elapsed_us(&pulse_time);
is_timing = false;
}
else
{
// not timing and/or level is not equal to desired level
// so we "wait".
upm_delay_us(10);
}
} while (upm_elapsed_ms(&max_time) < 1000); // 1 second
if (is_timing)
{
// if we were still timing when the loop expired, add the
// accumulated time.
total_pulse_time += upm_elapsed_us(&pulse_time);
}
return total_pulse_time;
}
// Assumes density, shape, and size of dust to estimate mass concentration from particle count.
//
// This method was described in a 2009 paper
// Preliminary Screening System for Ambient Air Quality in Southeast Philadelphia by Uva, M., Falcone, R., McClellan, A., and Ostapowicz, E.
// https://www.yumpu.com/en/document/view/31692906/preliminary-screening-system-for-ambient-air-quality-in-southeast-
//
// This method does not use the correction factors, based on the presence of humidity and rain in the paper.
//
// convert from particles/0.01 ft3 to ug/m3
double pcs2ugm3 (double concentration_pcs)
{
double pi = 3.14159;
// All particles are spherical, with a density of 1.65E12 ug/m3
double density = 1.65 * pow (10, 12);
// The radius of a particle in the PM2.5 channel is .44 um
double r25 = 0.44 * pow (10, -6);
double vol25 = (4/3) * pi * pow (r25, 3);
double mass25 = density * vol25; // ug
double K = 3531.5; // per m^3
return concentration_pcs * K * mass25;
}