Skip to content

About

Wall Display for 3D printers (farm)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

30 Commits

Folders and files

Repository files navigation

Walldisplay for real-time monitoring of a 3D printing farm.

Live data for my farm here: https://farma.pihrt.com/

Where does it run?

Raspberry Pi 3+ Raspberry Pi OS connected to a monitor (HDMI)

How do I access it?

Locally on the monitor (kiosk) or from the LAN: http://IP_RASPBERRY/

What does it display?

status of all printers print progress (%) remaining time ETA (end date/time) nearest print end visual alerts (color changes)

Meaning of colors

Color <-> Meaning

  • Gray <-> Not printing / idle
  • Yellow <-> Printing in progress
  • Orange <-> Nearing completion
  • Red <-> Error

Updates data

  • Data every 5 seconds
  • Time every 1 second

Printers:

  • Prusa XL (LAN)
  • Prusa MK4 (LAN)
  • Prusa MK3 farm (USB → PrusaLink multi-instance)
  • Raise3D Pro2, Pro3 (LAN)

Devices:

  • Shelly 4PM for measuring voltage, consumption, and power usage from Shelly devices (LAN).
  • Shelly Plus H&T for temperature and humidity through Shelly Cloud.

Shelly Plus H&T (Shelly Cloud)

Set SHELLY_CLOUD["enabled"] to True in app/config.py, then enter the Shelly Cloud server URI, authorization key, and each sensor's display name and device ID. The authorization key remains on the Raspberry Pi and is never included in status.json. The display shows a separate card for every configured sensor with temperature, humidity, and the source measurement time. Cloud responses are cached for 60 seconds by default to respect Shelly Cloud API limits; change refresh_seconds only when necessary.

The power card and each temperature/humidity card include a 12-hour history graph. The Raspberry Pi stores samples locally under app/data/history/ by default and serves them through /api/power_history and /api/sensor_history; set WALLDISPLAY_HISTORY_DIR to a persistent writable directory if the application code directory is read-only or replaced during updates. Local power samples are saved at most once per minute and sensor samples once per 10 minutes. The remote PHP receiver builds its own history from incoming live measurements and stores power_history.json and sensor_history.json beside status.json; remote power samples are limited to one per minute and sensor samples to one per 10 minutes. The browser checks remote history once per minute and redraws a graph only when its plotted sample values change. Graphs are bottom-aligned inside equal-height telemetry cards, follow their actual container width, and increase to 180 pixels high on screens narrower than 768 pixels so axes and legends remain readable.

Changes

2026-10-12 — walldisplay: Aligned power and temperature graphs inside their cards, kept complete graph labels visible on the kiosk display, and made graphs use the full available width and a larger height on phones.

2026-10-10 — walldisplay: Made telemetry history loading independent from live status rendering, retained last known Shelly readings on Cloud request failures, and moved Shelly refreshes and remote exports off the /api/status response path.

2026-10-10 — walldisplay: Added local power and sensor history storage/API, remote temperature and humidity history, and graphs in power and sensor cards.

2026-10-02 — walldisplay: Added Shelly Plus H&T support through Shelly Cloud, including configurable sensors on the Raspberry Pi, secure backend export, and temperature/humidity cards in the web display.

Remote json export

The remote page adds a changing query parameter to every JSON request, in addition to cache: "no-store". Its index.html also versions the JavaScript URL so a browser with a long-lived cached app.js receives this behavior immediately. This prevents browser, proxy, or CDN caches from showing an old status.json response as current data. The backend also logs a failed HTTP response from update.php; check journalctl -u wallboard if the remote timestamp stops moving.

  • POST https://your_ip/update.php
  • Authorization: Bearer YOUR_SECRET_TOKEN

The MK3 farm accepts both configuration formats: the current printers list and the legacy instances mapping used by early wallboard installations. This allows an application update without replacing deployed PrusaLink API keys.

Python libraries

  • Tested in Python 3.13 runs in virtualenv (venv)
  • fastapi
  • uvicorn
  • requests
  • pydantic

WEB SERVER Nginx

  • index.html
  • style.css
  • app.js
  • logo.png
  • favicon.png

On remote webpage (SECRET_TOKEN):

  • index.html
  • style.css
  • app.js
  • logo.png
  • favicon.png
  • update.php

Reverse proxy on FastAPI: GET /api/status

  • /api → http://127.0.0.1:8000

URL

  • http://localhost/ → wallboard UI
  • http://localhost/api/status → JSON data

Backend

Printers

  • prusa_link.py – local PrusaLink (XL, MK4, MK3)
  • prusa_farm.py – local multi-instance MK3
  • raise3d.py – local Raise (Pro2+, Pro3+)
  • only one JSON format for all printers

Devices

  • shelly_4pm.py - local Shelly devices (Pro 4PM)

Backend output (JSON)

{"generated_at":1767091183,"printers":[{"name":"XL-01","vendor":"Prusa","model":"XL 5H","state":"offline"},{"name":"MK4-01","vendor":"Prusa","model":"MK4","state":"idle","job":null,"progress":0,"time_left":null},{"name":"MK3-09","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-03","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-07","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-13","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-12","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-10","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-08","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-11","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-02","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-06","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-05","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-01","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"MK3-04","vendor":"Prusa","model":"MK3","state":"error","job":null,"progress":0,"time_left":null},{"name":"Raise-Pro2","vendor":"Raise3D","model":"Pro2+","state":"idle","job":null,"progress":0,"time_left":null,"temps":{"nozzle":21,"bed":19}},{"name":"Raise-Pro3","vendor":"Raise3D","model":"Pro3+","state":"idle","job":null,"progress":0,"time_left":null,"temps":{"nozzle":25,"bed":20}}], "power": {"power_w": 1842.5,"power_kw": 1.84,"voltage_v": 231.2,"energy_kwh": 1520.3}}  

Reinstalling on a new Raspberry Pi

BACKEND

sudo apt update
sudo apt install -y python3 python3-venv nginx
cd /opt
git clone pihrt-com/walldisplay
cd walldisplay
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

FASTAPI SERVICE

sudo systemctl enable walldisplay
sudo systemctl start walldisplay

NGINX

sudo apt install -y nginx
sudo mkdir /var/www/walldisplay
sudo chown -R www-data:www-data /var/www/walldisplay
sudo nano /etc/nginx/sites-available/walldisplay

server {
    listen 80;
    server_name _;
    root /var/www/walldisplay;
    index index.html;
    location / {
        try_files $uri $uri/ =404;
    }
    location /api/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
sudo ln -s /etc/nginx/sites-available/wallboard /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable walldisplay
sudo systemctl start walldisplay
systemctl status walldisplay

Logs:

journalctl -u wallboard -f

KIOSK

sudo apt install -y chromium unclutter
xset s off
xset -dpms
/opt/walldisplay/app/
├── main.py
├── aggregator.py
├── remote_export.py
├── config.py
├── printers/
    ├─ init.py
    ├─ prusa_link.py
    ├─ raise3d.py    
    └─ prusa_farm.py
└── devices/
    └─ shelly_4pm.py   

Test

cd /opt/walldisplay
source venv/bin/activate
uvicorn app.main:app --host 0.0.0.0 --port 8000

Raise - test points API URL

curl "http://192.168.88.172:10800/v1/printer/runningstatus?token=076ffdfe5a350aac3c3da4eea43e2248"
curl "http://192.168.88.172:10800/v1/job/currentjob?token=076ffdfe5a350aac3c3da4eea43e2248"
curl "http://192.168.88.172:10800/v1/printer/nozzle1?token=076ffdfe5a350aac3c3da4eea43e2248"
curl "http://192.168.88.172:10800/v1/printer/basic?token=076ffdfe5a350aac3c3da4eea43e2248"

Where token is example Python code:

def _login(base_url: str, password: str) -> str:
    ts = int(time.time() * 1000)
    raw = f"password={password}&timestamp={ts}"

    sha1_hex = hashlib.sha1(raw.encode("utf-8")).hexdigest()
    sign = hashlib.md5(sha1_hex.encode("utf-8")).hexdigest()

    url = f"{base_url}:10800/v1/login"

    r = requests.get(
        url,
        params={
            "timestamp": ts,
            "sign": sign
        },
        timeout=TIMEOUT
    )

    r.raise_for_status()
    j = r.json()

    if j.get("status") != 1:
        raise RuntimeError(j)

    token = j["data"]["token"]
    _TOKEN_CACHE[base_url] = (token, time.time())
    return token

About

Wall Display for 3D printers (farm)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages