The pin template was for a different plug
Contents
The smart plug's vendor app put its energy monitoring behind a subscription. The metering chip inside measures voltage alongside current and power all the same, whatever the app agrees to show. I wanted that voltage. Read off a plug on wall power, it says whether the grid is up, and my power-station UPS needed exactly that: a second opinion on the grid from something not behind a battery.
So the plug had to leave Tuya's firmware for OpenBeken, an open firmware for the Beken chips in most Tuya plugs. That took one evening. Getting the pins right took the next day, because the template I borrowed was for a different plug, and the plug's own leftover settings were the only thing that knew.
A plug that pairs only over Bluetooth#
It's an RZTK C3680W, a 16 A energy-metering plug from a Ukrainian retailer. The OEM part number on the Tuya side is FrankEver FK-PW802EC-F, and plugs that look identical sell under other names. FrankEver's product page says "this plug available with ESP8266 chip factory-flashed with the Tasmota firmware". This one isn't. It paired only over Bluetooth, which an ESP8266 doesn't have, and its Wi-Fi MAC starts with D8:1F:12, a block registered to Tuya, not Espressif. The same page lists the Tuya and Smart Life apps.
The usual way off Tuya without a soldering iron is tuya-cloudcutter, a tool that replaces the firmware over Wi-Fi while the plug sits in its access-point pairing mode. It only works on firmware Tuya hasn't patched, and only in that AP mode, the slow blink. Bluetooth pairing doesn't count. My plug gave the fast blink and nothing else. Six power cycles didn't change that.
The research I'd done that afternoon came back with a clear verdict: no route without opening the case, "high confidence". Read the module's silkscreen, find the UART pads, solder. Two minutes after I read that, the plug was blinking slowly. I didn't write down what I did in those two minutes, so I can't tell you the sequence.
The first cloudcutter run reached the plug and failed to flash it. The second, with a different firmware version profile for the same OEM family, flashed OpenBeken 1.18.219, and the plug came back as an OpenBeken access point. The chip was a BK7231T.
My PC's Wi-Fi didn't come back with it. Cloudcutter moves the whole Wi-Fi adapter into its container for the run, and afterwards the adapter was gone from the host entirely: no interface, no phy, iw dev empty. Reloading the driver brought it back:
sudo modprobe -r mt7921e && sudo modprobe mt7921e
A template from a different plug#
OpenBeken needs a pin map: which GPIO drives the relay, which reads the button, which go to the energy-metering chip. Nobody had published one for this plug. The firmware it came with, though, belongs to a Tuya OEM family that tuya-cloudcutter lists for 28 devices across about 20 brands, from Arlec to LEDVANCE. The Arlec PC191HA (Series 1) has a known OpenBeken template, so I used that:
| Pin | Arlec template |
|---|---|
| P6 | Relay, channel 1 |
| P7 / P8 / P24 | BL0937 meter (CF, CF1, SEL) |
| P10 | WiFi LED |
| P11 | Button |
| P26 | LED, channel 1 |
The meter pins were right, which is why everything looked right. OpenBeken's BL0937 driver started, the plug read the mains voltage, and VoltageSet 235.8 calibrated it against the AC180's own voltage reading. The voltage came out over plain HTTP, no cloud and no app:
curl 'http://<plug>/cm?cmnd=STATUS%208'
The only loose end was the blue LED ring. The template's two LED pins did nothing visible.
The LED that went dark when the relay closed#
To find the LED I wrote a sweep: set each free pin to an output, blink it, announce the pin number, and watch the plug. Nothing blinked on any pin. I concluded the LED was wired straight to power, a power indicator nothing could switch. That was wrong twice over.
The sweep set pin roles with OpenBeken's setPinRole command, passing role numbers. Its documentation describes the argument as [RoleIndexOrName]. The code does this:
roleIndex = PIN_ParsePinRoleName(role);
if(roleIndex == IOR_Total_Options) {
ADDLOG_INFO(LOG_FEATURE_CMD, "Unknown role");
return CMD_RES_BAD_ARGUMENT;
PIN_ParsePinRoleName only matches names, in 1.18.219, which the plug ran then, as in 1.18.318. A number is an unknown role, and the command changes nothing. The HTTP API doesn't say so: it answers {} whether the command worked or not, and "Unknown role" goes only to the device log, which the sweep never read. It blinked nothing because it set nothing.
Later tests, done through the pin configuration page that does take effect, still found no LED on P10 or P26, in either polarity. One observation should have given it away sooner: whenever the relay was on, the LED ring stayed dark.
The plug remembered its own pins#
Tuya's firmware keeps its device settings in a small store near the top of the flash. Cloudcutter overwrites the application, not that store, so the old settings were still on the chip. OpenBeken serves its flash read-only over HTTP, so I read the top of it and decoded the store with bk7231tools. The store also holds the Wi-Fi password and the plug's Tuya keys, so the script printed only the fields that describe pins. The module, it said, is a WB2S:
| Function | Arlec template | The plug's own Tuya config |
|---|---|---|
| Relay | P6 | P26, active high |
| LED | P26 | P6, active low |
| Button | P11 | P10, active low |
| Meter CF / CF1 / SEL | P7 / P8 / P24 | P7 / P8 / P24 |
The template had the relay and the LED swapped. Both were bound to channel 1, so turning "the relay" on closed the real relay through a pin I had labelled LED, and drove the real LED's pin through a pin I had labelled relay. The LED is active low: a relay output going high switched it off. Relay on, LED dark, exactly what I'd seen, and from the outside the outlet worked.
The template's WiFi LED pin, P10, is the real button, so OpenBeken was driving a button input as an output. When I restored the template late that night, the plug rebooted about every 100 seconds, most likely because of that. Clearing P10 stopped the reboots. With the real map applied, the LED lit on the first boot.
The same config held the stock firmware's protection settings: over-current cut-off at 17.5 A, over- and under-voltage cut-offs disabled.
An LED worth looking at#
Once P6 was known, I gave it a PWM role instead of a plain LED. It's visible down to 1 % brightness, which makes it a small status display rather than an on/off lamp. A script on the plug's filesystem reads a mode from channel 6, so anything on the network can set the pattern with one HTTP call:
| Mode | Pattern |
|---|---|
| 0 | off |
| 1 | steady (boot default) |
| 2 | dim, 3 % |
| 3 | breathe |
| 4 | fast blink |
| 5 | short pulse on a dim base |
| 6 | slow blink |
The UPS observer uses four of them: steady when everything runs on wall power, pulse when a station is on battery, fast blink at low battery, slow blink when it has lost contact with the bridge. Dim belongs to the plug itself, as below. The catch is obvious once said: the plug runs on mains, so in a real blackout its LED is simply off, and the on-battery patterns only ever show while the plug itself still has power. The scripts are OpenBeken's own .bat language, uploaded with one POST each.
Before going further I saved a full 2 MiB flash dump, read twice with matching hashes, and updated OpenBeken to 1.18.318 over its own HTTP update endpoint. Pins and calibration survived.
The factory calibration was still there too#
The stock firmware for this family, version 1.3.5, is public as flash dumps from other owners. Decompiled, it explained a four-number record at a fixed offset in my plug's flash: {610, 9229, 9413, 4430}. The first three are pulse periods in microseconds that the factory fixture measured for voltage, current and power at a 220.0 V, 0.392 A, 86.4 W reference load. The fourth is pulses per watt-hour.
The voltage figure agrees with my hand calibration within 2.16 %. Power checks out against the pulses-per-watt-hour figure within 0.08 %, two different measurements agreeing. Current doesn't fit: on a BL0937, which is what OpenBeken's driver reads correctly on these pins, the record's current figure is inconsistent with the other two. I'll calibrate current against a kettle, where current is power over voltage.
The plug now guards the station#
The AC180's wall input now runs through the plug's relay, and a second script turns the plug into a voltage guard. The trip half of it:
watch:
delay_ms 1000
if $CH12!=0 then goto tripped
if $CH1==0 then goto relayoff
setChannel 15 0
if $voltage<50 then goto watch
if $voltage>$CH10 then goto bad
if $voltage<$CH11 then goto bad
setChannel 13 0
goto watch
bad:
addChannel 13 1
if $CH13<3 then goto watch
setChannel 1 0
setChannel 12 1
setChannel 13 0
goto watch
Outside 170–253 V for three seconds, the relay opens and channel 12 records the trip. The LED script checks that flag before anything else and stays dim while it is set, so no LED mode set over the network can hide a trip. Readings under 50 V are ignored, because the meter reads zero for a moment after boot. After the script's own trip, the relay closes again once the voltage has stayed at least 15 V above the lower limit and 8 V below the upper one for 30 seconds. The relayoff branch came a day later, on 17 October: a relay found off without a trip (the button, a stray command) is switched back on after 10 seconds, because this relay feeds a station. Why is in the alerts post's update. The limits live in channels 10 and 11, so they can be changed over HTTP without touching the script.
To test it, I set the upper limit below the mains voltage. On the morning of 16 October the relay opened, and the AC180 went to battery. The observer, which reads the station over Bluetooth through an ESP32 bridge, logged OB 31 seconds later. The relay closed again 31 seconds after the trip, and the observer went back to OL within a minute of that. That run is in the observer post.
The plug's day job is still the voltage reading. The observer polls STATUS 8 and counts the grid as up at 170 V or more. Together with a lamp that answers a TCP connect, it's one of two grid canaries, and an outage needs both to stop answering. A plug restart alone never reads as an outage.
The door ships unlocked#
OpenBeken ships with web authentication off. Until you set a password, anyone on the LAN can switch the relay, which now puts the AC180 on battery, upload new firmware, or read the flash, Wi-Fi password included. The option is a checkbox and an admin password on the configuration page, and it covers the command API and the flash and update endpoints too. It's HTTP Basic auth over plain HTTP, so it keeps out a curious device on the network, not someone capturing its traffic. I've switched mine on. The observer sends the password with its polls, and a request without it now gets a 401. An isolated IoT network that only the router and the observer can reach would be the next layer.
The scripts, the pin map and an OpenBeken template for this plug are public at gitlab.com/random-home-utils/openbeken-grid-canary-plug. If you have one of the 28 sibling plugs, the template is a starting point and nothing more: read your own plug's settings before you trust anyone's pin map, mine included.
Filed under: hardware, firmware, openbeken, tuya, smart-home, power, ups
Read next#
- A null battery reading cleared the low-battery alarm — the observer that polls this plug, and the trip test from the station's side.
- Twenty-five percent is a countdown — why the UPS needed a grid canary at all.
- The power went out twice. My UPS log saw it once. — the station whose wall input now runs through this relay.
Sources#
- OpenBK7231T_App (OpenBeken) — the firmware;
CMD_SetPinRoleinsrc/cmnds/cmd_channels.candPIN_ParsePinRoleNameinsrc/httpserver/new_http.c, web auth insrc/httpserver/http_basic_auth.c, at tag 1.18.318. - tuya-cloudcutter — over-the-air firmware replacement in AP mode; its known patched firmware list.
- tuya-cloudcutter device and profile list — the 28 devices sharing this plug's OEM firmware family.
- bk7231tools — decodes the Tuya settings store from a flash dump.
- FrankEver FK-PW802EC — the product page with the ESP8266/Tasmota claim next to the Tuya and Smart Life apps.
- openbeken-grid-canary-plug — the scripts and template from this post.