foxygit / infodisplay Log in
commits tags
ModeNameLast commitUpdatedSize
.gitignore Initial commit — kiosk display system 5 months ago 473 B
README.md uppdatering 5 months ago 14.91 KB
install.sh Initial commit — kiosk display system 5 months ago 4.03 KB
kiosk woop woop. förbättringar och buggfixar. 5 months ago
README.md

Kiosk

Digitalt skyltfönster / displayhanterare för Linux (Debian 13). Visar webbsidor, YouTube, bilder, video och lokala HTML-filer i helskärm via Chromium under Sway (Wayland). Styrs via en webbaserad adminpanel.


Installation

sudo bash install.sh
sudo reboot

Kiosken startar automatiskt vid omstart via autoinloggning på tty1.

Krav

PaketSyfte
swayWayland-compositor
chromiumKiosk-webbläsare
python3-flask, python3-flask-socketioWebbserver
python3-apschedulerSchemaläggare
python3-psutil, python3-eventletSysteminfo, async
iw, wpasupplicantWi-Fi-hantering
curlNätverkstest vid uppstart

Adminpanel

http://<maskinens-IP>:5000/admin

Standardinloggning — ändra i kiosk/start.sh:

VariabelStandardvärde
KIOSK_USERmrfox
KIOSK_PASSqweasdzxc
SECRET_KEYkiosk-hemlig-nyckel-byt-garna

Funktioner

Visa nu

Visa något direkt utan att det läggs till i schemat:

  • URL — valfri webbadress, YouTube-länk konverteras automatiskt till embed
  • Ladda upp fil — video, bild, HTML, PDF/presentation
  • Lokal fil — bläddra bland filer på servern
  • Töm skärm — visar idle-skärm med IP-adress

Schema

Jobb roterar automatiskt i ordning. Varje jobb har en varaktighet i minuter (decimaler OK, ex. 0.5 = 30 sek).

TypBeskrivning
urlValfri webbadress
youtubeYouTube-liveflöde eller video
videoVideofil (mp4, webm, avi, mkv, mov)
imageBildfil (jpg, png, gif, webp, bmp)
pdfPDF eller presentation (ppt, pptx, odp)
kiosk_chatInbyggd livechat i helskärm

Videolängd detekteras automatiskt vid filval.

Löptext (ticker)

Rullande textrad som overlay. Aktiveras/stängs av utan omstart.

  • Källa — manuell text eller RSS-flöde (uppdateras automatiskt)
  • Scrollläge — en rad i taget eller löpande (kontinuerligt)
  • Position — nederkant eller överkant
  • Hastighet, textfärg, storlek, typsnitt, bakgrundsfärg, opacitet
  • Separator mellan rader i löpande läge

BBCode:

TaggEffekt
[b]...[/b]Fet
[i]...[/i]Kursiv
[u]...[/u]Understruken
[s]...[/s]Genomstruken
[color=#ff0000]...[/color]Textfärg
[size=64]...[/size]Storlek i px
[shadow]...[/shadow]Skugga
[glow=#00ff00]...[/glow]Glödeffekt

Livechat

  • Kiosk Livechat — inbyggd chat, besökare ansluter via QR-kod på mobilen. Kan visas som helskärm (schema-typ kiosk_chat) eller som overlay ovanpå annat innehåll.
  • YouTube Livechat — overlay som visas automatiskt vid YouTube-liveflöde.

Inställningar per chat: position (höger/vänster), bredd, höjd, bakgrundsfärg, opacitet.

Klocka

Klock-overlay ovanpå allt innehåll.

  • Stil — digital (HH:MM:SS) eller analog (canvas med sekundvisare)
  • Datum — valfritt datum under klockan (sv: Mån 7 apr)
  • Position — övre vänster/höger, nedre vänster/höger, centrerad
  • Storlek, färg, opacitet
  • Schema — visa alltid, eller bara under schemalagda aktiviteter
  • Positioneras automatiskt bort från ticker-listen

Sovläge

Täcker displayen med svart skärm under angiven tid (ex. 22:00–07:00).

Wi-Fi

Hantera trådlöst nätverk direkt från adminpanelen.

  • Status — aktuell SSID, IP-adress, signalstyrka
  • Skanna — listar tillgängliga nätverk med signal och säkerhetstyp
  • Anslut — lösenordsmodal (öppna nätverk kräver inget lösenord)
  • Sparade nätverk — lista och ta bort

Krav: wpa_cli utan lösenord via sudoers. Sätts upp automatiskt av install.sh.

Inställningar

  • Volym — systemvolym via PipeWire (wpctl)
  • Ljudprofil — välj PipeWire-kortprofil (t.ex. HDMI Stereo, Analog Stereo)
  • Intern display (eDP-1) — aktivera/inaktivera den inbyggda skärmen med adminpanel
  • HDMI-utgång — välj vilken utgång som används (HDMI-A-1 etc.)
  • Upplösning — välj bland tillgängliga lägen eller skriv in manuellt

Om intern display är inaktiverad men ingen Wi-Fi-anslutning finns vid uppstart — aktiveras den automatiskt så att adminpanelen alltid är tillgänglig.

System

Starta om programmet, starta om datorn, stäng av datorn.


Uppstartflöde

Dator startar
  └─ Getty autologin → tty1 → .bash_profile
       └─ kiosk-launcher.sh
            ├─ Väntar på HDMI-A-1
            ├─ Startar kiosk/start.sh (Flask) i bakgrunden
            ├─ Väntar på Flask (127.0.0.1:5000)
            ├─ Väntar på Wi-Fi / IP-adress (max 40 s)
            ├─ Genererar /tmp/kiosk-sway-outputs.conf
            └─ Startar sway
                 └─ sway-apps.sh
                      ├─ Väntar på Wi-Fi / IP (max 40 s)
                      ├─ Pre-seedar Chromium-prefs (translate av)
                      ├─ Workspace 1 (HDMI): kiosk-display → http://<IP>:5000/display
                      └─ Workspace 2 (eDP-1): kiosk-admin → http://<IP>:5000/admin

Filstruktur

display/
├── install.sh                   # Installationsscript
└── kiosk/
    ├── app.py                   # Flask-app: core routes, WebSocket, startup
    ├── auth.py                  # login_required-dekoratör
    ├── config.py                # Delade konstanter (ALLOWED_ROOTS, UPLOAD_DIR m.m.)
    ├── db.py                    # SQLite-helpers + AddonDB
    ├── extensions.py            # Delad socketio-referens (undviker cirkulär import)
    ├── addon_loader.py          # Addon-discovery och Blueprint-registrering
    ├── scheduler.py             # Schemarotation (APScheduler)
    ├── start.sh                 # Startar Flask (sätter env-vars)
    ├── kiosk-launcher.sh        # Startar Flask + sway, genererar output-config
    ├── sway.conf                # Sway-konfiguration
    ├── sway-apps.sh             # Startar Chromium på rätt workspace
    ├── hdmi-monitor.sh          # Övervakar HDMI-anslutning
    ├── hdmi-wait.sh             # Väntar på HDMI vid start
    ├── kiosk.conf               # Genereras av admin (ljud/skärm-inställningar)
    ├── kiosk.db                 # SQLite-databas
    ├── addons/                  # Addon-paket (ett paket per funktion)
    │   ├── ticker/
    │   ├── clock/
    │   ├── music/
    │   ├── livechat/
    │   ├── sleep/
    │   ├── wifi/
    │   ├── bluetooth/
    │   ├── filebrowser/
    │   ├── settings/
    │   └── system/
    ├── utils/
    │   ├── display.py           # sway_env, apply_display_mode, apply_saved_audio
    │   ├── system.py            # local_ip
    │   └── youtube.py           # yt_extract, playlist_cache
    ├── uploads/                 # Uppladdade mediefiler
    ├── static/
    │   ├── admin.js
    │   ├── display.js
    │   ├── style.css
    │   ├── socket.io.min.js
    │   └── addons/              # Statiska filer per addon (overlay.js m.m.)
    └── templates/
        ├── admin.html           # Skaltemplate — inkluderar addon-paneler dynamiskt
        ├── display.html
        ├── login.html
        ├── chat_display.html
        ├── chat_mobile.html
        └── addons/              # HTML-fragment per addon-panel
            ├── ticker/panel.html
            ├── clock/panel.html
            └── ...

Skapa ett addon

Addons är vanliga Python-paket under kiosk/addons/. De autodiscoveras vid uppstart — ingen registrering behövs.

1. Skapa paketmappen

kiosk/addons/mitt_addon/
    __init__.py
    routes.py

2. Deklarera manifestet i __init__.py

ADDON = {
    "name":            "mitt_addon",   # måste matcha mappnamnet
    "label":           "Mitt Addon",   # visas i adminpanelen
    "version":         "1.0",
    "order":           10,             # sorteringsordning i adminpanelen
    "has_panel":       True,           # True om panel.html finns
    "has_overlay":     False,          # True om static/addons/<name>/overlay.js finns
    "has_socketio":    False,          # True om register_socketio() finns
    "state_prefix":    "mitt_addon_",  # prefix för alla DB-nycklar
    "default_enabled": True,
}

def create_blueprint():
    from .routes import bp
    return bp

# Behövs bara om has_socketio = True:
def register_socketio(socketio):
    from .events import MittNamespace
    socketio.on_namespace(MittNamespace('/mitt_addon'))

3. Definiera routes i routes.py

from flask import Blueprint, jsonify, request, abort
import db
import extensions
from auth import login_required

bp = Blueprint('mitt_addon', __name__, url_prefix='/api/mitt_addon')

@bp.route('/settings', methods=['GET'])
def get_settings():
    state = db.get_state()
    return jsonify({
        'enabled': state.get('mitt_addon_enabled', '0') == '1',
        'value':   state.get('mitt_addon_value', ''),
    })

@bp.route('/settings', methods=['POST'])
@login_required
def set_settings():
    data    = request.get_json() or {}
    enabled = bool(data.get('enabled', False))
    value   = str(data.get('value', ''))
    db.set_state('mitt_addon_enabled', '1' if enabled else '0')
    db.set_state('mitt_addon_value',   value)
    extensions.socketio.emit('mitt_addon_update', {'enabled': enabled, 'value': value})
    return '', 204

Tips — AddonDB för namnrymdsavgränsad DB-åtkomst:

from db import AddonDB
adb = AddonDB('mitt_addon_')   # prefixar alla nycklar automatiskt

adb.set('enabled', '1')        # lagras som "mitt_addon_enabled"
adb.get('value', 'default')    # läser "mitt_addon_value"
adb.get_all()                  # {'enabled': '1', 'value': '...'} (utan prefix)

4. Adminpanel — templates/addons/mitt_addon/panel.html

HTML-fragmentet inkluderas automatiskt i admin.html om has_panel = True. Det behöver ingen <html>/<body> — bara innehållet:

<section>
  <h2>Mitt Addon</h2>
  <div class="prop-grid">
    <label>Aktivera</label>
    <label style="display:flex;align-items:center;gap:.5rem;font-size:.9rem">
      <input type="checkbox" id="mitt-addon-enabled">
      Aktivera funktionen
    </label>

    <label>Värde</label>
    <input type="text" id="mitt-addon-value" placeholder="...">
  </div>
  <div class="row" style="margin-top:.75rem">
    <button onclick="sparaMittAddon()">Spara</button>
    <span id="mitt-addon-status" style="color:#aaa;font-size:.85rem;align-self:center"></span>
  </div>
</section>

5. Display-overlay — static/addons/mitt_addon/overlay.js (valfritt)

Om has_overlay = True laddas skriptet automatiskt på display-sidan. Skriptet skapar sina egna DOM-element och lyssnar på SocketIO-events:

(function () {
  const el = document.createElement('div');
  el.id = 'mitt-addon-overlay';
  el.style.cssText = 'display:none;position:fixed;z-index:9000;pointer-events:none;';
  document.body.appendChild(el);

  const socket = window._kioskSocket; // definieras i display.js

  socket.on('mitt_addon_update', (data) => {
    el.style.display = data.enabled ? 'block' : 'none';
    el.textContent = data.value;
  });
})();

6. SocketIO-events — events.py (valfritt)

Behövs bara om addons behöver lyssna på WebSocket-events (inte bara sända):

from flask import request
from flask_socketio import Namespace
import extensions

class MittNamespace(Namespace):
    def on_connect(self):
        extensions.socketio.emit('mitt_status', {'ok': True}, to=request.sid,
                                  namespace='/mitt_addon')

    def on_mitt_event(self, data):
        # hantera inkommande event
        extensions.socketio.emit('mitt_svar', {'echo': data}, namespace='/mitt_addon')

Referens — ADDON-manifest

FältTypBeskrivning
namestrUnikt ID — måste matcha mappnamnet
labelstrVisningsnamn i adminpanel
versionstrFri versionssträng
orderintSorteringsordning för panelen (lägre = högre upp)
has_panelboolTrue om templates/addons/<name>/panel.html finns
has_overlayboolTrue om static/addons/<name>/overlay.js finns
has_socketioboolTrue om register_socketio() är definierad i __init__.py
state_prefixstrPrefix för alla nycklar som addons sparar i DB
default_enabledboolOm addons är aktiverat första gången (kan ändras i admin)

Aktivera / inaktivera ett addon

Varje addon kan slås av och på via API (kräver admin-inloggning):

# Inaktivera
curl -X POST http://localhost:5000/api/addons/mitt_addon/toggle \
     -H "Cookie: session=..."

# Lista status för alla addons
curl http://localhost:5000/api/addons -H "Cookie: session=..."

Ändringen sparas i databasen och träder i kraft vid nästa omstart.


Konfiguration

kiosk.conf

Genereras automatiskt av adminpanelen. Kan redigeras manuellt:

KIOSK_DISPLAY_MODE=1920x1080@60Hz
KIOSK_DISPLAY_OUTPUT=HDMI-A-1
KIOSK_INTERNAL_DISPLAY=off
KIOSK_AUDIO_PROFILE_INDEX=1

Miljövariabler (kiosk/start.sh)

export KIOSK_USER=admin
export KIOSK_PASS=mittlösenord
export SECRET_KEY=en-lång-hemlig-sträng

Teknikstack

KomponentTeknologi
BackendPython 3, Flask, Flask-SocketIO, eventlet
Addon-systemFlask Blueprints + pkgutil.iter_modules()
SchemaläggareAPScheduler
DatabasSQLite
FrontendVanilla JS, Socket.IO
CompositorSway (Wayland)
WebbläsareChromium (kiosk-läge, ozone-wayland)
LjudPipeWire (wpctl, pw-cli)
Wi-Fiwpa_supplicant + wpa_cli

Felsökning

Kiosken startar inte:

bash /home/mrfox/codes/display/kiosk/kiosk-launcher.sh

Flask svarar inte:

cd /home/mrfox/codes/display/kiosk && bash start.sh

Sway startar inte / svart skärm:

# Kontrollera att HDMI är anslutet, sedan:
SWAYSOCK=$(ls /run/user/$(id -u)/sway-ipc.*.sock 2>/dev/null | head -1)
swaymsg -s "$SWAYSOCK" exit

Wi-Fi-sektionen i admin visar "sudo saknas":

echo 'mrfox ALL=(ALL) NOPASSWD: /usr/sbin/wpa_cli' | sudo tee /etc/sudoers.d/kiosk-wifi
sudo chmod 440 /etc/sudoers.d/kiosk-wifi

Markör syns på displayen: Kontrollera att seat seat0 hide_cursor 3000 finns i sway.conf (läggs dit av install.sh).

Translate-popup i Chromium: sway-apps.sh pre-seedar Default/Preferences med "translate": {"enabled": false} innan Chromium startar. Om det ändå visas: ta bort /tmp/kiosk-display och starta om.

Ett addon laddas inte: Kontrollera att mappnamnet matchar ADDON["name"] och att __init__.py innehåller ett ADDON-dict. Addons loggas vid uppstart:

[addon_loader] Laddade addon: mitt_addon (Mitt Addon)