mirror of
				https://github.com/eclipse/upm.git
				synced 2025-11-04 00:54:21 +03:00 
			
		
		
		
	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>
		
	
		
			
				
	
	
		
			197 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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;
 | 
						|
}
 |