Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
// ═══════════════════════════════════════════════════════════
// PatternFlow - the names a panel answers to on the LAN
//
// `patternflow.local` is the address everything points at - the console, the
// site's "send to my panel" links, the docs - and it fails for a real share
// of people, almost never because of the panel:
//
// - Android does not resolve .local in the browser at all;
// - Windows resolves it natively but gives up quietly behind a VPN, a
// virtual adapter or some Wi-Fi drivers, and then caches the failure;
// - routers with AP isolation or IGMP snooping drop the multicast;
// - two panels on one network fight over one name.
//
// The panel cannot fix the client's resolver, so it answers to more names
// instead, and tells the site its address so the site stops needing a name:
//
// 1. NetBIOS. Windows resolves `patternflow` (no .local) over NetBIOS
// without mDNS at all, so http://patternflow/ works where
// http://patternflow.local/ does not. One UDP listener on port 137.
// 2. A per-panel mDNS alias, `patternflow-<4 hex of the MAC>.local`,
// delegated beside the shared name. Two panels keep the shared name
// between them and each has one of its own; the alias is what the
// status API and the console show.
// 3. (In pf-console.js) every link from the console to patternflow.work
// carries `?device=<ip>`, and the site remembers it - so a person who
// opened the console once, by any route, never needs the name again.
//
// Re-announced on every Wi-Fi connect edge: the delegated alias carries an
// address, and the address can change with a new lease.
//
// License: MIT
// ═══════════════════════════════════════════════════════════
#pragma once
#include <Arduino.h>
#include "config.h"
#include "core_wifi.h"
#ifdef PF_WIFI_NEEDED
#include <ESPmDNS.h>
#include <NetBIOS.h>
#include <mdns.h>
#endif
namespace PatternflowNames {
#ifdef PF_WIFI_NEEDED
inline char aliasBuf[24] = "";
inline bool netbiosUp = false;
inline bool mdnsUp = false;
inline bool ready = false;
inline bool requested = true;
inline uint32_t lastAttemptMs = 0;
inline uint32_t announcedAddress = 0;
inline uint32_t announcements = 0;
constexpr uint32_t RETRY_MS = 5000;
// "patternflow-a1b2": the shared name plus the last two bytes of the STA MAC,
// which is what the panel shows on its NETWORK screen and status page.
inline const char* alias() {
if (aliasBuf[0] == 0) {
uint8_t mac[6];
WiFi.macAddress(mac);
snprintf(aliasBuf, sizeof(aliasBuf), "%s-%02x%02x", PF_OTA_HOSTNAME, mac[4], mac[5]);
}
return aliasBuf;
}
// Bootstrap before the first feature onNetwork() hook, preserving its
// existing expectation that mDNS exists when it registers a service. Only
// called before the network task is allowed to service routes/tick().
inline void begin() {
if (!mdnsUp) mdnsUp = MDNS.begin(PF_OTA_HOSTNAME);
}
// Called by the frame's connect hook. Re-announcement and retries happen
// on the network task so reconnects do not perform this work on a frame.
inline void announce() {
__atomic_store_n(&requested, true, __ATOMIC_RELEASE);
}
// Sole owner of mDNS startup and retries. ArduinoOTA used to start it once
// and the web updater assumed it worked, so a transient allocation failure
// left a working IP-only device until reboot. Keep successfully registered
// services and retry only the missing pieces; never tear down a healthy
// responder (features may have registered services of their own).
inline void tick() {
if (!PatternflowWifi::isConnected()) {
ready = false;
announcedAddress = 0;
return;
}
const uint32_t now = millis();
const uint32_t address = (uint32_t)WiFi.localIP();
const bool forced = __atomic_exchange_n(&requested, false, __ATOMIC_ACQ_REL);
// The worker can observe the new link before loop() consumes its connect
// edge. Coalesce that late request instead of removing/re-probing an alias
// we just registered for this address.
if (ready && announcedAddress == address &&
(!forced || (uint32_t)(now - lastAttemptMs) < RETRY_MS)) return;
if (!forced && (uint32_t)(now - lastAttemptMs) < RETRY_MS) return;
lastAttemptMs = now;
ready = false;
if (!mdnsUp) {
mdnsUp = MDNS.begin(PF_OTA_HOSTNAME);
if (!mdnsUp) return;
}
if (!mdns_service_exists("_http", "_tcp", nullptr) &&
mdns_service_add(nullptr, "_http", "_tcp", 80, nullptr, 0) != ESP_OK) return;
#if PF_OTA_ENABLED
if (!mdns_service_exists("_arduino", "_tcp", nullptr)) {
MDNS.enableArduino(3232, PF_OTA_PASSWORD[0] != '\0');
if (!mdns_service_exists("_arduino", "_tcp", nullptr)) return;
}
#endif
if (!netbiosUp) {
netbiosUp = NBNS.begin(PF_OTA_HOSTNAME);
Serial.printf("[NAMES] NetBIOS \"%s\" %s\n", PF_OTA_HOSTNAME, netbiosUp ? "up" : "FAILED");
}
esp_err_t rc = ESP_OK;
// NetBIOS may fail independently. Retrying it must not remove a working
// mDNS alias and force another probe every five seconds.
if (announcedAddress != address) {
mdns_ip_addr_t addr;
memset(&addr, 0, sizeof(addr));
addr.addr.type = ESP_IPADDR_TYPE_V4;
addr.addr.u_addr.ip4.addr = address;
addr.next = nullptr;
mdns_delegate_hostname_remove(alias()); // harmless when absent
rc = mdns_delegate_hostname_add(alias(), &addr);
if (rc == ESP_OK) announcedAddress = address;
}
ready = rc == ESP_OK && netbiosUp;
if (ready) announcements++;
Serial.printf("[NAMES] mDNS %s.local + %s.local (%s)\n", PF_OTA_HOSTNAME, alias(),
rc == ESP_OK ? "ok" : esp_err_to_name(rc));
}
#else
inline const char* alias() { return PF_OTA_HOSTNAME; }
inline void begin() {}
inline void announce() {}
inline void tick() {}
#endif
} // namespace PatternflowNames
07e931b46This file is pinned to the selected release. Download the original to open it in your design tool.