Compare commits

..

10 Commits

Author SHA1 Message Date
c39cd60d85 renamed for Gitea 2026-01-02 12:38:15 +01:00
ea7c02e270 updated readme to state the python3 is now required 2024-12-29 15:41:12 +01:00
c1cfdd7d21 updated plugins to python3
made specifying which temperatur for plugins/check_temperature mandatory
2024-12-29 15:36:33 +01:00
bbad51d9ca 2 optimizations:
- got rid of unnecessary read commands to set variables
- de-duplicate the list of nameservers to check before checking
2021-03-09 23:30:46 +01:00
dadb9bb181 First commit for check_dns_replication 2021-03-09 21:48:55 +01:00
302f2b0ade Fixed layout issues in GitLab 2019-03-10 13:12:51 +01:00
3128d9ae02 Minor updates to README.md 2019-03-10 13:04:44 +01:00
1ec60af44a Updated documentation 2019-03-10 13:03:10 +01:00
379482e729 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)
2019-03-10 13:01:49 +01:00
Frederik Lindenaar
28daf07d1a fixed scaling issue for Raspberry Pi CPU temperature 2019-03-03 20:47:38 +01:00
5 changed files with 515 additions and 114 deletions

218
README.md
View File

@@ -9,15 +9,19 @@ not really appropriate. I am publishing them separately so that others may
benefit from these as well. Use them freely and please let me know is you
encounter any issues or require changes.
The latest versions, documentation and bugtracker available on my
[GitLab instance](https://gitlab.lindenaar.net/scripts/privacyidea-checkotp)
Please note: as of December 2024 python scripts were upgraded to python3
Copyright (c) 2015 - 2016 Frederik Lindenaar. free for distribution under
The latest versions, documentation and bugtracker available on my
[GitLab instance](https://gitlab.lindenaar.net/scripts/nagios-plugins)
Copyright (c) 2015 - 2024 Frederik Lindenaar. free for distribution under
the GNU General Public License, see [below](#license)
contents
========
This repository contains the following scripts:
* [check_dns_replication](#check_dns_replication)
check DNS zone replication by comparing zone serial numbers on DNS servers
* [check_memory](#check_memory)
patched version of nagios-plugins check_memory script for Linux procps v3.3+
* [check_multiple_host_addresses](#host_addresses)
@@ -25,10 +29,71 @@ 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 CPU temperature of a RaspberryPi or that of a DS18b20 1-wire sensor on a RaspberryPi
plugin to monitor the RaspberryPi CPU temperature or that of an 1-wire
(DS18b20) or I2C (MCP9080) sensor attached to a RaspberryPi
* [nagiosstatus](#nagiosstatus)
CGI-BIN script to report the status of nagios (to monitor nagios itself)
<a name=check_dns_replication>plugins/check_dns_replication</a>
---------------------------------------------------------------
With this check plugin / script, Nagios can monitor the replication of DNS zones
between the authoritative DNS server for a domain and one or more of it's slave
(or secondary) DNS servers. The script can check one or multiple DNS zones and
can be pointed at one ore more specific DNS slave server(s) or us the NS records
of the zone to check all DNS servers of that domain (or a combination of this)
The script expects a (comma separated list of) DNS zone(s) to validate as its
first command line parameter. It optionally also accepts one or more DNS servers
to check as further parameters (either separate parameters or comma separated).
If no DNS Servers are provided or the `-n` command line option is passed it will
lookup the DNS Servers from the NS records in de DNS zone.
The script will first fetch the authoritative DNS server from the SOA record, so
that server must be reachable. This first lookup will be done against the first
DNS server, if provided, or the default nameserver of the host. Next it will
fetch the DNS zone's SOA record from each server and compare it with the master.
Installation is straightforward, after installing the script on your server, add
the following to your `commands.cmd` configuration file to make it available:
~~~
# 'check-dns-replication' command definition to check DNS replication of one or more zones
define command {
command_name check-dns-replication
command_line [install_path]/plugins/check_dns_replication -n '$ARG1$' '$HOSTADDRESS$'
}
# 'check-dns-slave' command to check DNS replication of one or more zones against a single server
define command {
command_name check-dns-slave
command_line [install_path]/plugins/check_dns_replication '$ARG1$' '$HOSTADDRESS$'
}
~~~
The example below shows how to check DNS zone replication for the primary DNS
server (which checks replication to all secondaries) and how to check an extra
secondary DNS server that is not listed as NS record in the zone.
~~~
# check DNS replication for an DNS zone to ensure all secondaries are in sync
define service {
host auth.dns.mydomain.tld
service_description DNS Zone Replication
check_command check-dns-replication!mydomain.tld
use generic-service
}
# check DNS replication to a specific secondary DNS server
define service {
host sec1.dns.mydomain.tld
service_description DNS Zone Replication to secondary
check_command check-dns-slave!mydomain.tld
use generic-service
}
~~~
<a name=check_memory>plugins/check_memory</a>
---------------------------------------------
Nagios check script to monitor the memory on Linux systems. Due to changes in
@@ -53,23 +118,23 @@ Installation is straightforward, after installing the script on your server, add
the following to your `commands.cmd` configuration file to make it available:
~~~
# 'check-host-alive' command definition for multi-homed/dual-stack servers
define command{
command_name check-addresses-alive
command_line [install_path]/plugins/check_multiplehost_addresses '$HOSTADDRESS$' '$_HOSTADDRESS6$'
}
# 'check-host-alive' command definition for multi-homed/dual-stack servers
define command {
command_name check-addresses-alive
command_line [install_path]/plugins/check_multiplehost_addresses '$HOSTADDRESS$' '$_HOSTADDRESS6$'
}
~~~
The example above assumes that the IPv6 address of the host is provided as part
of the host configuration, i.e.:
~~~
define host {
...
address 192.168.0.1
_address6 fdf8:f340:ab9d:c213::1
...
}
define host {
...
address 192.168.0.1
_address6 fdf8:f340:ab9d:c213::1
...
}
~~~
To use the script either add `check_command check-addresses-alive`
@@ -89,9 +154,9 @@ Please run `check_otp -h` for an actual overview of the available options. The
script currently supports 3 modes of operation:
* password - simply authenticate with the provided secret (no calculations)
* totp - calculate the TOTP code using a key and current time
* hotp - calculate the HOTP code using a key and a count (automatically
increments the count in case a count file is used)
* totp - calculate the TOTP code using a key and current time
* hotp - calculate the HOTP code using a key and a count (automatically
increments the count in case a count file is used)
Generic parameters (connection parameters, critical/warning thresholds, etc.)
should be provided before the mode of operation is specified, mode-specific
@@ -239,68 +304,128 @@ define service {
-------------------------------------------------------
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).
supports the following sensors:
* 1-wire:
* Maxim Integrated [DS18B20](https://learn.adafruit.com/adafruits-raspberry-pi-lesson-11-ds18b20-temperature-sensing)
* I2C
* Bosch Sensortec [BME280](https://learn.adafruit.com/adafruit-bme280-humidity-barometric-pressure-temperature-sensor-breakout)
* Bosch Sensortec [BMP280](https://learn.adafruit.com/adafruit-bmp280-barometric-pressure-plus-temperature-sensor-breakout)
* Microchip [MCP9080](https://learn.adafruit.com/adafruit-mcp9808-precision-i2c-temperature-sensor-guide)
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
~~~
or use `raspi-config` in interactive mode (9. Advanced Options --> A9. 1-Wire).
Please note that changing this requires a reboot.
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 click on the links in the list above. As per these,
most sensors require some configuration to make them available:
* No setup is required to read the CPU temperature.
Installation for is straightforward, after installing the script on the server
add the following to your Nagios `commands.cmd` configuration file:
* To enable 1-wire interface support on the RaspberryPi use the command:
~~~
sudo raspi-config nonint do_onewire 0
~~~
or use `raspi-config` interactively (1. Interfacing Options --> P7. 1-Wire).
Please note that changing this requires a reboot.
* To enable I2C interface support on the RaspberryPi use the command:
~~~
sudo raspi-config nonint do_i2c 0
~~~
or use `raspi-config` interactively (1. Interfacing Options --> P5. I2C).
Please note that changing this requires a reboot.
The I2C interface also requires the `SMBus` or `SMBus2` library, to install
the `SMBus` library on Raspbian Linux run:
~~~
sudo apt install python-smbus
~~~
`SMBus2` is a pure Python implementation that requires system-wide or a
`virtualenv`-based installation, less trivial than installing the package.
Configuration of Nagios to use the script is straightforward, after installing
the script on the server add the following to your Nagios `commands.cmd`
configuration file to enable checking the CPU temperature:
~~~
# '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_name check_cpu_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_name check_cpu_ftemperature
command_line [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$ rpi_cpu
}
~~~
To monitor a supported temperature sensor on its default address, add:
~~~
# '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$ w1_ds18b20
command_name check_cpu_temperature
command_line [install_path]/plugins/check_temperature -w $ARG1$ -c $ARG2$ <<sensor>>
}
# '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$ w1_ds18b20
command_name check_cpu_temperature_f
command_line [install_path]/plugins/check_temperature -F -w $ARG1$ -c $ARG2$ <<sensor>>
}
~~~
With `<<sensor>>` replaced by the sensor, e.g. w1_ds18b20 for a 1-wire DS18B20,
i2c_mcp9808 for an I2C MCP9808 sensor or i2c_bme280 for an I2C BME280. Run
`check_temperature -h` to get the list of supported sensors. In case you have
multiple sensors, add a separate definition for each sensor with a different
value for `command_name`.
If you need to pass on additional parameters, e.g. the sensor serial for an
1-wire DS18B20, you can do that like this:
~~~
# '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_name check_ds18b20_sensor
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_name check_ds18b20_sensor_f
command_line [install_path]/plugins/check_temperature -F -w $ARG2$ -c $ARG3$ w1_ds18b20 -s $ARG1$
}
~~~
Likewise, to pass the I2C address for an I2C MCP9808 use something like:
~~~
# 'check_temperature_sensor' command definition to monitor a single temperature in C
# parameters: sensor address (ARG1), warning (ARG2) and critical (ARG3) temperature in Celcius
define command {
command_name check_mcp9808_sensor
command_line [install_path]/plugins/check_temperature -w $ARG2$ -c $ARG3$ i2c_mcp9808 -a $ARG1$
}
# 'check_ftemperature_sensor' command definition to monitor a single temperature in F
# parameters: sensor address (ARG1), warning (ARG2) and critical (ARG3) temperature in Farenheit
define command {
command_name check_mcp9808_sensor_f
command_line [install_path]/plugins/check_temperature -F -w $ARG2$ -c $ARG3$ i2c_mcp9808 -a $ARG1$
}
~~~
For the list of supported sensors run `check_temperature -h` and to get the
options sensor ``<<sensor>>`` supports run `check_temperature <<sensor>> -h`.
Make sure to replace `[install_path]/plugins` with the location of the script.
To use the it define a service check like below:
@@ -313,7 +438,7 @@ define service {
use generic-service
}
# check temperature in Celcius using a DS18B20 sensor connected to a RaspberryPi
# check temperature in Celcius using a sensor connected to a RaspberryPi
define service {
host hostname.mydomain.tld
service_description Check Temperature
@@ -325,7 +450,15 @@ define service {
define service {
host hostname.mydomain.tld
service_description Check Temperature
check_command check_temperature_sensor!0000a31ea3de!30!35
check_command check_ds18b20_sensor!0000a31ea3de!30!35
use generic-service
}
# check temperature with MCP9808 sensor 0x19 connected to a RaspberryPi
define service {
host hostname.mydomain.tld
service_description Check Temperature
check_command check_mcp9808_sensor!0x19!30!35
use generic-service
}
~~~
@@ -362,4 +495,3 @@ 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/>.

103
plugins/check_dns_replication Executable file
View File

@@ -0,0 +1,103 @@
#!/bin/bash
# check_dns_replication - check DNS zone replication by comparing zone serials
#
# Version 1.0, latest version, documentation and bugtracker available at:
# https://gitlab.lindenaar.net/scripts/nagios-plugins
#
# Copyright (c) 2021 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
# Software Foundation, or (at your option) any later version of the license.
#
# This script is distributed in the hope that it will be useful but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, visit <http://www.gnu.org/licenses/> to download it.
# Usage: check_dns_replication [-n] dns_zone[,dns_zone...] [dns_server...]
if [ "$#" -eq 0 -o "$1" == "-n" -a "$#" -eq 1 ]; then
echo "DNSREPLICATION: UNKNOWN | missing parameter dns_zone"
exit 3
elif [ "$1" == "-n" ]; then
DNS_SERVER_LOOKUP=$1
shift
elif [ $1 == '-h' -o $1 == '--help' ]; then
cat << EOT
`basename $0` - check DNS zone replication by comparing SOA serial(s)
usage: $0 [-n] dns_zone[,dns_zone...] [dns_server...]
parameters:
-n when specified (or no dns_server provided) check domain's NS records
-h this help
dns_zone[,dns_zone...] list of DNS zones to check (comma separated!)
[dns_server...] DNS server(s) to compare with authoratative server
EOT
exit 3
elif [[ "$1" = -* ]]; then
echo "DNSREPLICATION: UNKNOWN | invalid parameter, for help run $0 -h"
exit 3
fi
DNS_ZONES=${1//,/ }
shift
DNS_SERVERS=${*//,/ }
n="
"
NAGIOS_STATE=OK
NAGIOS_RESULT=0
NAGIOS_DETAILS=
for DNS_ZONE in $DNS_ZONES; do
AUTH_NAMESERVER=$(host -t soa $DNS_ZONE ${DNS_SERVERS// .*/} | tail -1 | cut -d\ -f5 | sed "s/.$//")
[ -n "$AUTH_NAMESERVER" ] && AUTH_SOA_SERIAL=$(host -t soa $DNS_ZONE $AUTH_NAMESERVER | tail -1 | cut -d\ -f7)
if [ -z "$AUTH_SOA_SERIAL" ]; then
NAGIOS_STATE=CRITICAL
NAGIOS_RESULT=2
NAGIOS_DETAILS="$NAGIOS_DETAILS$n$DNS_ZONE: unknown domain (unable to resolve)"
else
NAMESERVER_OK=
NAMESERVER_HIGHER=
NAMESERVER_LOWER=
NAMESERVER_EMPTY=
NAMESERVERS=$DNS_SERVERS
if [ -z "$DNS_SERVERS" -o "$DNS_SERVER_LOOKUP" == '-n' -o "$DNS_SERVERS" == "$AUTH_NAMESERVER" ]; then
NAMESERVERS="$NAMESERVERS $(host -t ns $DNS_ZONE $AUTH_NAMESERVER | fgrep -v : | sed "s/.* //;s/\.$//")"
fi
NAMESERVERS=$(echo $NAMESERVERS | tr ' ' '\n' | sort -u)
for NAMESERVER in $NAMESERVERS; do
if [ "$NAMESERVER" != "$AUTH_NAMESERVER" ]; then
SOA_SERIAL=$(host -t soa $DNS_ZONE $NAMESERVER | tail -1 | cut -d\ -f 7)
if [ -z "$SOA_SERIAL" ]; then
NAMESERVER_EMPTY="$NAMESERVER_EMPTY$NAMESERVER,"
elif [ "$SOA_SERIAL" -lt "$AUTH_SOA_SERIAL" ]; then
NAMESERVER_LOWER="$NAMESERVER_LOWER$NAMESERVER,"
elif [ "$SOA_SERIAL" -gt "$AUTH_SOA_SERIAL" ]; then
NAMESERVER_HIGHER="$NAMESERVER_HIGHER$NAMESERVER,"
else
NAMESERVER_OK="$NAMESERVER_OK$NAMESERVER,"
fi
fi
done
NAGIOS_DETAILS="$NAGIOS_DETAILS$n$DNS_ZONE: $AUTH_NAMESERVER($AUTH_SOA_SERIAL)"
[ -n "$NAMESERVER_OK" ] && NAGIOS_DETAILS="$NAGIOS_DETAILS ok:$NAMESERVER_OK"
[ -n "$NAMESERVER_HIGHER" ] && NAGIOS_DETAILS="$NAGIOS_DETAILS higher:$NAMESERVER_HIGHER"
[ -n "$NAMESERVER_LOWER" ] && NAGIOS_DETAILS="$NAGIOS_DETAILS lower:$NAMESERVER_LOWER"
[ -n "$NAMESERVER_EMPTY" ] && NAGIOS_DETAILS="$NAGIOS_DETAILS error:$NAMESERVER_EMPTY"
if [ -n "$NAMESERVER_HIGHER$NAMESERVER_LOWER$NAMESERVER_EMPTY" ]; then
NAGIOS_STATE=CRITICAL
NAGIOS_RESULT=2
fi
NAGIOS_DETAILS="${NAGIOS_DETAILS%,}"
fi
done
echo "DNSREPLICATION: $NAGIOS_STATE$NAGIOS_DETAILS"
exit $NAGIOS_RESULT

View File

@@ -1,11 +1,11 @@
#! /usr/bin/env python
#! /usr/bin/env python3
#
# check_otp - Nagios check plugin for LinOTP/PrivacyIDEA OTP validation
#
# Version 1.1, latest version, documentation and bugtracker available at:
# Version 1.2, latest version, documentation and bugtracker available at:
# https://gitlab.lindenaar.net/scripts/nagios-plugins
#
# Copyright (c) 2018 Frederik Lindenaar
# Copyright (c) 2018 - 2024 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
@@ -23,8 +23,8 @@ from time import time
from struct import pack
from hashlib import sha1
from getpass import getpass
from urllib import urlencode
from urllib2 import Request, HTTPError, URLError, urlopen
from urllib.parse import urlencode
from urllib.request import Request, HTTPError, URLError, urlopen
from ssl import CertificateError, \
create_default_context as create_default_SSL_context, \
_create_unverified_context as create_unverified_SSL_context
@@ -33,7 +33,7 @@ from argparse import ArgumentParser as StandardArgumentParser, FileType, \
_StoreAction as StoreAction, _StoreConstAction as StoreConstAction
# Constants (no need to change but allows for easy customization)
VERSION="1.1"
VERSION="1.2"
PROG_NAME=os.path.splitext(os.path.basename(__file__))[0]
PROG_VERSION=PROG_NAME + ' ' + VERSION
HTTP_AGENT=PROG_NAME + '/' + VERSION
@@ -404,7 +404,7 @@ def nagios_exit(status, message, data=None):
if isinstance(v,list) else v) for k,v in data ])
else:
perfdata = ''
print 'OTP %s: %s%s' % (status[0], message, perfdata)
print('OTP %s: %s%s' % (status[0], message, perfdata))
sys.exit(status[1])
@@ -412,9 +412,9 @@ if __name__ == '__main__':
try:
args = parse_args()
except ArgumentParserError as e:
nagios_exit(NAGIOS_UNKNOWN,'error with setup: ' + e.message)
nagios_exit(NAGIOS_UNKNOWN,'error with setup: ' + ','.join(e.args))
except (KeyboardInterrupt, EOFError) as e:
print
print()
nagios_exit(NAGIOS_UNKNOWN,'initialization aborted')
message = args.func.__name__ + ' authentication'

View File

@@ -1,11 +1,11 @@
#! /usr/bin/env python
#! /usr/bin/env python3
#
# check_temperature - Nagios temperature check for DS18B20 sensor on RaspberryPi
# check_temperature - Nagios temperature check for RaspberryPi-connected sensors
#
# Version 1.2, latest version, documentation and bugtracker available at:
# https://gitlab.lindenaar.net/scripts/nagios-plugins
# Version 1.3 latest version, documentation and bugtracker available at:
# https://gitlab.lindenaar.net/scripts/nagios-plugins
#
# Copyright (c) 2017 - 2019 Frederik Lindenaar
# Copyright (c) 2017 - 2024 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,16 +27,37 @@ from argparse import ArgumentParser as StandardArgumentParser, FileType, \
import logging
# Constants (no need to change but allows for easy customization)
VERSION="1.2"
VERSION="1.3"
PROG_NAME=splitext(basename(__file__))[0]
PROG_VERSION=PROG_NAME + ' ' + VERSION
CPU_SENSOR_DEV = '/sys/class/thermal/thermal_zone0/temp'
CPU_SENSOR_SCALE=1000
I2C_BMX280_DEFAULT_ADDR=0x77
I2C_BMX280_CALIBRATE_ADDR=0x88
I2C_BMX280_CALIBRATE_LEN=24
I2C_BMX280_CAL_HUM0_ADDR=0xA1
I2C_BMX280_CAL_HUM_ADDR=0xE1
I2C_BMX280_CAL_HUM_LEN=7
I2C_BMX280_CONFIG_ADDR=0xF5
I2C_BMX280_CONFIG = 0xA0 # Stand_by time = 1000 ms
I2C_BMX280_CTRL_MEAS_ADDR=0xF4
I2C_BMX280_CTRL_MEAS=0x27 # Normal mode, Pressure
# and Temperature Oversampling rate = 1
I2C_BMX280_CTRL_HUM_ADDR=0xF2
I2C_BMX280_CTRL_HUM=1 # Humidity Oversampling rate = 1
I2C_BMX280_MEAS_ADDR=0xF7
I2C_BMX280_MEAS_LEN=8
I2C_BMX280_SENSOR_SCALE=5120.0
I2C_MCP9808_DEFAULT_ADDR=0x18
I2C_MCP9808_CONFIG_ADDR=0x1
I2C_MCP9808_CONFIG = [ 0x00, 0x00 ] # continuous conversion (power-up default)
I2C_MCP9808_PRECISION_ADDR=0x08
I2C_MCP9808_PRECISION=3 # 0=0.5, 1=0.25, 2=0.125, 3=0.0625 degr. C
I2C_MCP9808_TEMP_ADDR=0x05
I2C_MCP9808_SENSOR_SCALE=16
W1_SENSOR_DEV_DIR = '/sys/bus/w1/devices/'
W1_SENSOR_DEV_PREFIX = '28-'
W1_SENSOR_DEV_SUFFIX = '/w1_slave'
@@ -87,6 +108,11 @@ class SetLogFile(StoreAction):
###############################################################################
def hex_int(string):
"""Use int()'s auto-detection to parse 10-base and 16-base (0x..) numbers"""
return int(string, 0);
def convert_celcius(temp_read, scale = 1):
"""Converts raw temperature sensore value to degrees Celcius"""
return float(temp_read) / float(scale)
@@ -99,32 +125,22 @@ def convert_farenheit(temp_read, scale = 1):
CONVERT_FARENHEIT = ( convert_farenheit, 'F', 'Farenheit' )
def isempty(string):
"""Checks whether string 'str' provided is unset or empty"""
return string is None or len(string) == 0
def hex_int(string):
"""Use int()'s auto-detection to parse 10-base and 16-base (0x..) numbers"""
return int(string, 0);
####################[ Get CPU temperature of Raspberry Pi ]####################
def read_rpi_cpu_temp(args):
"""Reads CPU temperature and converts it to desired unit, returns temperature"""
"""Reads CPU temperature ands returns it converted to desired unit"""
with open(args.file, 'r') as f:
lines = f.readlines()
logger.debug('Temperature sensor data read from %s: %s', f.name, lines)
temp_read = int(lines[0])
temp = args.converter[0](temp_read)
temp = args.converter[0](temp_read, CPU_SENSOR_SCALE)
logger.debug('Temperature sensor value %d is %.2f%s', temp_read,
temp, args.converter[1])
temp, args.converter[1])
return temp, 1
def read_i2c_mcp9808_temp(args):
"""Returns temperature from I2C MCP9808 sensor in desired unit"""
###############[ Get I2C (sm)bus object and I2C device address ]###############
def i2c_get_smbus_devaddr(args):
try:
import smbus
except ImportError:
@@ -132,47 +148,166 @@ def read_i2c_mcp9808_temp(args):
import smbus2 as smbus
except ImportError:
logger.critical("Unable to import either smbus or smbus2 library");
raise ImportError("missing I2C library, please install python-smbus or smbus2");
raise ImportError("missing I2C library, please install smbus2 "
"or Debian python-smbus package ");
try:
bus = smbus.SMBus(args.i2cbus) # Get I2C bus
return (smbus.SMBus(args.i2cbus), # get i2c bus
args.default_address if args.address is None else args.address)
except OSError as e:
logger.critical(e)
raise IOError("Invalid I2C bus: %d" % args.i2cbus)
####################[ Get I2C BME280/BMP280 Sensor values ]####################
# Inspired by https://github.com/ControlEverythingCommunity/BME280
def read_i2c_bmX280(args):
"""Returns temperature from I2C BME280/BMP280 sensor in desired unit"""
i2c_bus, i2c_addr = i2c_get_smbus_devaddr(args)
def convertLE16(data, offset, signed=False):
result = (data[offset + 1] << 8) | data[offset]
if signed and result > 32767:
result -= 65536
return result
try:
bus.write_i2c_block_data(args.address, I2C_MCP9808_CONFIG_ADDR,
# Get Temperature and Pressure Calibration Data
data = i2c_bus.read_i2c_block_data(i2c_addr, I2C_BMX280_CALIBRATE_ADDR,
I2C_BMX280_CALIBRATE_LEN)
dig_T1 = convertLE16(data, 0)
dig_T2 = convertLE16(data, 2, True)
dig_T3 = convertLE16(data, 4, True)
dig_P1 = convertLE16(data, 6)
dig_P2 = convertLE16(data, 8, True)
dig_P3 = convertLE16(data, 10, True)
dig_P4 = convertLE16(data, 12, True)
dig_P5 = convertLE16(data, 14, True)
dig_P6 = convertLE16(data, 16, True)
dig_P7 = convertLE16(data, 18, True)
dig_P8 = convertLE16(data, 20, True)
dig_P9 = convertLE16(data, 22, True)
if args.read_humidity:
# Get Humidity Calibration Data
dig_H1 = i2c_bus.read_byte_data(i2c_addr,I2C_BMX280_CAL_HUM0_ADDR)
data = i2c_bus.read_i2c_block_data(i2c_addr,I2C_BMX280_CAL_HUM_ADDR,
I2C_BMX280_CAL_HUM_LEN)
dig_H2 = convertLE16(data, 0, True)
dig_H3 = data[2]
dig_H4 = (data[3] << 4) | (data[4] & 0x0f)
dig_H5 = (data[5] << 4) | (data[4] >> 4)
dig_H6 = data[6]
if dig_H6 > 127:
dig_H6 -= 256
i2c_bus.write_byte_data(i2c_addr, I2C_BMX280_CTRL_HUM_ADDR,
I2C_BMX280_CTRL_HUM)
# Setup BMP280/BME280 configuration and wait 0.5 seconds for things to settle
i2c_bus.write_byte_data(i2c_addr, I2C_BMX280_CTRL_MEAS_ADDR,
I2C_BMX280_CTRL_MEAS)
i2c_bus.write_byte_data(i2c_addr, I2C_BMX280_CONFIG_ADDR,
I2C_BMX280_CONFIG)
sleep(0.5)
# Read sensor data and and convert using calibration data
data = i2c_bus.read_i2c_block_data(i2c_addr, I2C_BMX280_MEAS_ADDR,
I2C_BMX280_MEAS_LEN)
# Convert 20-bits Temperature and calculate value with calibration data
adc_t = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
var1 = ((adc_t) / 16384.0 - (dig_T1) / 1024.0) * (dig_T2)
var2 = (((adc_t) / 131072.0 - (dig_T1) / 8192.0) * ((adc_t)/131072.0 - (dig_T1)/8192.0)) * (dig_T3)
t_fine = (var1 + var2)
# Convert 20-bits Pressure and calculate value with calibration data
adc_p = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
var1 = (t_fine / 2.0) - 64000.0
var2 = var1 * var1 * (dig_P6) / 32768.0
var2 = var2 + var1 * (dig_P5) * 2.0
var2 = (var2 / 4.0) + ((dig_P4) * 65536.0)
var1 = ((dig_P3) * var1 * var1 / 524288.0 + ( dig_P2) * var1) / 524288.0
var1 = (1.0 + var1 / 32768.0) * (dig_P1)
p = 1048576.0 - adc_p
p = (p - (var2 / 4096.0)) * 6250.0 / var1
var1 = (dig_P9) * p * p / 2147483648.0
var2 = p * (dig_P8) / 32768.0
pressure = (p + (var1 + var2 + (dig_P7)) / 16.0) / 100
extra_readings = [ ('pressure', pressure, 'hPa'), ]
if args.read_humidity:
# Convert 16-byte Humidity and calculate value with calibration data
adc_h = (data[6] << 8) | data[7]
var_H = ((t_fine) - 76800.0)
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)))
humidity = var_H * (1.0 - dig_H1 * var_H / 524288.0)
if humidity > 100.0 :
humidity = 100.0
elif humidity < 0.0 :
humidity = 0.0
extra_readings += ('humidity', humidity, '%', 0, 100),
logger.debug('BME280 sensor data: temperature %#02x: %#x = %d, '
'pressure: %#x, humidity: %#x', i2c_addr, adc_t,
t_fine, adc_p, adc_h)
logger.debug('Sensor Humidity value is %.2f %%', humidity)
else:
logger.debug('BMP280 sensor data: temperature %#02x: %#x = %d, '
'pressure: %#x',i2c_addr, adc_t, t_fine, adc_p)
# convert temperature to right units and scale and return value
temp = args.converter[0](t_fine, I2C_BMX280_SENSOR_SCALE)
logger.debug('Sensor Temperature value %d is %.2f%s',
t_fine, temp, args.converter[1])
logger.debug('Sensor Pressure value is %.2f hPa', pressure)
return (temp, extra_readings), 1
except IOError as e:
logger.critical(e)
raise IOError("Error while communicating with I2C device %#02x" %
i2c_addr)
########################[ Get I2C MCP9808 Temperature ]########################
# Inspired by https://github.com/ControlEverythingCommunity/MCP9808
def read_i2c_mcp9808(args):
"""Returns temperature from I2C mcp9808 sensor in desired unit"""
i2c_bus, i2c_addr = i2c_get_smbus_devaddr(args)
try:
# Setup MCP9808 configuration and wait 0.5 seconds for things to settle
i2c_bus.write_i2c_block_data(i2c_addr, I2C_MCP9808_CONFIG_ADDR,
I2C_MCP9808_CONFIG)
bus.write_byte_data(args.address, I2C_MCP9808_PRECISION_ADDR,
i2c_bus.write_byte_data(i2c_addr, I2C_MCP9808_PRECISION_ADDR,
I2C_MCP9808_PRECISION)
sleep(0.5)
# Read temperature (0x05), 2 bytes (MSB, LSB)
data = bus.read_i2c_block_data(args.address, I2C_MCP9808_TEMP_ADDR, 2)
logger.debug('Temperature sensor data from MCP9808 %#02x: 0x%02x%02x',
args.address, data[0], data[1])
# Read temperature (2 bytes - MSB,LSB) and convert to 13-bit signed int
data = i2c_bus.read_i2c_block_data(i2c_addr,I2C_MCP9808_TEMP_ADDR,2)
temp_read = ((data[0] & 0xF) << 8) | data[1]
if (data[0] & 0x10):
temp_read = -temp_read
logger.debug('MCP9808 sensor data %#02x: 0x%02x%02x = %d',
i2c_addr, data[0], data[1], temp_read)
# Convert the data to 13-bits
temp_read = ((data[0] & 0x1F) * 256) + data[1]
if temp_read > 4095 :
temp_read -= 8192
temp = args.converter[0](temp_read, 16)
# convert temperatur to right units and scale and return value
temp = args.converter[0](temp_read, I2C_MCP9808_SENSOR_SCALE)
logger.debug('Temperature sensor value %d is %.2f%s',
temp_read, temp, args.converter[1])
return temp, 1
except IOError as e:
logger.critical(e)
raise IOError("Error while communicating with I2C device %#02x" %
args.address)
i2c_addr)
#####################[ Get 1-Wire sensor device filename ]#####################
def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR,
prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX):
prefix=W1_SENSOR_DEV_PREFIX, suffix=W1_SENSOR_DEV_SUFFIX):
"""Auto-determine sensor datafile name (unless args.file is set)"""
if isempty(args.file):
if not args.file:
search_pat = dev_dir + ('/' if dev_dir[-1]!='/' else '')
search_pat+= prefix + '*' if isempty(args.serial) else '*' + args.serial
search_pat+= prefix + ('*' + args.serial if args.serial else '*')
logger.debug('looking for sensors with search pattern %s', search_pat)
device_folders = glob(search_pat)
@@ -194,7 +329,8 @@ def get_w1_sensor_device_filename(args, dev_dir=W1_SENSOR_DEV_DIR,
return filename
def read_w1_ds18b20_temp(args):
###################[ Get 1-Wire DS18b20 sensor temperature ]###################
def read_w1_ds18b20(args):
"""Returns temperature from 1-wire ds18b20 sensor in desired unit"""
device_file = get_w1_sensor_device_filename(args)
lines=[ '' ]
@@ -225,6 +361,7 @@ def read_w1_ds18b20_temp(args):
raise ValueError(errmsg)
#######################[ Parse Command Line parameters ]#######################
def parse_args():
"""Parse command line and get parameters from environment, if present"""
@@ -260,25 +397,42 @@ def parse_args():
parser.add_argument('-l', '--logfile', action=SetLogFile,
help='send logging output to logfile')
subparser = parser.add_subparsers(title='Supported temperature sensors')
subparser = parser.add_subparsers(title='Supported temperature sensors', required=True)
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)
help='read built-in Raspberry Pi CPU temperature')
cmdparser.set_defaults(func=read_rpi_cpu_temp,cmdparser=cmdparser,retries=0)
i2cparser = ArgumentParser(add_help=False)
i2cparser.add_argument('-a', '--address', type=hex_int,
help='I2C Address of sensor, use 0x.. for hex (*)')
i2cparser.add_argument('-b', '--i2cbus', default=1, type=int,
help='I2C Bus to use (defaults to 1)')
cmdparser = subparser.add_parser('i2c_bme280', parents=[i2cparser],
help='read I2C connected BME280 sensor',
epilog='(*) default I2C address for an BME280 is %#x' %
I2C_BMX280_DEFAULT_ADDR)
cmdparser.set_defaults(func=read_i2c_bmX280, cmdparser=cmdparser, retries=0,
read_humidity=True, default_address=I2C_BMX280_DEFAULT_ADDR)
cmdparser = subparser.add_parser('i2c_bmp280', parents=[i2cparser],
help='read I2C connected BMP280 sensor',
epilog='(*) default I2C address for an BMP280 is %#x' %
I2C_BMX280_DEFAULT_ADDR)
cmdparser.set_defaults(func=read_i2c_bmX280, cmdparser=cmdparser, retries=0,
read_humidity=False, default_address=I2C_BMX280_DEFAULT_ADDR)
cmdparser = subparser.add_parser('i2c_mcp9808', parents=[i2cparser],
help='read I2C connected MCP9808 sensor',
epilog='(*) default I2C address for an MCP9808 is 0x18')
cmdparser.set_defaults(func=read_i2c_mcp9808_temp, cmdparser=cmdparser, retries=0, address=0x18)
epilog='(*) default I2C address for an MCP9808 is %#x' %
I2C_MCP9808_DEFAULT_ADDR)
cmdparser.set_defaults(func=read_i2c_mcp9808, cmdparser=cmdparser,
retries=0, default_address=I2C_MCP9808_DEFAULT_ADDR)
w1parser = ArgumentParser(add_help=False)
pgroup = w1parser.add_mutually_exclusive_group(required=False)
@@ -286,16 +440,17 @@ def parse_args():
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)
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',
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))
cmdparser.set_defaults(func=read_w1_ds18b20_temp, cmdparser=cmdparser)
cmdparser.set_defaults(func=read_w1_ds18b20, cmdparser=cmdparser)
# parse arguments and post-process command line options
args = parser.parse_args()
@@ -304,6 +459,7 @@ def parse_args():
return args
############################[ Exit the Nagios way ]############################
def nagios_exit(status, message, data=None):
"""exit 'nagios-style', print status and message followed by perf. data"""
if logger.isEnabledFor(logging.CRITICAL):
@@ -313,17 +469,18 @@ def nagios_exit(status, message, data=None):
if isinstance(v,list) else v) for k,v in data ])
else:
perfdata = ''
print 'Temperature %s: %s%s' % (status[0], message, perfdata)
print('Temperature %s: %s%s' % (status[0], message, perfdata))
exit(status[1])
#################################[ Main logic ]#################################
if __name__ == '__main__':
try:
args = parse_args()
except ArgumentParserError as e:
nagios_exit(NAGIOS_UNKNOWN,'error with setup: ' + e.message)
nagios_exit(NAGIOS_UNKNOWN,'error with setup: ' + ','.join(e.args))
except (KeyboardInterrupt, EOFError) as e:
print
print()
nagios_exit(NAGIOS_UNKNOWN,'initialization aborted')
try:
@@ -331,11 +488,16 @@ if __name__ == '__main__':
temperature, tries = args.func(args)
endtime = time()
if isinstance(temperature, tuple):
temperature, extra_data = temperature
else:
extra_data = ()
except (KeyboardInterrupt) as e:
nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read aborted by user')
nagios_exit(NAGIOS_UNKNOWN,'sensor read aborted by user')
except (IOError, ValueError, ImportError) as e:
nagios_exit(NAGIOS_UNKNOWN,'temperature sensor read failed: %s' % e)
nagios_exit(NAGIOS_UNKNOWN,'sensor read failed: %s' % e)
elapse = endtime-starttime
logger.info('Got temperature reading of %.2f degrees %s in %fs',
@@ -343,6 +505,10 @@ if __name__ == '__main__':
unit = args.converter[1]
message = 'current temperature is %.2f%s' % (temperature, unit)
data = [ ('temperature', [ '%f%s' % (temperature, unit),
args.warn, args.critical, None, None]),
('retries', [ tries-1, None, args.retries, 0, None ]),
('checktime', [ '%fs' % elapse, None, None, 0, None]) ]
if args.critical is not None and temperature > args.critical:
nagiosresult = NAGIOS_CRITICAL
message+= ' and exceeds critical threshold %.2f%s' %(args.critical,unit)
@@ -352,10 +518,10 @@ if __name__ == '__main__':
else:
nagiosresult = NAGIOS_OK
nagios_exit(nagiosresult, message, [
('temperature', [ '%f%s' % (temperature, unit),
args.warn, args.critical, None, None]),
('retries', [ tries-1, None, args.retries, 0, None ]),
('checktime', [ '%fs' % elapse, None, None, 0, None])
])
for e in extra_data:
message += ', %s is %.2f%s' % (e[0], e[1], e[2])
data += (e[0], [ '%f%s' % (e[1], e[2]), None, None,
e[3] if len(e)>3 else None, e[4] if len(e)>4 else None ]),
nagios_exit(nagiosresult, message, data)