Commit 2349fc1add0c1f5d2b38dafb8d52d3fc473b6abb

Authored by Frederik Lindenaar
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