Genom att sätta upp en lapp med en QR-kod på dörren kan en besökare enkelt skanna koden med sin mobil och göra dig uppmärksam på att någon vill komma in. Med hjälp av ntfy – en enkel och självhostad notifikationstjänst – kan detta knapptryck omedelbart ge både ljud och visuell signal. Genom att återanvända en gammal, uttjänt dator som ringklockskonsol får du full kontroll över när någon ringer på dörren. För den som föredrar en mer traditionell lösning går det dessutom att bygga en fysisk ringknapp, till exempel med en Raspberry Pi Pico. Resultatet är en modern, flexibel och helt egen ringklocka, byggd med öppna webbtekniker och utan beroende av proprietära molntjänster.

ntfy är en enkel, öppen och självhostad publish–subscribe-tjänst som låter dig skicka notiser via vanliga HTTP-anrop. Ursprungligen är den tänkt för script, servrar och automation – men den lämpar sig utmärkt för fysiska projekt, till exempel en egen ringklocka.
I den här artikeln visar vi hur man kan använda ntfy för att bygga en modern ringklocka där:
- Besökaren trycker på en knapp på en webbsida (eller skannar en QR-kod)
- Ett ringljud spelas lokalt i webbläsaren
- En notis skickas via ntfy
- En dator (t.ex. en gammal Ubuntu-laptop) lyssnar och visar tydligt att någon ringt, samt spelar upp ljud
Allt bygger på öppna standarder: HTML, JavaScript, HTTPS och curl.
Översikt: hur lösningen fungerar
Arkitekturen ser ut så här:
Besökare (mobil/webb)
↓ HTTPS
Ringknapp (HTML + JS)
↓ HTTPS POST
ntfy-server (publik)
↓ HTTPS stream
Konsol (webbsida eller script)
Viktiga principer:
- ntfy körs självhostat
- All extern trafik går via HTTPS
- ntfy lyssnar endast lokalt (t.ex. på 127.0.0.1:8080)
- En webbserver (Apache/Nginx) fungerar som reverse proxy
Förutsättningar för att köra ntfy
Innan du börjar är det viktigt att tänka på följande:
1. Publik IP eller publik domän
För att besökare ska kunna nå ringknappen och för att klienter ska kunna prenumerera krävs att ntfy är nåbar från internet.
- Antingen: server med publik IP
- Eller: VPS / server hos leverantör
- Eller: portforwarding + dynamisk DNS (mindre stabilt)
2. HTTPS är ett krav
Moderna webbläsare kräver HTTPS för:
- JavaScript
fetch() - ljuduppspelning
- push/notiser
- EventSource (SSE)
Rekommenderad lösning:
Låt Apache eller Nginx hantera HTTPS med Let’s Encrypt, och proxy:a vidare till ntfy.
3. Säkerhet
- Använd långa, svårgissade topics
- Exponera inte ntfy:s interna port (8080)
- Kör ntfy bakom proxy (
behind-proxy: true)
Exempel: ntfy-konfiguration (server.yml)
base-url: https://ntfy.exempel.se
listen-http: 127.0.0.1:8080
behind-proxy: true
cache-file: /var/cache/ntfy/cache.db
ntfy startas som systemtjänst och är endast åtkomlig lokalt – Apache tar hand om port 80/443.
Ringknappen – webbsida för besökare
Den här sidan:
- spelar upp ett ringljud lokalt (
ring.mp3) - skickar ett meddelande till ntfy
- tillåter även att man skriver ett kort meddelande
Placering
/var/www/html/ring.html
/var/www/html/sounds/ring.mp3
Komplett kod: ring.html
<!doctype html>
<html lang="sv">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ring på</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
.box { max-width: 520px; margin: 0 auto; }
.btn { width: 100%; font-size: 22px; padding: 16px; border: 0; border-radius: 16px; }
textarea { width: 100%; min-height: 90px; }
.note { margin-top: 12px; opacity: 0.85; }
</style>
</head>
<body>
<div class="box">
<h1>Ring på dörren</h1>
<button class="btn" id="ring">🔔 RING</button>
<p>
<textarea id="msg" placeholder="Skriv ett meddelande (valfritt)"></textarea>
<button class="btn" id="send">✉️ SKICKA MEDDELANDE</button>
</p>
<div class="note" id="status"></div>
</div>
<audio id="ringSound" preload="auto">
<source src="/sounds/ring.mp3" type="audio/mpeg">
</audio>
<script>
const NTFY_URL = "https://ntfy.exempel.se/ringklocka-Ringer";
const ringSound = document.getElementById("ringSound");
const status = document.getElementById("status");
function playSound() {
ringSound.currentTime = 0;
ringSound.play().catch(() => {});
}
async function send(msg, title) {
const r = await fetch(NTFY_URL, {
method: "POST",
headers: { "Title": title, "Priority": "5" },
body: msg
});
if (!r.ok) throw new Error();
}
document.getElementById("ring").onclick = async () => {
playSound();
try {
await send("🔔 Någon ringde på!", "Ringklocka");
status.textContent = "✅ Ringt!";
} catch {
status.textContent = "❌ Kunde inte ringa";
}
};
document.getElementById("send").onclick = async () => {
const text = document.getElementById("msg").value.trim();
if (!text) return;
try {
await send("✉️ " + text, "Meddelande vid dörren");
status.textContent = "✅ Skickat!";
} catch {
status.textContent = "❌ Kunde inte skicka";
}
};
</script>
</body>
</html>
Konsol: visa ringningar på en dator
En gammal laptop med webläsare fungerar utmärkt som ringklockskonsol.
Lyssna via terminal (script)
curl -sN -H "Accept: application/json" \
https://ntfy.exempel.se/ringklocka-Ringer/json \
| jq -r 'select(.event=="message") | .message' \
| while read -r msg; do
notify-send "🔔 Det ringer" "$msg"
paplay /usr/share/sounds/freedesktop/stereo/message-new-instant.oga
done
Detta ger:
- popup-notis på skärmen
- ljud varje gång någon ringer
Scriptet kan köras som systemd user service för autostart.
Webkonsol
<!doctype html>
<html lang="sv">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ringklocka-konsol</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 18px; }
.wrap { max-width: 900px; margin: 0 auto; }
.card { border: 1px solid #ddd; border-radius: 16px; padding: 16px; margin: 12px 0; }
.big { font-size: 36px; font-weight: 800; }
.muted { opacity: 0.7; }
button { font-size: 16px; padding: 10px 14px; border-radius: 12px; border: 1px solid #bbb; background: #fff; }
ul { margin: 0; padding-left: 18px; }
li { margin: 6px 0; }
.ok { color: #0a7; font-weight: 700; }
.bad { color: #c00; font-weight: 700; }
</style>
</head>
<body>
<div class="wrap">
<h1>🔔 Ringklocka-konsol</h1>
<div class="card">
<div class="muted">Status</div>
<div id="status" class="bad">Inte ansluten</div>
<div class="muted" style="margin-top:10px">
Webbläsare kräver ofta ett klick för att ljud ska tillåtas.
</div>
<div style="margin-top:10px">
<button id="enableSound">Aktivera ljud</button>
<span id="soundState" class="muted">Ljud ej aktiverat</span>
</div>
</div>
<div class="card">
<div class="muted">Senaste</div>
<div id="latest" class="big">—</div>
<div id="latestTime" class="muted">—</div>
</div>
<div class="card">
<div class="muted">Historik (senaste 20)</div>
<ul id="log"></ul>
</div>
</div>
<!-- Lägg ring.mp3 på /sounds/ring.mp3 -->
<audio id="ringSound" preload="auto">
<source src="/sounds/ring.mp3" type="audio/mpeg">
</audio>
<script>
/* ========= KONFIGURATION ========= */
const NTFY_BASE = "https://ntfy.example.org"; // ← ändra till din ntfy-domän
const TOPIC = "ringklocka-Ringer"; // ← välj eget topic
/* ================================= */
const SSE_URL = `${NTFY_BASE}/${TOPIC}/sse`;
const statusEl = document.getElementById("status");
const latestEl = document.getElementById("latest");
const latestTimeEl = document.getElementById("latestTime");
const logEl = document.getElementById("log");
const ringSound = document.getElementById("ringSound");
let soundEnabled = false;
function playRing() {
if (!soundEnabled) return;
ringSound.currentTime = 0;
ringSound.play().catch(() => {});
}
function addLogLine(text, when) {
const li = document.createElement("li");
li.textContent = `${when} — ${text}`;
logEl.insertBefore(li, logEl.firstChild);
while (logEl.children.length > 20) logEl.removeChild(logEl.lastChild);
}
function setConnected(ok) {
statusEl.textContent = ok ? "Ansluten ✅" : "Inte ansluten";
statusEl.className = ok ? "ok" : "bad";
}
document.getElementById("enableSound").addEventListener("click", async () => {
try {
await ringSound.play();
ringSound.pause();
ringSound.currentTime = 0;
soundEnabled = true;
document.getElementById("soundState").textContent = "Ljud aktiverat ✅";
} catch {
document.getElementById("soundState").textContent = "Kunde inte aktivera ljud ❌";
}
});
const es = new EventSource(SSE_URL);
es.addEventListener("open", () => setConnected(true));
es.addEventListener("error", () => setConnected(false));
es.onmessage = (evt) => {
try {
const data = JSON.parse(evt.data);
if (data.event !== "message") return;
const msg = data.message || "(tomt)";
const when = new Date((data.time || Date.now()/1000) * 1000);
const whenStr = when.toLocaleString();
latestEl.textContent = msg;
latestTimeEl.textContent = whenStr;
addLogLine(msg, whenStr);
playRing();
} catch (_) {}
};
</script>
</body>
</html>
Fördelar med ntfy som ringklockelösning
- ✔ Kräver inga appar (webb + curl räcker)
- ✔ Öppen källkod
- ✔ Körs på egen server
- ✔ Fungerar med mobil, dator, Raspberry Pi
- ✔ Lätt att bygga vidare (kamera, logg, relä, LED)
Saker att tänka på i drift
- Använd HTTPS överallt
- Håll ntfy uppdaterad
- Använd hemliga topic-namn
- Begränsa exponering (lyssna lokalt)
- Säkerhetskopiera cache/databas vid behov
Avslutning
Artikeln ovan ska ses som inspiration till vad man kan göra. Med hjälp av AI-tjänster, som till exempel ChatGPT, kan du experimentera och laborera fram kod som passar dig och dina behov. Framför allt visar den vad man kan göra med datorer som andra tycker är skrot.
- Ringknapp (HTML och JavaScript) som spelar lokalt ljud och skickar notiser via HTTPS.
- ntfy körs självhostat och tar emot HTTP-anrop samt strömmar händelser i realtid.
- HTTPS hanteras via reverse proxy (Apache eller Nginx) med Let’s Encrypt.
- Webbkonsol i HTML som lyssnar via Server-Sent Events och visar ringningar i realtid.
- Scriptbaserad lyssning via curl och JSON för automation och vidare integration.
- Säkerhet: använd långa och svårgissade topic-namn samt exponera inte interna portar.
- Drift: lösningen kräver publik IP eller publik domän och korrekt TLS-konfiguration.
- Återbruk: äldre datorer med Linux lämpar sig väl som permanenta ringklockskonsoler.
- Utbyggbarhet: lösningen kan kompletteras med fysisk knapp, kamera, loggning eller relä.


