Commit 2349fc1add0c1f5d2b38dafb8d52d3fc473b6abb
1 parent
d54130d1
Added support for I2C MCP9808 sensor (closes #4) based on https://github.com/Con…
…trolEverythingCommunity/MCP9808/blob/master/Python/MCP9808.py
Showing
1 changed file
with
86 additions
and
20 deletions
plugins/check_temperature
@@ -2,10 +2,10 @@ | @@ -2,10 +2,10 @@ | ||
2 | # | 2 | # |
3 | # check_temperature - Nagios temperature check for DS18B20 sensor on RaspberryPi | 3 | # check_temperature - Nagios temperature check for DS18B20 sensor on RaspberryPi |
4 | # | 4 | # |
5 | -# Version 1.1, latest version, documentation and bugtracker available at: | 5 | +# Version 1.2, latest version, documentation and bugtracker available at: |
6 | # https://gitlab.lindenaar.net/scripts/nagios-plugins | 6 | # https://gitlab.lindenaar.net/scripts/nagios-plugins |
7 | # | 7 | # |
8 | -# Copyright (c) 2017 Frederik Lindenaar | 8 | +# Copyright (c) 2017 - 2019 Frederik Lindenaar |
9 | # | 9 | # |
10 | # This script is free software: you can redistribute and/or modify it under the | 10 | # This script is free software: you can redistribute and/or modify it under the |
11 | # terms of version 3 of the GNU General Public License as published by the Free | 11 | # terms of version 3 of the GNU General Public License as published by the Free |
@@ -27,15 +27,21 @@ from argparse import ArgumentParser as StandardArgumentParser, FileType, \ | @@ -27,15 +27,21 @@ from argparse import ArgumentParser as StandardArgumentParser, FileType, \ | ||
27 | import logging | 27 | import logging |
28 | 28 | ||
29 | # Constants (no need to change but allows for easy customization) | 29 | # Constants (no need to change but allows for easy customization) |
30 | -VERSION="1.1" | 30 | +VERSION="1.2" |
31 | PROG_NAME=splitext(basename(__file__))[0] | 31 | PROG_NAME=splitext(basename(__file__))[0] |
32 | PROG_VERSION=PROG_NAME + ' ' + VERSION | 32 | PROG_VERSION=PROG_NAME + ' ' + VERSION |
33 | -SENSOR_SCALE=1000 | 33 | + |
34 | +CPU_SENSOR_DEV = '/sys/class/thermal/thermal_zone0/temp' | ||
35 | +I2C_MCP9808_CONFIG_ADDR=0x1 | ||
36 | +I2C_MCP9808_CONFIG = [ 0x00, 0x00 ] # continuous conversion (power-up default) | ||
37 | +I2C_MCP9808_PRECISION_ADDR=0x08 | ||
38 | +I2C_MCP9808_PRECISION=3 # 0=0.5, 1=0.25, 2=0.125, 3=0.0625 degr. C | ||
39 | +I2C_MCP9808_TEMP_ADDR=0x05 | ||
34 | W1_SENSOR_DEV_DIR = '/sys/bus/w1/devices/' | 40 | W1_SENSOR_DEV_DIR = '/sys/bus/w1/devices/' |
35 | W1_SENSOR_DEV_PREFIX = '28-' | 41 | W1_SENSOR_DEV_PREFIX = '28-' |
36 | W1_SENSOR_DEV_SUFFIX = '/w1_slave' | 42 | W1_SENSOR_DEV_SUFFIX = '/w1_slave' |
37 | W1_SENSOR_READ_RETRIES=10 | 43 | W1_SENSOR_READ_RETRIES=10 |
38 | -CPU_SENSOR_DEV = '/sys/class/thermal/thermal_zone0/temp' | 44 | +W1_SENSOR_SCALE=1000 |
39 | 45 | ||
40 | LOG_FORMAT='%(levelname)s - %(message)s' | 46 | LOG_FORMAT='%(levelname)s - %(message)s' |
41 | LOG_FORMAT_FILE='%(asctime)s - ' + LOG_FORMAT | 47 | LOG_FORMAT_FILE='%(asctime)s - ' + LOG_FORMAT |
@@ -81,15 +87,15 @@ class SetLogFile(StoreAction): | @@ -81,15 +87,15 @@ class SetLogFile(StoreAction): | ||
81 | 87 | ||
82 | ############################################################################### | 88 | ############################################################################### |
83 | 89 | ||
84 | -def convert_celcius(temp_read): | 90 | +def convert_celcius(temp_read, scale = 1): |
85 | """Converts raw temperature sensore value to degrees Celcius""" | 91 | """Converts raw temperature sensore value to degrees Celcius""" |
86 | - return float(temp_read) / float(SENSOR_SCALE) | 92 | + return float(temp_read) / float(scale) |
87 | CONVERT_CELCIUS = ( convert_celcius, 'C', 'Celcius' ) | 93 | CONVERT_CELCIUS = ( convert_celcius, 'C', 'Celcius' ) |
88 | 94 | ||
89 | 95 | ||
90 | -def convert_farenheit(temp_read): | 96 | +def convert_farenheit(temp_read, scale = 1): |
91 | """Converts raw temperature sensore value to degrees Farenheit""" | 97 | """Converts raw temperature sensore value to degrees Farenheit""" |
92 | - return float(temp_read * 9) / float(5 * SENSOR_SCALE) + 32.0 | 98 | + return float(temp_read * 9) / float(5 * scale) + 32.0 |
93 | CONVERT_FARENHEIT = ( convert_farenheit, 'F', 'Farenheit' ) | 99 | CONVERT_FARENHEIT = ( convert_farenheit, 'F', 'Farenheit' ) |
94 | 100 | ||
95 | 101 | ||
@@ -98,6 +104,11 @@ def isempty(string): | @@ -98,6 +104,11 @@ def isempty(string): | ||
98 | return string is None or len(string) == 0 | 104 | return string is None or len(string) == 0 |
99 | 105 | ||
100 | 106 | ||
107 | +def hex_int(string): | ||
108 | + """Use int()'s auto-detection to parse 10-base and 16-base (0x..) numbers""" | ||
109 | + return int(string, 0); | ||
110 | + | ||
111 | + | ||
101 | def read_rpi_cpu_temp(args): | 112 | def read_rpi_cpu_temp(args): |
102 | """Reads CPU temperature and converts it to desired unit, returns temperature""" | 113 | """Reads CPU temperature and converts it to desired unit, returns temperature""" |
103 | with open(args.file, 'r') as f: | 114 | with open(args.file, 'r') as f: |
@@ -111,6 +122,51 @@ def read_rpi_cpu_temp(args): | @@ -111,6 +122,51 @@ def read_rpi_cpu_temp(args): | ||
111 | return temp, 1 | 122 | return temp, 1 |
112 | 123 | ||
113 | 124 | ||
125 | +def read_i2c_mcp9808_temp(args): | ||
126 | + """Returns temperature from I2C MCP9808 sensor in desired unit""" | ||
127 | + | ||
128 | + try: | ||
129 | + import smbus | ||
130 | + except ImportError: | ||
131 | + try: | ||
132 | + import smbus2 as smbus | ||
133 | + except ImportError: | ||
134 | + logger.critical("Unable to import either smbus or smbus2 library"); | ||
135 | + raise ImportError("missing I2C library, please install python-smbus or smbus2"); | ||
136 | + | ||
137 | + try: | ||
138 | + bus = smbus.SMBus(args.i2cbus) # Get I2C bus | ||
139 | + except OSError as e: | ||
140 | + logger.critical(e) | ||
141 | + raise IOError("Invalid I2C bus: %d" % args.i2cbus) | ||
142 | + | ||
143 | + try: | ||
144 | + bus.write_i2c_block_data(args.address, I2C_MCP9808_CONFIG_ADDR, | ||
145 | + I2C_MCP9808_CONFIG) | ||
146 | + bus.write_byte_data(args.address, I2C_MCP9808_PRECISION_ADDR, | ||
147 | + I2C_MCP9808_PRECISION) | ||
148 | + sleep(0.5) | ||
149 | + | ||
150 | + # Read temperature (0x05), 2 bytes (MSB, LSB) | ||
151 | + data = bus.read_i2c_block_data(args.address, I2C_MCP9808_TEMP_ADDR, 2) | ||
152 | + logger.debug('Temperature sensor data from MCP9808 %#02x: 0x%02x%02x', | ||
153 | + args.address, data[0], data[1]) | ||
154 | + | ||
155 | + # Convert the data to 13-bits | ||
156 | + temp_read = ((data[0] & 0x1F) * 256) + data[1] | ||
157 | + if temp_read > 4095 : | ||
158 | + temp_read -= 8192 | ||
159 | + | ||
160 | + temp = args.converter[0](temp_read, 16) | ||
161 | + logger.debug('Temperature sensor value %d is %.2f%s', | ||
162 | + temp_read, temp, args.converter[1]) | ||
163 | + return temp, 1 | ||
164 | + except IOError as e: | ||
165 | + logger.critical(e) | ||
166 | + raise IOError("Error while communicating with I2C device %#02x" % | ||
167 | + args.address) | ||
168 | + | ||
169 | + | ||
114 | def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR, | 170 | def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR, |
115 | prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX): | 171 | prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX): |
116 | """Auto-determine sensor datafile name (unless args.file is set)""" | 172 | """Auto-determine sensor datafile name (unless args.file is set)""" |
@@ -138,8 +194,8 @@ def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR, | @@ -138,8 +194,8 @@ def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR, | ||
138 | return filename | 194 | return filename |
139 | 195 | ||
140 | 196 | ||
141 | -def read_w1_temp(args): | ||
142 | - """Reads sensor data and converts it to desired unit, returns temperature""" | 197 | +def read_w1_ds18b20_temp(args): |
198 | + """Returns temperature from 1-wire ds18b20 sensor in desired unit""" | ||
143 | device_file = get_w1_sensor_device_filename(args) | 199 | device_file = get_w1_sensor_device_filename(args) |
144 | lines=[ '' ] | 200 | lines=[ '' ] |
145 | tries = 0 | 201 | tries = 0 |
@@ -160,7 +216,7 @@ def read_w1_temp(args): | @@ -160,7 +216,7 @@ def read_w1_temp(args): | ||
160 | errmsg = 'temperature sensor data format is not supported' | 216 | errmsg = 'temperature sensor data format is not supported' |
161 | else: | 217 | else: |
162 | temp_read = int(lines[1][equals_pos+2:]) | 218 | temp_read = int(lines[1][equals_pos+2:]) |
163 | - temp = args.converter[0](temp_read) | 219 | + temp = args.converter[0](temp_read, W1_SENSOR_SCALE) |
164 | logger.debug('Temperature sensor value %d is %.2f%s', temp_read, | 220 | logger.debug('Temperature sensor value %d is %.2f%s', temp_read, |
165 | temp, args.converter[1]) | 221 | temp, args.converter[1]) |
166 | return temp, tries | 222 | return temp, tries |
@@ -174,11 +230,7 @@ def parse_args(): | @@ -174,11 +230,7 @@ def parse_args(): | ||
174 | 230 | ||
175 | # Setup argument parser, the workhorse gluing it all together | 231 | # Setup argument parser, the workhorse gluing it all together |
176 | parser = ArgumentParser( | 232 | parser = ArgumentParser( |
177 | - epilog='(*) by default the script will look for the first device that ' | ||
178 | - 'matches %s* in %s, if multiple entries are found -s or -f must ' | ||
179 | - 'be used to specify which sensor to read.' % | ||
180 | - (W1_SENSOR_DEV_PREFIX, W1_SENSOR_DEV_DIR), | ||
181 | - description='Nagios check plugin for 1-wire temp. sensor on RaspberryPi' | 233 | + description='Nagios check plugin for temperature sensors on RaspberryPi' |
182 | ) | 234 | ) |
183 | parser.add_argument('-V', '--version',action="version",version=PROG_VERSION) | 235 | parser.add_argument('-V', '--version',action="version",version=PROG_VERSION) |
184 | 236 | ||
@@ -218,6 +270,16 @@ def parse_args(): | @@ -218,6 +270,16 @@ def parse_args(): | ||
218 | help='read built-in Raspberry Pi CPU temperature') | 270 | help='read built-in Raspberry Pi CPU temperature') |
219 | cmdparser.set_defaults(func=read_rpi_cpu_temp, cmdparser=cmdparser, retries=0) | 271 | cmdparser.set_defaults(func=read_rpi_cpu_temp, cmdparser=cmdparser, retries=0) |
220 | 272 | ||
273 | + i2cparser = ArgumentParser(add_help=False) | ||
274 | + i2cparser.add_argument('-a', '--address', type=hex_int, | ||
275 | + help='I2C Address of sensor, use 0x.. for hex (*)') | ||
276 | + i2cparser.add_argument('-b', '--i2cbus', default=1, type=int, | ||
277 | + help='I2C Bus to use (defaults to 1)') | ||
278 | + cmdparser = subparser.add_parser('i2c_mcp9808', parents=[i2cparser], | ||
279 | + help='read I2C connected MCP9808 sensor', | ||
280 | + epilog='(*) default I2C address for an MCP9808 is 0x18') | ||
281 | + cmdparser.set_defaults(func=read_i2c_mcp9808_temp, cmdparser=cmdparser, retries=0, address=0x18) | ||
282 | + | ||
221 | w1parser = ArgumentParser(add_help=False) | 283 | w1parser = ArgumentParser(add_help=False) |
222 | pgroup = w1parser.add_mutually_exclusive_group(required=False) | 284 | pgroup = w1parser.add_mutually_exclusive_group(required=False) |
223 | pgroup.add_argument('-s', '--serial', | 285 | pgroup.add_argument('-s', '--serial', |
@@ -228,8 +290,12 @@ def parse_args(): | @@ -228,8 +290,12 @@ def parse_args(): | ||
228 | help='number of times to retry reading sensor data when' | 290 | help='number of times to retry reading sensor data when' |
229 | ' unstable (defaults to %d)' % W1_SENSOR_READ_RETRIES) | 291 | ' unstable (defaults to %d)' % W1_SENSOR_READ_RETRIES) |
230 | cmdparser = subparser.add_parser('w1_ds18b20', parents=[w1parser], | 292 | cmdparser = subparser.add_parser('w1_ds18b20', parents=[w1parser], |
231 | - help='read 1-wire connected DS18b20 sensor') | ||
232 | - cmdparser.set_defaults(func=read_w1_temp, cmdparser=cmdparser) | 293 | + help='read 1-wire connected DS18b20 sensor', |
294 | + epilog='(*) by default the script will look for the first device that ' | ||
295 | + 'matches %s* in %s, if multiple entries are found -s or -f must ' | ||
296 | + 'be used to specify which sensor to read.' % | ||
297 | + (W1_SENSOR_DEV_PREFIX, W1_SENSOR_DEV_DIR)) | ||
298 | + cmdparser.set_defaults(func=read_w1_ds18b20_temp, cmdparser=cmdparser) | ||
233 | 299 | ||
234 | # parse arguments and post-process command line options | 300 | # parse arguments and post-process command line options |
235 | args = parser.parse_args() | 301 | args = parser.parse_args() |
@@ -268,7 +334,7 @@ if __name__ == '__main__': | @@ -268,7 +334,7 @@ if __name__ == '__main__': | ||
268 | except (KeyboardInterrupt) as e: | 334 | except (KeyboardInterrupt) as e: |
269 | nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read aborted by user') | 335 | nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read aborted by user') |
270 | 336 | ||
271 | - except (IOError, ValueError) as e: | 337 | + except (IOError, ValueError, ImportError) as e: |
272 | nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read failed: %s' % e) | 338 | nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read failed: %s' % e) |
273 | 339 | ||
274 | elapse = endtime-starttime | 340 | elapse = endtime-starttime |