Skip to content

Instantly share code, notes, and snippets.

@sorz
Created August 26, 2021 09:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sorz/0a037f07139d581e1fdad5f5fda9d1ed to your computer and use it in GitHub Desktop.
Save sorz/0a037f07139d581e1fdad5f5fda9d1ed to your computer and use it in GitHub Desktop.
A simple Prometheus export that collect A/C target temperature & load power from Xiaomi Air Conditioning Companion.

Prometheus exporter for Xiaomi A/C Companion

A simple Prometheus export that collect A/C target temperature & load power from Xiaomi Air Conditioning Companion.

It serves as a simple demo, you may expand it to other Mi devices & metrics. See python-miio's document.

#!/usr/bin/env python3
import os
from io import TextIOWrapper
from typing import Dict
from pathlib import Path
from http.server import HTTPServer, BaseHTTPRequestHandler
from miio.airconditioningcompanion import AirConditioningCompanion
HTTP_BIND = ('127.0.0.1', 8000)
AC_ADDR = '192.0.2.12'
def load_tokens() -> Dict[str, str]:
cred_dir = os.environ.get('CREDENTIALS_DIRECTORY', '.')
token_path = Path(cred_dir) / Path('tokens')
addrs_token = dict()
with token_path.open() as f:
for line in f:
line = line.strip()
if line.startswith('#') or '/' not in line:
continue
addr, token = line.rsplit('/', 1)
addrs_token[addr] = token
return addrs_token
class HTTPHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path != '/metrics':
self.send_response(404)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(b"404 not found\n")
return
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
body = TextIOWrapper(self.wfile)
def metric(key, value, typ, help):
print(f"# HELP miio_{key} {help}", file=body)
print(f"# TYPE miio_{key} {typ}", file=body)
print(f"miio_{key} {value}", file=body)
print("", file=body)
status = self.server.ac.status()
if status.power:
metric("ac_targettemp", status.target_temperature, "gauge", "Target temperature")
metric("ac_loadpower", status.load_power, "gauge", "Current power load of the air conditioner")
body.flush()
body.detach()
def main():
addr_tokens = load_tokens()
ac = AirConditioningCompanion(AC_ADDR, addr_tokens[AC_ADDR])
with HTTPServer(HTTP_BIND, HTTPHandler) as httpd:
httpd.ac = ac
httpd.serve_forever()
if __name__ == '__main__':
main()
[Unit]
Description=Xiaomi A/C Companion Prometheus exporter
[Service]
Type=simple
DynamicUser=yes
LoadCredential=tokens:/opt/mi_ac_exporter/tokens
WorkingDirectory=/opt/mi_ac_exporter
ExecStart=/opt/mi_ac_exporter/mi_ac_exporter.py
Environment=PYTHONUNBUFFERED=1
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelModules=true
ProtectControlGroups=true
[Install]
WantedBy=multi-user.target
# <IP-Address>/<Token-in-HEX>
192.0.2.12/abcdefabcdef12345678901234567890
192.0.2.15/12345678901234567890abcdefabcdef
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment