Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.
Fibre ONT Monitor: the finished build
ESP32Network & ISP tools

Fibre ONT status monitor — fibre cut or ISP fault? (ESP32 + LDRs)

Reads the ONT’s PON and LOS lights optically and names the fault for you.

Difficulty
Medium
Build time
1.5 h
Parts cost
₹890 · $10
Verified — core esp32 3.3.12, Adafruit SSD1306 2.5.17, 3 Nov 2026Compile-only

Community draft — report a mistake

Parts list

PartQtyApprox.Buy
ESP32 DevKit V1 (30-pin)1₹380Buy ESP32 DevKit V1 (30-pin)
LDR light sensor module (4-pin, with AO)2₹80Buy LDR light sensor module (4-pin, with AO)
0.96" SSD1306 I2C OLED (4-pin)1₹220Buy 0.96" SSD1306 I2C OLED (4-pin)
Black insulation tape + blu-tack (to shroud each LDR over its LED)1₹40Buy Black insulation tape + blu-tack (to shroud each LDR over its LED)
Jumper wires (female–female, 20 cm)10₹50Buy Jumper wires (female–female, 20 cm)
5 V USB adapter + cable (on the router’s inverter/UPS)1₹120Buy 5 V USB adapter + cable (on the router’s inverter/UPS)
Total₹890

Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.

Wiring

ESP32 DevKit V1Only the pins used are shown · 3.3 V logicLDR light sensor moduleLDR light sensor module #2SSD1306 OLED 0.96″ (128×64, I2C)D34D35GNDD22D213V3VCCGNDDO (not connected)AOVCCGNDDO (not connected)AOGNDVCCSCLSDA
Every connection
ESP32 DevKit V1Part · pinWireNotes
D34LDR light sensor module · AOgreenPON light. GPIO34 is ADC1 (works with Wi-Fi on) and input-only
3V3LDR light sensor module · VCCred3.3 V so AO never exceeds the ADC limit
GNDLDR light sensor module · GNDblack
D35LDR light sensor module #2 · AOpurpleLOS light (red on most ONTs). GPIO35, ADC1
3V3LDR light sensor module #2 · VCCred
GNDLDR light sensor module #2 · GNDblack
D22SSD1306 OLED 0.96″ (128×64, I2C) · SCLyellow
D21SSD1306 OLED 0.96″ (128×64, I2C) · SDAblue
3V3SSD1306 OLED 0.96″ (128×64, I2C) · VCCred
GNDSSD1306 OLED 0.96″ (128×64, I2C) · GNDblack

Board quirks: Nothing is wired to the ONT. The LDRs only look at its lights, so there’s no risk to the ISP’s equipment and nothing to explain to the technician. Shroud each LDR so it sees only its own LED: a 1 cm tube of black tape, blu-tack around the edge. Room light leaking in is the main cause of wrong readings. LDR modules differ: most read lower when lit, some higher. The Serial Monitor prints the raw value of each; note it with the light on and off and set the threshold halfway (flip <code>LIT_IS_LOW</code> if yours rises with light). Light names vary by ISP and model: PON may be labelled “PON”, “REG” or “FIBER”; LOS may be “LOS” or “ALARM”. A blinking PON means the ONT is trying to register with the ISP’s OLT.

Code

fibre-ont-status-monitor.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// Fibre ONT status monitor — ESP32 DevKit V1 + 2 LDR modules + SSD1306 OLED
// Two LDR modules taped over the ONT's PON and LOS lights read them optically — no wire ever
// touches the ISP's box. PON AO → D34, LOS AO → D35 (ADC1, works with Wi-Fi on), both at 3V3.
// OLED: SDA D21, SCL D22. With the lights plus TCP checks to your router and 1.1.1.1 it tells
// a fibre cut (LOS red) from an ONT that isn't registered (PON blinking) from an ISP fault.
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "fr_settings.h"

const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASS = "YOUR_PASSWORD";
// Filled in from the FlashReady flasher over USB (kept on the board), or type the values above.
FrSetting SETTINGS[] = {{"wifi_ssid", &WIFI_SSID, true}, {"wifi_pass", &WIFI_PASS, true}};

constexpr uint8_t PON_PIN = 34;
constexpr uint8_t LOS_PIN = 35;
// Most LDR modules read LOWER when lit. Watch the Serial raw values with the light on and off,
// then set the threshold halfway between them (and flip LIT_IS_LOW if yours reads higher).
constexpr bool LIT_IS_LOW = true;
constexpr int PON_THRESHOLD = 2000;
constexpr int LOS_THRESHOLD = 2000;

Adafruit_SSD1306 oled(128, 64, &Wire, -1);

enum Light { OFF, ON, BLINK };
const char* lightName(Light l) { return l == ON ? "ON" : l == BLINK ? "BLINK" : "OFF"; }

bool lit(uint8_t pin, int threshold, int& raw) {
  raw = analogRead(pin);
  return LIT_IS_LOW ? raw < threshold : raw > threshold;
}

// Sample both lights 40 times over 2 s and count on/off changes to spot blinking.
void readLights(Light& pon, Light& los, int& ponRaw, int& losRaw) {
  int ponOn = 0, losOn = 0, ponFlips = 0, losFlips = 0;
  bool pPrev = lit(PON_PIN, PON_THRESHOLD, ponRaw), lPrev = lit(LOS_PIN, LOS_THRESHOLD, losRaw);
  for (int i = 0; i < 40; i++) {
    bool p = lit(PON_PIN, PON_THRESHOLD, ponRaw), l = lit(LOS_PIN, LOS_THRESHOLD, losRaw);
    ponOn += p;
    losOn += l;
    ponFlips += p != pPrev;
    losFlips += l != lPrev;
    pPrev = p;
    lPrev = l;
    delay(50);
  }
  pon = ponFlips >= 2 ? BLINK : ponOn > 20 ? ON : OFF;
  los = losFlips >= 2 ? BLINK : losOn > 20 ? ON : OFF;
}

long probe(const IPAddress& ip, uint16_t port) {
  WiFiClient c;
  unsigned long t = millis();
  bool ok = c.connect(ip, port, 2000);
  long ms = ok ? (long)(millis() - t) : -1;
  c.stop();
  return ms;
}

const char* diagnose(bool wifi, long routerMs, long netMs, Light pon, Light los) {
  if (los != OFF) return "FIBRE CUT (LOS)";
  if (pon == BLINK) return "ONT NOT REGISTERED";
  if (pon == OFF) return "ONT OFF / NO PON";
  if (!wifi) return "WI-FI DOWN";
  if (routerMs < 0) return "ROUTER NOT ANSWERING";
  if (netMs < 0) return "ISP FAULT (PON OK)";
  return "ALL OK";
}

String lastVerdict;
unsigned long verdictSince = 0;

void setup() {
  Serial.begin(115200);
  frSettingsBegin(SETTINGS);
  analogSetPinAttenuation(PON_PIN, ADC_11db);
  analogSetPinAttenuation(LOS_PIN, ADC_11db);
  Wire.begin(21, 22);
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) Serial.println("OLED not found at 0x3C");
  if (*WIFI_SSID) WiFi.begin(WIFI_SSID, WIFI_PASS);
  else Serial.println("Wi-Fi off — no Wi-Fi name set; only the ONT lights are watched. Flash from flashready.in or type it into the code.");
}

void loop() {
  Light pon, los;
  int ponRaw, losRaw;
  readLights(pon, los, ponRaw, losRaw);
  bool wifi = WiFi.status() == WL_CONNECTED;
  long routerMs = wifi ? probe(WiFi.gatewayIP(), 53) : -1;
  long netMs = wifi ? probe(IPAddress(1, 1, 1, 1), 53) : -1;
  if (!*WIFI_SSID) routerMs = netMs = 0;  // no Wi-Fi to test: judge on the lights only
  String verdict = diagnose(wifi || !*WIFI_SSID, routerMs, netMs, pon, los);

  if (verdict != lastVerdict) {
    unsigned long mins = (millis() - verdictSince) / 60000UL;
    if (lastVerdict.length()) Serial.printf("[%lu min] %s -> %s (after %lu min)\n", millis() / 60000UL, lastVerdict.c_str(), verdict.c_str(), mins);
    lastVerdict = verdict;
    verdictSince = millis();
  }
  Serial.printf("pon=%s(%d) los=%s(%d) router=%ldms net=%ldms -> %s\n", lightName(pon), ponRaw, lightName(los), losRaw, routerMs, netMs, verdict.c_str());

  oled.clearDisplay();
  oled.setTextColor(SSD1306_WHITE);
  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.printf("PON %-5s LOS %s", lightName(pon), lightName(los));
  oled.setCursor(0, 12);
  if (routerMs >= 0) oled.printf("Router %ld ms", routerMs);
  else oled.print("Router --");
  oled.setCursor(0, 22);
  if (netMs >= 0) oled.printf("1.1.1.1 %ld ms", netMs);
  else oled.print("1.1.1.1 --");
  oled.drawFastHLine(0, 34, 128, SSD1306_WHITE);
  oled.setCursor(0, 40);
  oled.print(verdict);
  oled.setCursor(0, 54);
  oled.printf("for %lu min", (millis() - verdictSince) / 60000UL);
  oled.display();
  delay(3000);
}

Arduino IDE: keep fr_settings.h in the same folder as the .ino (tab menu → New Tab, same name). Flashing from this page doesn’t need it.

fr_settings.hArduino C++
// fr_settings.h — FlashReady on-board settings (Wi-Fi name, phone number, ...) over USB.
//
// Why: the pre-built file on flashready.in is the same for everyone, so your own settings
// can't be inside it. Instead the board asks for them over USB serial and keeps them in
// its EEPROM. If you use Arduino IDE you can still type them into the constants in the
// sketch — this file then changes nothing except a 2-second pause at start-up.
//
// The protocol, in plain words (one text line each way, ending in \n, at 115200 baud):
//  1. Right after Serial.begin(115200) the board prints
//       FR?SETTINGS v1 keys=wifi_ssid,wifi_pass missing=wifi_pass
//     "missing" lists the required settings that still have no real value.
//  2. If nothing is missing it listens for 2 seconds and then carries on. If something is
//     missing it keeps listening, printing the line again every 2 seconds, for up to
//     30 seconds; then it carries on anyway and the sketch skips whatever needs the
//     missing value (an alarm still sounds, it just can't send the SMS).
//  3. The flasher answers   FR!SET wifi_ssid=My%20Home&wifi_pass=secret
//     (values URL-encoded). The board stores them and answers FR!OK, then carries on with
//     the new values. Errors: FR!ERR unknown <key>, FR!ERR long <key> (nothing is stored).
//     A key that is left out keeps its stored value; "key=" (empty) removes it.
//     "FR!SET" with nothing after it means "carry on now": FR!OK, nothing is stored.
//  4. FR!CLEAR wipes everything that was stored, answers FR!OK and keeps listening.
//  Other lines are ignored.
//
// A stored value wins over the constant in the sketch. A constant that is still a
// placeholder ("YOUR_SSID", "your-wifi", "+91XXXXXXXXXX") or empty counts as not set.
//
// EEPROM block at FR_SETTINGS_ADDR: 'F' 'R' 0x01, length (uint16, low byte first), then
// "key=value\n" lines. Uses FR_SETTINGS_SIZE bytes; move FR_SETTINGS_ADDR with a #define
// before the #include if your sketch uses EEPROM for something else.
//
// Use in a sketch:
//   const char* WIFI_SSID = "YOUR_SSID";
//   FrSetting SETTINGS[] = { {"wifi_ssid", &WIFI_SSID, true} };   // key, constant, required
//   void setup() { Serial.begin(115200); frSettingsBegin(SETTINGS); ... WiFi.begin(WIFI_SSID, ...); }
// After frSettingsBegin() each constant points at the value to use ("" if none is set).
#pragma once
#include <Arduino.h>
#include <EEPROM.h>
#include <string.h>

#ifndef FR_SETTINGS_ADDR
#define FR_SETTINGS_ADDR 0
#endif
#ifndef FR_SETTINGS_SIZE
#if defined(__AVR__)
#define FR_SETTINGS_SIZE 96  // 2 KB of RAM on an Uno: a phone number fits easily
#else
#define FR_SETTINGS_SIZE 512
#endif
#endif
#ifndef FR_LINE_MAX
#if defined(__AVR__)
#define FR_LINE_MAX 128
#else
#define FR_LINE_MAX 1024
#endif
#endif
#define FR_MAX_KEYS 8
#define FR_WAIT_MAX_MS 30000UL  // never wait longer than this for missing settings
#define FR_DATA_MAX (FR_SETTINGS_SIZE - 5)

struct FrSetting {
  const char* key;     // [a-z0-9_], as in settings.json
  const char** value;  // the sketch's constant; replaced by the value to use
  bool required;
};

// Stored settings in RAM as "key=value\0key=value\0" (only non-empty values).
static char frData[FR_DATA_MAX + 1];
static uint16_t frLen = 0;

static inline void frEepromBegin() {
#if defined(ESP32) || defined(ESP8266)
  static bool started = false;
  if (!started) EEPROM.begin(FR_SETTINGS_ADDR + FR_SETTINGS_SIZE);
  started = true;
#endif
}

static inline void frLoad() {
  frEepromBegin();
  frLen = 0;
  frData[0] = 0;
  if (EEPROM.read(FR_SETTINGS_ADDR) != 'F' || EEPROM.read(FR_SETTINGS_ADDR + 1) != 'R' || EEPROM.read(FR_SETTINGS_ADDR + 2) != 1) return;
  uint16_t n = EEPROM.read(FR_SETTINGS_ADDR + 3) | (EEPROM.read(FR_SETTINGS_ADDR + 4) << 8);
  if (n > FR_DATA_MAX) return;
  for (uint16_t i = 0; i < n; i++) {
    char c = EEPROM.read(FR_SETTINGS_ADDR + 5 + i);
    frData[i] = c == '\n' ? '\0' : c;
  }
  frData[n] = 0;
  frLen = n;
}

static inline void frPut(int addr, uint8_t b) {
#if defined(__AVR__)
  EEPROM.update(addr, b);
#else
  EEPROM.write(addr, b);
#endif
}

static inline void frSave() {
  frEepromBegin();
  frPut(FR_SETTINGS_ADDR, 'F');
  frPut(FR_SETTINGS_ADDR + 1, 'R');
  frPut(FR_SETTINGS_ADDR + 2, 1);
  frPut(FR_SETTINGS_ADDR + 3, frLen & 0xFF);
  frPut(FR_SETTINGS_ADDR + 4, frLen >> 8);
  for (uint16_t i = 0; i < frLen; i++) frPut(FR_SETTINGS_ADDR + 5 + i, frData[i] ? frData[i] : '\n');
#if defined(ESP32) || defined(ESP8266)
  EEPROM.commit();
#endif
}

// The stored value for key, or nullptr.
static inline const char* frStored(const char* key) {
  size_t kl = strlen(key);
  for (uint16_t i = 0; i < frLen; i += strlen(frData + i) + 1) {
    if (strncmp(frData + i, key, kl) == 0 && frData[i + kl] == '=') return frData + i + kl + 1;
  }
  return nullptr;
}

// Empty, "your_…" / "YOUR-…" / "your …", or a "+91XXXX…" phone number.
static inline bool frIsPlaceholder(const char* v) {
  if (!v || !*v) return true;
  if (strncasecmp(v, "your", 4) == 0 && (v[4] == '_' || v[4] == '-' || v[4] == ' ')) return true;
  return v[0] == '+' && strstr(v, "XXXX") != nullptr;
}

// The stored value for key, else the fallback constant; "" if that is a placeholder.
static inline const char* frSettingsGet(const char* key, const char* fallback) {
  const char* v = frStored(key);
  if (!v) v = fallback;
  return frIsPlaceholder(v) ? "" : v;
}

static inline bool frIsMissing(const FrSetting& s) {
  return s.required && !*frSettingsGet(s.key, *s.value);
}

static inline void frRequest(FrSetting* s, uint8_t n) {
  Serial.print(F("FR?SETTINGS v1 keys="));
  for (uint8_t i = 0; i < n; i++) {
    if (i) Serial.print(',');
    Serial.print(s[i].key);
  }
  Serial.print(F(" missing="));
  bool first = true;
  for (uint8_t i = 0; i < n; i++) {
    if (!frIsMissing(s[i])) continue;
    if (!first) Serial.print(',');
    Serial.print(s[i].key);
    first = false;
  }
  Serial.print('\n');
}

static inline void frReply(const __FlashStringHelper* what, const char* key) {
  Serial.print(what);
  Serial.print(key);
  Serial.print('\n');
}

static inline int8_t frHex(char c) {
  if (c >= '0' && c <= '9') return c - '0';
  c |= 0x20;
  return (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1;
}

// Percent-decodes in place. Drops NUL, CR and LF: they can't be stored.
static inline void frDecode(char* s) {
  char* o = s;
  for (; *s; s++) {
    char c = *s;
    if (c == '%' && frHex(s[1]) >= 0 && frHex(s[2]) >= 0) {
      c = (char)(frHex(s[1]) << 4 | frHex(s[2]));
      s += 2;
    }
    if (c != '\0' && c != '\n' && c != '\r') *o++ = c;
  }
  *o = 0;
}

// "k1=v1&k2=v2" (cut short if the line overflowed). Saves and returns true, or replies with an error.
static inline bool frSet(FrSetting* s, uint8_t n, char* p, bool overflow) {
  const char* upd[FR_MAX_KEYS] = {};
  while (*p) {
    char* amp = strchr(p, '&');
    bool last = !amp;
    if (amp) *amp = 0;
    char* eq = strchr(p, '=');
    if (eq) *eq = 0;
    if (*p) {
      if (last && overflow) {
        frReply(F("FR!ERR long "), p);
        return false;
      }
      uint8_t i = 0;
      while (i < n && strcmp(s[i].key, p) != 0) i++;
      if (i == n) {
        frReply(F("FR!ERR unknown "), p);
        return false;
      }
      char* v = eq ? eq + 1 : p + strlen(p);  // "key" alone = "key="
      frDecode(v);
      upd[i] = v;
    }
    if (last) break;
    p = amp + 1;
  }
  // Build the new block: for each key, the new value or the one already stored.
  char next[FR_DATA_MAX + 1];
  uint16_t len = 0;
  for (uint8_t i = 0; i < n; i++) {
    const char* v = upd[i] ? upd[i] : frStored(s[i].key);
    if (!v || !*v) continue;
    size_t kl = strlen(s[i].key), vl = strlen(v);
    if (len + kl + vl + 2 > FR_DATA_MAX) {
      frReply(F("FR!ERR long "), s[i].key);
      return false;
    }
    memcpy(next + len, s[i].key, kl);
    next[len + kl] = '=';
    memcpy(next + len + kl + 1, v, vl + 1);
    len += kl + vl + 2;
  }
  memcpy(frData, next, len);
  frData[len] = 0;
  frLen = len;
  frSave();
  return true;
}

// Handles one received line. Returns true once settings were saved (boot carries on).
static inline bool frCommand(FrSetting* s, uint8_t n, char* line, bool overflow) {
  if (strcmp(line, "FR!CLEAR") == 0) {
    frLen = 0;
    frData[0] = 0;
    frSave();
    Serial.print(F("FR!OK\n"));
    return false;
  }
  if (strncmp(line, "FR!SET", 6) != 0 || (line[6] != ' ' && line[6] != 0)) return false;  // not for us
  char* p = line + 6;
  while (*p == ' ') p++;
  if (*p && !frSet(s, n, p, overflow)) return false;  // empty FR!SET = carry on, store nothing
  Serial.print(F("FR!OK\n"));
  return true;
}

// Call right after Serial.begin(115200). Prints the request, waits as described at the top
// (30 s at most), then points every constant in the list at the value to use ("" = not set).
static inline void frSettingsBegin(FrSetting* s, uint8_t n) {
  frLoad();
  char line[FR_LINE_MAX];
  uint16_t len = 0;
  bool overflow = false;
  frRequest(s, n);
  unsigned long start = millis(), quietSince = start;
  while (millis() - start < FR_WAIT_MAX_MS) {
    int c = Serial.read();
    if (c >= 0) {
      quietSince = millis();
      if (c == '\r') continue;
      if (c != '\n') {
        if (len < FR_LINE_MAX - 1) line[len++] = (char)c;
        else overflow = true;
        continue;
      }
      line[len] = 0;
      bool saved = frCommand(s, n, line, overflow);
      len = 0;
      overflow = false;
      if (saved) break;
      continue;
    }
    if (millis() - quietSince < 2000) {
      delay(1);  // also keeps the ESP8266 watchdog fed
      continue;
    }
    len = 0;  // drop half a line
    overflow = false;
    bool missing = false;
    for (uint8_t i = 0; i < n; i++) missing |= frIsMissing(s[i]);
    if (!missing) break;
    frRequest(s, n);
    quietSince = millis();
  }
  for (uint8_t i = 0; i < n; i++) *s[i].value = frSettingsGet(s[i].key, *s[i].value);
}

template <size_t N>
static inline void frSettingsBegin(FrSetting (&s)[N]) {
  static_assert(N <= FR_MAX_KEYS, "fr_settings.h: at most FR_MAX_KEYS settings");
  frSettingsBegin(s, (uint8_t)N);
}

Compiled for esp32 on esp32 3.3.12 with Adafruit SSD1306 2.5.17 · 3 Nov 2026

How it works

When home FTTH internet stops, the first question from customer care is always “what lights are on the box?”. The ONT (the small fibre modem, often combined with the Wi-Fi router) has two that matter:

  • PON (also REG / FIBER): steady when the ONT is registered with the ISP’s equipment at the exchange; blinking while it tries; off when it has given up.
  • LOS (Loss Of Signal, usually red): off when light arrives through the fibre; on or blinking when it doesn’t — a cut cable, a pinched bend, a dirty connector or a fault at the splitter box.

This monitor watches both lights with LDR modules taped over them, and at the same time times a TCP connection to your router and to 1.1.1.1. Together they give a diagnosis in plain words on the OLED, with how long it has been so.

OLED says Meaning What to do
FIBRE CUT (LOS) No light reaching the ONT Call the ISP and say “LOS is red” — that gets a field technician, not a script
ONT NOT REGISTERED PON blinking Usually an ISP-side fault or a new ONT not yet activated
ISP FAULT (PON OK) Fibre fine, internet down ISP routing or PPPoE problem; a router restart sometimes helps
ROUTER NOT ANSWERING Your router or Wi-Fi Restart the router
ALL OK Everything answers —

How it works

Every 3 seconds the ESP32 samples both LDRs 40 times over 2 seconds. If a light changes state at least twice in that window it is BLINK, otherwise it’s ON or OFF by majority. The LDRs connect to D34 and D35 — ADC1 pins, which keep working while Wi-Fi is on (ADC2 pins don’t). They are input-only, which suits a sensor.

Then it opens a TCP connection to the gateway on port 53 and to 1.1.1.1 on port 53, with a 2-second timeout — the same trick as the ISP link monitor, because the ESP32 core has no ICMP ping. Each change of verdict is printed to Serial with the minutes it lasted, so you can paste the log into a complaint.

Set-up

  1. Flash it, then hold each LDR against its LED and watch the Serial Monitor’s raw numbers with the LED on and off (unplug the fibre patch cord briefly to make LOS light up — plug it back gently and never look into the fibre end).
  2. Set PON_THRESHOLD and LOS_THRESHOLD halfway between the two values.
  3. Shroud each LDR with black tape and fix it with blu-tack. Keep the OLED somewhere you’ll see it.

Power the ESP32 from the same inverter or UPS as the router, otherwise a power cut looks like a dead ONT. During a long power cut, the ISP’s own street equipment may also go down — that shows as PON off or blinking even though your fibre is fine.

Troubleshooting

LOS always shows ON

Room light is reaching the LDR, or the threshold is wrong. Cover the ONT with a cloth: if LOS turns OFF, improve the shroud; if not, look at the raw value in Serial and adjust LOS_THRESHOLD.

PON shows BLINK when it’s steady

Some ONT LEDs are driven with a fast PWM that an LDR averages, but an LDR at the edge of its threshold can still flicker. Move the threshold further from the “lit” value.

It says ROUTER NOT ANSWERING but the internet works

Your router doesn’t accept a TCP connection on port 53. Change probe(WiFi.gatewayIP(), 53) to port 80.

Upgrades

  • Add an NTP clock and log each fault with its start and end time for your complaint.
  • Send an ntfy alert over mobile data (a second phone hotspot) when LOS lights up.
  • Combine with the router reboot controller: reboot only when PON is steady but the ISP link is down.

↑↓ to move · Enter to open