Commit 379482e72969f01c9a308a7af5c6592071c4368f

Authored by Frederik Lindenaar
1 parent 28daf07d

Added Bosch Sensortec I2C BME280/BMP280 sensor

Added support for additional measurements in addition to the temperature
Fixed handling of default I2C address
Updated documentation for the new I2C sensors (and fixed a few errors)
README.md
... ... @@ -25,7 +25,8 @@ This repository contains the following scripts:
25 25 * [check_otp](#check_otp)
26 26 plugin to monitor PrivacyIDEA (and LinOTP) OTP validation
27 27 * [check_temperature](#check_temperature)
28   - plugin to monitor the CPU temperature of a RaspberryPi or that of a DS18b20 1-wire sensor on a RaspberryPi
  28 + plugin to monitor the RaspberryPi CPU temperature or that of an 1-wire
  29 + (DS18b20) or I2C (MCP9080) sensor attached to a RaspberryPi
29 30 * [nagiosstatus](#nagiosstatus)
30 31 CGI-BIN script to report the status of nagios (to monitor nagios itself)
31 32  
... ... @@ -239,68 +240,121 @@ define service {
239 240 -------------------------------------------------------
240 241 Plugin (check) to monitor monitor the Raspberry Pi CPU temperature or that of a
241 242 temperature sensor connected to a RaspberryPi. This implementation currently
242   -supports the DS18B20 1-wire temperature sensor. Other methods and interfaces can
243   -be plugged in easily (just raise a request or provide a patch). For information
244   -on how to connect sensor to the RaspberryPi and to get it working please see
245   -[this Adafruit tutorial](
246   -https://learn.adafruit.com/adafruits-raspberry-pi-lesson-11-ds18b20-temperature-sensing).
247   -
248   -No setup is required to read the CPU temperature. To enable the 1-wire interface
249   -support on the RaspberryPi one can use the command:
250   -~~~
251   - sudo raspi-config nonint do_onewire 0
252   -~~~
253   -or use `raspi-config` in interactive mode (9. Advanced Options --> A9. 1-Wire).
254   -Please note that changing this requires a reboot.
255   -
256   -Installation for is straightforward, after installing the script on the server
257   -add the following to your Nagios `commands.cmd` configuration file:
258   -
  243 +supports the following sensors:
  244 + * 1-wire:
  245 + * Maxim Integrated [DS18B20](https://learn.adafruit.com/adafruits-raspberry-pi-lesson-11-ds18b20-temperature-sensing)
  246 + * I2C
  247 + * Bosch Sensortec [BME280](https://learn.adafruit.com/adafruit-bme280-humidity-barometric-pressure-temperature-sensor-breakout)
  248 + * Bosch Sensortec [BMP280](https://learn.adafruit.com/adafruit-bmp280-barometric-pressure-plus-temperature-sensor-breakout)
  249 + * Microchip [MCP9080](https://learn.adafruit.com/adafruit-mcp9808-precision-i2c-temperature-sensor-guide)
  250 +
  251 +Other methods and interfaces can be plugged in easily (just raise a request or
  252 +provide a patch). For information on how to connect sensor to the RaspberryPi
  253 +and to get it working please click on the links in the list above. As per these,
  254 +most sensors require some configuration to make them available:
  255 + * No setup is required to read the CPU temperature.
  256 + * To enable 1-wire interface support on the RaspberryPi use the command:
  257 + ~~~
  258 + sudo raspi-config nonint do_onewire 0
  259 + ~~~
  260 + or use `raspi-config` interactively (1. Interfacing Options --> P7. 1-Wire).
  261 + Please note that changing this requires a reboot.
  262 + * To enable I2C interface support on the RaspberryPi use the command:
  263 + ~~~
  264 + sudo raspi-config nonint do_i2c 0
  265 + ~~~
  266 + or use `raspi-config` interactively (1. Interfacing Options --> P5. I2C).
  267 + Please note that changing this requires a reboot.
  268 + The I2C interface also requires the `SMBus` or `SMBus2` library, to install
  269 + the `SMBus` library on Raspbian Linux run:
  270 + ~~~
  271 + sudo apt install python-smbus
  272 + ~~~
  273 + `SMBus2` is a pure Python implementation that requires system-wide or a
  274 + `virtualenv`-based installation, less trivial than installing the package.
  275 +
  276 +Configuration of Nagios to use the script is straightforward, after installing
  277 +the script on the server add the following to your Nagios `commands.cmd`
  278 +configuration file to enable checking the CPU temperature:
259 279 ~~~
260 280 # 'check_cpu_temperature' command definition to monitor CPU temperature in C
261 281 # parameters: warning (ARG1) and critical (ARG2) temperature in Celcius
262 282 define command {
263   - command_name check_temperature
  283 + command_name check_cpu_temperature
264 284 command_line [install_path]/plugins/check_temperature -w $ARG1$ -c $ARG2$ rpi_cpu
265 285 }
266 286  
267 287 # 'check_cpu_ftemperature' command definition to monitor CPU temperature in F
268 288 # parameters: warning (ARG1) and critical (ARG2) temperature in Celcius
269 289 define command {
270   - command_name check_temperature
  290 + command_name check_cpu_ftemperature
271 291 command_line [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$ rpi_cpu
272 292 }
  293 +~~~
273 294  
  295 +To monitor a supported temperature sensor on its default address, add:
  296 +~~~
274 297 # 'check_temperature' command definition to monitor a single temperature in C
275 298 # parameters: warning (ARG1) and critical (ARG2) temperature in Celcius
276 299 define command {
277   - command_name check_temperature
278   - command_line [install_path]/plugins/check_temperature -w $ARG1$ -c $ARG2$ w1_ds18b20
  300 + command_name check_cpu_temperature
  301 + command_line [install_path]/plugins/check_temperature -w $ARG1$ -c $ARG2$ <<sensor>>
279 302 }
280 303  
281 304 # 'check_ftemperature' command definition to monitor a single temperature in F
282 305 # parameters: warning (ARG1) and critical (ARG2) temperature in Farenheit
283 306 define command {
284   - command_name check_ftemperature
285   - command_line [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$ w1_ds18b20
  307 + command_name check_cpu_temperature_f
  308 + command_line [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$ <<sensor>>
286 309 }
  310 +~~~
  311 +With `<<sensor>>` replaced by the sensor, e.g. w1_ds18b20 for a 1-wire DS18B20,
  312 +i2c_mcp9808 for an I2C MCP9808 sensor or i2c_bme280 for an I2C BME280. Run the
  313 +
  314 +In case you have multiple sensors, add multiple definitions with different
  315 +values for `command_name`.
287 316  
  317 +If you need to pass on additional parameters, e.g. the sensor serial for an
  318 +1-wire DS18B20, you can do that like this:
  319 +~~~
288 320 # 'check_temperature_sensor' command definition to monitor a single temperature in C
289 321 # parameters: sensor serial (ARG1), warning (ARG2) and critical (ARG3) temperature in Celcius
290 322 define command {
291   - command_name check_temperature_sensor
  323 + command_name check_ds18b20_sensor
292 324 command_line [install_path]/plugins/check_temperature -w $ARG2$ -c $ARG3$ w1_ds18b20 -s $ARG1$
293 325 }
294 326  
295 327 # 'check_ftemperature_sensor' command definition to monitor a single temperature in F
296 328 # parameters: sensor serial (ARG1), warning (ARG2) and critical (ARG3) temperature in Farenheit
297 329 define command {
298   - command_name check_ftemperature_sensor
  330 + command_name check_ds18b20_sensor_f
299 331 command_line [install_path]/plugins/check_temperature -F -w $ARG2$ -c $ARG3$ w1_ds18b20 -s $ARG1$
300 332 }
301 333  
302 334 ~~~
303 335  
  336 +Likewise, to pass the I2C address for an I2C MCP9808 use something like:
  337 +~~~
  338 +# 'check_temperature_sensor' command definition to monitor a single temperature in C
  339 +# parameters: sensor address (ARG1), warning (ARG2) and critical (ARG3) temperature in Celcius
  340 +define command {
  341 + command_name check_mcp9808_sensor
  342 + command_line [install_path]/plugins/check_temperature -w $ARG2$ -c $ARG3$ i2c_mcp9808 -a $ARG1$
  343 +}
  344 +
  345 +# 'check_ftemperature_sensor' command definition to monitor a single temperature in F
  346 +# parameters: sensor address (ARG1), warning (ARG2) and critical (ARG3) temperature in Farenheit
  347 +define command {
  348 + command_name check_mcp9808_sensor_f
  349 + command_line [install_path]/plugins/check_temperature -F -w $ARG2$ -c $ARG3$ i2c_mcp9808 -a $ARG1$
  350 +}
  351 +
  352 +~~~
  353 +
  354 +For the list of available sensors, please run `check_temperature -h` and run
  355 +`check_temperature <<sensor>> -h` to get the list of accepted options for sensor
  356 +``<<sensor>>``.
  357 +
304 358 Make sure to replace `[install_path]/plugins` with the location of the script.
305 359 To use the it define a service check like below:
306 360  
... ... @@ -313,7 +367,7 @@ define service {
313 367 use generic-service
314 368 }
315 369  
316   -# check temperature in Celcius using a DS18B20 sensor connected to a RaspberryPi
  370 +# check temperature in Celcius using a sensor connected to a RaspberryPi
317 371 define service {
318 372 host hostname.mydomain.tld
319 373 service_description Check Temperature
... ... @@ -325,7 +379,15 @@ define service {
325 379 define service {
326 380 host hostname.mydomain.tld
327 381 service_description Check Temperature
328   - check_command check_temperature_sensor!0000a31ea3de!30!35
  382 + check_command check_ds18b20_sensor!0000a31ea3de!30!35
  383 + use generic-service
  384 +}
  385 +
  386 +# check temperature with MCP9808 sensor 0x19 connected to a RaspberryPi
  387 +define service {
  388 + host hostname.mydomain.tld
  389 + service_description Check Temperature
  390 + check_command check_mcp9808_sensor!0x19!30!35
329 391 use generic-service
330 392 }
331 393 ~~~
... ...
plugins/check_temperature
... ... @@ -33,6 +33,25 @@ PROG_VERSION=PROG_NAME + &#39; &#39; + VERSION
33 33  
34 34 CPU_SENSOR_DEV = '/sys/class/thermal/thermal_zone0/temp'
35 35 CPU_SENSOR_SCALE=1000
  36 +
  37 +I2C_BMX280_DEFAULT_ADDR=0x77
  38 +I2C_BMX280_CALIBRATE_ADDR=0x88
  39 +I2C_BMX280_CALIBRATE_LEN=24
  40 +I2C_BMX280_CAL_HUM0_ADDR=0xA1
  41 +I2C_BMX280_CAL_HUM_ADDR=0xE1
  42 +I2C_BMX280_CAL_HUM_LEN=7
  43 +I2C_BMX280_CONFIG_ADDR=0xF5
  44 +I2C_BMX280_CONFIG = 0xA0 # Stand_by time = 1000 ms
  45 +I2C_BMX280_CTRL_MEAS_ADDR=0xF4
  46 +I2C_BMX280_CTRL_MEAS=0x27 # Normal mode, Pressure
  47 + # and Temperature Oversampling rate = 1
  48 +I2C_BMX280_CTRL_HUM_ADDR=0xF2
  49 +I2C_BMX280_CTRL_HUM=1 # Humidity Oversampling rate = 1
  50 +I2C_BMX280_MEAS_ADDR=0xF7
  51 +I2C_BMX280_MEAS_LEN=8
  52 +I2C_BMX280_SENSOR_SCALE=5120.0
  53 +
  54 +I2C_MCP9808_DEFAULT_ADDR=0x18
36 55 I2C_MCP9808_CONFIG_ADDR=0x1
37 56 I2C_MCP9808_CONFIG = [ 0x00, 0x00 ] # continuous conversion (power-up default)
38 57 I2C_MCP9808_PRECISION_ADDR=0x08
... ... @@ -89,6 +108,11 @@ class SetLogFile(StoreAction):
89 108  
90 109 ###############################################################################
91 110  
  111 +def hex_int(string):
  112 + """Use int()'s auto-detection to parse 10-base and 16-base (0x..) numbers"""
  113 + return int(string, 0);
  114 +
  115 +
92 116 def convert_celcius(temp_read, scale = 1):
93 117 """Converts raw temperature sensore value to degrees Celcius"""
94 118 return float(temp_read) / float(scale)
... ... @@ -101,18 +125,9 @@ def convert_farenheit(temp_read, scale = 1):
101 125 CONVERT_FARENHEIT = ( convert_farenheit, 'F', 'Farenheit' )
102 126  
103 127  
104   -def isempty(string):
105   - """Checks whether string 'str' provided is unset or empty"""
106   - return string is None or len(string) == 0
107   -
108   -
109   -def hex_int(string):
110   - """Use int()'s auto-detection to parse 10-base and 16-base (0x..) numbers"""
111   - return int(string, 0);
112   -
113   -
  128 +####################[ Get CPU temperature of Raspberry Pi ]####################
114 129 def read_rpi_cpu_temp(args):
115   - """Reads CPU temperature and converts it to desired unit, returns temperature"""
  130 + """Reads CPU temperature ands returns it converted to desired unit"""
116 131 with open(args.file, 'r') as f:
117 132 lines = f.readlines()
118 133 logger.debug('Temperature sensor data read from %s: %s', f.name, lines)
... ... @@ -124,9 +139,8 @@ def read_rpi_cpu_temp(args):
124 139 return temp, 1
125 140  
126 141  
127   -def read_i2c_mcp9808_temp(args):
128   - """Returns temperature from I2C MCP9808 sensor in desired unit"""
129   -
  142 +###############[ Get I2C (sm)bus object and I2C device address ]###############
  143 +def i2c_get_smbus_devaddr(args):
130 144 try:
131 145 import smbus
132 146 except ImportError:
... ... @@ -134,31 +148,149 @@ def read_i2c_mcp9808_temp(args):
134 148 import smbus2 as smbus
135 149 except ImportError:
136 150 logger.critical("Unable to import either smbus or smbus2 library");
137   - raise ImportError("missing I2C library, please install python-smbus or smbus2");
138   -
  151 + raise ImportError("missing I2C library, please install smbus2 "
  152 + "or Debian python-smbus package ");
139 153 try:
140   - bus = smbus.SMBus(args.i2cbus) # Get I2C bus
  154 + return (smbus.SMBus(args.i2cbus), # get i2c bus
  155 + args.default_address if args.address is None else args.address)
141 156 except OSError as e:
142 157 logger.critical(e)
143 158 raise IOError("Invalid I2C bus: %d" % args.i2cbus)
144 159  
  160 +####################[ Get I2C BME280/BMP280 Sensor values ]####################
  161 +# Inspired by https://github.com/ControlEverythingCommunity/BME280
  162 +def read_i2c_bmX280(args):
  163 + """Returns temperature from I2C BME280/BMP280 sensor in desired unit"""
  164 +
  165 + i2c_bus, i2c_addr = i2c_get_smbus_devaddr(args)
  166 +
  167 + def convertLE16(data, offset, signed=False):
  168 + result = (data[offset + 1] << 8) | data[offset]
  169 + if signed and result > 32767:
  170 + result -= 65536
  171 + return result
  172 +
145 173 try:
146   - bus.write_i2c_block_data(args.address, I2C_MCP9808_CONFIG_ADDR,
  174 + # Get Temperature and Pressure Calibration Data
  175 + data = i2c_bus.read_i2c_block_data(i2c_addr, I2C_BMX280_CALIBRATE_ADDR,
  176 + I2C_BMX280_CALIBRATE_LEN)
  177 + dig_T1 = convertLE16(data, 0)
  178 + dig_T2 = convertLE16(data, 2, True)
  179 + dig_T3 = convertLE16(data, 4, True)
  180 + dig_P1 = convertLE16(data, 6)
  181 + dig_P2 = convertLE16(data, 8, True)
  182 + dig_P3 = convertLE16(data, 10, True)
  183 + dig_P4 = convertLE16(data, 12, True)
  184 + dig_P5 = convertLE16(data, 14, True)
  185 + dig_P6 = convertLE16(data, 16, True)
  186 + dig_P7 = convertLE16(data, 18, True)
  187 + dig_P8 = convertLE16(data, 20, True)
  188 + dig_P9 = convertLE16(data, 22, True)
  189 +
  190 + if args.read_humidity:
  191 + # Get Humidity Calibration Data
  192 + dig_H1 = i2c_bus.read_byte_data(i2c_addr,I2C_BMX280_CAL_HUM0_ADDR)
  193 + data = i2c_bus.read_i2c_block_data(i2c_addr,I2C_BMX280_CAL_HUM_ADDR,
  194 + I2C_BMX280_CAL_HUM_LEN)
  195 + dig_H2 = convertLE16(data, 0, True)
  196 + dig_H3 = data[2]
  197 + dig_H4 = (data[3] << 4) | (data[4] & 0x0f)
  198 + dig_H5 = (data[5] << 4) | (data[4] >> 4)
  199 + dig_H6 = data[6]
  200 + if dig_H6 > 127:
  201 + dig_H6 -= 256
  202 +
  203 + i2c_bus.write_byte_data(i2c_addr, I2C_BMX280_CTRL_HUM_ADDR,
  204 + I2C_BMX280_CTRL_HUM)
  205 +
  206 + # Setup BMP280/BME280 configuration and wait 0.5 seconds for things to settle
  207 + i2c_bus.write_byte_data(i2c_addr, I2C_BMX280_CTRL_MEAS_ADDR,
  208 + I2C_BMX280_CTRL_MEAS)
  209 + i2c_bus.write_byte_data(i2c_addr, I2C_BMX280_CONFIG_ADDR,
  210 + I2C_BMX280_CONFIG)
  211 + sleep(0.5)
  212 +
  213 + # Read sensor data and and convert using calibration data
  214 + data = i2c_bus.read_i2c_block_data(i2c_addr, I2C_BMX280_MEAS_ADDR,
  215 + I2C_BMX280_MEAS_LEN)
  216 +
  217 + # Convert 20-bits Temperature and calculate value with calibration data
  218 + adc_t = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
  219 + var1 = ((adc_t) / 16384.0 - (dig_T1) / 1024.0) * (dig_T2)
  220 + var2 = (((adc_t) / 131072.0 - (dig_T1) / 8192.0) * ((adc_t)/131072.0 - (dig_T1)/8192.0)) * (dig_T3)
  221 + t_fine = (var1 + var2)
  222 +
  223 + # Convert 20-bits Pressure and calculate value with calibration data
  224 + adc_p = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
  225 + var1 = (t_fine / 2.0) - 64000.0
  226 + var2 = var1 * var1 * (dig_P6) / 32768.0
  227 + var2 = var2 + var1 * (dig_P5) * 2.0
  228 + var2 = (var2 / 4.0) + ((dig_P4) * 65536.0)
  229 + var1 = ((dig_P3) * var1 * var1 / 524288.0 + ( dig_P2) * var1) / 524288.0
  230 + var1 = (1.0 + var1 / 32768.0) * (dig_P1)
  231 + p = 1048576.0 - adc_p
  232 + p = (p - (var2 / 4096.0)) * 6250.0 / var1
  233 + var1 = (dig_P9) * p * p / 2147483648.0
  234 + var2 = p * (dig_P8) / 32768.0
  235 + pressure = (p + (var1 + var2 + (dig_P7)) / 16.0) / 100
  236 +
  237 + extra_readings = [ ('pressure', pressure, 'hPa'), ]
  238 +
  239 + if args.read_humidity:
  240 + # Convert 16-byte Humidity and calculate value with calibration data
  241 + adc_h = (data[6] << 8) | data[7]
  242 + var_H = ((t_fine) - 76800.0)
  243 + var_H = (adc_h - (dig_H4 * 64.0 + dig_H5 / 16384.0 * var_H)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * var_H * (1.0 + dig_H3 / 67108864.0 * var_H)))
  244 + humidity = var_H * (1.0 - dig_H1 * var_H / 524288.0)
  245 + if humidity > 100.0 :
  246 + humidity = 100.0
  247 + elif humidity < 0.0 :
  248 + humidity = 0.0
  249 + extra_readings += ('humidity', humidity, '%', 0, 100),
  250 + logger.debug('BME280 sensor data: temperature %#02x: %#x = %d, '
  251 + 'pressure: %#x, humidity: %#x', i2c_addr, adc_t,
  252 + t_fine, adc_p, adc_h)
  253 + logger.debug('Sensor Humidity value is %.2f %%', humidity)
  254 + else:
  255 + logger.debug('BMP280 sensor data: temperature %#02x: %#x = %d, '
  256 + 'pressure: %#x',i2c_addr, adc_t, t_fine, adc_p)
  257 +
  258 + # convert temperature to right units and scale and return value
  259 + temp = args.converter[0](t_fine, I2C_BMX280_SENSOR_SCALE)
  260 + logger.debug('Sensor Temperature value %d is %.2f%s',
  261 + t_fine, temp, args.converter[1])
  262 + logger.debug('Sensor Pressure value is %.2f hPa', pressure)
  263 + return (temp, extra_readings), 1
  264 + except IOError as e:
  265 + logger.critical(e)
  266 + raise IOError("Error while communicating with I2C device %#02x" %
  267 + i2c_addr)
  268 +
  269 +
  270 +########################[ Get I2C MCP9808 Temperature ]########################
  271 +# Inspired by https://github.com/ControlEverythingCommunity/MCP9808
  272 +def read_i2c_mcp9808(args):
  273 + """Returns temperature from I2C mcp9808 sensor in desired unit"""
  274 +
  275 + i2c_bus, i2c_addr = i2c_get_smbus_devaddr(args)
  276 +
  277 + try:
  278 + # Setup MCP9808 configuration and wait 0.5 seconds for things to settle
  279 + i2c_bus.write_i2c_block_data(i2c_addr, I2C_MCP9808_CONFIG_ADDR,
147 280 I2C_MCP9808_CONFIG)
148   - bus.write_byte_data(args.address, I2C_MCP9808_PRECISION_ADDR,
  281 + i2c_bus.write_byte_data(i2c_addr, I2C_MCP9808_PRECISION_ADDR,
149 282 I2C_MCP9808_PRECISION)
150 283 sleep(0.5)
151 284  
152   - # Read temperature (0x05), 2 bytes (MSB, LSB)
153   - data = bus.read_i2c_block_data(args.address, I2C_MCP9808_TEMP_ADDR, 2)
154   - logger.debug('Temperature sensor data from MCP9808 %#02x: 0x%02x%02x',
155   - args.address, data[0], data[1])
156   -
157   - # Convert the data to 13-bits
158   - temp_read = ((data[0] & 0x1F) * 256) + data[1]
159   - if temp_read > 4095 :
160   - temp_read -= 8192
  285 + # Read temperature (2 bytes - MSB,LSB) and convert to 13-bit signed int
  286 + data = i2c_bus.read_i2c_block_data(i2c_addr,I2C_MCP9808_TEMP_ADDR,2)
  287 + temp_read = ((data[0] & 0xF) << 8) | data[1]
  288 + if (data[0] & 0x10):
  289 + temp_read = -temp_read
  290 + logger.debug('MCP9808 sensor data %#02x: 0x%02x%02x = %d',
  291 + i2c_addr, data[0], data[1], temp_read)
161 292  
  293 + # convert temperatur to right units and scale and return value
162 294 temp = args.converter[0](temp_read, I2C_MCP9808_SENSOR_SCALE)
163 295 logger.debug('Temperature sensor value %d is %.2f%s',
164 296 temp_read, temp, args.converter[1])
... ... @@ -166,15 +298,16 @@ def read_i2c_mcp9808_temp(args):
166 298 except IOError as e:
167 299 logger.critical(e)
168 300 raise IOError("Error while communicating with I2C device %#02x" %
169   - args.address)
  301 + i2c_addr)
170 302  
171 303  
  304 +#####################[ Get 1-Wire sensor device filename ]#####################
172 305 def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR,
173   - prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX):
  306 + prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX):
174 307 """Auto-determine sensor datafile name (unless args.file is set)"""
175   - if isempty(args.file):
  308 + if not args.file:
176 309 search_pat = dev_dir + ('/' if dev_dir[-1]!='/' else '')
177   - search_pat+= prefix + '*' if isempty(args.serial) else '*' + args.serial
  310 + search_pat+= prefix + ('*' + args.serial if args.serial else '*')
178 311 logger.debug('looking for sensors with search pattern %s', search_pat)
179 312  
180 313 device_folders = glob(search_pat)
... ... @@ -196,7 +329,8 @@ def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR,
196 329 return filename
197 330  
198 331  
199   -def read_w1_ds18b20_temp(args):
  332 +###################[ Get 1-Wire DS18b20 sensor temperature ]###################
  333 +def read_w1_ds18b20(args):
200 334 """Returns temperature from 1-wire ds18b20 sensor in desired unit"""
201 335 device_file = get_w1_sensor_device_filename(args)
202 336 lines=[ '' ]
... ... @@ -227,6 +361,7 @@ def read_w1_ds18b20_temp(args):
227 361 raise ValueError(errmsg)
228 362  
229 363  
  364 +#######################[ Parse Command Line parameters ]#######################
230 365 def parse_args():
231 366 """Parse command line and get parameters from environment, if present"""
232 367  
... ... @@ -269,18 +404,35 @@ def parse_args():
269 404 help='input file (or device) to obtain data from'
270 405 ' (defaults to %s)' % CPU_SENSOR_DEV)
271 406 cmdparser = subparser.add_parser('rpi_cpu', parents=[cpuparser],
272   - help='read built-in Raspberry Pi CPU temperature')
273   - cmdparser.set_defaults(func=read_rpi_cpu_temp, cmdparser=cmdparser, retries=0)
  407 + help='read built-in Raspberry Pi CPU temperature')
  408 + cmdparser.set_defaults(func=read_rpi_cpu_temp,cmdparser=cmdparser,retries=0)
274 409  
275 410 i2cparser = ArgumentParser(add_help=False)
276 411 i2cparser.add_argument('-a', '--address', type=hex_int,
277 412 help='I2C Address of sensor, use 0x.. for hex (*)')
278 413 i2cparser.add_argument('-b', '--i2cbus', default=1, type=int,
279 414 help='I2C Bus to use (defaults to 1)')
  415 +
  416 + cmdparser = subparser.add_parser('i2c_bme280', parents=[i2cparser],
  417 + help='read I2C connected BME280 sensor',
  418 + epilog='(*) default I2C address for an BME280 is %#x' %
  419 + I2C_BMX280_DEFAULT_ADDR)
  420 + cmdparser.set_defaults(func=read_i2c_bmX280, cmdparser=cmdparser, retries=0,
  421 + read_humidity=True, default_address=I2C_BMX280_DEFAULT_ADDR)
  422 +
  423 + cmdparser = subparser.add_parser('i2c_bmp280', parents=[i2cparser],
  424 + help='read I2C connected BMP280 sensor',
  425 + epilog='(*) default I2C address for an BMP280 is %#x' %
  426 + I2C_BMX280_DEFAULT_ADDR)
  427 + cmdparser.set_defaults(func=read_i2c_bmX280, cmdparser=cmdparser, retries=0,
  428 + read_humidity=False, default_address=I2C_BMX280_DEFAULT_ADDR)
  429 +
280 430 cmdparser = subparser.add_parser('i2c_mcp9808', parents=[i2cparser],
281 431 help='read I2C connected MCP9808 sensor',
282   - epilog='(*) default I2C address for an MCP9808 is 0x18')
283   - cmdparser.set_defaults(func=read_i2c_mcp9808_temp, cmdparser=cmdparser, retries=0, address=0x18)
  432 + epilog='(*) default I2C address for an MCP9808 is %#x' %
  433 + I2C_MCP9808_DEFAULT_ADDR)
  434 + cmdparser.set_defaults(func=read_i2c_mcp9808, cmdparser=cmdparser,
  435 + retries=0, default_address=I2C_MCP9808_DEFAULT_ADDR)
284 436  
285 437 w1parser = ArgumentParser(add_help=False)
286 438 pgroup = w1parser.add_mutually_exclusive_group(required=False)
... ... @@ -288,16 +440,17 @@ def parse_args():
288 440 help='(unique part of) temperature sensor serial (*)')
289 441 pgroup.add_argument('-f', '--file',
290 442 help='input file (or device) to obtain data from (*)')
291   - w1parser.add_argument('-r', '--retries', type=int,default=W1_SENSOR_READ_RETRIES,
292   - help='number of times to retry reading sensor data when'
293   - ' unstable (defaults to %d)' % W1_SENSOR_READ_RETRIES)
  443 + w1parser.add_argument('-r', '--retries', type=int,
  444 + default=W1_SENSOR_READ_RETRIES,
  445 + help='number of times to retry reading sensor data when'
  446 + ' unstable (defaults to %d)' % W1_SENSOR_READ_RETRIES)
294 447 cmdparser = subparser.add_parser('w1_ds18b20', parents=[w1parser],
295 448 help='read 1-wire connected DS18b20 sensor',
296 449 epilog='(*) by default the script will look for the first device that '
297 450 'matches %s* in %s, if multiple entries are found -s or -f must '
298 451 'be used to specify which sensor to read.' %
299 452 (W1_SENSOR_DEV_PREFIX, W1_SENSOR_DEV_DIR))
300   - cmdparser.set_defaults(func=read_w1_ds18b20_temp, cmdparser=cmdparser)
  453 + cmdparser.set_defaults(func=read_w1_ds18b20, cmdparser=cmdparser)
301 454  
302 455 # parse arguments and post-process command line options
303 456 args = parser.parse_args()
... ... @@ -306,6 +459,7 @@ def parse_args():
306 459 return args
307 460  
308 461  
  462 +############################[ Exit the Nagios way ]############################
309 463 def nagios_exit(status, message, data=None):
310 464 """exit 'nagios-style', print status and message followed by perf. data"""
311 465 if logger.isEnabledFor(logging.CRITICAL):
... ... @@ -319,6 +473,7 @@ def nagios_exit(status, message, data=None):
319 473 exit(status[1])
320 474  
321 475  
  476 +#################################[ Main logic ]#################################
322 477 if __name__ == '__main__':
323 478 try:
324 479 args = parse_args()
... ... @@ -333,11 +488,16 @@ if __name__ == &#39;__main__&#39;:
333 488 temperature, tries = args.func(args)
334 489 endtime = time()
335 490  
  491 + if isinstance(temperature, tuple):
  492 + temperature, extra_data = temperature
  493 + else:
  494 + extra_data = ()
  495 +
336 496 except (KeyboardInterrupt) as e:
337   - nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read aborted by user')
  497 + nagios_exit(NAGIOS_UNKNOWN,'sensor read aborted by user')
338 498  
339 499 except (IOError, ValueError, ImportError) as e:
340   - nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read failed: %s' % e)
  500 + nagios_exit(NAGIOS_UNKNOWN,'sensor read failed: %s' % e)
341 501  
342 502 elapse = endtime-starttime
343 503 logger.info('Got temperature reading of %.2f degrees %s in %fs',
... ... @@ -345,6 +505,10 @@ if __name__ == &#39;__main__&#39;:
345 505  
346 506 unit = args.converter[1]
347 507 message = 'current temperature is %.2f%s' % (temperature, unit)
  508 + data = [ ('temperature', [ '%f%s' % (temperature, unit),
  509 + args.warn, args.critical, None, None]),
  510 + ('retries', [ tries-1, None, args.retries, 0, None ]),
  511 + ('checktime', [ '%fs' % elapse, None, None, 0, None]) ]
348 512 if args.critical is not None and temperature > args.critical:
349 513 nagiosresult = NAGIOS_CRITICAL
350 514 message+= ' and exceeds critical threshold %.2f%s' %(args.critical,unit)
... ... @@ -354,10 +518,10 @@ if __name__ == &#39;__main__&#39;:
354 518 else:
355 519 nagiosresult = NAGIOS_OK
356 520  
357   - nagios_exit(nagiosresult, message, [
358   - ('temperature', [ '%f%s' % (temperature, unit),
359   - args.warn, args.critical, None, None]),
360   - ('retries', [ tries-1, None, args.retries, 0, None ]),
361   - ('checktime', [ '%fs' % elapse, None, None, 0, None])
362   - ])
  521 + for e in extra_data:
  522 + message += ', %s is %.2f%s' % (e[0], e[1], e[2])
  523 + data += (e[0], [ '%f%s' % (e[1], e[2]), None, None,
  524 + e[3] if len(e)>3 else None, e[4] if len(e)>4 else None ]),
  525 +
  526 + nagios_exit(nagiosresult, message, data)
363 527  
... ...