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 | 2 | # |
3 | 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 | 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 | 10 | # This script is free software: you can redistribute and/or modify it under the |
11 | 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 | 27 | import logging |
28 | 28 | |
29 | 29 | # Constants (no need to change but allows for easy customization) |
30 | -VERSION="1.1" | |
30 | +VERSION="1.2" | |
31 | 31 | PROG_NAME=splitext(basename(__file__))[0] |
32 | 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 | 40 | W1_SENSOR_DEV_DIR = '/sys/bus/w1/devices/' |
35 | 41 | W1_SENSOR_DEV_PREFIX = '28-' |
36 | 42 | W1_SENSOR_DEV_SUFFIX = '/w1_slave' |
37 | 43 | W1_SENSOR_READ_RETRIES=10 |
38 | -CPU_SENSOR_DEV = '/sys/class/thermal/thermal_zone0/temp' | |
44 | +W1_SENSOR_SCALE=1000 | |
39 | 45 | |
40 | 46 | LOG_FORMAT='%(levelname)s - %(message)s' |
41 | 47 | LOG_FORMAT_FILE='%(asctime)s - ' + LOG_FORMAT |
... | ... | @@ -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 | 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 | 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 | 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 | 99 | CONVERT_FARENHEIT = ( convert_farenheit, 'F', 'Farenheit' ) |
94 | 100 | |
95 | 101 | |
... | ... | @@ -98,6 +104,11 @@ def isempty(string): |
98 | 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 | 112 | def read_rpi_cpu_temp(args): |
102 | 113 | """Reads CPU temperature and converts it to desired unit, returns temperature""" |
103 | 114 | with open(args.file, 'r') as f: |
... | ... | @@ -111,6 +122,51 @@ def read_rpi_cpu_temp(args): |
111 | 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 | 170 | def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR, |
115 | 171 | prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX): |
116 | 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 | 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 | 199 | device_file = get_w1_sensor_device_filename(args) |
144 | 200 | lines=[ '' ] |
145 | 201 | tries = 0 |
... | ... | @@ -160,7 +216,7 @@ def read_w1_temp(args): |
160 | 216 | errmsg = 'temperature sensor data format is not supported' |
161 | 217 | else: |
162 | 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 | 220 | logger.debug('Temperature sensor value %d is %.2f%s', temp_read, |
165 | 221 | temp, args.converter[1]) |
166 | 222 | return temp, tries |
... | ... | @@ -174,11 +230,7 @@ def parse_args(): |
174 | 230 | |
175 | 231 | # Setup argument parser, the workhorse gluing it all together |
176 | 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 | 235 | parser.add_argument('-V', '--version',action="version",version=PROG_VERSION) |
184 | 236 | |
... | ... | @@ -218,6 +270,16 @@ def parse_args(): |
218 | 270 | help='read built-in Raspberry Pi CPU temperature') |
219 | 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 | 283 | w1parser = ArgumentParser(add_help=False) |
222 | 284 | pgroup = w1parser.add_mutually_exclusive_group(required=False) |
223 | 285 | pgroup.add_argument('-s', '--serial', |
... | ... | @@ -228,8 +290,12 @@ def parse_args(): |
228 | 290 | help='number of times to retry reading sensor data when' |
229 | 291 | ' unstable (defaults to %d)' % W1_SENSOR_READ_RETRIES) |
230 | 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 | 300 | # parse arguments and post-process command line options |
235 | 301 | args = parser.parse_args() |
... | ... | @@ -268,7 +334,7 @@ if __name__ == '__main__': |
268 | 334 | except (KeyboardInterrupt) as e: |
269 | 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 | 338 | nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read failed: %s' % e) |
273 | 339 | |
274 | 340 | elapse = endtime-starttime |
... | ... |