From d0bd9cfdd559305e39aeffb28a7771ecbdbc90e2 Mon Sep 17 00:00:00 2001 From: Tomas Date: Tue, 29 Jul 2014 19:08:42 +0200 Subject: [PATCH] New barometer plugin --- README | 7 + configure.ac | 21 + src/Makefile.am | 9 + src/barometer.c | 1375 +++++++++++++++++++++++++++++++++++++++++++++++++ src/collectd.conf.in | 13 +- src/collectd.conf.pod | 125 +++++ src/types.db | 1 + 7 files changed, 1550 insertions(+), 1 deletion(-) create mode 100644 src/barometer.c diff --git a/README b/README index bcedb62e..0e2a6772 100644 --- a/README +++ b/README @@ -32,6 +32,10 @@ Features - ascent Statistics about Ascent, a free server for the game `World of Warcraft'. + - barometer + Using digital barometer sensor MPL115A2 or MPL3115 from Freescale provides + absolute barometric pressure, air pressure reduced to sea level and temperature. + - battery Batterycharge, -current and voltage of ACPI and PMU based laptop batteries. @@ -617,6 +621,9 @@ Prerequisites If present, the uuid plugin will check for UUID from HAL. + * libi2c-dev (optional) + Used for the plugin `barometer', provides just the i2c-dev.h header file for user space i2c development. + * libiptc (optional) For querying iptables counters. diff --git a/configure.ac b/configure.ac index 1907b859..aa95d3f3 100644 --- a/configure.ac +++ b/configure.ac @@ -4890,6 +4890,7 @@ dependency_warning="no" dependency_error="no" plugin_ascent="no" +plugin_barometer="no" plugin_battery="no" plugin_bind="no" plugin_cgroups="no" @@ -5028,6 +5029,23 @@ then plugin_tape="yes" fi +# libi2c-dev +with_libi2c="no" +if test "x$ac_system" = "xLinux" +then +AC_CHECK_DECL(i2c_smbus_read_i2c_block_data, + [with_libi2c="yes"], + [with_libi2c="no (symbol i2c_smbus_read_i2c_block_data not found - have you installed libi2c-dev ?)"], + [[#include + #include ]]) +fi + +if test "x$with_libi2c" = "xyes" +then + plugin_barometer="yes" +fi + + # libstatgrab if test "x$with_libstatgrab" = "xyes" then @@ -5224,6 +5242,7 @@ AC_PLUGIN([apcups], [yes], [Statistics of UPSes by APC]) AC_PLUGIN([apple_sensors], [$with_libiokit], [Apple's hardware sensors]) AC_PLUGIN([aquaero], [$with_libaquaero5], [Aquaero's hardware sensors]) AC_PLUGIN([ascent], [$plugin_ascent], [AscentEmu player statistics]) +AC_PLUGIN([barometer], [$plugin_barometer], [Barometer sensor on I2C]) AC_PLUGIN([battery], [$plugin_battery], [Battery statistics]) AC_PLUGIN([bind], [$plugin_bind], [ISC Bind nameserver statistics]) AC_PLUGIN([conntrack], [$plugin_conntrack], [nf_conntrack statistics]) @@ -5518,6 +5537,7 @@ Configuration: libesmtp . . . . . . $with_libesmtp libganglia . . . . . $with_libganglia libgcrypt . . . . . . $with_libgcrypt + libi2c-dev . . . . . $with_libi2c libiokit . . . . . . $with_libiokit libiptc . . . . . . . $with_libiptc libjvm . . . . . . . $with_java @@ -5573,6 +5593,7 @@ Configuration: aquaero . . . . . . . $enable_aquaero apple_sensors . . . . $enable_apple_sensors ascent . . . . . . . $enable_ascent + barometer . . . . . . $enable_barometer battery . . . . . . . $enable_battery bind . . . . . . . . $enable_bind conntrack . . . . . . $enable_conntrack diff --git a/src/Makefile.am b/src/Makefile.am index 84affcbf..b8a272a0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -219,6 +219,15 @@ collectd_LDADD += "-dlopen" ascent.la collectd_DEPENDENCIES += ascent.la endif +if BUILD_PLUGIN_BAROMETER +pkglib_LTLIBRARIES += barometer.la +barometer_la_SOURCES = barometer.c +barometer_la_LDFLAGS = -module -avoid-version +barometer_la_LIBADD = -lm +collectd_LDADD += "-dlopen" barometer.la +collectd_DEPENDENCIES += barometer.la +endif + if BUILD_PLUGIN_BATTERY pkglib_LTLIBRARIES += battery.la battery_la_SOURCES = battery.c diff --git a/src/barometer.c b/src/barometer.c new file mode 100644 index 00000000..95b05f4e --- /dev/null +++ b/src/barometer.c @@ -0,0 +1,1375 @@ +/** + * collectd - src/barometer.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; only version 2.1 of the License is + * applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Tomas Menzl + **/ + +#include "collectd.h" +#include "common.h" +#include "utils_cache.h" +#include "plugin.h" + +#include +#include +#include +#include +#include + +/* ------------ MPL115 defines ------------ */ +/* I2C address of the MPL115 sensor */ +#define MPL115_I2C_ADDRESS 0x60 + +/* register addresses */ +#define MPL115_ADDR_CONV 0x00 +#define MPL115_ADDR_COEFFS 0x04 + +/* register sizes */ +#define MPL115_NUM_CONV 4 +#define MPL115_NUM_COEFFS 12 + +/* commands / addresses */ +#define MPL115_CMD_CONVERT_PRESS 0x10 +#define MPL115_CMD_CONVERT_TEMP 0x11 +#define MPL115_CMD_CONVERT_BOTH 0x12 + +#define MPL115_CONVERSION_RETRIES 5 + + +/* ------------ MPL3115 defines ------------ */ +/* MPL3115 I2C address */ +#define MPL3115_I2C_ADDRESS 0x60 + +/* register addresses (only the interesting ones) */ +#define MPL3115_REG_STATUS 0x00 +#define MPL3115_REG_OUT_P_MSB 0x01 +#define MPL3115_REG_OUT_P_CSB 0x02 +#define MPL3115_REG_OUT_P_LSB 0x03 +#define MPL3115_REG_OUT_T_MSB 0x04 +#define MPL3115_REG_OUT_T_LSB 0x05 +#define MPL3115_REG_DR_STATUS 0x06 +#define MPL3115_REG_WHO_AM_I 0x0C +#define MPL3115_REG_SYSMOD 0x11 +#define MPL3115_REG_PT_DATA_CFG 0x13 +#define MPL3115_REG_BAR_IN_MSB 0x14 +#define MPL3115_REG_BAR_IN_LSB 0x15 +#define MPL3115_REG_CTRL_REG1 0x26 +#define MPL3115_REG_CTRL_REG2 0x27 +#define MPL3115_REG_CTRL_REG3 0x28 +#define MPL3115_REG_CTRL_REG4 0x29 +#define MPL3115_REG_CTRL_REG5 0x2A +#define MPL3115_REG_OFF_P 0x2B +#define MPL3115_REG_OFF_T 0x2C +#define MPL3115_REG_OFF_H 0x2D + +/* Register values, masks */ +#define MPL3115_WHO_AM_I_RESP 0xC4 + +#define MPL3115_PT_DATA_DREM 0x04 +#define MPL3115_PT_DATA_PDEF 0x02 +#define MPL3115_PT_DATA_TDEF 0x01 + +#define MPL3115_DR_STATUS_TDR 0x02 +#define MPL3115_DR_STATUS_PDR 0x04 +#define MPL3115_DR_STATUS_PTDR 0x08 +#define MPL3115_DR_STATUS_DR (MPL3115_DR_STATUS_TDR | MPL3115_DR_STATUS_PDR | MPL3115_DR_STATUS_PTDR) + +#define MPL3115_DR_STATUS_TOW 0x20 +#define MPL3115_DR_STATUS_POW 0x40 +#define MPL3115_DR_STATUS_PTOW 0x80 + +#define MPL3115_CTRL_REG1_ALT 0x80 +#define MPL3115_CTRL_REG1_RAW 0x40 +#define MPL3115_CTRL_REG1_OST_MASK 0x38 +#define MPL3115_CTRL_REG1_OST_1 0x00 +#define MPL3115_CTRL_REG1_OST_2 0x08 +#define MPL3115_CTRL_REG1_OST_4 0x10 +#define MPL3115_CTRL_REG1_OST_8 0x18 +#define MPL3115_CTRL_REG1_OST_16 0x20 +#define MPL3115_CTRL_REG1_OST_32 0x28 +#define MPL3115_CTRL_REG1_OST_64 0x30 +#define MPL3115_CTRL_REG1_OST_128 0x38 +#define MPL3115_CTRL_REG1_RST 0x04 +#define MPL3115_CTRL_REG1_OST 0x02 +#define MPL3115_CTRL_REG1_SBYB 0x01 +#define MPL3115_CTRL_REG1_SBYB_MASK 0xFE + +#define MPL3115_NUM_CONV_VALS 5 + + +/* ------------ Normalization ------------ */ +/* Mean sea level pressure normalization methods */ +#define MSLP_NONE 0 +#define MSLP_INTERNATIONAL 1 +#define MSLP_DEU_WETT 2 + +/** Temperature reference history depth for averaging. See #get_reference_temperature */ +#define REF_TEMP_AVG_NUM 5 + +/* ------------------------------------------ */ +static const char *config_keys[] = +{ + "Device", + "Oversampling", + "PressureOffset", /**< only for MPL3115 */ + "TemperatureOffset", /**< only for MPL3115 */ + "Altitude", + "Normalization", + "TemperatureSensor" +}; + +static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); + +static char * config_device = NULL; /**< I2C bus device */ +static int config_oversample = 1; /**< averaging window */ + +static double config_press_offset = 0.0; /**< pressure offset */ +static double config_temp_offset = 0.0; /**< temperature offset */ + +static double config_altitude = NAN; /**< altitude */ +static int config_normalize = 0; /**< normalization method */ + +static _Bool configured = 0; /**< the whole plugin config status */ + +static int i2c_bus_fd = -1; /**< I2C bus device FD */ + +static _Bool is_MPL3115 = 0; /**< is this MPL3115? */ +static __s32 oversample_MPL3115 = 0; /**< MPL3115 CTRL1 oversample setting */ + + +/* MPL115 conversion coefficients */ +static double mpl115_coeffA0; +static double mpl115_coeffB1; +static double mpl115_coeffB2; +static double mpl115_coeffC12; +static double mpl115_coeffC11; +static double mpl115_coeffC22; + +/* ------------------------ averaging ring buffer ------------------------ */ +/* Used only for MPL115. MPL3115 supports real oversampling in the device so */ +/* no need for any postprocessing. */ + +static _Bool avg_initialized = 0; /**< already initialized by real values */ + +typedef struct averaging_s { + long int * ring_buffer; + int ring_buffer_size; + long int ring_buffer_sum; + int ring_buffer_head; +} averaging_t; + + +static averaging_t pressure_averaging = { NULL, 0, 0L, 0 }; +static averaging_t temperature_averaging = { NULL, 0, 0L, 0 }; + + +/** + * Create / allocate averaging buffer + * + * The buffer is initialized with zeros. + * + * @param avg pointer to ring buffer to be allocated + * @param size requested buffer size + * + * @return Zero when successful + */ +static int averaging_create(averaging_t * avg, int size) +{ + int a; + + avg->ring_buffer = (long int *) malloc(size * sizeof(*avg)); + if (avg->ring_buffer == NULL) + { + ERROR ("barometer: averaging_create - ring buffer allocation of size %d failed", + size); + return -1; + } + + for (a=0; aring_buffer[a] = 0L; + } + + avg->ring_buffer_size = size; + avg->ring_buffer_sum = 0L; + avg->ring_buffer_head = 0; + + return 0; +} + + +/** + * Delete / free existing averaging buffer + * + * @param avg pointer to the ring buffer to be deleted + */ +static void averaging_delete(averaging_t * avg) +{ + if (avg->ring_buffer != NULL) + { + free(avg->ring_buffer); + avg->ring_buffer = NULL; + } + avg->ring_buffer_size = 0; + avg->ring_buffer_sum = 0L; + avg->ring_buffer_head = 0; +} + + +/* + * Add new sample to the averaging buffer + * + * A new averaged value is returned. Note that till the buffer is full + * returned value is inaccurate as it is an average of real values and initial + * zeros. + * + * @param avg pointer to the ring buffer + * @param sample new sample value + * + * @return Averaged sample value + */ +static double averaging_add_sample(averaging_t * avg, long int sample) +{ + double result; + + avg->ring_buffer_sum += sample - avg->ring_buffer[avg->ring_buffer_head]; + avg->ring_buffer[avg->ring_buffer_head] = sample; + avg->ring_buffer_head = (avg->ring_buffer_head+1) % avg->ring_buffer_size; + result = (double)(avg->ring_buffer_sum) / (double)(avg->ring_buffer_size); + + DEBUG ("barometer: averaging_add_sample - added %ld, result = %lf", + sample, + result); + + return result; +} + + +/* ------------------------ temperature refference ------------------------ */ + +/** + * Linked list type of temperature sensor references + */ +typedef struct temperature_list_s { + char * sensor_name; /**< sensor name/reference */ + size_t num_values; /**< number of values (usually one) */ + _Bool initialized; /**< sensor already provides data */ + struct temperature_list_s * next; /**< next in the list */ +} temperature_list_t; + +static temperature_list_t * temp_list = NULL; + + +/* + * Add new sensor to the temperature reference list + * + * @param list the list + * @param sensor reference name (as provided by the config file) + * + * @return Zero when successful + */ +static int temp_list_add(temperature_list_t * list, const char * sensor) +{ + temperature_list_t * new_temp; + + new_temp = (temperature_list_t *) malloc(sizeof(*new_temp)); + if(new_temp == NULL) + return -1; + + new_temp->sensor_name = strdup(sensor); + new_temp->initialized = 0; + new_temp->num_values = 0; + if(new_temp->sensor_name == NULL) + { + free(new_temp); + return -1; + } + + new_temp->next = temp_list; + temp_list = new_temp; + return 0; +} + + +/* + * Delete the whole temperature reference list + * + * @param list the list to be deleted + */ +static void temp_list_delete(temperature_list_t ** list) +{ + temperature_list_t * tmp; + + while (*list != NULL) + { + tmp = (*list); + (*list) = (*list)->next; + free(tmp->sensor_name); + free(tmp); + tmp = NULL; + } +} + + +/* + * Get reference temperature value + * + * First initially uc_get_rate_by_name is tried. At the startup due to nondeterministic + * order the temperature may not be read yet (then it fails and first measurment gives + * only absolute air pressure reading which is acceptable). Once it succedes (should be + * second measurement at the latest) we use average of few last readings from + * uc_get_history_by_name. It may take few readings to start filling so again we use + * uc_get_rate_by_name as a fallback. + * The idea is to use basic "noise" filtering (history averaging) across all the values + * which given sensor provides (up to given depth). Then we get minimum among + * the sensors. + * + * @param result where the result is stored. When not available NAN is stored. + * + * @return Zero when successful + */ +static int get_reference_temperature(double * result) +{ + temperature_list_t * list = temp_list; + + gauge_t * values = NULL; /**< rate values */ + size_t values_num = 0; /**< number of rate values */ + int i; + + gauge_t values_history[REF_TEMP_AVG_NUM]; + + double avg_sum; /**< Value sum for computing average */ + int avg_num; /**< Number of values for computing average */ + double average; /**< Resulting value average */ + + *result = NAN; + + while(list != NULL) + { + avg_sum = 0.0; + avg_num = 0; + + /* First time need to read current rate to learn how many values are + there (typically for temperature it would be just one). + We do not expect dynamic changing of number of temperarure values + in runtime yet (are there any such cases?). */ + if(!list->initialized) + { + if(uc_get_rate_by_name(list->sensor_name, + &values, + &values_num)) + { + DEBUG ("barometer: get_reference_temperature - rate \"%s\" not found yet", + list->sensor_name); + list = list->next; + continue; + } + + DEBUG ("barometer: get_reference_temperature - initialize \"%s\", %zu vals", + list->sensor_name, + values_num); + + list->initialized = 1; + list->num_values = values_num; + + for(i=0; isensor_name, + values_history, + REF_TEMP_AVG_NUM, + list->num_values)) + { + ERROR ("barometer: get_reference_temperature - history \"%s\" lost", + list->sensor_name); + list->initialized = 0; + list->num_values = 0; + list = list->next; + continue; + } + + for(i=0; inum_values; ++i) + { + DEBUG ("barometer: get_reference_temperature - history %d: %lf", + i, + values_history[i]); + if(!isnan(values_history[i])) + { + avg_sum += values_history[i]; + ++avg_num; + } + } + + if(avg_num == 0) /* still no history? fallback to current */ + { + if(uc_get_rate_by_name(list->sensor_name, + &values, + &values_num)) + { + ERROR ("barometer: get_reference_temperature - rate \"%s\" lost", + list->sensor_name); + list->initialized = 0; + list->num_values = 0; + list = list->next; + continue; + } + + for(i=0; isensor_name); + list->initialized = 0; + list->num_values = 0; + } + else + { + average = avg_sum / (double) avg_num; + if(isnan(*result)) + *result=average; + else if(*result>average) + *result=average; + } + list = list->next; + } /* while sensor list */ + + if(*result == NAN) + { + ERROR("barometer: get_reference_temperature - no sensor available (yet?)"); + return -1; + } + DEBUG ("barometer: get_reference_temperature - temp is %lf", *result); + return 0; +} + +/* ------------------------ MPL115 access ------------------------ */ + +/** + * Read the MPL115 sensor conversion coefficients. + * + * These are (device specific) constants so we can read them just once. + * + * @return Zero when successful + */ +static int MPL115_read_coeffs(void) +{ + uint8_t mpl115_coeffs[MPL115_NUM_COEFFS]; + int32_t res; + + int8_t sia0MSB, sia0LSB, sib1MSB, sib1LSB, sib2MSB, sib2LSB; + int8_t sic12MSB, sic12LSB, sic11MSB, sic11LSB, sic22MSB, sic22LSB; + int16_t sia0, sib1, sib2, sic12, sic11, sic22; + + char errbuf[1024]; + + res = i2c_smbus_read_i2c_block_data(i2c_bus_fd, + MPL115_ADDR_COEFFS, + MPL115_NUM_COEFFS, + mpl115_coeffs); + if (res < 0) + { + ERROR ("barometer: read_mpl115_coeffs - problem reading data: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + + /* Using perhaps less elegant/efficient code, but more readable. */ + /* a0: 16total 1sign 12int 4fract 0pad */ + sia0MSB = mpl115_coeffs[0]; + sia0LSB = mpl115_coeffs[1]; + sia0 = (int16_t) sia0MSB <<8; /* s16 type, Shift to MSB */ + sia0 += (int16_t) sia0LSB & 0x00FF; /* Add LSB to 16bit number */ + mpl115_coeffA0 = (double) (sia0); + mpl115_coeffA0 /= 8.0; /* 3 fract bits */ + + /* b1: 16total 1sign 2int 13fract 0pad */ + sib1MSB= mpl115_coeffs[2]; + sib1LSB= mpl115_coeffs[3]; + sib1 = sib1MSB <<8; /* Shift to MSB */ + sib1 += sib1LSB & 0x00FF; /* Add LSB to 16bit number */ + mpl115_coeffB1 = (double) (sib1); + mpl115_coeffB1 /= 8192.0; /* 13 fract */ + + /* b2: 16total 1sign 1int 14fract 0pad */ + sib2MSB= mpl115_coeffs[4]; + sib2LSB= mpl115_coeffs[5]; + sib2 = sib2MSB <<8; /* Shift to MSB */ + sib2 += sib2LSB & 0x00FF; /* Add LSB to 16bit number */ + mpl115_coeffB2 = (double) (sib2); + mpl115_coeffB2 /= 16384.0; /* 14 fract */ + + /* c12: 14total 1sign 0int 13fract 9pad */ + sic12MSB= mpl115_coeffs[6]; + sic12LSB= mpl115_coeffs[7]; + sic12 = sic12MSB <<8; /* Shift to MSB only by 8 for MSB */ + sic12 += sic12LSB & 0x00FF; + mpl115_coeffC12 = (double) (sic12); + mpl115_coeffC12 /= 4.0; /* 16-14=2 */ + mpl115_coeffC12 /= 4194304.0; /* 13+9=22 fract */ + + /* c11: 11total 1sign 0int 11fract 11pad */ + sic11MSB= mpl115_coeffs[8]; + sic11LSB= mpl115_coeffs[9]; + sic11 = sic11MSB <<8; /* Shift to MSB only by 8 for MSB */ + sic11 += sic11LSB & 0x00FF; + mpl115_coeffC11 = (double) (sic11); + mpl115_coeffC11 /= 32.0; /* 16-11=5 */ + mpl115_coeffC11 /= 4194304.0; /* 11+11=22 fract */ + + /* c12: 11total 1sign 0int 10fract 15pad */ + sic22MSB= mpl115_coeffs[10]; + sic22LSB= mpl115_coeffs[11]; + sic22 = sic22MSB <<8; /* Shift to MSB only by 8 for MSB */ + sic22 += sic22LSB & 0x00FF; + mpl115_coeffC22 = (double) (sic22); + mpl115_coeffC22 /= 32.0; //16-11=5 + mpl115_coeffC22 /= 33554432.0; /* 10+15=25 fract */ + + DEBUG("barometer: read_mpl115_coeffs: a0=%lf, b1=%lf, b2=%lf, c12=%lf, c11=%lf, c22=%lf", + mpl115_coeffA0, + mpl115_coeffB1, + mpl115_coeffB2, + mpl115_coeffC12, + mpl115_coeffC11, + mpl115_coeffC22); + return 0; +} + + +/* + * Convert raw adc values to real data using the sensor coefficients. + * + * @param adc_pressure adc pressure value to be converted + * @param adc_temp adc temperature value to be converted + * @param pressure computed real pressure + * @param temperature computed real temperature + */ +static void MPL115_convert_adc_to_real(double adc_pressure, + double adc_temp, + double * pressure, + double * temperature) +{ + double Pcomp; + Pcomp = mpl115_coeffA0 + \ + (mpl115_coeffB1 + mpl115_coeffC11*adc_pressure + mpl115_coeffC12*adc_temp) * adc_pressure + \ + (mpl115_coeffB2 + mpl115_coeffC22*adc_temp) * adc_temp; + + *pressure = ((1150.0-500.0) * Pcomp / 1023.0) + 500.0; + *temperature = (472.0 - adc_temp) / 5.35 + 25.0; + DEBUG ("barometer: convert_adc_to_real - got %lf hPa, %lf C", + *pressure, + *temperature); +} + + +/** + * Read sensor averegaed measurements + * + * @param pressure averaged measured pressure + * @param temperature averaged measured temperature + * + * @return Zero when successful + */ +static int MPL115_read_averaged(double * pressure, double * temperature) +{ + uint8_t mpl115_conv[MPL115_NUM_CONV]; + int8_t res; + int retries; + int conv_pressure; + int conv_temperature; + double adc_pressure; + double adc_temperature; + char errbuf[1024]; + + *pressure = 0.0; + *temperature = 0.0; + + /* start conversion of both temp and presure */ + retries = MPL115_CONVERSION_RETRIES; + while (retries>0) + { + /* write 1 to start conversion */ + res = i2c_smbus_write_byte_data (i2c_bus_fd, + MPL115_CMD_CONVERT_BOTH, + 0x01); + if (res >= 0) + break; + + --retries; + if(retries>0) + { + ERROR ("barometer: MPL115_read_averaged - requesting conversion: %s, " \ + "will retry at most %d more times", + sstrerror (errno, errbuf, sizeof (errbuf)), + retries); + } + else + { + ERROR ("barometer: MPL115_read_averaged - requesting conversion: %s, "\ + "too many failed retries", + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + } + + usleep (10000); /* wait 10ms for the conversion */ + + retries=MPL115_CONVERSION_RETRIES; + while (retries>0) + { + res = i2c_smbus_read_i2c_block_data(i2c_bus_fd, + MPL115_ADDR_CONV, + MPL115_NUM_CONV, + mpl115_conv); + if (res >= 0) + break; + + --retries; + if (retries>0) + { + ERROR ("barometer: MPL115_read_averaged - reading conversion: %s, " \ + "will retry at most %d more times", + sstrerror (errno, errbuf, sizeof (errbuf)), + retries); + } + else + { + ERROR ("barometer: MPL115_read_averaged - reading conversion: %s, " \ + "too many failed retries", + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + } + + conv_pressure = ((mpl115_conv[0] << 8) | mpl115_conv[1]) >> 6; + conv_temperature = ((mpl115_conv[2] << 8) | mpl115_conv[3]) >> 6; + DEBUG ("barometer: MPL115_read_averaged, raw pressure ADC value = %d, " \ + "raw temperature ADC value = %d", + conv_pressure, + conv_temperature); + + adc_pressure = averaging_add_sample (&pressure_averaging, conv_pressure); + adc_temperature = averaging_add_sample (&temperature_averaging, conv_temperature); + + MPL115_convert_adc_to_real(adc_pressure, adc_temperature, pressure, temperature); + + DEBUG ("barometer: MPL115_read_averaged - averaged ADC pressure = %lf / temperature = %lf, " \ + "real pressure = %lf hPa / temperature = %lf C", + adc_pressure, + adc_temperature, + *pressure, + *temperature); + + return 0; +} + +/* ------------------------ MPL3115 access ------------------------ */ + +/** + * Detect presence of a MPL3115 pressure sensor by checking register "WHO AM I" + * + * @return 1 if MPL3115, 0 otherwise + */ +static int MPL3115_detect(void) +{ + __s32 res; + + res = i2c_smbus_read_byte_data(i2c_bus_fd, MPL3115_REG_WHO_AM_I); + if(res == MPL3115_WHO_AM_I_RESP) + { + DEBUG ("barometer: MPL3115_detect - positive detection"); + return 1; + } + + DEBUG ("barometer: MPL3115_detect - negative detection"); + return 0; +} + +/** + * Adjusts oversampling to values supported by MPL3115 + * + * MPL3115 supports only power of 2 in the range 1 to 128. + */ +static void MPL3115_adjust_oversampling(void) +{ + int new_val = 0; + + if(config_oversample > 100) + { + new_val = 128; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_128; + } + else if(config_oversample > 48) + { + new_val = 64; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_64; + } + else if(config_oversample > 24) + { + new_val = 32; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_32; + } + else if(config_oversample > 12) + { + new_val = 16; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_16; + } + else if(config_oversample > 6) + { + new_val = 8; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_8; + } + else if(config_oversample > 3) + { + new_val = 4; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_4; + } + else if(config_oversample > 1) + { + new_val = 2; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_2; + } + else + { + new_val = 1; + oversample_MPL3115 = MPL3115_CTRL_REG1_OST_1; + } + + DEBUG("barometer: correcting oversampling for MPL3115 from %d to %d", + config_oversample, + new_val); + config_oversample = new_val; +} + +/** + * Read sensor averegaed measurements + * + * @param pressure averaged measured pressure + * @param temperature averaged measured temperature + * + * @return Zero when successful + */ +static int MPL3115_read(double * pressure, double * temperature) +{ + __s32 res; + __s32 ctrl ; + __u8 data[MPL3115_NUM_CONV_VALS]; + long int tmp_value = 0; + char errbuf[1024]; + + /* Set Active - activate the device from standby */ + res = i2c_smbus_read_byte_data(i2c_bus_fd, MPL3115_REG_CTRL_REG1); + if (res < 0) + { + ERROR ("barometer: MPL3115_read - cannot read CTRL_REG1: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return 1; + } + ctrl = res; + res = i2c_smbus_write_byte_data(i2c_bus_fd, + MPL3115_REG_CTRL_REG1, + ctrl | MPL3115_CTRL_REG1_SBYB); + if (res < 0) + { + ERROR ("barometer: MPL3115_read - problem activating: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return 1; + } + + /* base sleep is 5ms x OST */ + usleep(5000 * config_oversample); + + /* check the flags/status if ready */ + res = i2c_smbus_read_byte_data(i2c_bus_fd, MPL3115_REG_STATUS); + if (res < 0) + { + ERROR ("barometer: MPL3115_read - cannot read status register: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return 1; + } + + while ((res & MPL3115_DR_STATUS_DR) != MPL3115_DR_STATUS_DR) + { + /* try some extra sleep... */ + usleep(10000); + + /* ... and repeat the check. The conversion has to finish sooner or later. */ + res = i2c_smbus_read_byte_data(i2c_bus_fd, MPL3115_REG_STATUS); + if (res < 0) + { + ERROR ("barometer: MPL3115_read - cannot read status register: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return 1; + } + } + + /* Now read all the data in one block. There is address autoincrement. */ + res = i2c_smbus_read_i2c_block_data(i2c_bus_fd, + MPL3115_REG_OUT_P_MSB, + MPL3115_NUM_CONV_VALS, + data); + if (res < 0) + { + ERROR ("barometer: MPL3115_read - cannot read data registers: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return 1; + } + + tmp_value = (data[0] << 16) | (data[1] << 8) | data[2]; + *pressure = ((double) tmp_value) / 4.0 / 16.0 / 100.0; + DEBUG ("barometer: MPL3115_read, absolute pressure = %lf hPa", *pressure); + + if(data[3] > 0x7F) + { + data[3] = ~data[3] + 1; + *temperature = data[3]; + *temperature = - *temperature; + } + else + { + *temperature = data[3]; + } + + *temperature += (double)(data[4]) / 256.0; + DEBUG ("barometer: MPL3115_read, temperature = %lf C", *temperature); + + return 0; +} + +/** + * Initialize MPL3115 for barometeric measurements + * + * @return 0 if successful + */ +static int MPL3115_init_sensor(void) +{ + __s32 res; + __s8 offset; + char errbuf[1024]; + + /* Reset the sensor. It will reset immediately without ACKing */ + /* the transaction, so no error handling here. */ + i2c_smbus_write_byte_data(i2c_bus_fd, + MPL3115_REG_CTRL_REG1, + MPL3115_CTRL_REG1_RST); + + /* wait some time for the reset to finish */ + usleep(100000); + + /* now it should be in standby already so we can go and configure it */ + + /* Set temperature offset. */ + /* result = ADCtemp + offset [C] */ + offset = (__s8) (config_temp_offset * 16.0); + res = i2c_smbus_write_byte_data(i2c_bus_fd, MPL3115_REG_OFF_T, offset); + if (res < 0) + { + ERROR ("barometer: MPL3115_init_sensor - problem setting temp offset: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + + /* Set pressure offset. */ + /* result = ADCpress + offset [hPa] */ + offset = (__s8) (config_press_offset * 100.0 / 4.0); + res = i2c_smbus_write_byte_data(i2c_bus_fd, MPL3115_REG_OFF_P, offset); + if (res < 0) + { + ERROR ("barometer: MPL3115_init_sensor - problem setting pressure offset: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + + /* Enable Data Flags in PT_DATA_CFG - flags on both pressure and temp */ + res = i2c_smbus_write_byte_data(i2c_bus_fd, + MPL3115_REG_PT_DATA_CFG, + MPL3115_PT_DATA_DREM \ + | MPL3115_PT_DATA_PDEF \ + | MPL3115_PT_DATA_TDEF); + if (res < 0) + { + ERROR ("barometer: MPL3115_init_sensor - problem setting PT_DATA_CFG: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + + /* Set to barometer with an OSR */ + res = i2c_smbus_write_byte_data(i2c_bus_fd, + MPL3115_REG_CTRL_REG1, + oversample_MPL3115); + if (res < 0) + { + ERROR ("barometer: MPL3115_init_sensor - problem configuring CTRL_REG1: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + + return 0; +} + + +/* ------------------------ Common functionality ------------------------ */ + +/** + * Convert absolute pressure (in hPa) to mean sea level pressure + * + * Implemented methods are: + * - MSLP_NONE - no converions, returns absolute pressure + * + * - MSLP_INTERNATIONAL - see http://en.wikipedia.org/wiki/Atmospheric_pressure#Altitude_atmospheric_pressure_variation + * Requires #config_altitude + * + * - MSLP_DEU_WETT - formula as recommended by the Deutsche Wetterdienst. See + * http://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Theorie + * Requires both #config_altitude and temperature reference(s). + * + * @param abs_pressure absloute pressure to be converted + * + * @return mean sea level pressure if successful, NAN otherwise + */ +static double abs_to_mean_sea_level_pressure(double abs_pressure) +{ + double mean = -1.0; + double temp = 0.0; + int result = 0; + + DEBUG ("barometer: abs_to_mean_sea_level_pressure: absPressure = %lf, method = %d", + abs_pressure, + config_normalize); + + if (config_normalize >= MSLP_DEU_WETT) + { + result = get_reference_temperature(&temp); + if(result) + { + return NAN; + } + } + + switch(config_normalize) + { + case MSLP_NONE: + mean = abs_pressure; + break; + + case MSLP_INTERNATIONAL: + mean = abs_pressure / \ + pow(1.0 - 0.0065*config_altitude/288.15, 0.0065*0.0289644/(8.31447*0.0065)); + break; + + case MSLP_DEU_WETT: + { + double E; /* humidity */ + double x; + if(temp<9.1) + E = 5.6402 * (-0.0916 + exp(0.06*temp) ); + else + E = 18.2194 * (1.0463 - exp(-0.0666*temp) ); + x = 9.80665 / (287.05 * (temp+273.15 + 0.12*E + 0.0065*config_altitude/2)) * config_altitude; + mean = abs_pressure * exp(x); + } + break; + + default: + ERROR ("barometer: abs_to_mean_sea_level_pressure: wrong conversion method %d", + config_normalize); + mean = abs_pressure; + break; + } + + return mean; +} + +/* ------------------------ main plugin callbacks ------------------------ */ + +/** + * Main plugin configuration callback (using simple config) + * + * @param key configuration key we should process + * @param value configuration value we should process + * + * @return Zero when successful. + */ +static int collectd_barometer_config (const char *key, const char *value) +{ + DEBUG("barometer: collectd_barometer_config"); + + if (strcasecmp (key, "Device") == 0) + { + sfree (config_device); + config_device = strdup (value); + } + else if (strcasecmp (key, "Oversampling") == 0) + { + int oversampling_tmp = atoi (value); + if (oversampling_tmp < 1 || oversampling_tmp > 1024) + { + WARNING ("barometer: collectd_barometer_config: invalid oversampling: %d." \ + " Allowed values are 1 to 1024 (for MPL115) or 128 (for MPL3115).", + oversampling_tmp); + return 1; + } + config_oversample = oversampling_tmp; + } + else if (strcasecmp (key, "Altitude") == 0) + { + config_altitude = atof (value); + } + else if (strcasecmp (key, "Normalization") == 0) + { + int normalize_tmp = atoi (value); + if (normalize_tmp < 0 || normalize_tmp > 2) + { + WARNING ("barometer: collectd_barometer_config: invalid normalization: %d", + normalize_tmp); + return 1; + } + config_normalize = normalize_tmp; + } + else if (strcasecmp (key, "TemperatureSensor") == 0) + { + if(temp_list_add(temp_list, value)) + { + return -1; + } + } + else if (strcasecmp (key, "PressureOffset") == 0) + { + config_press_offset = atof(value); + } + else if (strcasecmp (key, "TemperatureOffset") == 0) + { + config_temp_offset = atof(value); + } + else + { + return -1; + } + + return 0; +} + + +/** + * Shutdown callback. + * + * Close I2C and delete all the buffers. + * + * @return Zero when successful (at the moment the only possible outcome) + */ +static int collectd_barometer_shutdown(void) +{ + DEBUG ("barometer: collectd_barometer_shutdown"); + + if(!is_MPL3115) + { + averaging_delete (&pressure_averaging); + averaging_delete (&temperature_averaging); + + temp_list_delete(&temp_list); + } + + if (i2c_bus_fd > 0) + { + close (i2c_bus_fd); + i2c_bus_fd = -1; + sfree (config_device); + } + + return 0; +} + + +/** + * Plugin read callback for MPL115. + * + * Dispatching will create values: + * - /barometer-mpl115/pressure-normalized + * - /barometer-mpl115/pressure-absolute + * - /barometer-mpl115/temperature + * + * @return Zero when successful. + */ +static int MPL115_collectd_barometer_read (void) +{ + int result = 0; + + double pressure = 0.0; + double temperature = 0.0; + double norm_pressure = 0.0; + + value_list_t vl = VALUE_LIST_INIT; + value_t values[1]; + + DEBUG("barometer: MPL115_collectd_barometer_read"); + + if (!configured) + { + return -1; + } + + /* Rather than delaying init, we will intitialize during first read. This + way at least we have a better chance to have the reference temperature + already available. */ + if(!avg_initialized) + { + int i; + for(i=0; i/barometer-mpl3115/pressure-normalized + * - /barometer-mpl3115/pressure-absolute + * - /barometer-mpl3115/temperature + * + * @return Zero when successful. + */ +static int MPL3115_collectd_barometer_read (void) +{ + int result = 0; + + double pressure = 0.0; + double temperature = 0.0; + double norm_pressure = 0.0; + + value_list_t vl = VALUE_LIST_INIT; + value_t values[1]; + + DEBUG("barometer: MPL3115_collectd_barometer_read"); + + if (!configured) + { + return -1; + } + + result = MPL3115_read(&pressure, &temperature); + if(result) + return result; + + norm_pressure = abs_to_mean_sea_level_pressure(pressure); + + sstrncpy (vl.host, hostname_g, sizeof (vl.host)); + sstrncpy (vl.plugin, "barometer", sizeof (vl.plugin)); + sstrncpy (vl.plugin_instance, "mpl3115", sizeof (vl.plugin_instance)); + + vl.values_len = 1; + vl.values = values; + + /* dispatch normalized air pressure */ + sstrncpy (vl.type, "pressure", sizeof (vl.type)); + sstrncpy (vl.type_instance, "normalized", sizeof (vl.type_instance)); + values[0].gauge = norm_pressure; + plugin_dispatch_values (&vl); + + /* dispatch absolute air pressure */ + sstrncpy (vl.type, "pressure", sizeof (vl.type)); + sstrncpy (vl.type_instance, "absolute", sizeof (vl.type_instance)); + values[0].gauge = pressure; + plugin_dispatch_values (&vl); + + /* dispatch sensor temperature */ + sstrncpy (vl.type, "temperature", sizeof (vl.type)); + sstrncpy (vl.type_instance, "", sizeof (vl.type_instance)); + values[0].gauge = temperature; + plugin_dispatch_values (&vl); + + return 0; +} + + +/** + * Initialization callback + * + * Check config, initialize I2C bus access, conversion coefficients and averaging + * ring buffers + * + * @return Zero when successful. + */ +static int collectd_barometer_init (void) +{ + char errbuf[1024]; + + DEBUG ("barometer: collectd_barometer_init"); + + if (config_device == NULL) + { + ERROR("barometer: collectd_barometer_init I2C bus device not configured"); + return -1; + } + + if (config_normalize >= MSLP_INTERNATIONAL && isnan(config_altitude)) + { + ERROR("barometer: collectd_barometer_init no altitude configured " \ + "for mean sea level pressure normalization."); + return -1; + } + + if (config_normalize == MSLP_DEU_WETT + && + temp_list == NULL) + { + ERROR("barometer: collectd_barometer_init no temperature reference "\ + "configured for mean sea level pressure normalization."); + return -1; + } + + + i2c_bus_fd = open(config_device, O_RDWR); + if (i2c_bus_fd < 0) + { + ERROR ("barometer: collectd_barometer_init problem opening I2C bus device \"%s\": %s (is loaded mod i2c-dev?)", + config_device, + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + + if (ioctl(i2c_bus_fd, I2C_SLAVE_FORCE, MPL115_I2C_ADDRESS) < 0) + { + ERROR("barometer: collectd_barometer_init problem setting i2c slave address to 0x%02X: %s", + MPL115_I2C_ADDRESS, + sstrerror (errno, errbuf, sizeof (errbuf))); + return -1; + } + + /* detect sensor type - MPL115 or MPL3115 */ + is_MPL3115 = MPL3115_detect(); + + /* init correct sensor type */ + if(is_MPL3115) /* MPL3115 */ + { + MPL3115_adjust_oversampling(); + + if(MPL3115_init_sensor()) + return -1; + + plugin_register_read ("barometer", MPL3115_collectd_barometer_read); + } + else /* MPL115 */ + { + if (averaging_create (&pressure_averaging, config_oversample)) + { + ERROR("barometer: collectd_barometer_init pressure averaging init failed"); + return -1; + } + + if (averaging_create (&temperature_averaging, config_oversample)) + { + ERROR("barometer: collectd_barometer_init temperature averaging init failed"); + return -1; + } + + if (MPL115_read_coeffs() < 0) + return -1; + + plugin_register_read ("barometer", MPL115_collectd_barometer_read); + } + + configured = 1; + return 0; +} + +/* ------------------------ plugin register / entry point ------------------------ */ + +/** + * Plugin "entry" - register all callback. + * + */ +void module_register (void) +{ + plugin_register_config ("barometer", + collectd_barometer_config, + config_keys, + config_keys_num); + plugin_register_init ("barometer", collectd_barometer_init); + plugin_register_shutdown ("barometer", collectd_barometer_shutdown); +} diff --git a/src/collectd.conf.in b/src/collectd.conf.in index fc9b10c5..0dc571b1 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -87,6 +87,7 @@ #@BUILD_PLUGIN_APPLE_SENSORS_TRUE@LoadPlugin apple_sensors #@BUILD_PLUGIN_AQUAERO_TRUE@LoadPlugin aquaero #@BUILD_PLUGIN_ASCENT_TRUE@LoadPlugin ascent +#@BUILD_PLUGIN_BAROMETER_TRUE@LoadPlugin barometer #@BUILD_PLUGIN_BATTERY_TRUE@LoadPlugin battery #@BUILD_PLUGIN_BIND_TRUE@LoadPlugin bind #@BUILD_PLUGIN_CONNTRACK_TRUE@LoadPlugin conntrack @@ -258,7 +259,17 @@ # CACert "/etc/ssl/ca.crt" # -# +# +# Device "/dev/i2c-0"; +# Oversampling 512 +# PressureOffset 0.0 +# TemperatureOffset 0.0 +# Normalization 2 +# Altitude 238.0 +# TemperatureSensor "myserver/onewire-F10FCA000800/temperature" +# + +# # URL "http://localhost:8053/" # ParseTime false # OpCodes true diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 22e0d608..a86df335 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -812,6 +812,131 @@ and are checked by default depends on the distribution you use. =back +=head2 Plugin C + +This plugin reads absolute air pressure using digital barometer sensor MPL115A2 +or MPL3115 from Freescale (sensor attached to any I2C bus available in +the computer, for HW details see +I or +I). +The sensor type - one fo these two - is detected automatically by the plugin +and indicated in the plugin_instance (typically you will see subdirectory +"barometer-mpl115" or "barometer-mpl3115"). + +The plugin provides absolute barometric pressure, air pressure reduced to sea +level (several possible approximations) and as an auxiliary value also internal +sensor temperature. It uses (expects/provides) typical metric units - pressure +in [hPa], temperature in [C], altitude in [m]. + +It was developed and tested under Linux only. The only platform dependency is +the standard Linux i2c-dev interface (the particular bus driver has to +support the SM Bus command subset). + +The reduction or normalization to mean sea level pressure requires (depedning on +selected method/approximation) also altitude and reference to temperature sensor(s). +When multiple temperature sensors are configured the minumum of their values is +always used (expecting that the warmer ones are affected by e.g. direct sun light +at that moment). + +Synopsis: + + + Device "/dev/i2c-0"; + Oversampling 512 + PressureOffset 0.0 + TemperatureOffset 0.0 + Normalization 2 + Altitude 238.0 + TemperatureSensor "myserver/onewire-F10FCA000800/temperature" + + +=over 4 + +=item B I + +Device name of the I2C bus to which the sensor is connected. Note that typically +you need to have loaded the i2c-dev module. +Using i2c-tools you can check/list i2c buses available on your system by: + + i2cdetect -l + +Then you can scan for devices on given bus. E.g. to scan the whole bus 0 use: + + i2cdetect -y -a 0 + +This way you should be able to verify that the pressure sensor (either type) is +connected and detected on address 0x60. + +=item B I + +For MPL115 this is the size of the averaging window. To filter out sensor noise +a simple averaging using floating window of configurable size is used. The plugin +will use average of the last C measurements (value of 1 means no averaging). +Minimal size is 1, maximal 1024. + +For MPL3115 this is the oversampling value. The actual oversampling is performed +by the sensor and the higher value the higher accuracy and longer conversion time +(although nothing to worry about in the collectd context). Supported values are: +1, 2, 4, 8, 16, 32, 64 and 128. Any other value is adjusted by the plugin to +the closest supported one. Default is 128. + +=item B I + +You can further calibrate the sensor by supplying pressure and/or temperature offsets. +This is added to the measured/caclulated value (i.e. if the measured value is too high +then use negative offset). +In hPa, default is 0.0. + +=item B I + +You can further calibrate the sensor by supplying pressure and/or temperature offsets. +This is added to the measured/caclulated value (i.e. if the measured value is too high +then use negative offset). +In C, default is 0.0. + +=item B I + +Normalization method - what approximation/model is used to compute mean sea +level pressure from the air absolute pressure. + +Supported values of the C (integer between from 0 to 2) are: + +=over 5 + +=item B<0> - no conversion, absolute pressrure is simply copied over. For this method you + do not need to configure C or C. + +=item B<1> - international formula for conversion , +See I. +For this method you have to configure C but do not need C +(uses fixed global temperature average instead). + +=item B<2> - formula as recommended by the Deutsche Wetterdienst (German +Meteorological Service). +See I +For this method you have to configure both C and C. + +=back + + +=item B I + +The altitude (in meters) of the location where you meassure the pressure. + +=item B I + +Temperature sensor which should be used as a reference when normalizing the pressure. +When specified more sensors a minumum is found and uses each time. +The temperature reading directly from this pressure sensor/plugin +is typically not suitable as the pressure sensor +will be probably inside while we want outside temperature. +The collectd reference name is something like +/-/- +( is usually omitted when there is just single value type). +Or you can figure it out from the path of the output data files. + +=back + =head2 Plugin C Starting with BIND 9.5.0, the most widely used DNS server software provides diff --git a/src/types.db b/src/types.db index 97cc4cc0..06f6fd2e 100644 --- a/src/types.db +++ b/src/types.db @@ -134,6 +134,7 @@ ping_stddev value:GAUGE:0:65535 ping value:GAUGE:0:65535 players value:GAUGE:0:1000000 power value:GAUGE:0:U +pressure value:GAUGE:0:U protocol_counter value:DERIVE:0:U ps_code value:GAUGE:0:9223372036854775807 ps_count processes:GAUGE:0:1000000, threads:GAUGE:0:1000000 -- 2.11.0