A command-line client and MQTT bridge for the Ubiquiti mFi mPower smart power
strip. It runs without the mFi cloud or controller, both of which Ubiquiti has
discontinued. It talks to the strip's built-in local HTTP API (/mfi/sensors.cgi).
The strip shows up in Home Assistant through MQTT discovery.
$ ./mpower.py status
mPower @ 192.168.1.50
P Zustand Leistung Strom Spannung Name
1 AN 0.00 W 0.000 A 230.1 V pf=0.00 Port 1
2 AN 0.00 W 0.000 A 230.2 V pf=0.00 Port 2
3 AN 0.00 W 0.026 A 230.4 V pf=0.00 Port 3
Summe: 0.00 W
git clone https://github.com/feiglein74/mpower && cd mpower
export MPOWER_HOST=192.168.1.50 MPOWER_USER=admin MPOWER_PW='<password>'
./mpower.py status # state, power, current, voltage of every outlet
./mpower.py on 2 # switch outlet 2 on (also: off, toggle, watch)
pip install paho-mqtt # only needed for the MQTT bridge
./mqtt_bridge.py -n 15 # mirror to MQTT every 15 s, HA autodiscoverympower.py uses only the Python standard library. Settings can also go in
~/.config/mpower/config.ini (see Benutzung).
Detailed documentation is in German.
- Tested device: mFi mPower EU, 3 outlets, firmware
MF.v2.1.8, Atheros AR9330. Other mPower models (US, mPower Pro) use the same API but have not been tested. - API pitfalls: If the outlet number in
POST /mfi/sensors.cgiis not sent asid, the strip switches all outlets.POST /mfi/sensors.cgi/<port>returns HTTP 200 and does nothing. Expired sessions return HTML instead of 401. The client switches withPUT /sensors/<port>and reads the state back after every write. - No HTTPS: the firmware only offers TLS 1.0 with a 512-bit certificate,
which modern OpenSSL rejects. This is also why the Home Assistant
mfiintegration stopped working. Use plain HTTP on a trusted LAN only. - Measurement accuracy: compared against a Janitza UMG 96RM power analyzer at four reference points (2–42 W, resistive, inductive and capacitive loads). The mPower's active power reading stayed within 2 % at all of them. Short load spikes are under-reported because of the meter's averaging window.
- Self-consumption: about 2.1 W for the strip, plus about 0.43 W for each energized relay coil.
- SSH fallback: Dropbear 0.51 accepts only legacy algorithms. Outlets can be
switched directly through
/proc/power/relayN. OpenWrt does not support this device, so the stock firmware stays.
Ubiquiti hat die mFi-Plattform eingestellt: Cloud und Controller sind tot, die Hardware läuft lokal aber unverändert weiter. Dieses Projekt steuert die Steckdosenleiste direkt über ihre lokale HTTP-API — ohne Cloud, ohne Controller.
| Modell | mFi mPower EU, 3 Ausgänge |
| Firmware | MF.v2.1.8 (Build 09.02.2015) |
| Kernel | Linux 2.6.32.29, MIPS |
| SoC | Atheros AR9330 (Hornet), MIPS 24Kc V7.4 |
| RAM | 32 MB (≈29,5 MB nutzbar) |
| Flash | 8 MB — u-boot 256K | u-boot-env 64K | kernel 1M | rootfs 6,6M | cfg 256K | EEPROM 64K |
| Rootfs | zu 100 % belegt — auf dem Gerät ist kein Platz für eigene Software |
| Zugang | admin / ubnt, HTTP (80), SSH (22), Telnet (23) alle offen |
./mpower.py status # Zustand aller Ports
./mpower.py status --json # maschinenlesbar
./mpower.py on 2 # Port 2 einschalten
./mpower.py off 2 3 # mehrere Ports
./mpower.py toggle 1
./mpower.py watch -n 2 # Leistung fortlaufendKonfiguration über ~/.config/mpower/config.ini oder Umgebungsvariablen
(MPOWER_HOST, MPOWER_USER, MPOWER_PW); Vorgabe ist 10.10.1.78.
[mpower]
host = 10.10.1.78
user = admin
password = <geraetepasswort>
[mqtt]
host = <broker-ip>
port = 1883
user = <mqtt-benutzer>
password = <mqtt-passwort>
prefix = mpower
device_topic = mfi/mpowerDie Datei gehört nicht ins Repository (chmod 600).
mqtt_bridge.py pollt die Leiste und spiegelt sie nach MQTT. Home Assistant legt
die Entitäten per Autodiscovery selbst an — dort ist keine Konfiguration nötig,
solange die MQTT-Integration denselben Broker benutzt.
./mqtt_bridge.py --once # einmal abfragen (Test)
./mqtt_bridge.py -n 15 # Dauerbetrieb, 15 s Intervall
./mqtt_bridge.py --no-discovery # ohne HA-Discovery-NachrichtenEntscheidend ist, woher Home Assistant die Daten liest. Das legt --source fest.
./mqtt_bridge.py --source device --once # empfohlen: HA redet direkt mit dem Gerät
./mqtt_bridge.py --source bridge -n 15 # schneller, braucht diesen Dienst dauerhaft--source device |
--source bridge |
|
|---|---|---|
| Datenquelle | die Steckdose selbst | dieser Rechner |
| Läuft ohne Notebook | ja | nein |
| Dauerdienst nötig | nein — ein --once genügt |
ja |
| Aktualisierung | 60 s (nach Schaltbefehl sofort) | 15 s |
| Entitäten | 12 (Schalter, Leistung, Spannung, Energie) | 15 (zusätzlich Strom) |
| Strom-Sensor | nein — Gerät publiziert ihn nicht | ja |
| Auflösung Leistung | 0,1 W | 0,01 W |
| Auflösung Energie | 1 Wh | — |
Die gröbere Auflösung im Geräte-Modus stammt daher, dass die mFi-tools vor dem Veröffentlichen runden. Warum das bei der Leistung folgenlos ist und nur bei der Energie zählt, steht unter Wie genau ist die Messung?.
device ist die robustere Wahl. Die gerätseitigen mFi-tools können alles
Wesentliche selbst: schalten (port<N>/relay/set mit 1/0), Zustand melden und
Energie zählen. Home Assistant spricht dann unmittelbar mit der Steckdose, und
dieses Projekt wird nach dem einmaligen Anmelden nicht mehr gebraucht.
Der Schaltweg ist verifiziert — Kommando über MQTT, Gegenprobe direkt an der Hardware:
Hardware vorher : p1=1 p2=1 p3=1 | MQTT meldet: 1
nach set=0 : p1=1 p2=0 p3=1 | MQTT meldet: 0
nach set=1 : p1=1 p2=1 p3=1 | MQTT meldet: 1
Die Rückmeldung kommt dabei nicht erst nach 60 s: mqsub.sh setzt nach jedem
Schaltbefehl echo 5 > $tmpfile und erzwingt damit eine sofortige Aktualisierung.
Achtung beim Wechsel: Läuft die Bridge im Modus bridge nicht dauerhaft,
werden Leistung, Strom und Spannung in HA unavailable — die Bridge meldet beim
Beenden korrekt offline. Entweder als Dienst starten (mpower-mqtt.service) oder
--source device verwenden.
Auf diesem Gerät sind keine Labels gesetzt — das ist der Auslieferungszustand.
Die API liefert dann kein label-Feld, und mpower.py setzt den Rückfall
Port <N> ein. Das Gerät kennt diese Namen also gar nicht.
Labels würden in /etc/persistent/cfg/config_file stehen, nullbasiert:
port.0.label=... ← physischer Port 1
port.1.label=... ← physischer Port 2
Hier stand ursprünglich port.0.label=Notebook, vermutlich vom alten
mFi-Controller vergeben. Der Eintrag wurde entfernt, damit alle drei Ports
einheitlich bei den Defaults starten; die daneben stehende port.0.sensorId
blieb unangetastet, da ihre Funktion ohne Controller unklar ist.
Umbenannt wird bewusst in Home Assistant, nicht am Gerät. Zwei Gründe:
- HA speichert manuelle Umbenennungen in der Entity Registry, und die haben
Vorrang vor dem
nameaus der Discovery-Nachricht. Die Bridge kann also jederzeit erneut mit--oncelaufen, ohne die Namen zu überschreiben. - Es ist der robustere Ort.
config_fileübersteht zwar einen Stromausfall (geprüft), aber die Namen liegen dort in einer Firmware-Konfiguration, deren Verhalten wir nur empirisch kennen —vpower_cfgetwa wird trotzcfgmtdzurückgesetzt.
Wer die Labels trotzdem am Gerät setzen will, findet den Weg bei
TinkerTry
beschrieben — anschließend cfgmtd -w -p /etc/ und die Bridge einmal neu laufen
lassen.
Im Modus bridge entstehen 15 Entitäten: pro Port ein Schalter plus Leistungs-,
Strom-, Spannungs- und Energiesensor. Im Modus device sind es 12 (ohne Strom).
Der Energiezähler ist der einzige Wert, den die Bridge nicht selbst liefert — die HTTP-API gibt ihn nicht her:
GET /sensors/1/energy_sum → {"status":"fail","error":"'energy_sum' is not a valid resource"}
Über HTTP gäbe es nur thismonth (grober Monatswert). Der echte Zähler steht in
/proc/power/energy_sum<N> und wäre nur per SSH erreichbar.
Statt dafür einen zweiten Transport in die Bridge zu bauen, zeigt die Discovery-Konfiguration direkt auf das Topic, das die gerätseitigen mFi-tools ohnehin publizieren:
"state_topic": "mfi/mpower/port1/energy",
"availability_topic": "mfi/mpower/$online"Das kostet nichts extra: keine SSH-Sitzung, keine Zusatzlast auf dem Gerät, und die Werte sind retained — Home Assistant hat sie sofort nach einem Neustart. Die Verfügbarkeit hängt bewusst am Gerät statt an der Bridge, denn das Gerät publiziert auch weiter, wenn die Bridge aus ist.
Voraussetzung: Die gerätseitigen mFi-tools müssen laufen und auf denselben
Broker zeigen (siehe „Änderungen am Gerät"). Ohne sie bleibt der Energiesensor
unavailable — alles andere funktioniert weiter. Abschalten mit
--device-topic "".
Genauer als über MQTT geht es per SSH, dann aber nur auf Abruf:
./mpower.py status --energy # liest /proc/power/energy_sum<N> via SSHDer Unterschied: Das Gerät rundet auf ganze Wattstunden (11), SSH liefert den
vollen Wert (10.94). Die Umrechnung ist cf_count<N> × 0,3125 Wh.
Der Zähler springt bei Stromausfall auf null. cf_count und energy_sum
liegen in /proc, also im RAM. Nachgemessen an der Tagesstatistik über einen
Neustart hinweg:
1)13:00=36 letzter Wert vor dem Stromausfall
1)14:00=9 danach faengt der Zaehler wieder bei null an
1)15:00=27
Deshalb steht der Sensor auf "state_class": "total_increasing" und nicht auf
"total": Home Assistant deutet einen Rückwärtssprung damit als Zählerreset und
addiert korrekt weiter. Mit "total" würde nach jedem Stromausfall ein negativer
Verbrauch verbucht.
Die Historie überlebt dagegen: Das Gerät schreibt Stundenwerte nach
/etc/persistent/data/<JJJJ-MM-TT> und Monatssummen nach
/etc/persistent/data/<JJJJ-MM>:<port>. Diese Dateien bleiben über Neustarts
erhalten — die ältesten auf diesem Gerät stammen von August 2020.
| Topic | Inhalt |
|---|---|
mpower/<node>/availability |
online / offline (LWT) |
mpower/<node>/port<N>/state |
ON / OFF (retained) |
mpower/<node>/port<N>/set |
Kommandos hierher |
mpower/<node>/port<N>/power |
Watt |
mpower/<node>/port<N>/current |
Ampere |
mpower/<node>/port<N>/voltage |
Volt |
<node> ist mpower_<ip_mit_unterstrichen>, also mpower_10_10_1_78.
Als Dauerdienst: siehe mpower-mqtt.service (systemd-Benutzerdienst, kein root).
Vorgabe sind 15 s. Ob schnelleres Polling dem 2015er-SoC schadet, ist nicht
belegt — es gibt keinen reproduzierbaren Bericht dazu. Die gepflegten
Fremdprojekte fahren aber durchweg träge (die MQTT-Tools mit 60 s), daher der
konservative Start. Wer es eiliger braucht, dreht mit -n 5 runter und beobachtet,
ob das Gerät stabil bleibt.
Am Gerät wurde Konfiguration verändert. Sicherungen aller Originaldateien liegen in
backup-geraet/.
1. Controller-Suche abgeschaltet — /etc/persistent/cfg/mgmt
Das Gerät suchte alle ~19 s einen mFi-Controller, den es nicht mehr gibt, und blinkte deswegen. Der Syslog war voll mit:
ace_reporter.reporter_connected(): connect(http://10.0.0.89:6080/inform)
failed with errors: 0 148 - No route to host
ace_reporter.reporter_fail(): [STATE] entering SELFRUN!!!!
Entfernt wurden beide Einträge:
mgmt.servers.1.url=http://10.0.0.89:6080/inform (Host existiert nicht mehr)
mgmt.servers.2.url=http://mfi:6080/inform (Name nicht auflösbar)
Ergebnis: /proc/led/freq fiel von 1 auf 0 — das Blinken hörte auf.
Nicht vollständig gelöst: mcad versucht weiterhin http://mfi:6080/inform.
Diese Adresse ist offenbar fest eingebaut und kommt nicht aus der Konfiguration.
Der Versuch scheitert aber harmlos an der Namensauflösung, ohne Timeout-Spam.
mcad selbst lässt sich nicht dauerhaft beenden — es steht in /etc/inittab als
null::respawn:/bin/mcad und wird von init sofort neu gestartet. Deshalb wurde die
Konfiguration entschärft statt gegen respawn anzukämpfen.
2. Vorhandene MQTT-Installation umgebogen — /etc/persistent/mqtt/client/mqtt.cfg
Auf dem Gerät lagen bereits die
mFi-tools und publizierten seit
Ewigkeiten an einen Broker, den es nicht mehr gibt (10.0.0.220).
- mqtthost=10.0.0.220
- #mqttusername=username
- #mqttpassword=password
+ mqtthost=<dein-broker>
+ mqttusername=<dein-mqtt-benutzer>
+ mqttpassword=<dein-mqtt-passwort>
refresh=60
topic=mfi/mpower/etc liegt im RAM. Ohne Commit ins Flash ist nach dem nächsten Stromausfall alles
weg. Der Befehl heißt in der interaktiven Shell save, das ist aber nur ein Alias
(/etc/profile) und greift über SSH nicht:
cfgmtd -w -p /etc/ # das ist, was 'save' wirklich tutErfolgsmeldung sieht so aus (schreibt Active- und Backup-Slot):
Found Backup1 on[1] ...
Storing Active[1] ... [%0][%63][%100]
Active->Backup[2] ... [%0][%63][%100]
backup-device.py lädt /etc/persistent/ vollständig herunter (566 Dateien,
jede gegen md5sum auf dem Gerät verifiziert):
./backup-device.pyDie Übertragung läuft per cat über einen SSH-exec-Kanal — Dropbear 0.51 hat
keinen SFTP-Server, und BusyBox bringt kein base64 mit. Ohne PTY kommen Rohbytes
unverfälscht durch, solange man sie nicht dekodiert; deshalb bleiben auch die
MIPS-Binaries (mosquitto_pub, libmosquitto.so.1) heil.
Nebenbefund: Unter data/ liegen ~548 Dateien Verbrauchshistorie, tageweise ab
August 2020.
Drei Dateien der Sicherung enthalten Geheimnisse und sind per .gitignore
ausgeschlossen. Sie liegen lokal vor und werden für ein Rollback gebraucht —
wer das Repository klont, hat sie nicht:
| Datei | Inhalt | Vorlage im Repo |
|---|---|---|
backup-geraet/cfg/mgmt |
mgmt.authkey, mgmt.cloud_pass |
cfg/mgmt.beispiel |
backup-geraet/mqtt/client/mqtt.cfg |
Broker-Passwort | mqtt/client/mqtt.cfg.beispiel |
backup-geraet/system.cfg.vor-ntp-aenderung |
WLAN-PSK, Passwort-Hash admin |
system.cfg.vor-ntp-aenderung.beispiel |
# Originaldateien aus backup-geraet/ zurückspielen, dann ins Flash schreiben:
cfgmtd -w -p /etc/Unabhängig von der Controller-Suche lässt sich die LED direkt schalten:
cat /proc/led/status # 0=AUS 1=BLAU 2=GELB 3=BEIDE 4=WECHSELND
# 99=DAUERHAFT AUS (zum Entsperren auf 0 setzen)
cat /proc/led/freq # Blinkfrequenz in Mal pro Sekunde, 0 = kein Blinken
echo 0 > /proc/led/statusDie mFi-tools bringen dafür client/led.cfg mit (afterboot, relay_on,
relay_off) — die LED kann also dem Relaiszustand folgen.
Durch Aus- und Wiedereinstecken verifiziert — alle Änderungen haben überlebt:
| Ergebnis | |
|---|---|
cfg/mgmt |
Controller-Adressen weiterhin entfernt ✅ |
mqtt.cfg |
Broker und Zugangsdaten erhalten ✅ |
| LED | freq=0, kein Blinken ✅ |
| MQTT-Client | per rc.poststart selbstständig gestartet ✅ |
| Relais | alle drei kamen wieder eingeschaltet hoch |
vpower_cfg |
wird beim Booten zurückgesetzt ❌ (folgenlos, siehe oben) |
Damit ist cfgmtd -w -p /etc/ als Persistenzweg bestätigt. Über mehrere
Neustarts und einen echten Stromausfall hinweg geprüft:
| Datei | Übersteht cfgmtd + Neustart |
|---|---|
cfg/mgmt |
✅ |
cfg/config_file |
✅ |
mqtt/client/mqtt.cfg |
✅ |
cfg/vpower_cfg |
❌ — die Firmware schreibt sie beim Start neu |
vpower_cfg ist die einzige Ausnahme, und sie ist folgenlos: Der Eintrag
vpower.<N>.enabled beeinflusst die Messung ohnehin nicht.
Das Gerät braucht nach einem Neustart rund 60 Sekunden, bis es wieder auf
Ping antwortet. Die LED blinkt in den ersten Minuten (freq=1), während mcad
seine Kontaktversuche zum nicht existierenden Controller startet, und wird
danach ruhig (freq=0).
Wichtig für die Erwartungshaltung: Der MQTT-Client braucht nach dem
Einschalten rund vier Minuten, bis er publiziert — trotz sleep 10 in
rc.poststart. Der Rest der Zeit geht für den Bootvorgang und die
WLAN-Anmeldung drauf. Wer direkt nach dem Einstecken nachsieht, findet in Home
Assistant $online = false und eingefrorene Werte aus der letzten Sitzung
(retained). Das ist kein Fehler, sondern nur Geduldssache.
Ebenfalls beachten: Die Relais gehen nach Stromausfall auf EIN. Für angeschlossene Geräte, die nicht selbsttätig wieder anlaufen sollen, ist das relevant.
Nach knapp drei Stunden Dauerbetrieb gemessen:
| Kennzahl | Wert |
|---|---|
spi_err_counter |
0 — fehlerfreie Kommunikation mit dem Messchip |
| Speicher | 21 128 KB von 29 564 KB belegt (bei Boot: 21 236 KB) — kein Leck |
| MQTT-Client | unveränderte PIDs, keine Neustarts |
| Uhrzeit | sekundengenau synchron (NTP funktioniert) |
Im Syslog fallen drei Muster auf, die harmlos sind und leicht falsch gedeutet werden:
ace_reporter: server unreachableunddns resolv failed, rund 40× pro Stunde. Das istmcad, das weiterhin den fest einkompilierten Namenmfisucht. Der existiert nicht — die DNS-Auflösung an sich funktioniert einwandfrei.ntpclient ... exited. Scheduling for restart.— gemessen ~93× pro Stunde (15 Neustarts in 9 min 43 s), in Bündeln von zwei bis drei Starts alle rund 2,5 Minuten. Dazwischen läuft der Prozess stabil. Die Uhr stimmt trotzdem sekundengenau, es ist also kein funktionales Problem — aber auch kein geplantes Verhalten, sondern Verschleiß auf einem 400-MHz-Chip.avahi-daemon starting up / exiting, gehäuft nach dem Booten. Avahi scheitert aninotify(im Kernel 2.6.32 nicht vorhanden), stabilisiert sich danach aber.
Das Log rotiert bei 200 KB ohne Backups (syslogd -s 200 -b 0), läuft also nicht
voll.
Die Uhrzeit kommt von pool.ntp.org, direkt aus dem Internet:
/etc/inittab: null::respawn:/sbin/ntpclient -n -s -c 0 -l -h pool.ntp.org
/tmp/system.cfg: ntpclient.1.server=pool.ntp.org
ntpclient.1.status=enabled
Zeitzone: TZ=GMT-1GDT (POSIX-Notation für UTC+1 mit Sommerzeit)
Der Weg dorthin funktioniert nachweislich: DNS über 10.0.0.251 löst
pool.ntp.org auf, der Zeitserver antwortet in 13 ms, und die Gerätezeit stimmt
sekundengenau. Das Kürzel GDT im date-Ausgabe ist nur der generische
Sommerzeit-Name aus der POSIX-Zeitzone, kein Fehler.
Ungeklärt bleibt, warum der Prozess sich alle paar Minuten beendet.
ntpclient schreibt selbst nichts ins Log — nur init meldet das Beenden.
Eine naheliegende Vermutung wäre, dass pool.ntp.org wechselnde Adressen
liefert und der Client bei einer unerreichbaren aussteigt. Diese Erklärung
trägt aber nicht: Zeigt man ntpclient auf einen Host, der gar nicht
antwortet, bleibt er hängen, statt sich zu beenden. Die Ursache der Neustarts
ist damit weiterhin offen.
Ein lokaler Zeitserver ändert nichts an den Neustarts. Das wurde getestet:
Auf 10.0.0.251 läuft inzwischen chrony (Stratum 3), vom Gerät aus erreichbar
und mit rund 26 ms Abweichung. Umgestellt wurde an zwei Stellen —
ntpclient.1.server in system.cfg und die Aufrufzeile in /etc/inittab —,
danach cfgmtd -w -p /etc /tmp/system.cfg.
Ergebnis: Die Uhr läuft weiterhin sekundengenau, die Neustartrate blieb aber
praktisch gleich (vorher ~93/h, danach ~100/h). Die Ursache liegt also nicht
am Zeitserver, sondern in der Aufrufweise -c 0 -l aus der inittab-Zeile.
Behalten wurde die lokale Quelle trotzdem: gleiche Genauigkeit, 5 ms statt Internetlatenz und keine Abhängigkeit von externer Erreichbarkeit.
Wer einen eigenen Zeitserver aufsetzen will — systemd-timesyncd kann das
nicht, es ist reiner Client. Mit chrony:
# /etc/chrony/chrony.conf
allow 10.0.0.0/24
allow 10.10.1.0/24
local stratum 10 # liefert weiter Zeit, auch ohne Internetsudo systemctl restart chrony
sudo ss -lunp | grep :123 # muss 0.0.0.0:123 zeigen, nicht 127.0.0.1:123Die letzte Zeile ist der eigentliche Beweis: Bindet chrony nur an Localhost,
fehlt ein allow.
Kurz: unbekannt. Weder Prolific noch Ubiquiti veröffentlichen eine Genauigkeitsangabe.
Prolific PL7223, ein „2ch Power/Energy Metering AFE Controller". Auf dem
Gerät als Kernelmodul powerdev angebunden, Versionen über
/proc/power/meter_ic_ver<N> auslesbar:
CFG(1..3) version = 0x0006c300
DSP(1) version = 0x12110503 DSP(2) = ...502 DSP(3) = ...501
Aus der Produktbroschüre (V1.0, 06/2015):
| ADC | 2 Kanäle, vollständig differentiell, 16 Bit |
| PGA | 1× bis 32×, programmierbar |
| DSP | programmierbar, eigener Programm- und Datenbereich |
| MTP | speichert DSP-Code und Kalibrierdaten |
| Schnittstelle | SPI |
„2ch" meint zwei ADC-Kanäle — Spannung und Strom, also eine Netzleitung pro Chip. Die drei unterschiedlichen DSP-Versionen belegen entsprechend drei Chips, einen je Steckdose.
Das vollständige Datenblatt steht unter NDA. Selbst der von Electric Imp veröffentlichte Treiber sagt dazu nur: „based on a datasheet under NDA … we cannot open source the driver class". Auch das mFi-Datenblatt nennt lediglich „Energy Monitoring" als Funktion, ohne Toleranzangabe.
Die absolute Genauigkeit hängt an drei unbekannten Größen: den Kalibrierdaten im MTP, der Toleranz des Strom-Shunts und der des Spannungsteilers.
power ist kein eigener Messwert. Über 30 Messungen ergab U × I × PF
exakt den gemeldeten Wert — Abweichung 0,000 % bei σ = 0,000 %. Die Firmware
rechnet ihn aus den anderen drei Größen. Ein Vergleich der vier Felder
untereinander kann deshalb prinzipiell nichts aufdecken.
Die Spannungsmessung läuft ruhig: σ = 0,31 V auf 229 V, also 0,14 %.
Alle drei Ports messen immer — unabhängig vom enabled-Flag. Das ist
kontraintuitiv, weil /proc/power/enabled<N> und das JSON-Feld "enabled" bei
unbenutzten Ports auf 0 stehen. Gemessen wird trotzdem:
enabled3=0 pwr=39.205 → 39.138 → 39.319 → 39.302 W (lebende Messwerte)
HTTP-API: {"port":3, "enabled":0, "power":39.32}
enabled beschreibt also die Aktivierung des Ports auf Controller-Seite, nicht
den Messkanal. Wer 0 als „misst nicht" liest, zieht den falschen Schluss —
diese Fehldeutung hat hier einige Umwege gekostet.
Zeigt ein Port dauerhaft exakt 0.0 W, hängt schlicht nichts Verbrauchendes
daran. Eine Empfindlichkeitsgrenze wurde nicht gefunden; Werte bis herunter zu
2,9 W wurden sauber gemeldet.
Der Eintrag vpower.<N>.enabled in /etc/persistent/cfg/vpower_cfg lässt sich
zwar ändern und mit cfgmtd schreiben, wird beim Booten aber wieder auf die
Werkseinstellung zurückgesetzt. Da er die Messung ohnehin nicht beeinflusst, ist
das folgenlos.
Mit 21 Messungen pro Sekunde über 86 s und fünf Schaltvorgängen an einer 40-W-Glühlampe gemessen. Der jeweils erste Messwert nach dem Einschalten:
39,46 W 10,20 W 5,99 W 2,90 W 11,96 W
Keine Messung lag über dem Ruhewert von 39,6 W — der Einschaltstoß einer kalten Glühwendel (rund das Zehnfache des Betriebsstroms) taucht nirgends auf.
Der Grund ist nicht die Abtastrate, sondern die Mittelung: Der Chip bildet Effektivwerte über ein Fenster von etwa einer Sekunde. Fällt das Einschalten mitten hinein, enthält der Mittelwert auch die stromlose Zeit davor — daher die Bruchteile. Ein 100-ms-Stoß von ~350 W trägt zu einem Sekundenmittel nur wenige Watt bei und verschwindet darin.
Praktische Folgen:
- Lastspitzen sind unsichtbar. Anlaufströme von Motoren, Kompressoren oder Netzteilen erfasst dieses Gerät prinzipbedingt nicht.
- Kurz laufende Verbraucher werden zu niedrig gemeldet, solange sie nur einen Teil des Mittelungsfensters abdecken.
- Dauerlasten und die Energiezählung sind davon nicht betroffen.
cf_countakkumuliert Impulse, statt Momentanwerte zu mitteln.
Gemessen an einer 40-W-Glühlampe (ohmsch, damit fällt die PF-Messung als Fehlerquelle weg), 30 Messungen über 61 s:
| Leistung | 39,62 W, σ = 0,066 W (0,17 % Streuung) |
| Strom | 0,1719 A, σ = 0,0002 A |
| Spannung | 230,66 V |
| Leistungsfaktor | 0,999 — bestätigt die PF-Messung, bei einer Glühlampe ist 1,0 zwingend |
Abweichung zum Nennwert: −0,9 %. Spannungskorrigiert (bei 230,66 V müsste eine 40-W-Lampe 40,23 W ziehen, da P ∝ U²): −1,5 %.
Gegenprobe über das ohmsche Gesetz:
gemessen: R = U/I = 230,66 / 0,1719 = 1342 Ω
Nennwert: R = U²/P = 230² / 40 = 1322 Ω
Die 1,5 % höhere Widerstand passen zu einer gealterten Glühwendel — Wolfram verdampft über die Lebensdauer, der Draht wird dünner.
Was das beweist und was nicht: Die Präzision ist ausgezeichnet (0,17 % Streuung). Die absolute Genauigkeit lässt sich damit nicht besser als auf ±5 % eingrenzen, denn die Referenz ist schlechter als die Messung: Glühlampen haben typisch ±5 % Fertigungstoleranz, dazu kommt die Alterung. Die Anzeige ist mit dem Nennwert verträglich — mehr gibt der Versuchsaufbau nicht her. Für eine belastbare Aussage bräuchte es ein kalibriertes Referenzmessgerät.
Ein Schaltnetzteil taugt als Referenz übrigens nicht: Ein Notebook (PF ≈ 0,4) schwankte im Test um σ = 1,63 W bei 9,22 W Mittelwert, also 18 % — darin verschwindet jeder Messfehler des Geräts.
Gemessen gegen ein Klasse-0,2-Gerät. Soweit bekannt die erste veröffentlichte Genauigkeitsangabe für den mFi mPower. Aufbau:
Netz → Shelly → mPower Port 2 → Janitza UMG 96RM (Direktanschluss) → Prüfling
| Last | THD-I | PF | mPower vs. Janitza |
|---|---|---|---|
| Glühlampe 40 W | 8,8 % | 0,999 | +0,08 % (über die Bilanz) |
| GaN-Netzteil, 20 W Ausgang | 47,0 % | 0,73 | +1,9 % |
| Trafo-Netzteil, Leerlauf | 67,8 % | 0,27 | +0,6 % |
Der mPower liegt bei beiden Lastarten innerhalb von 2 % — und ausgerechnet beim extremeren Fall besser. Beim Trafo im Leerlauf sind 96 % der Scheinleistung Blindleistung; das ist ungefähr das Schwierigste, was man einem Leistungsmesser vorsetzen kann.
Im Detail:
| Janitza | mPower | Abweichung | |
|---|---|---|---|
| GaN-Netzteil | |||
| Wirkleistung | 23,00 W | 23,44 W | +1,9 % |
| Strom | 0,1356 A | 0,1385 A | +2,2 % |
| Leistungsfaktor | 0,7302 | 0,7286 | −0,2 % |
| Trafo-Netzteil | |||
| Wirkleistung | 14,50 W | 14,59 W | +0,6 % |
| Strom | 0,2355 A | 0,2336 A | −0,8 % |
| Leistungsfaktor | 0,2656 | 0,2694 | +1,4 % |
Beide Messungen sind in sich stimmig. Zwischen Verschiebungsfaktor, THD und
echtem Leistungsfaktor gilt PF = cos φ / √(1 + THD²):
| GaN | Trafo | |
|---|---|---|
cos φ angezeigt |
0,80 | 0,32 |
cos φ aus P und Q |
0,8075 | 0,3215 |
PF gemessen (P/S) |
0,7302 | 0,2656 |
PF aus cos φ und THD |
0,7308 | 0,2649 |
| Abweichung | 0,1 % | 0,3 % |
Dazu die Plausibilität: 23,0 W Eingang bei 20 W Ausgang ergeben 87 % Wirkungsgrad, was für ein GaN-Netzteil passt. Und die Blindleistung ist beim Schaltnetzteil kapazitiv (Eingangsfilter), beim Trafo induktiv (Magnetisierungsstrom) — beides physikalisch erwartbar.
Dieselben Lasten ergaben zuvor −20,7 % und +58 %, also einen
Vorzeichenwechsel, und die Janitza maß beim GaN-Netzteil 14,5 W Eingang bei 20 W
Ausgang — 138 % Wirkungsgrad, physikalisch unmöglich. Zusätzlich war cos φ
negativ.
Ursache war der Stromwandler: ein 30/5-A-Typ bei tatsächlich 0,13 bis 0,23 A Laststrom, also 0,4 bis 0,8 % des Nennstroms.
Klasse 0,2 → spezifiziert ab 5 % des Nennstroms
Klasse 0,2S → spezifiziert ab 1 % des Nennstroms
Unterhalb dieses Bereichs darf ein Wandler beliebig danebenliegen, ohne seine Klasse zu verletzen — vor allem im Phasenwinkel, und der entscheidet bei niedrigem Leistungsfaktor über die Wirkleistung. Gelöst durch Direktanschluss ohne Wandler (die Stromeingänge der 96RM sind für 5 A ausgelegt) und Wandlerverhältnis auf 5/5.
Merkregel: Der Wirkungsgrad ist die beste Plausibilitätsprüfung. Ein Netzteil kann nicht mehr abgeben als es aufnimmt — daran ist der fehlerhafte Aufbau aufgefallen, nicht an der Messtechnik. Aus den Fehlmessungen wurde zwischenzeitlich geschlossen, die Genauigkeit hänge stark an der Lastart. Das war falsch und ist mit den korrigierten Messungen widerlegt.
Mit der Janitza vor der Leiste und einer 40-W-Glühlampe an Port 3:
mPower Port 3 40,0442 W (σ = 0,038 W, PF 0,9994)
+ Eigenverbrauch der Leiste 2,09 W
= erwartet 42,13 W
Janitza misst 42,10 W Abweichung 0,034 W
Dieser Messpunkt isoliert den Betragsfehler: Bei einem Leistungsfaktor von 0,9994 fallen Phasenlage und Oberwellen als Fehlerquellen praktisch weg. Was bleibt, ist die reine Strommessung — und die stimmt.
Nebenbefund: Die 8,77 % Stromverzerrung stammen nicht von der Glühlampe, sondern von der Leiste selbst.
Glühlampe 172,5 mA praktisch oberwellenfrei
Leiste 18,5 mA davon 16,1 mA Oberwellen (THD 176 %)
Gesamt 180 mA → 16,1/180 = 8,9 % gemessen 8,77 %
Damit ist der Eigenverbrauch der Leiste ein drittes Mal unabhängig bestätigt.
Ein zweiter, unabhängiger Nachweis. Mit der Janitza vor der Leiste und dem Trafo-Netzteil an Port 2 muss die Differenz beider Messungen dem Eigenverbrauch der Leiste entsprechen:
Janitza (ganze Leiste) 16,50 W
mPower (nur Port 2) 14,49 W
───────
Differenz 2,01 W
Eigenverbrauch (separat) 2,09 W Abweichung 0,08 W
Läge der mPower um 20 % daneben, käme hier ein Rest von rund 5 W oder −0,9 W heraus. Die Genauigkeit ist damit auf zwei voneinander unabhängigen Wegen belegt.
Bei derselben Messung, zur selben Zeit:
| Janitza | Shelly | Abweichung | |
|---|---|---|---|
| Wirkleistung | 16,50 W | 17,7 – 18,1 W | +7 bis +10 % |
Die Anzeige des Shelly schwankt dabei um mehrere Zehntelwatt, obwohl die Last konstant ist — das Trafo-Netzteil zieht mit σ = 0,05 W praktisch unverändert.
Der mPower liegt näher an der Wahrheit als der Shelly — unter 2 % gegenüber rund 9 %. Ein Gerät von 2015, dessen Plattform seit Jahren eingestellt ist, misst genauer als ein aktuelles Produkt.
Die Leiste hing hinter einem Shelly 4PM, sodass beide Geräte dieselben Lasten sehen konnten. Gemessen an einem linear geregelten Trafo-Labornetzteil — induktiv und nichtlinear, also eine ganz andere Lastart als die Glühlampe.
| Last | mPower | Shelly | Differenz |
|---|---|---|---|
| Labornetzteil, Leerlauf | 14,30 W | 15,70 W | −8,9 % |
| Labornetzteil, 1 A / 12 V | 33,28 W | 34,90 W | −4,6 % |
| Glühlampe 40 W (ohmsch) | 39,62 W | — (gegen Nennwert) | −1,5 % |
Der mPower liest bei verzerrten Lasten durchweg niedriger, bei ohmscher Last kaum. Das deutet darauf hin, dass beide Geräte Oberwellen unterschiedlich in die Wirkleistung einrechnen — ein bekannter Unterschied zwischen Messchips. Ohne kalibriertes Referenzgerät ist nicht auflösbar, welches näher an der Wahrheit liegt. Ein Messplan dafür steht in TODO.md.
Die Anzeigen beider Geräte schienen sich zunächst zu widersprechen —
U × I × PF ergab beim Shelly nie die angezeigte Leistung. Die Auflösung: Die
beiden melden verschiedene Größen.
| Last | PF echt (P/S) |
Shelly zeigt | mPower zeigt |
|---|---|---|---|
| mPower-Leiste selbst (Schaltnetzteil) | 0,354 | 0,70 | — |
| Notebook-Netzteil (Schaltnetzteil) | 0,406 | 0,70 | 0,41 |
| Labornetzteil belastet (Trafo) | 0,531 | 0,50 | 0,499 |
Der mPower meldet den echten Leistungsfaktor P/S, der Shelly offenbar den
Verschiebungsfaktor cos φ der Grundschwingung. Der Beleg liegt darin, wann
sie auseinanderlaufen: Bei den Schaltnetzteilen klafft eine Lücke, weil deren
verzerrter Strom den echten PF weit unter cos φ drückt. Beim Trafo-Netzteil,
das die Phase verschiebt aber kaum verzerrt, stimmen beide fast überein.
Wer Werte zweier Messgeräte vergleicht, sollte das prüfen, bevor er aus einer Differenz auf einen Messfehler schließt.
Gemessen mit der Janitza UMG 96RM, die dafür vor die Leiste geschaltet wurde, bei komplett leeren Ports:
| Wirkleistung | 2,09 W im Ruhezustand |
| Spitzen | bis 2,3 W, wenn das Gerät aktiv ist |
| Scheinleistung | 4,30 VA |
cos φ |
0,99 |
Leistungsfaktor (P/S) |
0,486 |
| THD des Stroms | 176 % |
Rund 18 kWh im Jahr, nur fürs Dasein. Die Spitzen fallen vermutlich mit dem MQTT-Publisher zusammen, der alle 60 s alle Ports abfragt, dazu WLAN-Aktivität.
Ein früherer Wert von 2,6 W stammte vom Shelly und wurde zeitversetzt gemessen — ob die Differenz Messfehler oder ein erwischter Aktivitätspeak war, lässt sich aus zwei Einzelwerten nicht trennen.
Es sind keine bistabilen Relais — jede angezogene Spule kostet dauerhaft Leistung. Gemessen mit einer Glühlampe als Stromsockel (damit die Janitza im zuverlässigen Bereich arbeitet) und Differenzbildung gegen die mPower-Messung:
1 Relais angezogen 41,8 W − 39,906 W = 1,894 W
1 Relais, Wiederholung 42,0 W − 40,101 W = 1,899 W ← 5 mW Unterschied
3 Relais angezogen 42,8 W − 40,040 W = 2,760 W
─────────
zwei zusätzliche Spulen 0,863 W → rund 0,43 W je Relais
Bei drei angezogenen Relais entfällt damit etwa die Hälfte der gesamten Leistungsaufnahme auf die Spulen. Wer eine Steckdose dauerhaft nicht braucht, spart durch Ausschalten also zusätzlich ein knappes halbes Watt.
Offen: Zwei frühere Messungen mit zwei angezogenen Relais ergaben 2,01 und 2,06 W, wo ein lineares Modell 2,33 W erwarten ließe. In beiden Fällen war Port 1 aus und die Ports 2 und 3 an. Möglicherweise ziehen die Spulen nicht gleich viel — belegt ist das nicht, dafür fehlen die übrigen Kombinationen.
Die Leiste versorgt sich über ein kleines Schaltnetzteil ohne Leistungsfaktorkorrektur. Das Ergebnis ist lehrreich:
Gesamtstrom 18,5 mA
Grundschwingung 9,2 mA (49 %)
Oberwellen 16,1 mA (87 %)
cos φ von 0,99 sagt: Die Grundschwingung ist fast exakt in Phase — ein Gerät,
das nur den Verschiebungsfaktor anzeigt, würde hier eine nahezu ideale Last
melden. Tatsächlich beträgt der echte Leistungsfaktor nur 0,486, weil 87 % des
Stroms Oberwellen sind. Die Verzerrungsblindleistung liegt bei 3,75 var — fast
doppelt so hoch wie die Wirkleistung.
Genau dieser Unterschied hat weiter oben für scheinbar widersprüchliche PF-Anzeigen zwischen den Geräten gesorgt.
Wo unterwegs Stellen verlorengehen:
Messchip PL7223
16-Bit-ADC, Mittelung über ~1 Sekunde
│
▼
/proc/power/active_pwr3 = 39.405690908
volle Rechengenauigkeit — aber Scheingenauigkeit:
die hinteren Stellen bedeuten physikalisch nichts
│
├──▶ HTTP-API ──▶ mqtt_bridge.py ──▶ MQTT: 39.41 (2 Nachkommastellen)
│
└──▶ mqpub.sh, printf "%.1f" ─────▶ MQTT: 39.3 (1 Nachkommastelle)
Gerundet wird erst ganz am Ende. Im Geräte-Modus erledigt das mqpub.sh:
power_val=`printf "%.1f" $power_val` # eine Nachkommastelle
voltage_val=`printf "%.1f" $voltage_val` # eine Nachkommastelle
energy_val=`printf "%.0f" $energy_val` # ganzzahligZur selben Zeit gemessen:
| MQTT (Gerät) | HTTP-API | |
|---|---|---|
| Leistung | 39.3 |
39.405690908 |
| Spannung | 230.6 |
230.780298233 |
Bei der Leistung ist das folgenlos. Die reale Streuung liegt bei σ = 0,066 W
— alles jenseits der ersten Nachkommastelle ist Rauschen und kostet nur Platz im
Broker. Die vielen Stellen der HTTP-API sind ohnehin Scheingenauigkeit:
9.542111933 W suggeriert Nanowatt-Auflösung, wo der Chip über eine ganze
Sekunde mittelt.
Nur bei der Energie zählt die Rundung wirklich, denn dort ist sie gröber als das, was der Chip kann:
intern: cf_count × 0,3125 Wh → 0,3125 Wh je Schritt
MQTT: printf "%.0f" → 1 Wh je Schritt
Bei einem 40-W-Verbraucher fällt das nicht auf — der erzeugt gut 40 Wh pro Stunde. Bei 5 W Standby dauert es 12 Minuten, bis sich der Wert überhaupt bewegt. In der stündlich aggregierenden Energiestatistik von Home Assistant verschwindet das wieder; nur für Auswertungen auf Minutenebene wäre es zu grob.
Wer es feiner braucht: /etc/persistent/mqtt/client/mqpub.sh ist ein normales
Shell-Skript, %.1f statt %.0f in der energy_val-Zeile wäre die ganze
Änderung — plus cfgmtd -w -p /etc/. Ob das einen Neustart übersteht, wäre
allerdings erst zu prüfen: vpower_cfg wird von der Firmware ebenfalls
zurückgesetzt.
Nach dem Umbiegen laufen zwei unabhängige Wege auf denselben Broker. Sie kollidieren nicht — verschiedene Topic-Bäume, verschiedene Zwecke.
| Gerät (mFi-tools) | Bridge (mqtt_bridge.py) |
|
|---|---|---|
| Läuft auf | der Steckdose selbst | der Kali-Box |
| Topic | mfi/mpower/… |
mpower/mpower_10_10_1_78/… |
| Konvention | Homie 2.1.0 | HA-Autodiscovery |
| Intervall | 60 s | 15 s |
| HA-Erkennung | manuell einrichten | automatisch |
| Energiezähler | ja (energy) |
nein |
| Braucht Notebook | nein | ja |
Die gerätseitige Variante ist robuster (läuft auch ohne eingeschaltetes Notebook)
und liefert zusätzlich den Energiezähler. Die Bridge ist bequemer, weil Home
Assistant die Entitäten von selbst anlegt. Wer nur eine will: die gerätseitige
behalten und in HA manuell einbinden, oder die Bridge um energy_sum erweitern.
Alles hier wurde am Gerät nachgemessen, nicht aus Dokumentation übernommen — eine offizielle Doku existiert nicht.
| Zweck | Aufruf |
|---|---|
| Session | GET / → Cookie AIROS_SESSIONID |
| Login | POST /login.cgi mit username, password, uri → 302 |
| Logout | GET /logout.cgi |
| Lesen | GET /mfi/sensors.cgi → JSON, alle Ports |
| Schalten | PUT /sensors/<port> mit output=0|1 |
| Schalten (alt.) | POST /mfi/sensors.cgi mit id=<port>&output=0|1 |
Antwort von sensors.cgi:
{"sensors":[{"port":1,"id":"536fa6c3a0f781599afa565d","label":"Notebook",
"model":"Outlet","output":1,"power":10.49,"enabled":1,"current":0.112,
"voltage":230.85,"powerfactor":0.404,"relay":1,"lock":0,"thismonth":0}],
"status":"success"}relay ist der physische Relaiszustand, output der befohlene — im Normalbetrieb
identisch. Labels werden mitgeliefert, sofern am Gerät gesetzt.
Drei Dinge, die hier Zeit gekostet haben:
POST /mfi/sensors.cgierwartet den Port alsid. Schickt man ihn unter einem anderen Namen (etwaport), erkennt das Gerät keinen Zielport und wendetoutputauf alle Steckdosen an. So haben wir versehentlich die ganze Leiste abgeschaltet.mpower.pybenutzt deshalbPUT /sensors/<port>, wo der Port im Pfad steht und nicht stillschweigend verlorengehen kann.POST /mfi/sensors.cgi/<port>liefert HTTP 200 und tut nichts. Ein Erfolgsstatus beweist bei diesem Gerät gar nichts — nach jedem Schreibzugriff zurücklesen.MPower.set()macht das automatisch.- Abgelaufene Sessions liefern HTML statt JSON, nicht etwa 401. Der Client erkennt das am JSON-Parsefehler und meldet sich einmal neu an.
Die Firmware bringt OpenSSL 1.0.0g (2012) mit: nur TLSv1.0, selbstsigniertes
512-Bit-Zertifikat. Modernes Python/OpenSSL lehnt das ab. Genau daran ist auch die
mfi-Integration in Home Assistant gestorben. Also: nur unverschlüsseltes HTTP
im vertrauenswürdigen LAN.
SSH funktioniert, braucht aber Legacy-Krypto — Dropbear 0.51 von 2008 kann nur
diffie-hellman-group1-sha1, ssh-rsa und aes128-cbc:
ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 \
-o HostKeyAlgorithms=+ssh-rsa \
-o Ciphers=+aes128-cbc,3des-cbc \
admin@10.10.1.78Darüber liegt in /proc/power/ je Port: relayN (schreibbar), outputN,
active_pwrN, i_rmsN, v_rmsN, pfN, energy_sumN, cf_countN, lockN,
enabledN, meter_ic_verN. Schalten geht direkt:
echo 0 > /proc/power/relay3Statische Konfiguration (Labels, Board-Infos) liegt in /etc/persistent/cfg/ und
ist nur über SSH erreichbar — Änderungen brauchen anschließend save.
Mainline-OpenWrt unterstützt die mPower-Serie nicht. Im aktuellen Baum gibt es
keine Treffer für mfi/mpower unter target/linux/ath79/, kein passendes DTS
und keinen Eintrag in der Table of Hardware. Der
archivierte OpenWrt-Thread
enthält nur Reverse-Engineering-Notizen (Relais-Mux über zwei 74x138, SPI-Kommandos,
PL7223-Messchip) — kein lauffähiger Port, Energiemessung nie implementiert.
Da das Gerät keine herausgeführte serielle Konsole hat, wäre ein fehlgeschlagener
Flash-Versuch ein Totalverlust. Der übliche Weg der Community ist stattdessen:
Stock-Firmware behalten und eigene Skripte nach /etc/persistent/ legen
(überlebt Neustarts via save).
- pasbec/mfi-mpower — die einzige aktiv gepflegte Library (Python, async). Ist in v2.0.0 bewusst von HTTP auf reines SSH umgestiegen.
- pasbec/home-assistant-mfi-mpower — HA-Integration via HACS, aktiv gepflegt.
- maletazul/mFi-tools — MQTT-Bridge, läuft auf dem Gerät selbst.
- chorankates/h4ck (ubiquiti/mfi) — Reverse-Engineering-Notizen.
- TinkerTry: Outlet-Namen ohne Controller setzen
Die eingebaute HA-Integration mfi ist unbrauchbar: Sie spricht nicht mit der
Leiste, sondern mit der alten Controller-Software (Port 6080/6443), ist seit HA
2022.7 defekt, und
Issue #120042 wurde als
"not planned" geschlossen.
Weitere Quellen und Recherchenotizen: QUELLEN.md.
MIT — siehe LICENSE. Die unter backup-geraet/mqtt/ gesicherten
Dateien stammen aus der Gerätefirmware bzw. den
mFi-tools und stehen unter den
Bedingungen ihrer jeweiligen Urheber — siehe QUELLEN.md.