From 8afdb5e31132beed3c0a9b5641314c33c16d3a7c Mon Sep 17 00:00:00 2001
From: Frederik Lindenaar <frederik@lindenaar.nl>
Date: Sat, 6 Jan 2018 20:06:27 +0100
Subject: [PATCH] added Raspberry Pi CPU temperature reading

---
 README.md                 |  49 ++++++++++++++++++++++++++++++++++++-------------
 plugins/check_temperature | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------
 2 files changed, 140 insertions(+), 94 deletions(-)

diff --git a/README.md b/README.md
index 28e9028..c294809 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ This repository contains the following scripts:
   * [check_otp](#check_otp)
     plugin to monitor PrivacyIDEA (and LinOTP) OTP validation
   * [check_temperature](#check_temperature)
-    plugin to monitor the temperature of a 1-wire sensor on a RaspberryPi
+    plugin to monitor the CPU temperature of a RaspberryPi or that of a DS18b20  1-wire sensor on a RaspberryPi
   * [nagiosstatus](#nagiosstatus)
     CGI-BIN script to report the status of nagios (to monitor nagios itself)
 
@@ -237,15 +237,16 @@ define service {
 
 <a name=check_temperature>plugins/check_temperature</a>
 -------------------------------------------------------
-Plugin (check) to monitor monitor the temperature using a sensor connected to a
-RaspberryPi. This implementation is specifically for the DS18B20 1-wire
-temperature sensor. Other methods and interfaces can be plugged in easily (just
-raise a request or provide a patch). For information on how to connect sensor
-to the RaspberryPi and to get it working please see [this Adafruit tutorial](
+Plugin (check) to monitor monitor the Raspberry Pi CPU temperature or that of a
+temperature sensor connected to a RaspberryPi. This implementation currently
+supports the DS18B20 1-wire temperature sensor. Other methods and interfaces can
+be plugged in easily (just raise a request or provide a patch). For information
+on how to connect sensor to the RaspberryPi and to get it working please see
+[this Adafruit tutorial](
 https://learn.adafruit.com/adafruits-raspberry-pi-lesson-11-ds18b20-temperature-sensing).
 
-To enable the 1-wire interface support on the RaspberryPi one can use the
-command:
+No setup is required to read the CPU temperature. To enable the 1-wire interface
+support on the RaspberryPi one can use the command:
 ~~~
    sudo raspi-config nonint do_onewire 0
 ~~~
@@ -256,32 +257,46 @@ Installation for is straightforward, after installing the script on the server
 add the following to your Nagios `commands.cmd` configuration file:
 
 ~~~
+# 'check_cpu_temperature' command definition to monitor CPU temperature in C
+# parameters: warning (ARG1) and critical (ARG2) temperature in Celcius
+define command {
+        command_name    check_temperature
+        command_line    [install_path]/plugins/check_temperature -w $ARG1$ -c $ARG2$ rpi_cpu
+}
+
+# 'check_cpu_ftemperature' command definition to monitor CPU temperature in F
+# parameters: warning (ARG1) and critical (ARG2) temperature in Celcius
+define command {
+        command_name    check_temperature
+        command_line    [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$ rpi_cpu
+}
+
 # 'check_temperature' command definition to monitor a single temperature in C
 # parameters: warning (ARG1) and critical (ARG2) temperature in Celcius
 define command {
         command_name    check_temperature
-        command_line    [install_path]/plugins/check_temperature -w $ARG1$ -c $ARG2$
+        command_line    [install_path]/plugins/check_temperature -w $ARG1$ -c $ARG2$ w1_ds18b20
 }
 
 # 'check_ftemperature' command definition to monitor a single temperature in F
 # parameters: warning (ARG1) and critical (ARG2) temperature in Farenheit
 define command {
         command_name    check_ftemperature
-        command_line    [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$
+        command_line    [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$ w1_ds18b20
 }
 
 # 'check_temperature_sensor' command definition to monitor a single temperature in C
 # parameters: sensor serial (ARG1), warning (ARG2) and critical (ARG3) temperature in Celcius
 define command {
         command_name    check_temperature_sensor
-        command_line    [install_path]/plugins/check_temperature -s $ARG1$ -w $ARG2$ -c $ARG3$
+        command_line    [install_path]/plugins/check_temperature -w $ARG2$ -c $ARG3$ w1_ds18b20 -s $ARG1$
 }
 
 # 'check_ftemperature_sensor' command definition to monitor a single temperature in F
 # parameters: sensor serial (ARG1), warning (ARG2) and critical (ARG3) temperature in Farenheit
 define command {
         command_name    check_ftemperature_sensor
-        command_line    [install_path]/plugins/check_temperature -F -s $ARG1$ -w $ARG2$ -c $ARG3$
+        command_line    [install_path]/plugins/check_temperature -F -w $ARG2$ -c $ARG3$ w1_ds18b20 -s $ARG1$
 }
 
 ~~~
@@ -290,6 +305,14 @@ Make sure to replace `[install_path]/plugins` with the location of the script.
 To use the it define a service check like below:
 
 ~~~
+# check RaspberryPi CPU temperature in Celcius
+define service {
+        host                   hostname.mydomain.tld
+        service_description    CPU Temperature
+        check_command          check_cpu_temperature!55!75
+        use                    generic-service
+}
+
 # check temperature in Celcius using a DS18B20 sensor connected to a RaspberryPi
 define service {
         host                   hostname.mydomain.tld
@@ -298,7 +321,6 @@ define service {
         use                    generic-service
 }
 
-
 # check temperature with DS18B20 sensor 0000a31ea3de connected to a RaspberryPi
 define service {
         host                   hostname.mydomain.tld
@@ -340,3 +362,4 @@ General Public License for more details.
 
 You should have received a copy of the GNU General Public License along with
 this program.  If not, download it from <http://www.gnu.org/licenses/>.
+
diff --git a/plugins/check_temperature b/plugins/check_temperature
index 493de8c..34a106f 100755
--- a/plugins/check_temperature
+++ b/plugins/check_temperature
@@ -2,10 +2,10 @@
 #
 # check_temperature - Nagios temperature check for DS18B20 sensor on RaspberryPi
 #
-# Version 1.0, latest version, documentation and bugtracker available at:
+# Version 1.1, latest version, documentation and bugtracker available at:
 #              https://gitlab.lindenaar.net/scripts/nagios-plugins
 #
-# Copyright (c) 2016 Frederik Lindenaar
+# Copyright (c) 2017 Frederik Lindenaar
 #
 # This script is free software: you can redistribute and/or modify it under the
 # terms of version 3 of the GNU General Public License as published by the Free
@@ -27,14 +27,15 @@ from argparse import ArgumentParser as StandardArgumentParser, FileType, \
 import logging
 
 # Constants (no need to change but allows for easy customization)
-VERSION="1.0"
+VERSION="1.1"
 PROG_NAME=splitext(basename(__file__))[0]
 PROG_VERSION=PROG_NAME + ' ' + VERSION
 SENSOR_SCALE=1000
-SENSOR_DEV_DIR =  '/sys/bus/w1/devices/'
-SENSOR_DEV_PREFIX = '28-'
-SENSOR_DEV_SUFFIX = '/w1_slave'
-SENSOR_READ_RETRIES=10
+W1_SENSOR_DEV_DIR =  '/sys/bus/w1/devices/'
+W1_SENSOR_DEV_PREFIX = '28-'
+W1_SENSOR_DEV_SUFFIX = '/w1_slave'
+W1_SENSOR_READ_RETRIES=10
+CPU_SENSOR_DEV = '/sys/class/thermal/thermal_zone0/temp'
 
 LOG_FORMAT='%(levelname)s - %(message)s'
 LOG_FORMAT_FILE='%(asctime)s - ' + LOG_FORMAT
@@ -97,64 +98,21 @@ def isempty(string):
     return string is None or len(string) == 0
 
 
-def parse_args():
-    """Parse command line and get parameters from environment, if present"""
-
-    # Setup argument parser, the workhorse gluing it all together
-    parser = ArgumentParser(
-        epilog='(*) by default the script will look for the first device that '
-               'matches %s* in %s, if multiple entries are found -s or -f must '
-               'be used to specify which sensor to read.' %
-               (SENSOR_DEV_PREFIX, SENSOR_DEV_DIR),
-        description='Nagios check plugin for 1-wire temp. sensor on RaspberryPi'
-    )
-    parser.add_argument('-V', '--version',action="version",version=PROG_VERSION)
-
-    pgroup = parser.add_mutually_exclusive_group(required=False)
-    pgroup.add_argument('-C', '--celcius',  action='store_const',
-                        dest='converter',   const=CONVERT_CELCIUS,
-                        help='measure, critical and warn values in Celcius '
-                             '(default)',   default=CONVERT_CELCIUS)
-    pgroup.add_argument('-F', '--farenheit',action='store_const',
-                        dest='converter',   const=CONVERT_FARENHEIT,
-                        help='measure, critical and warn values in Farenheit')
-
-    parser.add_argument('-w', '--warn',     type=float,
-                        help='temperature for warning status')
-    parser.add_argument('-c','--critical',  type=float,
-                        help='temperature for critical status')
-
-    parser.add_argument('-r', '--retries', type=int,default=SENSOR_READ_RETRIES,
-                        help='number of times to retry reading sensor data when'
-                             ' unstable (defaults to %d)' % SENSOR_READ_RETRIES)
-
-    pgroup = parser.add_mutually_exclusive_group(required=False)
-    pgroup.add_argument('-s', '--serial',
-                        help='(unique part of) temperature sensor serial (*)')
-    pgroup.add_argument('-f', '--file',
-                        help='input file (or device) to obtain data from (*)')
-
-    pgroup = parser.add_mutually_exclusive_group(required=False)
-    pgroup.add_argument('-q', '--quiet',    default=logging.CRITICAL,
-                        action=SetLogLevel, const=LOGGING_NONE,
-                        help='quiet (no output, only exit with exit code)')
-    pgroup.add_argument('-v', '--verbose',  help='more verbose output',
-                        action=SetLogLevel, const=logging.INFO)
-    pgroup.add_argument('-d', '--debug',    help='debug output (more verbose)',
-                        action=SetLogLevel, const=logging.DEBUG)
-
-    parser.add_argument('-l', '--logfile',  action=SetLogFile,
-                        help='send logging output to logfile')
-
-    # parse arguments and post-process command line options
-    args = parser.parse_args()
+def read_rpi_cpu_temp(args):
+    """Reads CPU temperature and converts it to desired unit, returns temperature"""
+    with open(args.file, 'r') as f:
+        lines = f.readlines()
+        logger.debug('Temperature sensor data read from %s: %s', f.name, lines)
 
-    # if we got here all seems OK
-    return args
+    temp_read = int(lines[0])
+    temp = args.converter[0](temp_read)
+    logger.debug('Temperature sensor value %d is %.2f%s', temp_read,
+    temp, args.converter[1])
+    return temp, 1
 
 
-def get_sensor_device_filename(args, dev_dir=SENSOR_DEV_DIR,
-                            prefix=SENSOR_DEV_PREFIX, suffix=SENSOR_DEV_SUFFIX):
+def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR,
+                            prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX):
     """Auto-determine sensor datafile name (unless args.file is set)"""
     if isempty(args.file):
         search_pat = dev_dir + ('/' if dev_dir[-1]!='/' else '')
@@ -180,23 +138,19 @@ def get_sensor_device_filename(args, dev_dir=SENSOR_DEV_DIR,
     return filename
 
 
-def read_sensor_raw(device_file):
-    """Reads the raw data from the sensor device file, returns array of lines"""
-    with open(device_file, 'r') as f:
-        lines = f.readlines()
-        logger.debug('Temperature sensor data read from %s: %s', f.name, lines)
-    return lines
-
-
-def read_temp(device_file, converter=CONVERT_CELCIUS, maxretries=10):
+def read_w1_temp(args):
     """Reads sensor data and converts it to desired unit, returns temperature"""
-    lines = read_sensor_raw(device_file)
-    tries = 1
-    while lines[0].strip()[-3:] != 'YES' and tries <= maxretries:
+    device_file = get_w1_sensor_device_filename(args)
+    lines=[ '' ]
+    tries = 0
+    while tries <= args.retries and lines[0].strip()[-3:] != 'YES':
+        if tries > 0:
+            logger.warn('Temperature sensor data not stable, reading once more')
+            sleep(0.2)
         tries += 1
-        sleep(0.2)
-        logger.warn('Temperature sensor data not stable, reading once more')
-        lines = read_temp_raw(device_file)
+        with open(device_file, 'r') as f:
+            lines = f.readlines()
+            logger.debug('Temperature sensor data read from %s: %s', f.name, lines)
 
     if lines[0].strip()[-3:] != 'YES':
         errmsg = 'no stable temperature sensor data after %d tries' % tries
@@ -206,15 +160,84 @@ def read_temp(device_file, converter=CONVERT_CELCIUS, maxretries=10):
             errmsg = 'temperature sensor data format is not supported'
         else:
             temp_read = int(lines[1][equals_pos+2:])
-            temp = converter[0](temp_read)
+            temp = args.converter[0](temp_read)
             logger.debug('Temperature sensor value %d is %.2f%s', temp_read,
-                         temp, converter[1])
+                         temp, args.converter[1])
             return temp, tries
 
     logger.critical(errmsg)
     raise ValueError(errmsg)
 
 
+def parse_args():
+    """Parse command line and get parameters from environment, if present"""
+
+    # Setup argument parser, the workhorse gluing it all together
+    parser = ArgumentParser(
+        epilog='(*) by default the script will look for the first device that '
+               'matches %s* in %s, if multiple entries are found -s or -f must '
+               'be used to specify which sensor to read.' %
+               (W1_SENSOR_DEV_PREFIX, W1_SENSOR_DEV_DIR),
+        description='Nagios check plugin for 1-wire temp. sensor on RaspberryPi'
+    )
+    parser.add_argument('-V', '--version',action="version",version=PROG_VERSION)
+
+    pgroup = parser.add_mutually_exclusive_group(required=False)
+    pgroup.add_argument('-C', '--celcius',  action='store_const',
+                        dest='converter',   const=CONVERT_CELCIUS,
+                        help='measure, critical and warn values in Celcius '
+                             '(default)',   default=CONVERT_CELCIUS)
+    pgroup.add_argument('-F', '--farenheit',action='store_const',
+                        dest='converter',   const=CONVERT_FARENHEIT,
+                        help='measure, critical and warn values in Farenheit')
+
+    parser.add_argument('-w', '--warn',     type=float,
+                        help='temperature for warning status')
+    parser.add_argument('-c','--critical',  type=float,
+                        help='temperature for critical status')
+
+    pgroup = parser.add_mutually_exclusive_group(required=False)
+    pgroup.add_argument('-q', '--quiet',    default=logging.CRITICAL,
+                        action=SetLogLevel, const=LOGGING_NONE,
+                        help='quiet (no output, only exit with exit code)')
+    pgroup.add_argument('-v', '--verbose',  help='more verbose output',
+                        action=SetLogLevel, const=logging.INFO)
+    pgroup.add_argument('-d', '--debug',    help='debug output (more verbose)',
+                        action=SetLogLevel, const=logging.DEBUG)
+
+    parser.add_argument('-l', '--logfile',  action=SetLogFile,
+                        help='send logging output to logfile')
+
+    subparser = parser.add_subparsers(title='Supported temperature sensors')
+
+    cpuparser = ArgumentParser(add_help=False)
+    cpuparser.add_argument('-f', '--file', default=CPU_SENSOR_DEV,
+                           help='input file (or device) to obtain data from'
+                                ' (defaults to %s)' % CPU_SENSOR_DEV)
+    cmdparser = subparser.add_parser('rpi_cpu', parents=[cpuparser],
+                                    help='read built-in Raspberry Pi CPU temperature')
+    cmdparser.set_defaults(func=read_rpi_cpu_temp, cmdparser=cmdparser, retries=0)
+
+    w1parser = ArgumentParser(add_help=False)
+    pgroup = w1parser.add_mutually_exclusive_group(required=False)
+    pgroup.add_argument('-s', '--serial',
+                        help='(unique part of) temperature sensor serial (*)')
+    pgroup.add_argument('-f', '--file',
+                        help='input file (or device) to obtain data from (*)')
+    w1parser.add_argument('-r', '--retries', type=int,default=W1_SENSOR_READ_RETRIES,
+                        help='number of times to retry reading sensor data when'
+                             ' unstable (defaults to %d)' % W1_SENSOR_READ_RETRIES)
+    cmdparser = subparser.add_parser('w1_ds18b20', parents=[w1parser],
+                                    help='read 1-wire connected DS18b20 sensor')
+    cmdparser.set_defaults(func=read_w1_temp,   cmdparser=cmdparser)
+
+    # parse arguments and post-process command line options
+    args = parser.parse_args()
+
+    # if we got here all seems OK
+    return args
+
+
 def nagios_exit(status, message, data=None):
     """exit 'nagios-style', print status and message followed by perf. data"""
     if logger.isEnabledFor(logging.CRITICAL):
@@ -239,8 +262,7 @@ if __name__ == '__main__':
 
     try:
         starttime = time()
-        devicefile = get_sensor_device_filename(args)
-        temperature, tries = read_temp(devicefile, args.converter, args.retries)
+        temperature, tries = args.func(args)
         endtime = time()
 
     except (KeyboardInterrupt) as e:
@@ -270,3 +292,4 @@ if __name__ == '__main__':
           ('retries', [ tries-1, None, args.retries, 0, None ]),
           ('checktime', [ '%fs' % elapse, None, None, 0, None])
     ])
+
--
libgit2 0.22.2