Skip to content

ShamgarBN/wallboard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wallboard

A self-hosted family wallboard — calendar, weather, photos, news, sports, quotes, and more — laid out as Bento tiles you can rearrange like Squarespace from a built-in admin panel. Designed for a wall-mounted display (Raspberry Pi + salvaged monitor works great), but it runs on any computer with Node.js.

demo

  • One screen, two layouts. Edit a portrait layout and a landscape layout separately — the wallboard auto-picks whichever matches your screen orientation.
  • Drag, drop, and resize tiles on a snapping grid right from the admin panel. No editing JSON unless you want to.
  • 17 built-in widgets (calendar, weather, news, sports, photos, quote carousel, NASA picture of the day, on-this-day memories, Bible verse, dinner-conversation questions, and more). Add your own with two small files.
  • No accounts, no API keys required out of the box. Everything works on first boot with sensible defaults; you can plug in your own iCloud calendar URL, photo folder, RSS feeds, and sports teams from the admin UI.
  • Runs anywhere Node runs — Mac, Linux, Windows, Raspberry Pi.

Table of contents

  1. Quick start
  2. Hardware setup
  3. The admin panel
  4. Widget catalog
  5. Personalizing your data
  6. Adding a custom widget
  7. Troubleshooting
  8. Project structure
  9. Security

Quick start

You need Node.js 18 or newer installed. On a Mac, the easiest way is to install Homebrew and then run brew install node.

# 1. clone this repository
git clone https://github.com/ShamgarBN/wallboard.git
cd wallboard

# 2. install dependencies (one-time, takes ~30 seconds)
npm install

# 3. start the server
npm start

You'll see:

✓  Wallboard running at http://localhost:3000
✓  Admin at        http://localhost:3000/admin

Open http://localhost:3000 in any browser — that's the wallboard. Open http://localhost:3000/admin to edit it.

If you're running this on a Raspberry Pi (or any other machine on your home network) and want to access the admin from your phone, replace localhost with the Pi's IP address or hostname, e.g. http://wallboard.local:3000/admin.


Hardware setup (Raspberry Pi + monitor)

Parts list (~$110 if you have a spare monitor):

Part Approx. cost Notes
Raspberry Pi 5 (4 GB) $60 Pi 4 also works
Official Pi 5 power supply $12 Use the official one — knockoffs cause undervolting glitches
microSD card 32 GB+ $10 Class 10 / U3 minimum
micro-HDMI to HDMI cable $8 Pi 5 has two micro-HDMI ports
Pi case with cooling $10 Any well-vented case
Monitor $0 if salvaged 1080p or higher, any aspect ratio
(Optional) PIR motion sensor $4 Wires to GPIO for auto-on/off
(Optional) Shadowbox frame $30 At any craft store — turns a salvaged monitor into wall art

One-time setup on the Pi:

  1. Flash Raspberry Pi OS Lite (64-bit) to the microSD card using Raspberry Pi Imager. In the Imager's settings, configure SSH, wifi, your username, and password.
  2. Boot the Pi and SSH into it: ssh [email protected] (or whatever hostname you chose).
  3. Install Node.js:
    curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
    sudo apt-get install -y nodejs git chromium-browser unclutter xserver-xorg x11-xserver-utils xinit openbox
  4. Clone and run the wallboard:
    git clone https://github.com/ShamgarBN/wallboard.git ~/wallboard
    cd ~/wallboard && npm install
  5. Run on boot — create a systemd service so the wallboard starts automatically:
    sudo tee /etc/systemd/system/wallboard.service > /dev/null <<'EOF'
    [Unit]
    Description=Wallboard server
    After=network.target
    
    [Service]
    Type=simple
    User=pi
    WorkingDirectory=/home/pi/wallboard
    ExecStart=/usr/bin/node server.js
    Restart=always
    RestartSec=5
    Environment=PORT=3000
    
    [Install]
    WantedBy=multi-user.target
    EOF
    sudo systemctl enable --now wallboard
  6. Launch a kiosk browser on boot — make Chromium open the wallboard fullscreen with the cursor hidden:
    mkdir -p ~/.config/openbox
    cat > ~/.config/openbox/autostart <<'EOF'
    xset -dpms       # disable energy star
    xset s off       # disable screensaver
    xset s noblank   # don't blank the screen
    unclutter -idle 0.5 -root &
    chromium-browser --kiosk --noerrdialogs --disable-infobars --disable-translate \
      --no-first-run --check-for-update-interval=31536000 \
      --app=http://localhost:3000 &
    EOF
    echo "exec openbox-session" > ~/.xinitrc
    # auto-start X on login
    echo '[ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ] && startx' >> ~/.bash_profile
    sudo raspi-config nonint do_boot_behaviour B2  # console autologin
    sudo reboot

After reboot the Pi should boot straight into the wallboard, full-screen.

Mounting the monitor:

  • Pull the monitor's bezel and stand off (most pop or unscrew). Just the bare panel inside a shadowbox frame looks dramatically more premium than the original monitor.
  • A 11"×14" shadowbox frame fits most 13"–15" monitors. Cut the mat opening to the screen's exact active area.
  • Run the power cable down through the back; the Pi can sit on the back panel held by velcro.

The admin panel

Open http://<your-server>:3000/admin. There are five tabs:

1. Layout Editor

The Bento grid editor:

  • Drag tiles to move them. They snap to the grid.
  • Drag the bottom-right corner of any tile to resize it (also snaps).
  • Click the dot row at the bottom-left of a tile to change its accent color.
  • Click the × in the corner to remove a tile.
  • Drag a widget from the sidebar onto the grid to add a new tile — or just click it to drop it in the first empty spot.
  • Toggle between Portrait and Landscape at the top of the sidebar. Each orientation has its own independent layout.
  • Change grid dimensions to make tiles smaller (more columns/rows) or larger (fewer).
  • Hit Save layout when done. The wallboard auto-reloads within 60 seconds.

2. General

  • Household name (shows on the wallboard).
  • Default orientation.
  • Location for weather and air-quality (label + lat/lon).
  • Optional admin password — set this if your Pi is reachable from outside your home network.

3. Widget Settings

Per-widget options like time format (12h/24h), temperature units (°F/°C), Bible translation, etc.

4. Sources

The big "give me my data" tab:

  • Calendar URL — paste your iCloud public-share webcal:// URL here.
  • News feeds — add, remove, or rename RSS feeds.
  • Sports teams — pick from MLB, NFL, NBA, NHL, College Football, College Basketball, MLS, WNBA.
  • Birthdays — name, month, day. The reminders widget surfaces them as they approach.
  • Trash day — pick your recycling/garbage night.
  • Dinner questions — one per line; rotates daily.

5. Quotes

A plain text editor over your data/quotes.csv file. Format: text,author,tags. The quote widget rotates through these. You can also edit data/quotes.csv directly with any spreadsheet program if you prefer — both work.


Widget catalog

Widget What it does Data source
Date & Time Big clock with day and date. 12/24h selectable. Local
Weather Current conditions plus a 3/5/7-day forecast with hi/lo + icons. Open-Meteo (free, no key)
Today's Agenda Today's events from your iCal/iCloud calendar. Public iCal URL
This Week Next 7 days, grouped by day. Same iCal URL
News Headlines Round-robin headlines from your RSS feeds. Any RSS
Quote Rotating quote carousel from your CSV. data/quotes.csv
Sports Last + next game for each followed team. ESPN public JSON
Photo Slideshow Rotating photos from data/photos/. Local folder
Reminders Upcoming birthdays + trash day. Admin-configured
Moon Phase Current phase + % illumination. Computed locally
NASA Picture of the Day Today's APOD image + caption. NASA APOD API (no key)
On This Day Photo from the same date in past years. data/photos/YYYY-MM-DD-*.jpg
Word of the Day New vocabulary word daily. Curated local list
Dinner Question Conversation starter, rotates daily. Admin-configured
Weekly Verse Bible verse, rotates weekly. bible-api.com (free, no key)
Air Quality AQI + UV index for your location. Open-Meteo Air Quality

Personalizing your data

Calendar (iCloud / iCal)

To pipe your Apple Calendar to the wallboard:

  1. Open Calendar.app on your Mac (works on iPhone too but the Mac flow is clearer).
  2. Right-click a calendar in the sidebar → Share Calendar.
  3. Check Public Calendar. A webcal:// URL appears.
  4. Copy that URL, paste it into the admin → SourcesiCal / Webcal URL field, hit Save.

Only the calendars you explicitly make public will be visible. The URL is opaque (unguessable) but treat it like a password — anyone with the URL can read events you've shared (not write).

Works with Google Calendar (use the "Secret iCal address" from a calendar's settings), Outlook/Office 365, Fastmail, ProtonMail, Nextcloud — anything that produces an iCal/webcal URL.

Photos

Drop image files (.jpg, .png, .webp, .gif) into data/photos/. The Photo Slideshow widget will rotate through them.

For the On This Day widget, name files like YYYY-MM-DD-anything.jpg (e.g. 2022-05-21-avery-birthday.jpg) and the widget will surface them on the matching date.

iCloud Shared Album support is on the roadmap (Phase 2.5) since Apple's photo-sharing API is undocumented and finicky. In the meantime, on a Mac you can subscribe to an iCloud shared album, drag-export new photos to a synced folder (e.g. via Hazel or a launchd job), and rsync them to the Pi. Detailed guide coming.

News

Add any RSS feed via admin → SourcesNews feeds. Some great defaults:

  • NYT https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml
  • BBC World http://feeds.bbci.co.uk/news/world/rss.xml
  • NPR https://feeds.npr.org/1001/rss.xml
  • Guardian World https://www.theguardian.com/world/rss
  • Hacker News https://hnrss.org/frontpage
  • ESPN https://www.espn.com/espn/rss/news
  • Your local paper — most newspapers have an /rss or /feed URL hiding somewhere.

Sports teams

Pre-configured for the Niemann family:

  • Atlanta Braves (MLB)
  • Carolina Panthers (NFL)
  • Georgia Bulldogs (NCAA Football)
  • Carolina Hurricanes (NHL)

Add or change in admin → SourcesSports teams. Each team needs a name (e.g. "Atlanta Braves"), abbreviation (e.g. "ATL"), and league.

Supported leagues: MLB, NFL, NBA, NHL, College Football, College Basketball (Men's), MLS, WNBA.

Quotes

Edit data/quotes.csv directly (a spreadsheet program handles this cleanly) or use the Quotes tab in admin. Format:

text,author,tags
"Be still, and know that I am God.",Psalm 46:10,scripture
"It does not do to dwell on dreams and forget to live.",J.R.R. Tolkien,literary

If your text contains a comma, wrap it in double quotes. Tags are optional and currently informational only (future versions may filter by tag).

Weather location

In admin → GeneralLocation:

  • Easiest: type a city name in City label for display purposes, then leave lat/lon. The system will auto-detect.
  • Most accurate: open Google Maps, right-click your house, click the coordinates that appear at the top to copy them, paste into Lat/Lon fields.

Adding a custom widget

A widget is two small files. Say you want a "Spotify Now Playing" widget:

1. Server-side fetcherlib/widgets/spotify.js

const cache = require('../cache');

module.exports = {
  meta: {
    name: 'Spotify Now Playing',
    description: 'What\'s playing right now.',
    category: 'Media',
    defaultSize: { colspan: 2, rowspan: 1 },
    defaultAccent: 'green',
    settings: [
      { key: 'username', type: 'text', label: 'Spotify username' },
    ],
  },
  async fetch(config) {
    // ... fetch from Spotify API, return JSON ...
    return { track: 'Hey Jude', artist: 'The Beatles' };
  },
};

Register it in lib/widgets/registry.js:

const spotify = require('./spotify');
// ...
const widgets = {
  // ...
  spotify,
};

2. Client-side rendererpublic/widgets/spotify.js

(function () {
  window.WIDGETS = window.WIDGETS || {};
  window.WIDGETS.spotify = {
    meta: { title: 'Now Playing' },
    render(body, data) {
      body.innerHTML = '<div>' + data.track + ' — ' + data.artist + '</div>';
    },
  };
})();

Add <script src="/widgets/spotify.js"></script> to public/index.html.

Restart the server. The widget appears in the admin's library, draggable onto the grid.


Troubleshooting

"The wallboard says 'Cannot reach server.'" The server probably isn't running. SSH into the Pi: sudo systemctl status wallboard. Restart: sudo systemctl restart wallboard.

"Weather shows 'Error' or wrong city." Check admin → General → Location. The lat/lon must be numbers. If they're empty, weather defaults to Nashville.

"Calendar is empty." Three things to check: (1) you pasted the right webcal:// URL in admin → Sources, (2) the iCloud calendar is actually marked Public, (3) you actually have events today (try the Week view to confirm).

"News only shows one source." The widget round-robins by recency. If a feed isn't returning items, check server logs: sudo journalctl -u wallboard -n 50. Possible causes: feed URL is wrong, the source is rate-limiting you, your Pi can't reach the internet.

"Sports widget shows 'No teams.'" Add teams in admin → Sources → Sports teams. The abbreviation matters — ESPN matches by abbreviation first.

"Tile won't drag on touchscreen." Use a mouse for now — touch support for the editor is a known gap (it's on the list).

"How do I reset everything?" Delete data/config.json and data/layout.json and restart. The defaults from data/defaults/ repopulate.


Project structure

wallboard/
├── server.js                 # Express server entry point
├── package.json
├── README.md
│
├── lib/
│   ├── config.js             # Loads and saves config + layout JSON
│   ├── cache.js              # Tiny in-memory cache for API responses
│   └── widgets/              # Server-side widget data fetchers
│       ├── registry.js       # Widget registry
│       ├── datetime.js
│       ├── weather.js
│       ├── agenda-today.js
│       ├── agenda-week.js
│       ├── news.js
│       ├── quote.js
│       ├── sports.js
│       ├── photo.js
│       ├── reminders.js
│       ├── moon.js
│       ├── apod.js
│       ├── on-this-day.js
│       ├── word-of-day.js
│       ├── dinner-question.js
│       ├── verse.js
│       └── air-quality.js
│
├── public/                   # Browser code
│   ├── index.html            # The wallboard itself
│   ├── styles/wallboard.css
│   ├── scripts/
│   │   ├── wallboard.js      # Layout renderer + refresh loop
│   │   └── icons.js          # Inline SVG icon set
│   ├── widgets/              # Client-side widget renderers (one per widget)
│   └── admin/
│       ├── index.html        # Admin panel UI
│       ├── admin.css
│       ├── admin.js          # Main admin controller
│       └── editor.js         # Drag-and-drop layout editor
│
├── data/                     # Local data (gitignored)
│   ├── config.json           # Generated on first boot
│   ├── layout.json           # Generated on first boot
│   ├── quotes.csv            # Curated, you edit this
│   ├── photos/               # Drop your photos here
│   └── defaults/             # Seed defaults shipped with the repo
│       ├── config.json
│       └── layout.json
│
└── mockups/                  # Phase 1 design picker (kept for reference)

Security

This is a home-LAN tool, but it follows defensive defaults:

  • HTTP-only Basic auth on /admin if you set a password (optional, off by default).
  • Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, Referrer-Policy headers set on every response.
  • Cache-Control: no-store, so admin tokens don't leak through caches.
  • Photo serving uses path.basename() and rejects directory-traversal attempts.
  • The API never reflects the admin password; it's only ever set, never read back to the browser.
  • All third-party data (RSS, ESPN, weather) is treated as untrusted and rendered with HTML escaping.

If you expose the wallboard outside your home network (via Cloudflare Tunnel, Tailscale, etc.):

  1. Set an admin password in admin → General → Admin password.
  2. Run behind HTTPS (Tailscale, Caddy, Cloudflare all handle this for free).
  3. Consider restricting access to the admin route via your reverse proxy.

License

MIT. See LICENSE.

About

Wall-hung Family Dashboard

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors