Prometheus exporter for Tuya-based smart plugs using TinyTuya.
It polls your plugs on your local network and exposes metrics on /metrics.
It can also auto-discover the right DPS keys (voltage, current, power, relay) and scaling, so your config.yaml can stay minimal.
This exporter is read-only. It does not change relay state or control devices.
- Metrics on
/metrics - Multi-device polling with parallel requests
- Auto version probing (Tuya protocol versions)
- Autodiscovery for:
- Voltage, current, power DPS keys
- Relay DPS key (on/off)
- Scaling (divisors)
- Health and readiness endpoints
/-/healthy(or/healthz)/-/ready(or/readyz)
- Python 3.10+ (3.11 recommended)
- Network access to your smart plugs (local LAN)
- Tuya
device_idandlocal_keyfor each plug
This exporter can be run either directly with Python or as a Docker container.
When running directly with Python, the exporter depends on the following libraries: tinytuya, prometheus-client, and pyyaml.
Using a virtual environment is recommended, especially on Raspberry Pi / Debian-based systems.
Linux/macOS (bash/zsh):
python3 -m venv venv
source venv/bin/activate
python -m pip install -U pip
pip install -r requirements.txtWindows PowerShell:
py -m venv venv
.\venv\Scripts\Activate.ps1
python -m pip install -U pip
pip install -r requirements.txtIf you prefer to install directly in your current Python environment:
pip install -r requirements.txtBy default, it looks for config.yaml in:
- current directory
- script directory
/config/config.yaml
python tuya_smart_plug_exporter.pyOr explicitly:
python tuya_smart_plug_exporter.py --config.file=config.yamldeactivateYou can run this exporter as a Docker container.
The exporter is available on GitHub Container Registry (GHCR).
If you prefer, you can also build it locally using the provided Dockerfile.
The container is stateless by design and does not include any configuration or credentials.
Mount your config.yaml to /config/config.yaml (default path, auto-detected).
You can also use --config.file or TUYA_SMART_PLUG_EXPORTER_CONFIG to point to a different path.
Any change to the configuration requires a container restart.
This container includes a built-in Docker healthcheck.
- Liveness:
/-/healthy - Readiness:
/-/ready
A container marked as healthy means the exporter HTTP server is running.
It does not guarantee that all devices are reachable or returning telemetry.
Use tuya_up and tuya_telemetry_ok to validate device state.
Create a config.yaml in your current directory, then run:
docker run -d \
--name tuya-smart-plug-exporter \
-p 9122:9122 \
-v "$(pwd)/config.yaml:/config/config.yaml:ro" \
--restart unless-stopped \
ghcr.io/luizbizzio/tuya-smart-plug-exporter:latestdocker run -d `
--name tuya-smart-plug-exporter `
-p 9122:9122 `
-v "${PWD}/config.yaml:/config/config.yaml:ro" `
--restart unless-stopped `
ghcr.io/luizbizzio/tuya-smart-plug-exporter:latestUse this if you want to build the image yourself from the repository.
docker build -t tuya-smart-plug-exporter .
docker run -d \
--name tuya-smart-plug-exporter \
-p 9122:9122 \
-v "$(pwd)/config.yaml:/config/config.yaml:ro" \
--restart unless-stopped \
tuya-smart-plug-exporterdocker build -t tuya-smart-plug-exporter .
docker run -d `
--name tuya-smart-plug-exporter `
-p 9122:9122 `
-v "${PWD}/config.yaml:/config/config.yaml:ro" `
--restart unless-stopped `
tuya-smart-plug-exporter- Metrics:
http://localhost:9122/metrics - Health:
http://localhost:9122/-/healthy - Ready:
http://localhost:9122/-/ready
Linux/macOS (bash/zsh):
curl http://localhost:9122/metrics
curl http://localhost:9122/-/healthy
curl http://localhost:9122/-/readyWindows PowerShell:
curl http://localhost:9122/metrics
curl http://localhost:9122/-/healthy
curl http://localhost:9122/-/readyIf curl behaves differently on your PowerShell version, use:
Invoke-WebRequest http://localhost:9122/metrics
Invoke-WebRequest http://localhost:9122/-/healthy
Invoke-WebRequest http://localhost:9122/-/readyTo use this exporter, you need the Tuya device_id and local_key for each smart plug.
Tuya does not provide an official way to retrieve the local key.
A widely used community method is explained in this tutorial.
Notes:
- This is an older method, but it still works for many Tuya devices.
- Tuya frequently changes their cloud APIs, so the process may break in the future.
- Once you have the
local_key, it usually does not change unless you re-pair the device.
Create a config.yaml file (recommended).
JSON is also supported if you pass it explicitly with --config.file.
With autodiscovery enabled, you only need ip, device_id, and local_key per device.
web:
listen_address: "0.0.0.0:9122"
telemetry_path: "/metrics"
scrape:
timeout_seconds: 3.0
max_parallel: 8
stale_seconds: 300
poll_interval_seconds: 10
ready_grace_seconds: 30
inferred_on_power_w: 3.0
inferred_off_power_w: 1.5
inferred_on_current_a: 0.03
inferred_off_current_a: 0.015
tuya:
versions: [3.4, 3.3, 3.2, 3.1, 3.0]
autodiscovery:
enabled: true
threshold: 0.85
relay_threshold: 0.60
samples: 4
tol_rel: 0.30
min_power_w: 8.0
min_current_a: 0.05
probe_dps: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
devices:
- name: "plug-1"
ip: "192.168.0.50"
device_id: "DEVICE_ID_1"
local_key: "LOCAL_KEY_1"
- name: "plug-2"
ip: "192.168.0.51"
device_id: "DEVICE_ID_2"
local_key: "LOCAL_KEY_2"If you set autodiscovery.enabled: false, each device must include dps and scale.
DPS keys and scale values vary by device model and firmware. The values below are only an example.
autodiscovery:
enabled: false
devices:
- name: "plug-1"
ip: "192.168.0.50"
device_id: "DEVICE_ID_1"
local_key: "LOCAL_KEY_1"
dps:
voltage: "20"
current: "18"
power: "19"
relay: "1"
scale:
voltage: 10
current: 1000
power: 10- It needs at least 2 polls (usually 4+ is better).
- It works best if the plug has a real load connected (not near zero).
- Until autodiscovery finishes for a device, you will usually see:
tuya_autodiscovery_pending = 1tuya_telemetry_ok = 0- no
tuya_consumption_*values yet for that device
- Metrics:
/metrics - Health:
/-/healthy(or/healthz) - Ready:
/-/ready(or/readyz)
Add this to your Prometheus config:
scrape_configs:
- job_name: "tuya-smart-plug-exporter"
static_configs:
- targets: ["YOUR_EXPORTER_IP:9122"]| Name | Type | Description | Scope |
|---|---|---|---|
tuya_up |
Gauge | Last scrape was OK (1) or failed (0) | Device |
tuya_telemetry_ok |
Gauge | Last poll had valid voltage, current, power (1) or not (0) | Device |
tuya_consumption_voltage |
Gauge | Voltage in volts | Device |
tuya_consumption_current |
Gauge | Current in amps | Device |
tuya_consumption_power |
Gauge | Power in watts | Device |
tuya_relay_state |
Gauge | Relay state from DPS (1 on, 0 off, -1 unknown) | Device |
tuya_relay_inferred |
Gauge | Relay inferred from consumption (1 on, 0 off) | Device |
tuya_relay_effective |
Gauge | Uses relay DPS if known, else inferred | Device |
tuya_last_success_timestamp |
Gauge | Unix timestamp of last successful scrape | Device |
tuya_last_telemetry_timestamp |
Gauge | Unix timestamp of last valid telemetry sample | Device |
tuya_device_scrape_duration_seconds |
Gauge | Time spent scraping a device | Device |
tuya_stale_seconds |
Gauge | Seconds since last valid telemetry sample (-1 never) | Device |
tuya_autodiscovery_ready |
Gauge | Autodiscovery ready (1) or not (0) | Device |
tuya_autodiscovery_pending |
Gauge | Autodiscovery pending (1) or not (0) | Device |
tuya_autodiscovery_confidence |
Gauge | Autodiscovery confidence score (0..1) | Device |
tuya_autodiscovery_attempts_total |
Counter | Autodiscovery attempts | Device |
tuya_autodiscovery_relay_confidence |
Gauge | Relay autodiscovery confidence (0..1) | Device |
tuya_autodiscovery_relay_ready |
Gauge | Relay autodiscovery ready (1) or not (0) | Device |
tuya_errors_total |
Counter | Total device scrape errors | Device |
tuya_scrapes_total |
Counter | Total device scrapes | Device |
tuya_last_scrape_error |
Gauge | Last poll cycle had any error (1) or not (0) | Global |
tuya_last_scrape_duration_seconds |
Gauge | Duration of the last poll cycle (all devices) | Global |
tuya_smart_plug_exporter_build_info |
Gauge | Exporter version and Python version | Global |
If you get tuya_up = 1 but tuya_telemetry_ok = 0 forever:
- Wait a few poll cycles.
samples: 4means it may need a bit more time. - Plug in a device that draws real power (8W+ if you kept
min_power_w: 8.0). - If your plug never exposes voltage/current/power DPS, disable autodiscovery and set
dpsandscalemanually. - If the exporter says missing config, check the file name is exactly
config.yaml, or explicitly set--config.file(orTUYA_SMART_PLUG_EXPORTER_CONFIG).
If tuya_up = 0:
- Check IP, device_id, local_key
- Try other Tuya protocol versions in
tuya.versions - Check firewall rules and LAN routing
This exporter requires access to Tuya local credentials, specifically:
device_idlocal_key
These values allow local control and telemetry access to your Tuya devices.
They are not passwords, but they must be treated as secrets. Do not commit your real credentials to GitHub.
This project is licensed under the Apache License 2.0.


