Commit 379482e72969f01c9a308a7af5c6592071c4368f
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)
Showing
2 changed files
with
303 additions
and
77 deletions
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 + ' ' + 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__ == '__main__': |
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__ == '__main__': |
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__ == '__main__': |
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 | |
... | ... |