Skip to content

Screen Time

Screen Time — a holographic parental control dashboard with glowing shield protecting sleeping orbs, network lines connecting to gaming consoles and TVs with teal status indicators

Every parental control app on the market assumes you can install something on the device you want to control. That assumption is wrong roughly four times: the PS5, the Apple TV, the grandmother’s iPad, and whatever Android tablet your kid’s friend brought over for a sleepover. MDM profiles are invasive, device-specific, and one factory reset away from irrelevant.

Screen Time takes a different approach. It runs inside Force Flow on port 4077 and enforces curfews at the network level via Firewalla. No agent on the device. No MDM profile. No app to uninstall. If it has a MAC address and it’s on your WiFi, it follows the rules of the haus.

The key insight that makes this work: the network is the only authority that can’t be uninstalled. Every device in the haus routes through Firewalla. If a MAC address exists on this subnet, Firewalla can pause it, block specific services on it, or cut it off entirely. No app to delete. No profile to remove. No clever workaround that doesn’t involve physically leaving the building.

Three enforcement tiers as concentric network rings — phone presence in the center drives blocking decisions for gaming devices and screens

Three tiers, because not every screen is the same screen.

Not blocked. Apple’s built-in Screen Time handles Albert’s iPhone and iPad. Doubling up with network-level blocking would create conflicts and confuse the device-level reporting that Andreanne actually checks. Leave Apple to Apple.

Hard curfew. The VRVANA-PC and any auto-discovered consoles (PS5, Switch, Xbox, Steam Deck) block at Albert’s curfew time and unblock at 10:00. No phone-presence heuristics, no “is he awake?” guessing. Curfew means curfew. Parents who want to use the PS5 after hours can manually unblock via the API, PWA, or by asking an agent.

Previously this tier used phone-presence detection (phone on WiFi = child awake), but it wasn’t reliable enough. A phone charging in the bedroom with WiFi on would keep the PS5 blocked all night. A phone in airplane mode would unblock everything immediately. Hard curfew is simpler and more predictable.

Hard curfew with service-level precision. Apple TVs and the VRVANA-PC get a fixed cutoff at 22:00 (10 PM), unblock at 10:00. The First Floor Apple TV doubles as the HomeKit hub, so it uses service-level blocking: Netflix, YouTube, Disney+, Crave, Crunchyroll, and 10 other streaming apps get blocked. Bell Fibe TV and HomeKit traffic are preserved. The haus doesn’t stop being smart just because it’s bedtime.

Three schedule tiers, resolved in priority order by _get_effective_schedule():

PriorityScheduleCurfewWakeSource
1 (highest)Quebec school holiday23:0010:00holidays.yaml
2Weekend (Fri/Sat night)23:0010:00Day-of-week check
3 (default)Weekday21:3010:00Default config

Holiday detection uses a curated holidays.yaml with Quebec school calendar dates — semaine de relache, Noel, summer break. The file is maintained manually because Quebec’s school calendar is published as a PDF, which is exactly the kind of government decision this system was built to route around.

Curfew isn’t a cliff — it’s a slope. Three phases of progressive service blocking:

OffsetAction
-30 minSocial media blocked (TikTok, Instagram, Snapchat, Discord, Reddit, Twitter, Facebook)
-15 minGaming blocked (Steam, Epic, Roblox, Minecraft, Fortnite)
0 minEverything blocked (full curfew)

Wind-down blocks are explicitly cleaned up at wake time. The system tracks which MACs had services blocked during wind-down and sends batch-unblock commands when daytime resumes. No orphaned DNS rules surviving into the next day.

The service_categories config in devices.yaml defines which domains belong to which category. Adding a new service is one YAML entry, not a code change.

POST /screen/homework activates a focused mode: gaming, social media, and streaming are blocked. Educational sites, Google Workspace, and reference tools remain open. Triggered manually via PWA, Siri shortcut, or Home Assistant. Deactivates automatically after 2 hours or via DELETE /screen/homework.

Albert can earn screen time extensions by doing real things in the real world:

PresetDurationDescription
chores30 minHaus tasks verified by parent
devoirs30 minHomework completion
lecture30 minReading (physical book, not a screen)
exercise30 minPhysical activity

Credits are capped at 90 minutes per day. They accumulate via POST /screen/credit and are consumed via POST /screen/credit/redeem. Unused credits do not roll over. This is a parental control system, not a loyalty program.

New devices on the network are auto-discovered via ARP scanning and classified by OUI (manufacturer) lookup. Unknown devices get haus rules applied immediately. Parents approve or remove guests via the PWA. Guest permissions expire after 24 hours.

MAC randomization detection: if a device with a locally-administered MAC bit appears and no known device has disconnected, it’s flagged as a potential randomized address. Not bulletproof, but it catches the obvious cases.

Every Sunday at 6 PM, a digest is sent via Signal and push notification. Contents: average bedtime for the week, number of overrides, credits earned and redeemed, any guest device activity. It’s the parental equivalent of a sprint retrospective, except the sprint is “did the 12-year-old go to bed on time.”

The system enforces curfews through Firewalla’s API, but APIs fail. The Firewalla Purple’s policy:delete endpoint has a known firmware bug where it returns “invalid policy” for every PID. If you trust the API alone, devices get blocked at night and never unblocked in the morning.

Three layers of defense:

Every block and unblock is recorded in a blocked_state SQLite table. On startup, Force Flow restores in-memory state from the DB. A restart at 3 AM no longer means the system “forgets” what’s blocked and skips the morning unblock.

The Firewalla bridge wraps every policy:delete call with an SSH fallback. If the API fails, the bridge SSHes directly into the Firewalla box and removes the iptables rules by their comment tag. Same for policy:create — if the MAC-level block rule can’t be created via API (stale policy ID), the bridge adds DROP rules via SSH. Batch operations use a single SSH call for all PIDs.

Every enforcement tick checks: if it’s daytime and a device should be unblocked, send the unblock command even if the in-memory state says “not blocked.” This fires once per day per device. It’s idempotent — a redundant unblock on an already-unblocked device is a no-op. But if the state was lost (restart, crash, Firewalla reboot), the safety net catches it within 30 seconds of the next enforcement cycle.

GET /export on the bridge (port 1984) dumps the full Firewalla configuration: all hosts, active policies, custom DNS records, and service domain mappings. Combined with the firewalla-export.sh and firewalla-import.sh scripts, the entire enforcement state can be backed up and restored on a new Firewalla box (hello, Gold Pro) without reconfiguring anything by hand.

Screen Time exposes four interfaces because every family member interacts with the haus differently.

Progressive Web App served directly from Force Flow. iOS standalone mode (Add to Home Screen), dark OLED theme, French UI. All features accessible: override, homework mode, credits, guest management, schedule view. Resolves via local DNS — no Tailscale required when home.

Full integration in packages/screen_time.yaml:

  • REST commands for all actions (override, homework, credit)
  • Sensors for curfew state, active blocks, phone presence
  • Lovelace card with current status, quick actions, schedule display
  • Automations for wind-down triggers and Sonos announcements
ShortcutAction
”Mode devoirs”Activate homework mode
”Fin devoirs”Deactivate homework mode
”Credit corvees”Grant chores credit
”Credit lecture”Grant reading credit
”Prolonger 30 min”30-minute curfew extension
”Couvre-feu maintenant”Immediate full curfew

All shortcuts route through Tailscale to http://100.0.0.25:4077. French invocations. Works from any family member’s iPhone via “Hey Siri.”

Native React component in the-holocron Electron app. Glass-morphism card with Framer Motion transitions. Moon icon in the nav bar. Displays: curfew countdown ring, phone presence indicator, quick action buttons, weekly schedule, per-screen status. Designed to match the Holocron’s existing dark aesthetic without looking like a corporate parental control dashboard.

All endpoints on port 4077 under the /screen prefix.

MethodPathDescription
GET/screen/statusCurrent curfew state, active blocks, phone presence
POST/screen/overrideTemporary curfew override (duration in minutes)
POST/screen/blockManually block a device by MAC
POST/screen/unblockManually unblock a device by MAC
GET/screen/reportUsage report (daily/weekly/custom range)
GET/screen/devicesAll known devices and their current state
POST/screen/assignAssign a device to a family member
POST/screen/reloadReload config from devices.yaml
GET/screen/scheduleCurrent effective schedule and next transition
POST/screen/homeworkActivate homework mode
GET/screen/homework/statusHomework mode state and remaining time
DELETE/screen/homeworkDeactivate homework mode
POST/screen/creditGrant earned credit (preset + optional note)
GET/screen/creditsCredit balance and history for today
POST/screen/credit/redeemRedeem accumulated credits
GET/screen/digestGenerate digest on demand (also sent weekly)
GET/screen/guestsList detected guest devices
POST/screen/guest/approveApprove a guest device
DELETE/screen/guest/removeRemove a guest device
GET/pwa/PWA index
GET/pwa/{filename}PWA static assets
GET/pwa/manifest.jsonPWA manifest
GET/pwa/sw.jsPWA service worker

The entire system is driven by ~/.sanctum/screen-time/devices.yaml:

family:
- name: Albert
role: child
phone_mac: "FA:CE:DE:CA:CA:01" # Presence detection anchor
curfew_enabled: true
- name: Bert
role: parent
phone_mac: "FA:CE:DE:CA:CA:02"
- name: Andreanne
role: parent
phone_mac: "FA:CE:DE:CA:CA:03"
- name: Lise Phenix
role: grandparent
phone_mac: "FA:CE:DE:CA:CA:04"
curfew_enabled: false # Grand-maman goes to bed when she wants
shared_devices:
- name: PS5
mac: "FA:CE:DE:CA:CA:10"
tier: gaming # Presence-based blocking
bound_to: Albert
- name: VRVANA-PC
mac: "FA:CE:DE:CA:CA:11"
tier: gaming
bound_to: Albert
screens:
- name: Living Room Apple TV
mac: "FA:CE:DE:CA:CA:20"
tier: screen
is_homekit_hub: true # Service-mode: block streaming, keep HomeKit
hard_curfew: "22:00"
service_categories:
social:
- tiktok.com
- instagram.com
- snapchat.com
- youtube.com
gaming:
- store.steampowered.com
- playstation.net
- xboxlive.com
- epicgames.com
streaming:
- netflix.com
- disneyplus.com
- primevideo.com
credits:
presets:
chores: 30
devoirs: 30
lecture: 30
exercise: 30
daily_cap_minutes: 90
rollover: false
guest_rules:
auto_apply_haus_rules: true
require_parent_approval: true
expiry_hours: 24
api_token: "redacted-but-you-get-the-idea"

Two blocking modes exist and they fail differently:

  • Soft pause (service-level): DNS block rules per domain, scoped to the device MAC. Check that the domain list in service_categories is current. Some services use CDN domains that rotate. nslookup from the device to verify what it’s resolving.
  • Hard block (MAC-level): ACL=false plus iptables DROP rules. If ACL-only is set (no iptables rules), existing connections survive — the device stays online until it tries to open something new. Check the bridge logs for “[pause] MAC block rule API failed” — this means the API couldn’t create the rule and the SSH fallback should have kicked in.

”Device didn’t unblock in the morning”

Section titled “”Device didn’t unblock in the morning””

Check in this order:

  1. Force Flow running? lsof -i :4077 on manoir. If it restarted, the blocked_state DB should have restored state. Check logs for “RESTORED blocked state.”
  2. Bridge running? lsof -i :1984. The bridge handles the actual Firewalla API calls and SSH fallback.
  3. Stale iptables rules? SSH to Firewalla and check: sudo iptables -S | grep <MAC>. If rules exist but Force Flow thinks the device is unblocked, the safety net should catch it within 30 seconds. If not, manually unblock via POST /screen/unblock.
  4. Stale DNS rules? POST /host/:mac/unrules on the bridge (port 1984) batch-removes all DNS blocks for a MAC with SSH fallback.

Three DNS layers in the haus, and they don’t always agree:

LayerResolverWhere it applies
Firewalla DNSNetwork-wideAll devices on WiFi
Tailscale MagicDNSTailnet onlyDevices with Tailscale installed
/etc/hostsPer-machineMac Mini, dev machines

If force.home works on the Mac but not on an iPhone, the Firewalla DNS override for force.home is missing. Add it via Firewalla app > DNS > Local DNS Records.

Check in this order:

  1. Holiday calendar: holidays.yaml might have a stale entry or missing date. Holidays take highest priority — if today is flagged as a holiday, weekend/weekday logic is irrelevant.
  2. Weekend detection: Remember, Friday night (weekday() == 4) and Saturday night (weekday() == 5) are weekends. Sunday night is a school night. The code uses the night-of, not the next-morning calendar day.
  3. Manual override: An active override (via PWA or API) supersedes all schedule logic. Check GET /screen/status for override_active: true.

Screen Time is a control plane, not a data plane. It makes one API call to Firewalla every few minutes to toggle a rule. It does not process packets, route traffic, or handle any load that would justify the compilation overhead of Rust. The sanctum-proxy is Rust because it handles thousands of requests per second. Screen Time handles dozens of requests per day. Python with a 1700-line module inside Force Flow is the right tool.

MDM (Mobile Device Management) is the “enterprise” answer to parental controls. It’s also:

  • Invasive: Full device management profile. Visible to the child. Creates an adversarial dynamic.
  • Platform-locked: Doesn’t touch the PS5, the PC, or grandma’s Android tablet.
  • Fragile: One “Remove Profile” tap and it’s gone. Network-level enforcement has no uninstall button.
  • Overkill: We need bedtime enforcement and homework mode, not remote wipe and app whitelisting.

Apple Screen Time on personal devices + Firewalla at the network layer covers every device in the haus without installing anything on any of them. The only software that runs is on the Mac Mini, which is the one machine in the building that no 12-year-old has the SSH key to.

PropertyValue
Port4077 (shared with Force Flow)
Engine~/.sanctum/force-flow/screen_time.py
Bridge~/.openclaw/firewalla-bridge.js (port 1984)
Config~/.sanctum/screen-time/devices.yaml
Holidays~/.sanctum/screen-time/holidays.yaml
Database~/.sanctum/screen-time/usage.db
PWA~/.sanctum/screen-time/pwa/
HA Package~/.openclaw/homeassistant/packages/screen_time.yaml
Holocron~/Projects/the-holocron/src/renderer/components/ScreenTimePanel.tsx
Exporttools/firewalla-export.sh / firewalla-import.sh
DigestSunday 18:00 via Signal + push