Need your Raspberry Pi to only update its display when content actually changes? Tired of constant refreshes wasting bandwidth and causing annoying flickers? I’ve got you covered!
This guide walks through three practical solutions – from simple HTTP caching to real-time WebSockets – that will make your Pi display work smarter, not harder. Let’s dive in and optimize your digital signage, kiosk, or dashboard project.
Solution 1: HTTP Caching with Conditional Requests (Simplest)
This approach uses standard HTTP caching mechanisms and is the easiest to implement.
Server Setup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// Example using Express.js const express = require('express'); const app = express(); const port = 3000; let content = "Initial content"; let etag = generateETag(content); // A function to generate a hash of the content app.get('/', (req, res) => { // Check if client sent If-None-Match header with the current ETag if (req.headers['if-none-match'] === etag) { // Content hasn't changed, return 304 Not Modified return res.status(304).end(); } // Content has changed or first request res.setHeader('ETag', etag); res.setHeader('Cache-Control', 'no-cache'); // Tell client to check with server before using cached content res.send(` <!DOCTYPE html> <html> <head> <meta http-equiv="refresh" content="60"> <!-- Auto-refresh every 60 seconds --> <title>Content Display</title> </head> <body> <div id="content">${content}</div> </body> </html> `); }); // Function to update content (called when content changes) function updateContent(newContent) { content = newContent; etag = generateETag(content); } function generateETag(content) { // Simple hash function for demo purposes return require('crypto').createHash('md5').update(content).digest('hex'); } app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); |
Raspberry Pi Setup:
- Install Raspberry Pi OS / Walkthrough
- Set up Chromium in kiosk mode:
1 2 3 4 5 6 7 |
# Add to ~/.config/lxsession/LXDE-pi/autostart @xset s off @xset -dpms @xset s noblank @chromium-browser --incognito --kiosk http://your-server-ip:3000 |
Benefits: Simple implementation, works with standard HTTP, minimal setup required.
Solution 2: Server-Sent Events (SSE) (Recommended)
SSE provides real-time updates from server to client with a simple implementation. Learn more about Server-Sent Events on MDN.
Server Setup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
const express = require('express'); const app = express(); const port = 3000; let content = "Initial content"; let clients = []; // Serve the HTML page app.get('/', (req, res) => { res.send(` <!DOCTYPE html> <html> <head> <title>Content Display</title> <script> document.addEventListener('DOMContentLoaded', () => { const contentDiv = document.getElementById('content'); // Create SSE connection const eventSource = new EventSource('/events'); // Listen for content updates eventSource.addEventListener('content-update', (event) => { contentDiv.innerHTML = event.data; console.log('Content updated'); }); // Handle connection errors eventSource.onerror = () => { console.log('SSE connection error, trying to reconnect...'); setTimeout(() => { // The browser will automatically try to reconnect }, 5000); }; }); </script> </head> <body> <div id="content">${content}</div> </body> </html> `); }); // SSE endpoint app.get('/events', (req, res) => { // Set headers for SSE res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // Send initial data res.write(`event: content-update\ndata: ${content}\n\n`); // Keep connection alive with heartbeat const keepAliveInterval = setInterval(() => { res.write(':\n\n'); // Comment line as heartbeat }, 15000); // Add this client to the connected clients const clientId = Date.now(); clients.push({ id: clientId, res }); // Remove client on connection close req.on('close', () => { clearInterval(keepAliveInterval); clients = clients.filter(client => client.id !== clientId); console.log(`Client ${clientId} disconnected`); }); }); // Function to update content (call this when content changes) function updateContent(newContent) { content = newContent; // Notify all connected clients clients.forEach(client => { client.res.write(`event: content-update\ndata: ${content}\n\n`); }); console.log('Content updated, notified all clients'); } app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); |
Raspberry Pi Setup:
Same kiosk setup as Solution 1.
Benefits: True real-time updates, only transfers content when changes occur, simple to implement, works well in browsers.
Solution 3: WebSockets (Most Advanced)
For bidirectional real-time communication with more features. Learn more about WebSockets on MDN.
Server Setup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const app = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); let content = "Initial content"; // Serve the HTML page app.get('/', (req, res) => { res.send(` <!DOCTYPE html> <html> <head> <title>Content Display</title> <script> document.addEventListener('DOMContentLoaded', () => { const contentDiv = document.getElementById('content'); // Create WebSocket connection const socket = new WebSocket('ws://' + window.location.host); // Connection opened socket.addEventListener('open', (event) => { console.log('WebSocket connected'); // Request initial content socket.send(JSON.stringify({ type: 'get-content' })); }); // Listen for content updates socket.addEventListener('message', (event) => { const data = JSON.parse(event.data); if (data.type === 'content-update') { contentDiv.innerHTML = data.content; console.log('Content updated'); } }); // Handle connection close socket.addEventListener('close', () => { console.log('WebSocket disconnected, trying to reconnect...'); setTimeout(() => { window.location.reload(); }, 5000); }); }); </script> </head> <body> <div id="content">${content}</div> </body> </html> `); }); // WebSocket connection handling wss.on('connection', (ws) => { console.log('Client connected'); // Send initial content ws.send(JSON.stringify({ type: 'content-update', content: content })); // Handle messages from client ws.on('message', (message) => { const data = JSON.parse(message); if (data.type === 'get-content') { ws.send(JSON.stringify({ type: 'content-update', content: content })); } }); // Handle disconnection ws.on('close', () => { console.log('Client disconnected'); }); }); // Function to update content (call this when content changes) function updateContent(newContent) { content = newContent; // Notify all connected clients wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ type: 'content-update', content: content })); } }); console.log('Content updated, notified all clients'); } server.listen(3000, () => { console.log('Server started on http://localhost:3000'); }); |
Raspberry Pi Setup:
Same kiosk setup as previous solutions.
Benefits: Full bidirectional communication, robust reconnection handling, most flexible for complex applications.
Recommendation
For most use cases, Solution 2 (Server-Sent Events) provides the best balance of simplicity and effectiveness. It’s specifically designed for server-to-client updates and is simpler to implement than WebSockets while providing true real-time updates without unnecessary polling.
Additional Considerations
- Connection Reliability:
- Add reconnection logic for all solutions
- Implement heartbeats to keep connections alive
- Raspberry Pi Configuration:
- Disable screen blanking/sleep
- Set up auto-restart of browser if it crashes
- Consider using Raspberry Pi OS Lite for kiosk applications
- Monitoring:
- Add logging to track connection status
- Consider implementing a watchdog to restart services if needed