kaaarim.com

Write-up,

NetWatch

NetWatch is a small Wi-Fi board at home that checks the internet once a minute and keeps its own record of every drop. It runs on a salvaged battery through power cuts, so the record has no gaps, and any phone on the home Wi-Fi can open its dashboard.

What the light says

Brightness 3 of 255. A steady glow means the board is alive and online. It doesn’t prove the dashboard page loads.

Dashboard demo

Demo. The readings are invented, shaped like the board’s real records. The dashboard is a redesigned copy of the one the board serves. The real board keeps six-minute detail for 24 hours and hourly after that; the demo shows six-minute detail for every day.

NetWatch

Online

Each square is one 6-minute record, a column is an hour. Tap or hover one; arrow keys work too.

  • Online
  • Partly offline
  • Offline
  • No record, board off
  • Not yet

How it works

Every minute
It opens a TCP connection to check the internet. The blue LED blinks for half a second while it does.
Every 6 minutes
It writes one history record to its own flash storage. That’s 240 a day, kept on the board itself.
Whenever I look
Any phone or laptop on the home Wi-Fi can open the dashboard the board serves. It isn’t reachable from the internet. Nothing outside points at it, and that’s by design.
Through a power cut
It runs from a salvaged lithium-ion cell all the time, so a cut doesn’t interrupt it and the record has no gap. Egypt has scheduled cuts, and I sized the test to roughly one of them, an hour or two.
When I change the code
It updates over Wi-Fi. An update sends firmware only, never the filesystem, so it can’t wipe the history. The one I timed took 9 seconds.

The dashboard’s footer shows the boot count and the supply voltage. If the boot count rises while nobody touched the board, that’s the sign of a power problem.

The board also looks after itself. It restarts if its largest free block of memory stays under 4 KB for 5 minutes, and it reconnects the Wi-Fi after 2 minutes offline. An outage that is still open during a reboot survives the reboot.

What it’s made of

An ESP8266 board, an old battery, and two cheap boards I call by their colours. This is the wiring it ended with.

USB chargerMains power
Blue boardTP4056 charger and cell protection
Green boardHT4928S, 5.1 V boost
NodeMCUESP8266 running NetWatch
Old Li-ion cellA salvaged IMR cell

The charger keeps the cell topped up through the blue board. The NodeMCU runs from the cell, through the green board, all the time.

The protection sits between the cell and everything else, so a long cut stops at about 2.4 V instead of killing the cell.
PartWhat it does here
NodeMCU v3 (LoLin), an ESP8266The Wi-Fi microcontroller. Runs the checks, stores the history, serves the dashboard. Draws about 70–80 mA on average, the typical NodeMCU figure.
On-board blue LEDThe status light.
Second LED on pin D5Blinks during an outage.
Old lithium-ion cellThe battery. The one in there now is an old IMR cell that still read 4.0 V. The first, an 18650, ended at 0.6 V and was retired.
TP4056 charger board, the blue boardCharges the cell to 4.2 V and stops. Its protection chips, a DW01 and an 8205, cut the cell off at about 2.4 V, but only for what is wired to its OUT+ and OUT− pads.
HT4928S power-bank board, the green boardA 5.1 V boost for the NodeMCU, with its own 0.8 A charger. Switches its output off if the load stays under about 60 mA for 8 seconds. Low-battery cut-off at 2.95 V. Tops the cell up again below 4.1 V.
A USB chargerMains power.

The firmware is an Arduino sketch called NetWatch, built with arduino-cli, with ElegantOTA for updates over Wi-Fi. The history lives on the board’s LittleFS flash. The dashboard is one gzipped HTML page compiled into the firmware, and its charts use Chart.js, stored on the flash next to the history. On the laptop there are two small Python scripts. One flashes firmware over Wi-Fi. The other watched the board every 10 seconds during the battery test.

One night, 13 to 14 Oct

It started with two symptoms. After the board had been on for a while the dashboard stopped loading, and the light was flashing, then dimming. They turned out to be two separate problems, the memory bug and the battery wiring. Times are Cairo time.

  1. Tue 13 Oct
  2. First flash, with the crash fix.

    The board had been running out of memory whenever the dashboard loaded.

    Boot 1 · External reset

  3. Second flash.

    No crashes since.

    Boot 2 · External reset

  4. The board goes dark.

    The first battery has run flat, far past the point where it should have been cut off. The cell ends at 0.6 V.

  5. 1 hour

    No record.

  6. Wed 14 Oct
  7. Power again.

    The boot log shows it plainly.

    Boot 3 · Power-on

  8. The power-cut test.

    I pull the charger. No reboot. The boot count stays at 3 and the uptime carries straight on.

    Still boot 3

  9. An update over Wi-Fi, with the new LED language.

    The first try had been refused with an HTTP 401. This one took 9 seconds, and the history was still there.

    Boot 4 · Restart

  10. Rewiring done.

    The extra boots were power-ups while I moved wires. The supply dipped to 2.72 V during them, still above the limit.

    Boot 7 · Power-on

What went wrong

Five things, in the order they came up. The third never actually happened.

  1. A firmware bug was rebooting the board. It ran out of memory whenever the dashboard loaded, because each history answer was built whole in RAM on a chip with about 43 KB free. The fix streams the history from flash record by record and answers with a 503 instead of crashing when memory is short. Before it, 5 of 5 concurrent rounds came back as corrupt JSON and 4 of 6 dashboard loads in a 30-minute soak ended in a reboot. After it, 20 of 20 rounds and 59 of 59 soak cycles were clean. Two flashes on the evening of 13 Oct put it on the board, External reset at 22:48 and 22:54 in the boot log. No crashes since.
  2. The first battery died. The cell was run down to 0.6 V, far below the green board’s 2.95 V cut-off. Once the boost switched off, the ESP most likely kept drawing current through the board, with nothing in between to cut it off. That’s my reading of the datasheet. I didn’t measure it. The board was dark for about an hour, 23:45 to 00:45, and came back as boot 3, reason Power-on. I retired that cell. The one in there now is an old IMR cell that still read 4.0 V.
  3. The light-load cut-out, which never happened. The green board turns its 5 V off when the load stays under about 60 mA for 8 seconds. A NodeMCU averages 70–80 mA but can dip under that between checks, which would mean cycling on and off. I tested it. No cut-out, no reboot.
  4. The first wireless update was refused with an HTTP 401. On the ESP8266 the web-server library always rejects the plain username-and-password login, because of a length bug. The update script now logs in the same way the browser’s /update page does. The second attempt took 9 seconds and was clean.
  5. The voltage on the dashboard isn’t the battery. It’s the ESP’s own supply after its regulator, so it reads about 2.99 V whatever powers it. The board can see that it’s alive. It can’t see how full the cell is.

The cell

0 to 4.4 V

4.2 VFull charge. Topped up again below 4.1 V.

2.95 VThe green board’s low-battery cut-off

2.4 VThe blue board’s protection cuts off

0.6 VWhere the first cell ended

What the dashboard reads

2.4 to 3.05 V

2.98–2.99 VNormal, whatever powers it

2.72 VDip during the rewiring

2.62 VLowest dip in the power-cut test

About 2.5 VThe ESP8266’s limit

Two different voltages, on two different scales. The dashboard only sees the right-hand one.

What worked

  • The rewiring. The cell goes to the blue board’s B+ and B−. The blue board’s OUT+ and OUT− feed the green board, and NetWatch plugs into the green board’s USB-A. The protection now sits between the cell and everything else, so a long cut stops at about 2.4 V instead of killing the cell.
  • My own wiring beat the suggested one. The charger feeds the blue board, which keeps the cell topped up, and the ESP runs from the cell through the green board all the time. When the mains cuts, nothing switches over, so the ESP doesn’t notice.
  • The power-cut test. Charger pulled. No reboot, the boot count stayed at 3 and the uptime carried straight on. The supply reading moved from 2.990 V to 2.973 V at the moment of the switch. It dipped once to 2.62 V about two minutes in, close to the ESP8266’s limit of about 2.5 V, without a reboot, then held steady around 2.98 V.
  • Wireless updates. 9 seconds, the board back cleanly, the history kept.
  • The LED language. A faint glow when the last check passed, a half-second blink while a check runs, and the D5 LED blinking when the internet is down.

Where it ended

It’s been running on the battery since 14 Oct 2026, on the home Wi-Fi, updated over the air. The notes are written and committed to git, kept local by choice. After the final rewiring the boot count read 7.

Still open

  • One resistor for the real battery level. A 100 kΩ resistor from the blue board’s OUT+ to the NodeMCU’s A0 pin. With the NodeMCU’s own divider that reads up to about 4.2 V, so the dashboard could show the real battery voltage and percentage, show whether it’s charging or on battery, and warn before the cell runs flat. It would draw about 10 µA.
  • An address reservation in the router, so the dashboard’s address never moves after a router reboot.
  • A check and a box. Make sure the green board is on the blue board’s OUT pads and not on B+ and B−, then put the bare boards in a small plastic box.