WriteWatch v2
Real-time video transfer monitor for macOS with integrated tally light controller — SwiftUI + ESP32-S3 firmware
WriteWatch — When the "Record" Light Talks to the Network
#W5500 #ESP32S3 #TCP_TOE #mDNS #Broadcast #TallyLight #SmallHD #SwiftUI #DIT #VideoProduction
📚 Context: Professional video-production / on-set tooling — built and shipped by 32Thirteen Productions LLC as a real working DIT (Digital Imaging Technician) utility. 🔧 Verification status: Firmware (
tally_light.ino, v5.8) and macOS app are public, versioned (15 commits, 6 releases), and the README documents hardware-verified behavior on a Waveshare ESP32-S3-ETH unit driving SmallHD OLED 22 monitors running PageOS 6.3.1.
01 — What is this project?
On a film or broadcast set, a tally light is the small red lamp that tells everyone "this camera is live, this monitor is hot — don't walk through the shot." Traditionally, tally is wired through expensive, proprietary switcher hardware, and getting a SmallHD monitor's GPI (General Purpose Input) port to react to a recording event usually means buying into a closed ecosystem.
WriteWatch flips that around with two cooperating pieces:
- A macOS app (SwiftUI + AppKit + AVFoundation) that watches capture folders in real time. It polls every 500 ms, detects video files that are actively growing (even over SMB/NFS network shares), tracks write rate and elapsed time, and validates the finished clip with AVFoundation (codec, duration, VFR detection).
- An ESP32-S3 + W5500 tally bridge that turns those "recording started / stopped" events into a physical tally signal. The app discovers bridge units on the LAN, and its "Follow WriteWatch" automation fires the tally lights the moment an active write is detected — and clears them when recording stops.
The result is a fully open, self-hostable tally system: when the camera rolls and bytes start hitting the disk, the red light comes on across every monitor — no proprietary switcher required.
02 — Why network-driven tally over Ethernet?
🔷 A set is a hostile RF environment
Film sets are crowded with wireless: video transmitters, comms, lighting control, monitoring. Wi-Fi alone is fragile here. A wired Ethernet tally bridge gives deterministic, interference-free signaling for the one thing that must never flicker — the "we are live" indicator. The firmware keeps Wi-Fi as a fallback only, with Ethernet as the primary path.
🔷 HTTP is the universal integration language
The bridge exposes a tiny HTTP API (/tally/on, /tally/off, /tally/test, /status). That means it drops straight into Bitfocus Companion (the de-facto standard for broadcast button-box control) via a generic HTTP GET action — no custom driver, no SDK. Any HTTP client on the network can trigger tally.
🔷 WIZnet ecosystem fit
For this class of device — a small embedded controller that must hold a rock-solid TCP server on a noisy LAN — the W5500's hardware TCP/IP stack is a far better fit than a software stack like LwIP on a bare MAC+PHY (e.g. an internal EMAC + LAN8720). It offloads the connection handling to silicon, freeing the ESP32-S3 to do GPIO timing and serve the config portal without contention.
03 — System architecture
┌──────────────────────────┐
│ macOS App (WriteWatch) │
│ SwiftUI + AVFoundation │
│ │
│ FolderWatcher (500ms) │ detects active video writes
│ │ │ (SMB / NFS / local)
│ ▼ │
│ TallyStore ──── HTTP GET ─┼────────────┐
│ Bonjour _tally._tcp scan │ │
└──────────────────────────┘ │ LAN (wired Ethernet primary)
▼
┌─────────────────────────────────┐
│ ESP32-S3 Tally Bridge │
│ Waveshare ESP32-S3-ETH │
│ │
│ ┌──────────┐ SPI ┌───────┐ │
│ │ ESP32-S3 │◄───────►│ W5500 │◄┼──── RJ45 ─── LAN
│ │ │ (MOSI11│ (HW │ │
│ │ WebServer│ MISO12│ TCP/IP)│ │
│ │ :80 TCP │ SCK13 └───────┘ │
│ │ mDNS UDP│ CS14) │
│ └────┬─────┘ │
│ │ GPIO18 │
│ ▼ │
│ PC817 Optocoupler ──► SmallHD │
│ (galvanic isolation) GPI Pin7│
└─────────────────────────────────┘04 — Why W5500? ⭐
🔷 Hardware TCP offload on a timing-sensitive device
The bridge's whole job is to be a reliable always-on HTTP endpoint while also toggling a GPIO with predictable timing. The W5500 carries its own hardware TCP/IP stack with 8 independent hardware sockets, so the ESP32-S3 never has to spend cycles reassembling segments or managing retransmits. The MCU handles the HTTP route logic and GPIO; the W5500 handles the wire.
🔷 Usage mode: TCP server (TOE) + mDNS over UDP
This project uses the W5500 in TCP (TOE) socket mode — it runs an HTTP WebServer listening on port 80 (/tally/on, /tally/off, /status, etc.). Alongside it, mDNS service advertisement (MDNS.addService("tally", "tcp", 80)) uses the UDP path so the macOS app's Bonjour scanner (_tally._tcp) can auto-discover units on the LAN. So the chip is exercised across both of its core socket modes: TCP for control, UDP for multicast discovery.
Note on integration: this firmware drives the W5500 through the ESP-IDF / Arduino-ESP32
ETHdriver (ETH.begin(ETH_PHY_W5500, ...)), where the W5500 is registered as an SPI-attached Ethernet interface and the LwIP stack rides on top — rather than through WIZnet's own ioLibrary with native socket APIs. The physical networking is W5500; the socket abstraction is the ESP-IDF layer. This is a common and fully legitimate way W5500 ships in ESP32-S3 designs (the Waveshare ESP32-S3-ETH board is built around exactly this pattern).
🔷 Why W5500 over the alternatives here
- vs. Wi-Fi-only: unacceptable on a set full of RF; W5500 gives a wired primary path.
- vs. internal EMAC + external PHY (LAN8720): the ESP32-S3 has no built-in EMAC (unlike the original ESP32), so an external MAC+PHY like the W5500 over SPI is the natural Ethernet solution for the S3 family.
- vs. ENC28J60: the W5500 brings a full hardware TCP/IP stack and 8 sockets versus the ENC28J60's raw-MAC-only design, which would force a software stack and more MCU load — exactly what you don't want on a real-time tally device.
🔷 Verified evidence ✅
- Explicit W5500 pin map in the README (RST GPIO9, INT GPIO10, MOSI GPIO11, MISO GPIO12, CLK GPIO13, CS GPIO14).
ETH.begin(ETH_PHY_W5500, ETH_PHY_ADDR, ETH_CS_PIN, ETH_INT_PIN, ETH_RST_PIN, SPI2_HOST, ...)insetup().- Network event handling for
ARDUINO_EVENT_ETH_GOT_IPconfirming live DHCP/static lease over Ethernet. - README documents hardware-verified GPI behavior on SmallHD OLED 22 (PageOS 6.3.1), Active High, Pin 7.
05 — Key components
🌐 WIZnet W5500 — TCP (TOE) server + UDP mDNS, SPI-attached
The networking heart of the bridge. Connected to the ESP32-S3 over SPI2 (HSPI host). Provides the wired Ethernet link that carries both the HTTP control API and the Bonjour discovery service.
📟 Waveshare ESP32-S3-ETH
The carrier board pairing the ESP32-S3 with the W5500 — chosen because the ESP32-S3 lacks an internal Ethernet MAC, making an SPI Ethernet controller the standard route.
💡 PC817 Optocoupler (NOYITO 1-channel)
Galvanically isolates the ESP32's GPIO18 tally output from the SmallHD monitor's GPI port — protecting both devices and matching the monitor's open-circuit-ON / closure-OFF logic.
🖥️ macOS App (SwiftUI + AVFoundation)
Folder-watching, write-rate tracking, AVFoundation media validation, Bonjour discovery, and the "Follow WriteWatch" tally automation. Pure SwiftUI + AppKit + AVFoundation, no external dependencies.
📺 SmallHD OLED 22 monitor (GPI)
The physical tally target. GPI configured as Tally Indicator, Active High, Pin 7, on PageOS 6.x.
06 — Application scenarios
01. Multi-camera production sets — Gang mode fires every tally bridge at once, or "Follow WriteWatch" automation lights monitors automatically the instant recording is detected, so talent and crew always know which cameras are hot.
02. DIT / data-wrangling carts — The original problem this solves: a DIT watching offload/capture folders gets a live, physical signal that ingest is actively writing, with disk-space warnings per volume.
03. Broadcast control rooms with Bitfocus Companion — Drop the HTTP endpoints onto Companion buttons for manual record-on/record-off tally control alongside the rest of a Stream Deck workflow.
04. Generic GPI signaling — Because the bridge is "just" an HTTP-to-GPI optocoupler with isolation, it generalizes to any device with a GPI trigger input, not only SmallHD monitors.
Conclusion
WriteWatch turns an invisible software event — "bytes are landing on disk" — into a hardware-reliable, network-distributed red light, using the W5500 to hold a rock-solid wired control plane on the most RF-hostile environment there is: a film set.
- ✅ Real, shipped DIT tool from a working production company (32Thirteen Productions LLC)
- ✅ W5500 used in both TCP (HTTP server) and UDP (mDNS discovery) modes
- ✅ Wired-Ethernet-primary design with Wi-Fi fallback — correct engineering for a set
- ✅ Standard HTTP API → instant Bitfocus Companion integration, no custom driver
- ✅ Galvanically isolated GPI output via PC817 optocoupler
- ✅ Browser-based config portal — flash once, configure per-unit via web, no per-unit recompiling
- ✅ Hardware-verified against real SmallHD OLED 22 monitors (PageOS 6.3.1)
- ✅ Clean, dependency-free codebase on both the macOS and firmware sides
Q&A
Q. Which W5500 socket mode does the tally bridge actually use? A. Both core modes. The HTTP control server (port 80) runs over TCP, and mDNS service discovery (_tally._tcp) runs over UDP multicast. Control = TCP, discovery = UDP.
Q. Why not just use the ESP32's built-in Ethernet? A. The ESP32-S3 has no internal Ethernet MAC — that was a feature of the original ESP32, not the S3. So an external SPI Ethernet controller like the W5500 is the standard and necessary way to give the S3 wired networking.
Q. Why Ethernet at all when there's Wi-Fi fallback? A. Film sets are saturated with wireless traffic. Tally is safety-and-coordination-critical, so the wired W5500 path is primary and Wi-Fi only steps in if the cable is unplugged.
Q. Is the W5500 driven through WIZnet's ioLibrary? A. No — it's driven through the ESP-IDF / Arduino-ESP32 ETH driver (ETH_PHY_W5500), which registers the W5500 as an SPI Ethernet interface under the LwIP stack. The hardware is W5500; the socket API is the ESP-IDF layer.
WriteWatch — '녹화' 표시등이 네트워크와 대화할 때
#W5500 #ESP32S3 #TCP_TOE #mDNS #Broadcast #TallyLight #SmallHD #SwiftUI #DIT #VideoProduction
📚 컨텍스트: 전문 영상 제작 / 현장 툴링 — 32Thirteen Productions LLC가 실제 DIT(Digital Imaging Technician) 업무용으로 제작·배포한 작동하는 도구. 🔧 검증 상태: 펌웨어(
tally_light.ino, v5.8)와 macOS 앱이 공개·버전 관리(15 커밋, 6 릴리스)되어 있으며, README는 PageOS 6.3.1을 구동하는 SmallHD OLED 22 모니터를 제어하는 Waveshare ESP32-S3-ETH 유닛에서의 하드웨어 검증 동작을 문서화하고 있다.
01 — 이 프로젝트는 무엇인가?
영화·방송 현장에서 탤리 라이트(tally light) 는 "이 카메라가 라이브다, 이 모니터가 작동 중이다 — 샷 안으로 들어오지 마라"를 모두에게 알리는 작은 빨간 등이다. 전통적으로 탤리는 값비싼 독점 스위처 하드웨어를 거쳐 배선되며, SmallHD 모니터의 GPI(General Purpose Input) 포트를 녹화 이벤트에 반응시키려면 보통 폐쇄적인 생태계에 종속되어야 한다.
WriteWatch는 두 개의 협력하는 구성 요소로 이를 뒤집는다.
- macOS 앱 (SwiftUI + AppKit + AVFoundation) — 캡처 폴더를 실시간으로 감시한다. 500ms마다 폴링하여 실제로 커지고 있는 비디오 파일을 (SMB/NFS 네트워크 공유에서도) 감지하고, 쓰기 속도와 경과 시간을 추적하며, 완료된 클립을 AVFoundation으로 검증한다(코덱, 길이, VFR 감지).
- ESP32-S3 + W5500 탤리 브리지 — 이 "녹화 시작/정지" 이벤트를 물리적 탤리 신호로 변환한다. 앱은 LAN 상의 브리지 유닛을 자동 발견하고, "Follow WriteWatch" 자동화가 활성 쓰기가 감지되는 순간 탤리 라이트를 켜고 — 녹화가 멈추면 끈다.
결과적으로 완전히 개방적이고 자체 호스팅 가능한 탤리 시스템이 된다. 카메라가 돌고 바이트가 디스크에 떨어지기 시작하면 모든 모니터에서 빨간 불이 켜진다 — 독점 스위처 없이.
02 — 왜 이더넷 기반 네트워크 탤리인가?
🔷 현장은 가혹한 RF 환경이다
영화 현장은 무선 트래픽으로 붐빈다 — 영상 송신기, 통신, 조명 제어, 모니터링. 여기서 Wi-Fi 단독은 취약하다. 유선 이더넷 탤리 브리지는 절대 깜빡여서는 안 되는 단 하나의 신호인 "우리는 라이브다" 표시등에 결정론적이고 간섭 없는 시그널링을 제공한다. 펌웨어는 Wi-Fi를 폴백 전용으로 두고 이더넷을 주 경로로 삼는다.
🔷 HTTP는 범용 통합 언어다
브리지는 아주 작은 HTTP API(/tally/on, /tally/off, /tally/test, /status)를 노출한다. 덕분에 방송용 버튼박스 제어의 사실상 표준인 Bitfocus Companion에 일반 HTTP GET 액션으로 그대로 연결된다 — 커스텀 드라이버도, SDK도 필요 없다. 네트워크상의 어떤 HTTP 클라이언트든 탤리를 트리거할 수 있다.
🔷 WIZnet 생태계 적합성
시끄러운 LAN에서 견고한 TCP 서버를 유지해야 하는 소형 임베디드 컨트롤러라는 이 부류의 장치에는, 베어 MAC+PHY(예: 내장 EMAC + LAN8720) 위의 소프트웨어 스택(LwIP)보다 W5500의 하드웨어 TCP/IP 스택이 훨씬 잘 맞는다. 연결 처리를 실리콘에 오프로드하여 ESP32-S3가 경합 없이 GPIO 타이밍을 다루고 설정 포털을 서빙할 수 있게 한다.
03 — 시스템 아키텍처
┌──────────────────────────┐
│ macOS 앱 (WriteWatch) │
│ SwiftUI + AVFoundation │
│ │
│ FolderWatcher (500ms) │ 활성 비디오 쓰기 감지
│ │ │ (SMB / NFS / 로컬)
│ ▼ │
│ TallyStore ──── HTTP GET ─┼────────────┐
│ Bonjour _tally._tcp 스캔 │ │
└──────────────────────────┘ │ LAN (유선 이더넷 우선)
▼
┌─────────────────────────────────┐
│ ESP32-S3 탤리 브리지 │
│ Waveshare ESP32-S3-ETH │
│ │
│ ┌──────────┐ SPI ┌───────┐ │
│ │ ESP32-S3 │◄───────►│ W5500 │◄┼──── RJ45 ─── LAN
│ │ │ (MOSI11│ (HW │ │
│ │ WebServer│ MISO12│ TCP/IP)│ │
│ │ :80 TCP │ SCK13 └───────┘ │
│ │ mDNS UDP│ CS14) │
│ └────┬─────┘ │
│ │ GPIO18 │
│ ▼ │
│ PC817 옵토커플러 ──► SmallHD │
│ (절연) GPI 7번 │
└─────────────────────────────────┘04 — 왜 W5500인가? ⭐
🔷 타이밍 민감 장치에서의 하드웨어 TCP 오프로드
브리지의 핵심 임무는 GPIO를 예측 가능한 타이밍으로 토글하면서 동시에 항상 켜져 있는 안정적인 HTTP 엔드포인트가 되는 것이다. W5500은 8개의 독립 하드웨어 소켓을 갖춘 자체 하드웨어 TCP/IP 스택을 탑재하므로, ESP32-S3는 세그먼트 재조립이나 재전송 관리에 사이클을 쓸 필요가 없다. MCU는 HTTP 라우트 로직과 GPIO를, W5500은 물리 회선을 담당한다.
🔷 사용 모드: TCP 서버 (TOE) + UDP 기반 mDNS
이 프로젝트는 W5500을 TCP(TOE) 소켓 모드로 사용한다 — 포트 80에서 HTTP WebServer를 리스닝한다(/tally/on, /tally/off, /status 등). 그와 함께 mDNS 서비스 광고(MDNS.addService("tally", "tcp", 80))는 UDP 경로를 사용해 macOS 앱의 Bonjour 스캐너(_tally._tcp)가 LAN상의 유닛을 자동 발견하게 한다. 즉 칩이 두 가지 핵심 소켓 모드에 걸쳐 활용된다 — 제어는 TCP, 발견은 UDP.
통합 방식에 대한 참고: 이 펌웨어는 W5500을 WIZnet 자체 ioLibrary의 네이티브 소켓 API가 아니라 ESP-IDF / Arduino-ESP32
ETH드라이버(ETH.begin(ETH_PHY_W5500, ...))를 통해 구동한다. W5500이 SPI 연결 이더넷 인터페이스로 등록되고 그 위에 LwIP 스택이 올라가는 구조다. 물리적 네트워킹은 W5500이고, 소켓 추상화는 ESP-IDF 레이어다. 이는 ESP32-S3 설계에서 W5500이 사용되는 흔하고 완전히 정당한 방식이다(Waveshare ESP32-S3-ETH 보드 자체가 정확히 이 패턴으로 설계됨).
🔷 대안 대비 W5500의 우위
- vs. Wi-Fi 단독: RF로 가득 찬 현장에서는 부적합 — W5500이 유선 주 경로를 제공한다.
- vs. 내장 EMAC + 외부 PHY(LAN8720): ESP32-S3는 (오리지널 ESP32와 달리) 내장 EMAC이 없다. 따라서 SPI 연결 W5500 같은 외부 MAC+PHY가 S3 계열에 자연스러운 이더넷 해법이다.
- vs. ENC28J60: W5500은 완전한 하드웨어 TCP/IP 스택과 8개 소켓을 제공하는 반면, ENC28J60은 로우 MAC 전용이라 소프트웨어 스택과 더 많은 MCU 부하를 강요한다 — 실시간 탤리 장치에서 가장 피하고 싶은 상황이다.
🔷 검증된 증거 ✅
- README의 명시적 W5500 핀 맵(RST GPIO9, INT GPIO10, MOSI GPIO11, MISO GPIO12, CLK GPIO13, CS GPIO14).
setup()내ETH.begin(ETH_PHY_W5500, ETH_PHY_ADDR, ETH_CS_PIN, ETH_INT_PIN, ETH_RST_PIN, SPI2_HOST, ...).- 이더넷을 통한 실제 DHCP/정적 임대를 확인하는
ARDUINO_EVENT_ETH_GOT_IP네트워크 이벤트 처리. - SmallHD OLED 22(PageOS 6.3.1)에서의 하드웨어 검증된 GPI 동작(Active High, 7번 핀) 문서화.
05 — 핵심 구성 요소
🌐 WIZnet W5500 — TCP(TOE) 서버 + UDP mDNS, SPI 연결
브리지의 네트워킹 심장. SPI2(HSPI 호스트)로 ESP32-S3에 연결된다. HTTP 제어 API와 Bonjour 발견 서비스 양쪽을 운반하는 유선 이더넷 링크를 제공한다.
📟 Waveshare ESP32-S3-ETH
ESP32-S3와 W5500을 짝지은 캐리어 보드 — ESP32-S3에 내장 이더넷 MAC이 없어 SPI 이더넷 컨트롤러가 표준 경로가 되기 때문에 선택됨.
💡 PC817 옵토커플러 (NOYITO 1채널)
ESP32의 GPIO18 탤리 출력을 SmallHD 모니터의 GPI 포트로부터 절연(galvanic isolation)한다 — 양 장치를 보호하고 모니터의 개방=ON / 단락=OFF 로직에 맞춘다.
🖥️ macOS 앱 (SwiftUI + AVFoundation)
폴더 감시, 쓰기 속도 추적, AVFoundation 미디어 검증, Bonjour 발견, "Follow WriteWatch" 탤리 자동화. 외부 의존성 없는 순수 SwiftUI + AppKit + AVFoundation.
📺 SmallHD OLED 22 모니터 (GPI)
물리적 탤리 대상. GPI를 Tally Indicator, Active High, 7번 핀으로 PageOS 6.x에서 구성.
06 — 응용 시나리오
01. 멀티카메라 제작 현장 — Gang 모드로 모든 탤리 브리지를 한 번에 점화하거나, "Follow WriteWatch" 자동화로 녹화가 감지되는 즉시 모니터를 자동 점등 — 출연자와 스태프가 어느 카메라가 작동 중인지 항상 알 수 있다.
02. DIT / 데이터 랭글링 카트 — 이 프로젝트가 원래 푸는 문제. 오프로드/캡처 폴더를 감시하는 DIT가 인제스트가 실제로 쓰기 중임을 알리는 물리적 신호를 받고, 볼륨별 디스크 공간 경고도 함께 받는다.
03. Bitfocus Companion 기반 방송 부조정실 — HTTP 엔드포인트를 Companion 버튼에 매핑하여 Stream Deck 워크플로우와 함께 수동 녹화 on/off 탤리 제어를 한다.
04. 범용 GPI 시그널링 — 브리지가 본질적으로 "절연된 HTTP-to-GPI 옵토커플러"이므로, SmallHD 모니터뿐 아니라 GPI 트리거 입력을 가진 어떤 장치에도 일반화된다.
결론
WriteWatch는 눈에 보이지 않는 소프트웨어 이벤트 — "바이트가 디스크에 떨어지고 있다" — 를 하드웨어처럼 신뢰할 수 있는, 네트워크로 분산된 빨간 불로 변환한다. 가장 RF가 가혹한 환경인 영화 현장에서 W5500을 사용해 견고한 유선 제어 평면을 유지하면서.
- ✅ 실제로 운영되는 제작사(32Thirteen Productions LLC)가 배포한 실전 DIT 도구
- ✅ W5500을 TCP(HTTP 서버)와 UDP(mDNS 발견) 양쪽 모드로 사용
- ✅ Wi-Fi 폴백을 둔 유선 이더넷 우선 설계 — 현장에 맞는 올바른 엔지니어링
- ✅ 표준 HTTP API → 커스텀 드라이버 없이 즉각적인 Bitfocus Companion 통합
- ✅ PC817 옵토커플러를 통한 절연된 GPI 출력
- ✅ 브라우저 기반 설정 포털 — 한 번만 플래시하고 유닛별로 웹에서 설정, 유닛별 재컴파일 불필요
- ✅ 실제 SmallHD OLED 22 모니터(PageOS 6.3.1)에서 하드웨어 검증됨
- ✅ macOS와 펌웨어 양쪽 모두 깔끔하고 의존성 없는 코드베이스
Q&A
Q. 탤리 브리지는 W5500의 어떤 소켓 모드를 실제로 사용하나? A. 두 핵심 모드 모두. HTTP 제어 서버(포트 80)는 TCP로, mDNS 서비스 발견(_tally._tcp)은 UDP 멀티캐스트로 동작한다. 제어 = TCP, 발견 = UDP.
Q. 그냥 ESP32 내장 이더넷을 쓰면 안 되나? A. ESP32-S3에는 내장 이더넷 MAC이 없다 — 그건 오리지널 ESP32의 기능이지 S3의 기능이 아니다. 그래서 W5500 같은 외부 SPI 이더넷 컨트롤러가 S3에 유선 네트워킹을 부여하는 표준적이고 필수적인 방법이다.
Q. Wi-Fi 폴백이 있는데 왜 굳이 이더넷인가? A. 영화 현장은 무선 트래픽으로 포화되어 있다. 탤리는 안전·협업에 치명적이므로 유선 W5500 경로가 주 경로이고, Wi-Fi는 케이블이 빠졌을 때만 개입한다.
Q. W5500을 WIZnet ioLibrary로 구동하나? A. 아니다 — ESP-IDF / Arduino-ESP32 ETH 드라이버(ETH_PHY_W5500)로 구동하며, W5500을 LwIP 스택 아래의 SPI 이더넷 인터페이스로 등록한다. 하드웨어는 W5500, 소켓 API는 ESP-IDF 레이어다.
