-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
293 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# check_air_purifier | ||
|
||
check_air_purifier.py is a python 3 script for monitoring air purifiers made by philips. Communication with the device is made with a customized version of py-air-control. Tested with AC1214_10, but should work with any other philips device. | ||
|
||
Features: | ||
|
||
* Monitor air-quality (allergen index / pm2.5) and filter status including thresholds and perfdata | ||
* Check device status (fan speed, power, light, updates, network) | ||
|
||
|
||
## Installation | ||
|
||
Use the package manager [pip3](https://pip.pypa.io/en/stable/) to install the required module py-air-control (tested with version 0.5.0). | ||
|
||
```bash | ||
pip3 install py-air-control | ||
``` | ||
|
||
Put the plugin into libexec and extend your checkcommands. For icinga2 you can use check_air_purifier.cfg. | ||
|
||
## Usage | ||
|
||
``` | ||
usage: check_air_purifier.py [-h] -H HOSTNAME -m | ||
{deviceinfo,filters,airquality} [-w WARNING] | ||
[-c CRITICAL] | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
-H HOSTNAME, --hostname HOSTNAME | ||
Hostname / IP of air purifier | ||
-m {deviceinfo,filters,airquality}, --mode {deviceinfo,filters,airquality} | ||
mode to check | ||
-w WARNING, --warning WARNING | ||
Warning threshold | ||
-c CRITICAL, --critical CRITICAL | ||
Critical threshold | ||
``` | ||
|
||
## Examples | ||
|
||
```bash | ||
check_air_purifier.py -H 192.168.10.120 -m 'deviceinfo' | ||
OK: Power is ON - Mode is auto - Fan Speed is 2 - Light brightness is 50 - Button Light is ON - Used Index is IAI - Child lock is False - name is AC1214_10 - version is 2 - upgrade is - state is idle - progress is 0 - statusmsg is - mandatory is False - ssid is myssid - password is mypassword - protection is wpa-2 - ipaddress is 192.168.10.120 - netmask is 255.255.255.0 - gateway is 192.168.10.1 - dhcp is True - macaddress is mymacaddress - cppid is mycppid|'Fan Speed'=2 'Light brightness'=50 | ||
|
||
check_air_purifier.py -H 192.168.10.120 -m 'filters' --warning 16 --critical 8 | ||
OK: Pre-filter and Wick is ok (44 hours remaining) - Active carbon filter is ok (2084 hours remaining) - HEPA filter is ok (4484 hours remaining)|'Pre-filter and Wick'=44 'Active carbon filter'=2084 'HEPA filter'=4484 | ||
|
||
check_air_purifier.py -H 192.168.10.120 -m 'airquality' --warning 8 --critical 10 | ||
OK: Allergen index is ok (4) - PM25 is 19|'Allergen index'=4 'PM25'=19 | ||
``` | ||
|
||
## Contributing | ||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. | ||
|
||
## License | ||
[MIT](https://choosealicense.com/licenses/mit/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
object CheckCommand "my-air-purifier" { | ||
import "ipv4-or-ipv6" | ||
command = [ PluginContribDir + "/check_air_purifier.py" ] | ||
|
||
arguments = { | ||
"--hostname" = { | ||
value = "$air_purifier_address$" | ||
description = "the air purifiers hostname" | ||
} | ||
"--warning" = { | ||
value = "$air_purifier_warning$" | ||
description = "the warning threshold" | ||
} | ||
"--critical" = { | ||
value = "$air_purifier_critical$" | ||
description = "the critical threshold" | ||
} | ||
"--mode" = { | ||
value = "$air_purifier_mode$" | ||
description = "the mode of the plugin" | ||
} | ||
} | ||
vars.air_purifier_address = "$check_address$" | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
#!/usr/bin/env python3 | ||
import sys | ||
import argparse | ||
from airctrl import airctrl as air | ||
import pprint | ||
|
||
class AirClient(air.AirClient): | ||
|
||
def get_status(self, debug=False): | ||
url = 'http://{}/di/v1/products/1/air'.format(self._host) | ||
status = self._get(url) | ||
return self._dump_status(status, debug=debug) | ||
|
||
def get_wifi(self): | ||
url = 'http://{}/di/v1/products/0/wifi'.format(self._host) | ||
wifi = self._get(url) | ||
return wifi | ||
|
||
def get_firmware(self): | ||
url = 'http://{}/di/v1/products/0/firmware'.format(self._host) | ||
firmware = self._get(url) | ||
return firmware | ||
|
||
def get_filters(self): | ||
values = {} | ||
|
||
url = 'http://{}/di/v1/products/1/fltsts'.format(self._host) | ||
filters = self._get(url) | ||
#print('Pre-filter and Wick: clean in {} hours'.format(filters['fltsts0'])) | ||
values['Pre-filter and Wick'] = filters['fltsts0'] | ||
if 'wicksts' in filters: | ||
values['Wick filter'] = filters['wicksts'] | ||
# print('Wick filter: replace in {} hours'.format(filters['wicksts'])) | ||
values['Active carbon filter'] = filters['fltsts2'] | ||
values['HEPA filter'] = filters['fltsts1'] | ||
#print('Active carbon filter: replace in {} hours'.format(filters['fltsts2'])) | ||
#print('HEPA filter: replace in {} hours'.format(filters['fltsts1'])) | ||
|
||
return values | ||
|
||
|
||
def _dump_status(self, status, debug=False): | ||
values = {} | ||
|
||
if debug: | ||
pprint.pprint(status) | ||
pprint() | ||
if 'pwr' in status: | ||
pwr = status['pwr'] | ||
pwr_str = {'1': 'ON', '0': 'OFF'} | ||
pwr = pwr_str.get(pwr, pwr) | ||
values['Power'] = pwr | ||
#print('[pwr] Power: {}'.format(pwr)) | ||
if 'pm25' in status: | ||
pm25 = status['pm25'] | ||
values['PM25'] = pm25 | ||
#print('[pm25] PM25: {}'.format(pm25)) | ||
if 'rh' in status: | ||
rh = status['rh'] | ||
values['Humidity'] = rh | ||
#print('[rh] Humidity: {}'.format(rh)) | ||
if 'rhset' in status: | ||
rhset = status['rhset'] | ||
values['Target humidity'] = rhset | ||
#print('[rhset] Target humidity: {}'.format(rhset)) | ||
if 'iaql' in status: | ||
iaql = status['iaql'] | ||
values['Allergen index'] = iaql | ||
#print('[iaql] Allergen index: {}'.format(iaql)) | ||
if 'temp' in status: | ||
temp = status['temp'] | ||
values['Temperature'] = temp | ||
#print('[temp] Temperature: {}'.format(temp)) | ||
if 'func' in status: | ||
func = status['func'] | ||
func_str = {'P': 'Purification', 'PH': 'Purification & Humidification'} | ||
func = func_str.get(func, func) | ||
values['Function'] = func | ||
#print('[func] Function: {}'.format(func)) | ||
if 'mode' in status: | ||
mode = status['mode'] | ||
mode_str = {'P': 'auto', 'A': 'allergen', 'S': 'sleep', 'M': 'manual', 'B': 'bacteria', 'N': 'night'} | ||
mode = mode_str.get(mode, mode) | ||
values['Mode'] = mode | ||
#print('[mode] Mode: {}'.format(mode)) | ||
if 'om' in status: | ||
om = status['om'] | ||
om_str = {'s': 'silent', 't': 'turbo'} | ||
om = om_str.get(om, om) | ||
values['Fan Speed'] = om | ||
#print('[om] Fan speed: {}'.format(om)) | ||
if 'aqil' in status: | ||
aqil = status['aqil'] | ||
values['Light brightness'] = aqil | ||
#print('[aqil] Light brightness: {}'.format(aqil)) | ||
if 'uil' in status: | ||
uil = status['uil'] | ||
uil_str = {'1': 'ON', '0': 'OFF'} | ||
uil = uil_str.get(uil, uil) | ||
values['Button Light'] = uil | ||
#print('[uil] Buttons light: {}'.format(uil)) | ||
if 'ddp' in status: | ||
ddp = status['ddp'] | ||
ddp_str = {'1': 'PM2.5', '0': 'IAI'} | ||
ddp = ddp_str.get(ddp, ddp) | ||
values['Used Index'] = ddp | ||
#print('[ddp] Used index: {}'.format(ddp)) | ||
if 'wl' in status: | ||
wl = status['wl'] | ||
values['Water level'] = wl | ||
#print('[wl] Water level: {}'.format(wl)) | ||
if 'cl' in status: | ||
cl = status['cl'] | ||
values['Child lock'] = cl | ||
#print('[cl] Child lock: {}'.format(cl)) | ||
if 'dt' in status: | ||
dt = status['dt'] | ||
if dt != 0: | ||
values['Timer'] = dt | ||
#print('[dt] Timer: {} hours'.format(dt)) | ||
if 'dtrs' in status: | ||
dtrs = status['dtrs'] | ||
if dtrs != 0: | ||
values['Times minutes'] = dtrs | ||
#print('[dtrs] Timer: {} minutes left'.format(dtrs)) | ||
if 'err' in status: | ||
err = status['err'] | ||
if err != 0: | ||
err_str = {49408: 'no water', 32768: 'water tank open'} | ||
err = err_str.get(err, err) | ||
values['Error'] = err | ||
#print('-'*20) | ||
#print('Error: {}'.format(err)) | ||
return values | ||
|
||
if __name__ == '__main__': | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument('-H', '--hostname', required=True, type=str, | ||
help='Hostname / IP of air purifier') | ||
parser.add_argument('-m', '--mode', required=True, choices=['deviceinfo','filters','airquality'], type=str, | ||
help='mode to check') | ||
parser.add_argument('-w', '--warning', type=float, | ||
help='Warning threshold') | ||
parser.add_argument('-c', '--critical', type=float, | ||
help='Critical threshold') | ||
args = parser.parse_args() | ||
|
||
mode = args.mode | ||
warning = args.warning | ||
critical = args.critical | ||
|
||
message = '' | ||
perfdata = '' | ||
RC = [0] | ||
RCStatus = ('OK','WARNING','CRITICAL','UNKNOWN') | ||
|
||
c = AirClient(args.hostname) | ||
c.load_key() | ||
|
||
if mode == 'deviceinfo': | ||
status = c.get_status() | ||
wifi = c.get_wifi() | ||
firmware = c.get_firmware() | ||
|
||
#message += 'Power: {}\n'.format(status['Power']) | ||
|
||
for item,value in status.items(): | ||
if item in ['Button Light','Child lock','Fan Speed','Light brightness','Mode','Used Index','Power','Humidity','Target humidity','Temperature','Function','Water level']: | ||
message += '{} is {} - '.format(item,value) | ||
|
||
if item in ['Fan Speed','Light brightness','Humidity','Target humidity','Temperature','Water level']: | ||
perfdata += "'{}'={} ".format(item,value) | ||
|
||
for item,value in firmware.items(): | ||
message += '{} is {} - '.format(item,value) | ||
for item,value in wifi.items(): | ||
message += '{} is {} - '.format(item,value) | ||
|
||
if mode == 'filters': | ||
filters = c.get_filters() | ||
for filter,hours in filters.items(): | ||
if hours <= critical: | ||
message += '{} is critical ({} hours remaining) - '.format(filter,hours) | ||
RC.append(2) | ||
elif hours <= warning: | ||
message += '{} is warning ({} hours remaining) - '.format(filter,hours) | ||
RC.append(1) | ||
else: | ||
message += '{} is ok ({} hours remaining) - '.format(filter,hours) | ||
perfdata += "'{}'={} ".format(filter,hours) | ||
|
||
if mode == 'airquality': | ||
airquality = c.get_status() | ||
allergenindex = airquality['Allergen index'] | ||
pm25 = airquality['PM25'] | ||
if allergenindex >= critical: | ||
message += 'Allergen index is critical ({}) - '.format(allergenindex) | ||
RC.append(2) | ||
elif allergenindex >= warning: | ||
message += 'Allergen index is warning ({}) - '.format(allergenindex) | ||
RC.append(1) | ||
else: | ||
message += 'Allergen index is ok ({}) - '.format(allergenindex) | ||
RC.append(0) | ||
message += 'PM25 is {} - '.format(pm25) | ||
perfdata += "'Allergen index'={} ".format(allergenindex) | ||
perfdata += "'PM25'={} ".format(pm25) | ||
|
||
print('{}: {}|{}'.format(RCStatus[max(RC)],message[:-3],perfdata)) | ||
sys.exit(max(RC)) |