Cleanup repo + harden offline install and watchdog

Repo hygiene:
- Remove dead scripts (.wait-for-display, .display-keepalive, .keep-screen-alive)
- Remove stale backup/scratch (install.sh.bak, New.txt, stale .pyc)
- Stop tracking machine-specific/generated state (config/app_config.json,
  playlists/server_playlist.json, src/player_auth.json, generated launchers)
- Prune duplicate wheel versions (one per package)
- Reorganize working_files -> dev/, dev/tests/, documentation/dev-notes/, scripts/
- Extend .gitignore; fix no-op rules in repo/.gitignore

Offline install (install.sh):
- Non-fatal system-dep step; per-package apt retry; .deb bundle install
- Skip pip self-upgrade offline; guard online fallbacks behind connectivity probe
- Dependency-free fetch_url (curl/wget/urllib); --no-reboot flag
- Fix corrupted literal backslash-bang escapes (shebangs, test -f, banner)
- Idempotent cron install (no more stacked @reboot lines)

Autostart / watchdog:
- start.sh: single-instance flock lock (.player_watchdog.lock) acquired only
  after a display is confirmed, preventing parallel watchdogs
- Set XDG_RUNTIME_DIR when missing; exit early if headless
- run_player.sh: activate .venv; stop_player.sh: clear lock file
This commit is contained in:
SKE087
2026-10-14 23:29:14 +03:00
parent f7b889be11
commit 31807f37bf
60 changed files with 633 additions and 1469 deletions
+245
View File
@@ -0,0 +1,245 @@
# Web Link Playlist Items — Player Integration Guide
This document describes the changes required on the **Kiwy-Signage player**
(<https://gitea.moto-adv.com/ske087/Kiwy-Signage.git>) to support a new
playlist item type: **`weblink`** (display a live web page / URL instead of an
uploaded media file).
> The DigiServer (this repo, `digiserver-v2`) side will be updated to emit
> `weblink` items in the playlist API. The player does **not** yet support them.
> Use this guide to implement the player side later.
---
## 1. Background — how items flow today
```
DigiServer API ──JSON──▶ player sync (get_playlists_v2.py) ──▶ playlist.json ──▶ main.py renders
/api/playlists downloads files to media/ by file extension
```
Each playlist item the server returns currently looks like:
```json
{
"id": 42,
"file_name": "promo.jpg",
"type": "image",
"duration": 10,
"position": 1,
"url": "https://server/digiserver/static/uploads/promo.jpg",
"edit_on_player": false
}
```
The player:
1. **Syncs** (`src/get_playlists_v2.py` → `download_media_files()`): downloads
`url` into the local `media/` directory, then rewrites each item keeping only
`file_name`, `url` (now a **local relative path**), `duration`,
`edit_on_player`. **Note: the `type` field is currently discarded here.**
2. **Renders** (`src/main.py` → `play_current_media()`): opens the local file
and chooses a Kivy widget purely by **file extension**
(`.mp4/.avi/...` → `Video`, `.jpg/.png/...` → `AsyncImage`). Unknown
extensions are skipped as "unsupported".
A web link breaks all three assumptions: there is no file to download, no
extension to switch on, and no widget that renders a web page.
---
## 2. New server contract (what DigiServer will send)
A web-link playlist item will look like this:
```json
{
"id": 91,
"file_name": "weblink-3f9c1a2b",
"type": "weblink",
"duration": 30,
"position": 4,
"url": "https://example.com/dashboard",
"edit_on_player": false
}
```
Key differences vs. a file item:
| Field | File item | Web-link item |
|--------------|-----------------------------------|----------------------------------------|
| `type` | `image` / `video` | **`weblink`** |
| `url` | Path to a file on the server | **The web page to display (the link)** |
| `file_name` | Real filename on disk | Synthetic id (`weblink-<uuid>`), **no file exists** |
The player must branch on `type == "weblink"` and treat `url` as the page to
open — **never** try to download it as a file.
---
## 3. Required player changes
### 3.1 Sync step — `src/get_playlists_v2.py`
**Function:** `download_media_files(playlist, media_dir, ...)`
1. **Skip download for web links.** At the top of the per-item loop, detect
`media.get('type') == 'weblink'` and do **not** call `session.get()` / write
any file for it.
2. **Preserve `type` and the original `url`.** The `updated_media` dict that is
appended to `updated_playlist` currently drops `type` and rewrites `url` to a
local path. It must now carry `type` through, and for web links keep `url`
as the original web address (do not convert to a local relative path).
Suggested shape of the per-item logic:
```python
item_type = media.get('type', '')
if item_type == 'weblink':
# No file to download — pass the web link through unchanged.
updated_playlist.append({
'file_name': media.get('file_name', ''),
'type': 'weblink',
'url': media.get('url', ''), # the actual web page
'duration': media.get('duration', 10),
'edit_on_player': False,
})
continue
# ... existing download logic for file items ...
updated_playlist.append({
'file_name': file_name,
'type': item_type, # <-- now preserved
'url': os.path.relpath(local_path, os.path.dirname(media_dir)),
'duration': duration,
'edit_on_player': media.get('edit_on_player', False),
})
```
3. **`delete_unused_media()`** walks `media/` using `file_name`. Web links have
no file, so they simply won't match anything on disk — no change strictly
required, but make sure a missing local file for a `weblink` item does not
trigger a re-download or an error elsewhere.
### 3.2 Render step — `src/main.py`
**Function:** `play_current_media(self, force_reload=False)`
The current logic builds `media_path = os.path.join(self.media_dir, file_name)`
and then does `os.stat(media_path)` — which will fail for a web link (no file).
Add a **web-link branch before the file-existence check**:
```python
media_item = self.playlist[self.current_index]
file_name = media_item.get('file_name', '')
duration = media_item.get('duration', 10)
# NEW: handle web links before any file/path handling
if media_item.get('type') == 'weblink':
self.play_weblink(media_item.get('url', ''), duration)
return
# ... existing file existence check + extension branching ...
```
Then add a new method `play_weblink(self, url, duration)`:
- Validate the scheme is `http`/`https` (reject anything else, e.g. `file://`).
- Open the page for `duration` seconds, then advance with `self.next_media()`.
- Wrap in `try/except`; on failure increment `self.consecutive_errors` and call
`self.next_media()`, matching the existing error-handling pattern.
- Make sure the previous widget (`self.current_widget`) is removed/stopped just
like the image/video paths do.
#### Rendering approach (pick one)
Kivy has **no production-grade embedded web view**, especially on Raspberry Pi.
Recommended options, in order of robustness:
1. **Chromium kiosk overlay (recommended).** Launch Chromium over the Kivy
window for the item's duration, then close it and return to Kivy:
```python
import subprocess, shutil
from urllib.parse import urlparse
from kivy.clock import Clock
def play_weblink(self, url, duration):
scheme = urlparse(url).scheme.lower()
if scheme not in ('http', 'https'):
Logger.warning(f"SignagePlayer: Refusing non-http(s) weblink: {url}")
self.next_media()
return
try:
browser = shutil.which('chromium-browser') or shutil.which('chromium')
self._weblink_proc = subprocess.Popen([
browser,
'--kiosk', '--app=' + url,
'--noerrdialogs', '--disable-infobars',
'--incognito', '--no-first-run',
'--check-for-update-interval=31536000',
])
Clock.schedule_once(lambda dt: self._close_weblink_and_next(), duration)
except Exception as e:
Logger.error(f"SignagePlayer: Error opening weblink: {e}")
self.consecutive_errors += 1
self.next_media()
def _close_weblink_and_next(self):
proc = getattr(self, '_weblink_proc', None)
if proc and proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except Exception:
proc.kill()
self._weblink_proc = None
self.next_media()
```
Requirements / notes:
- Install Chromium on the player image (`chromium-browser` on Raspberry Pi OS).
- Ensure Chromium gets window focus over Kivy and is fully killed before the
next item, including on pause/stop/restart paths and on app shutdown
(`on_stop`) so no stray browser window is left behind.
- On Wayland/X11 the player already sets `SDL_VIDEODRIVER`; verify Chromium
launches on the same display/session.
2. **Embedded web view widget** (`kivy_garden.webview`, WebKit/GTK, or WebView2).
Cleaner UX (stays inside the Kivy widget tree) but fragile and poorly
supported on Pi/Wayland — only pursue if option 1 is unacceptable.
3. **Server-side screenshot fallback (no player change).** If embedding a live
browser is not desirable, DigiServer can periodically screenshot the URL and
store it as a normal `image` item; the player then needs no changes. This
loses live/animated content. Documented here for completeness only.
---
## 4. Checklist for the player update
- [ ] `get_playlists_v2.py`: skip download when `type == 'weblink'`.
- [ ] `get_playlists_v2.py`: preserve `type` in the rewritten playlist items
(fixes the current loss of `type`).
- [ ] `get_playlists_v2.py`: keep the original web `url` for weblink items.
- [ ] `main.py` `play_current_media()`: branch to `play_weblink()` **before** the
`os.stat()` file check.
- [ ] `main.py`: implement `play_weblink(url, duration)` (Chromium kiosk).
- [ ] `main.py`: validate scheme is `http`/`https`; reject others.
- [ ] Kill/cleanup the browser process on next item, pause, stop, restart, and
`on_stop`.
- [ ] Install Chromium on the player image / document it in the player README.
- [ ] Test: mixed playlist (image → video → weblink → image) cycles correctly
and respects per-item `duration`.
---
## 5. Security notes
- Only allow `http`/`https` schemes on both server and player; never open
`file://`, `chrome://`, etc.
- The server validates and stores the URL when the operator adds it; the player
should still re-validate the scheme before launching the browser (defence in
depth).
- Consider running Chromium with `--incognito` (no persistent cookies/cache) as
shown above.
@@ -0,0 +1,182 @@
# USB Card Reader Authentication
This document describes the USB card reader authentication feature for the Kiwy Signage Player.
## Overview
The player now supports user authentication via USB card readers when accessing the edit/drawing interface. When a user clicks the edit button (pencil icon), they must swipe their card to authenticate before being allowed to edit the image.
## How It Works
1. **Edit Button Click**: User clicks the pencil icon to edit the current image
2. **Validation Checks**:
- Verify current media is an image (not video)
- Check if editing is allowed for this media (`edit_on_player` permission from server)
3. **Card Reader Prompt**:
- Display "Please swipe your card..." message
- Wait for card swipe (5 second timeout)
- Read card data from USB card reader
- Store the card data (no validation required)
4. **Open Edit Interface**: Edit interface opens with card data stored
5. **Save & Upload**: When user saves the edited image:
- Card data is included in the metadata JSON
- Both image and metadata (with card data) are uploaded to server
- Server receives `user_card_data` field for tracking who edited the image
## Card Reader Setup
### Hardware Requirements
- USB card reader (HID/keyboard emulation type)
- Compatible cards (magnetic stripe or RFID depending on reader)
### Software Requirements
The player requires the `evdev` Python library to interface with USB input devices:
```bash
# Install via apt (recommended for Raspberry Pi)
sudo apt-get install python3-evdev
# Or via pip
pip3 install evdev
```
### Fallback Mode
If `evdev` is not available, the player will:
- Log a warning message
- Use a default card value (`DEFAULT_USER_12345`) for testing
- This allows development and testing without hardware
## Card Data Storage
The card data is captured as a raw string and stored without validation or mapping:
- **No preprocessing**: Card data is stored exactly as received from the reader
- **Format**: Whatever the card reader sends (typically numeric or alphanumeric)
- **Sent to server**: Raw card data is included in the `user_card_data` field of the metadata JSON
- **Server-side processing**: The server can validate, map, or process the card data as needed
### Metadata JSON Format
When an image is saved, the metadata includes:
```json
{
"time_of_modification": "2025-12-08T10:30:00",
"original_name": "image.jpg",
"new_name": "image_e_v1.jpg",
"original_path": "/path/to/image.jpg",
"version": 1,
"user_card_data": "123456789"
}
```
If no card is swiped (timeout), `user_card_data` will be `null`.
## Testing the Card Reader
A test utility is provided to verify card reader functionality:
```bash
cd /home/pi/Desktop/Kiwy-Signage/dev/tests
python3 test_card_reader.py
```
The test tool will:
1. List all available input devices
2. Auto-detect the card reader (or let you select manually)
3. Listen for card swipes and display the data received
4. Show how the data will be processed
### Test Output Example
```
✓ Card data received: '123456789'
Length: 9 characters
Processed ID: card_123456789
```
## Implementation Details
### Main Components
1. **CardReader Class** (`main.py`)
- Handles USB device detection
- Reads input events from card reader
- Provides async callback interface
- Includes timeout handling (5 seconds)
2. **Card Read Flow** (`show_edit_interface()` method)
- Validates media type and permissions
- Initiates card read
- Stores raw card data
- Opens edit popup
3. **Metadata Creation** (`_save_metadata()` method)
- Includes card data in metadata JSON
- No processing or validation of card data
- Sent to server as-is
### Card Data Format
Card readers typically send data as keyboard input:
- Each character is sent as a key press event
- Data ends with an ENTER key press
- Reader format: `[CARD_DATA][ENTER]`
The CardReader class:
- Captures key press events
- Builds the card data string character by character
- Completes reading when ENTER is detected
- Returns the complete card data to the callback
### Security Considerations
1. **Server-Side Validation**: Card validation should be implemented on the server
2. **Timeout**: 5-second timeout prevents infinite waiting for card swipe
3. **Logging**: All card reads are logged with the raw card data
4. **Permissions**: Edit permission must be enabled on the server (`edit_on_player`)
5. **Raw Data**: Card data is sent as-is; server is responsible for validation and authorization
## Troubleshooting
### Card Reader Not Detected
- Check USB connection
- Run `ls /dev/input/` to see available devices
- Run the test script to verify detection
- Check `evdev` is installed: `python3 -c "import evdev"`
### Card Swipes Not Recognized
- Verify card reader sends keyboard events
- Test with the `test_card_reader.py` utility
- Check card format is compatible with reader
- Ensure card is swiped smoothly at proper speed
### Card Data Not Captured
- Check card data format in logs
- Enable debug logging to see raw card data
- Test in fallback mode (without evdev) to isolate hardware issues
- Verify card swipe completes within 5-second timeout
### Permission Denied Errors
- User may need to be in the `input` group:
```bash
sudo usermod -a -G input $USER
```
- Reboot after adding user to group
## Future Enhancements
Potential improvements for the card reader system:
1. **Server Validation**: Server validates cards against database and returns authorization
2. **Card Enrollment**: Server-side UI for registering new cards
3. **Multiple Card Types**: Support for different card formats (barcode, RFID, magnetic)
4. **Client-side Validation**: Add optional local card validation before opening edit
5. **Audit Trail**: Server tracks all card usage with timestamps
6. **RFID Support**: Test and optimize for RFID readers
7. **Barcode Scanners**: Support USB barcode scanners as alternative
8. **Retry Logic**: Allow re-swipe if card read fails
## Related Files
- `/src/main.py` - Main implementation (CardReader class, authentication flow)
- `/src/edit_drowing.py` - Drawing/editing interface (uses authenticated user)
- `/dev/tests/test_card_reader.py` - Card reader test utility
- `/requirements.txt` - Dependencies (includes evdev)
+170
View File
@@ -0,0 +1,170 @@
# Card Reader Fix - Multi-USB Device Support
## Problem Description
When a USB touchscreen was connected to the Raspberry Pi, the card reader authentication was not working. The system reported "no authentication was received" even though the card reader was physically connected on a different USB port.
### Root Cause
The original `find_card_reader()` function used overly broad matching criteria:
1. It would select the **first** device with "keyboard" in its name
2. USB touchscreens often register as HID keyboard devices (for touch input)
3. The touchscreen would be detected first, blocking the actual card reader
4. No exclusion logic existed to filter out touch devices
## Solution
The fix implements a **priority-based device selection** with **exclusion filters**:
### 1. Device Exclusion List
Devices containing these keywords are now skipped:
- `touch`, `touchscreen`
- `mouse`, `mice`
- `trackpad`, `touchpad`
- `pen`, `stylus`
- `video`, `button`, `lid`
### 2. Three-Priority Device Search
**Priority 1: Explicit Card Readers**
- Devices with "card", "reader", "rfid", or "hid" in their name
- Must have keyboard capabilities (EV_KEY)
- Excludes any device matching exclusion keywords
**Priority 2: USB Keyboards**
- Devices with both "usb" AND "keyboard" in their name
- Card readers typically appear as "USB Keyboard" or similar
- Excludes touch devices and other non-card peripherals
**Priority 3: Fallback to Any Keyboard**
- Any keyboard device not in the exclusion list
- Used only if no card reader or USB keyboard is found
### 3. Enhanced Logging
The system now logs:
- All detected input devices at startup
- Which devices are being skipped and why
- Which device is ultimately selected as the card reader
## Testing
### Using the Test Script
Run the enhanced test script to identify your card reader:
```bash
cd /home/pi/Desktop/Kiwy-Signage/dev/tests
python3 test_card_reader.py
```
The script will:
1. List all input devices with helpful indicators:
- `** LIKELY CARD READER **` - devices with "card" or "reader" in name
- `(Excluded: ...)` - devices that will be skipped
- `(USB Keyboard - could be card reader)` - potential card readers
2. Auto-detect the card reader using the same logic as the main app
3. Allow manual selection by device number if auto-detection is wrong
### Example Output
```
=== Available Input Devices ===
[0] /dev/input/event0
Name: USB Touchscreen Controller
Phys: usb-0000:01:00.0-1.1/input0
Type: Keyboard/HID Input Device
(Excluded: appears to be touch/mouse/other non-card device)
[1] /dev/input/event1
Name: HID 08ff:0009
Phys: usb-0000:01:00.0-1.2/input0
Type: Keyboard/HID Input Device
** LIKELY CARD READER **
[2] /dev/input/event2
Name: Logitech USB Keyboard
Phys: usb-0000:01:00.0-1.3/input0
Type: Keyboard/HID Input Device
(USB Keyboard - could be card reader)
```
### Verifying the Fix
1. **Check Logs**: When the main app starts, check the logs for device detection:
```bash
tail -f /path/to/logfile
```
Look for messages like:
```
CardReader: Scanning input devices...
CardReader: Skipping excluded device: USB Touchscreen Controller
CardReader: Found card reader: HID 08ff:0009 at /dev/input/event1
```
2. **Test Card Swipe**:
- Start the signage player
- Click the edit button (pencil icon)
- Swipe a card
- Should successfully authenticate
3. **Multiple USB Devices**: Test with various USB configurations:
- Touchscreen + card reader
- Mouse + keyboard + card reader
- Multiple USB hubs
## Configuration
### If Auto-Detection Fails
If the automatic detection still selects the wrong device, you can:
1. **Check device names**: Run `test_card_reader.py` to see all devices
2. **Identify your card reader**: Note the exact name of your card reader
3. **Add custom exclusions**: If needed, add more keywords to the exclusion list
4. **Manual override**: Modify the priority logic to match your specific hardware
### Permissions
Ensure the user running the app has permission to access input devices:
```bash
# Add user to input group
sudo usermod -a -G input $USER
# Logout and login again for changes to take effect
```
## Files Modified
1. **src/main.py**
- Updated `CardReader.find_card_reader()` method
- Added exclusion keyword list
- Implemented priority-based search
- Enhanced logging
2. **dev/tests/test_card_reader.py**
- Updated `list_input_devices()` to show device classifications
- Updated `test_card_reader()` to use same logic as main app
- Added visual indicators for device types
## Compatibility
This fix is backward compatible:
- Works with single-device setups (no touchscreen)
- Works with multiple USB devices
- Fallback behavior unchanged for systems without card readers
- No changes to card data format or server communication
## Future Enhancements
Potential improvements for specific use cases:
1. **Configuration file**: Allow specifying device path or name pattern
2. **Device caching**: Remember the working device path to avoid re-scanning
3. **Hot-plug support**: Detect when card reader is plugged in after app starts
4. **Multi-reader support**: Support for multiple card readers simultaneously
@@ -0,0 +1,211 @@
# Debugging Media File Skips - Guide
## Summary
Your playlist has been analyzed and all 3 media files are present and valid:
- ✅ music.jpg (36,481 bytes) - IMAGE - 15s
- ✅ 130414-746934884.mp4 (6,474,921 bytes) - VIDEO - 23s
- ✅ IMG_0386.jpeg (592,162 bytes) - IMAGE - 15s
## Enhanced Logging Added
The application has been updated with detailed logging to track:
- When each media file starts playing
- File path validation
- File size and existence checks
- Media type detection
- Widget creation steps
- Scheduling of next media
- Any errors or skips
## How to See Detailed Logs
### Method 1: Run with log output
```bash
cd /home/pi/Desktop/Kiwy-Signage
source .venv/bin/activate
cd src
python3 main.py 2>&1 | tee playback.log
```
### Method 2: Check Kivy logs location
Kivy logs are typically stored in:
- Linux: `~/.kivy/logs/`
- Check with: `ls -lth ~/.kivy/logs/ | head`
## Common Reasons Media Files Get Skipped
### 1. **File Not Found**
**Symptom**: Log shows "❌ Media file not found"
**Cause**: File doesn't exist at expected path
**Solution**: Run diagnostic tool
```bash
python3 diagnose_playlist.py
```
### 2. **Unsupported File Type**
**Symptom**: Log shows "❌ Unsupported media type"
**Supported formats**:
- Videos: .mp4, .avi, .mkv, .mov, .webm
- Images: .jpg, .jpeg, .png, .bmp, .gif
**Solution**: Convert files or check extension
### 3. **Video Codec Issues**
**Symptom**: Video file exists but doesn't play
**Cause**: Video codec not supported by ffpyplayer
**Check**: Look for error in logs about codec
**Solution**: Re-encode video with H.264 codec:
```bash
ffmpeg -i input.mp4 -c:v libx264 -preset fast -crf 23 output.mp4
```
### 4. **Corrupted Media Files**
**Symptom**: File exists but throws error when loading
**Check**: Try playing file with external player
```bash
# For images
feh media/music.jpg
# For videos
vlc media/130414-746934884.mp4
# or
ffplay media/130414-746934884.mp4
```
### 5. **Memory/Performance Issues**
**Symptom**: First few files play, then skipping increases
**Cause**: Memory leak or performance degradation
**Check**: Look for "consecutive_errors" in logs
**Solution**:
- Reduce resolution setting in settings popup
- Optimize video files (lower bitrate/resolution)
### 6. **Timing Issues**
**Symptom**: Files play too fast or skip immediately
**Cause**: Duration set too low or scheduler issues
**Check**: Verify durations in playlist.json
**Current durations**: 15s (images), 23s (video)
### 7. **Permission Issues**
**Symptom**: "Permission denied" in logs
**Check**: File permissions
```bash
ls -la media/
```
**Solution**: Fix permissions
```bash
chmod 644 media/*
```
## What to Look For in Logs
### Successful Playback Pattern:
```
SignagePlayer: ===== Playing item 1/3 =====
SignagePlayer: File: music.jpg
SignagePlayer: Duration: 15s
SignagePlayer: Full path: /path/to/media/music.jpg
SignagePlayer: ✓ File exists (size: 36,481 bytes)
SignagePlayer: Extension: .jpg
SignagePlayer: Media type: IMAGE
SignagePlayer: Creating AsyncImage widget...
SignagePlayer: Adding image widget to content area...
SignagePlayer: Scheduled next media in 15s
SignagePlayer: ✓ Image displayed successfully
SignagePlayer: ✓ Media started successfully (consecutive_errors reset to 0)
```
### Skip Pattern (File Not Found):
```
SignagePlayer: ===== Playing item 2/3 =====
SignagePlayer: File: missing.mp4
SignagePlayer: Full path: /path/to/media/missing.mp4
SignagePlayer: ❌ Media file not found: /path/to/media/missing.mp4
SignagePlayer: Skipping to next media...
SignagePlayer: Transitioning to next media (was index 1)
```
### Video Loading Error:
```
SignagePlayer: Loading video file.mp4 for 23s
SignagePlayer: Video provider: ffpyplayer
[ERROR ] [Video ] Error reading video
[ERROR ] SignagePlayer: Error playing video: ...
```
## Testing Tools Provided
### 1. Diagnostic Tool
```bash
python3 diagnose_playlist.py
```
Checks:
- Playlist file exists and is valid
- All media files exist
- File types are supported
- No case sensitivity issues
### 2. Playback Simulation
```bash
python3 test_playback_logging.py
```
Simulates the playback sequence without running the GUI
## Monitoring Live Playback
To see live logs while the app is running:
```bash
# Terminal 1: Start the app
./run_player.sh
# Terminal 2: Monitor logs
tail -f ~/.kivy/logs/kivy_*.txt
```
## Quick Fixes to Try
### 1. Clear any stuck state
```bash
rm -f src/*.pyc
rm -rf src/__pycache__
```
### 2. Test with simpler playlist
Create `playlists/test_playlist_v9.json`:
```json
{
"playlist": [
{
"file_name": "music.jpg",
"url": "media/music.jpg",
"duration": 5
}
],
"version": 9
}
```
### 3. Check video compatibility
```bash
# Install ffmpeg tools if not present
sudo apt-get install ffmpeg
# Check video info
ffprobe media/130414-746934884.mp4
```
## Getting Help
When reporting issues, please provide:
1. Output from `python3 diagnose_playlist.py`
2. Last 100 lines of Kivy log file
3. Any error messages from console
4. What you observe (which files skip? pattern?)
## Next Steps
1. **Run the app** and observe the console output
2. **Check logs** for error patterns
3. **Run diagnostic** if files are skipping
4. **Test individual files** with external players if needed
5. **Re-encode videos** if codec issues found
The enhanced logging will now tell you exactly why each file is being skipped!
@@ -0,0 +1,263 @@
# ✅ Kiwy-Signage Authentication Implementation Complete
## What Was Implemented
The Kiwy-Signage player now supports **secure authentication** with DigiServer v2 using the flow:
**hostname → password/quickconnect → get auth_code → use auth_code for API calls**
## Files Created
### 1. **Kiwy-Signage/src/player_auth.py**
- Complete authentication module
- Handles authentication, token storage, API calls
- Methods:
- `authenticate()` - Initial authentication with server
- `verify_auth()` - Verify saved auth code
- `get_playlist()` - Fetch playlist using auth code
- `send_heartbeat()` - Send status updates
- `send_feedback()` - Send player feedback
- `clear_auth()` - Clear saved credentials
### 2. **Kiwy-Signage/src/get_playlists_v2.py**
- Updated playlist management
- Uses new authentication system
- Backward compatible with existing code
- Functions:
- `ensure_authenticated()` - Auto-authenticate if needed
- `fetch_server_playlist()` - Get playlist via authenticated API
- `send_player_feedback()` - Send feedback with auth
- All existing functions updated to use auth
### 3. **Kiwy-Signage/test_authentication.py**
- Test script to verify authentication
- Run before updating main.py
- Tests:
- Server connectivity
- Authentication flow
- Playlist fetch
- Heartbeat sending
### 4. **Kiwy-Signage/MIGRATION_GUIDE.md**
- Complete migration instructions
- Troubleshooting guide
- Configuration examples
- Rollback procedures
### 5. **digiserver-v2/player_auth_module.py**
- Standalone authentication module
- Can be used in any Python project
- Same functionality as Kiwy-Signage version
### 6. **digiserver-v2/PLAYER_AUTH.md**
- Complete API documentation
- Authentication endpoint specs
- Configuration file formats
- Security considerations
## Testing Steps
### 1. Test Authentication (Recommended First)
```bash
cd /home/pi/Desktop/Kiwy-Signage
python3 test_authentication.py
```
**Expected output:**
```
✅ Server is healthy (version: 2.0.0)
🔐 Authenticating with server...
✅ Authentication successful!
Player: Demo Player
📋 Testing playlist fetch...
✅ Playlist received!
💓 Testing heartbeat...
✅ Heartbeat sent successfully
✅ All tests passed! Player is ready to use.
```
### 2. Update Main Player App
In `Kiwy-Signage/src/main.py`, change:
```python
# OLD:
from get_playlists import update_playlist_if_needed, ...
# NEW:
from get_playlists_v2 import update_playlist_if_needed, ...
```
### 3. Run Player
```bash
cd /home/pi/Desktop/Kiwy-Signage/src
python3 main.py
```
## Configuration
### No Changes Needed!
Your existing `app_config.txt` works as-is:
```json
{
"server_ip": "192.168.1.100",
"port": "5000",
"screen_name": "player-001",
"quickconnect_key": "QUICK123",
...
}
```
### Authentication Storage
Auto-created at `src/player_auth.json`:
```json
{
"hostname": "player-001",
"auth_code": "rrX4JtM99e4e6ni0VCsuIstjTVQQqILXeRmGu_Ek2Ks",
"player_id": 1,
"player_name": "Demo Player",
"group_id": 5,
"orientation": "Landscape",
"authenticated": true,
"server_url": "http://192.168.1.100:5000"
}
```
## DigiServer v2 Setup
### 1. Create Player
Via Web UI (http://your-server:5000):
1. Login as admin
2. Go to Players → Add Player
3. Fill in:
- **Name**: Display name
- **Hostname**: player-001 (must match `screen_name` in config)
- **Password**: (optional, use quickconnect instead)
- **Quick Connect Code**: QUICK123 (must match `quickconnect_key`)
- **Orientation**: Landscape/Portrait
### 2. Test API Manually
```bash
# Test authentication
curl -X POST http://your-server:5000/api/auth/player \
-H "Content-Type: application/json" \
-d '{
"hostname": "player-001",
"quickconnect_code": "QUICK123"
}'
# Expected response:
{
"success": true,
"player_id": 1,
"player_name": "Demo Player",
"auth_code": "rrX4JtM99e4e6ni0VCsuIstjTVQQqILXeRmGu_Ek2Ks",
...
}
```
## Security Features
✅ **Auth Code Storage**: Saved locally, not transmitted after initial auth
✅ **Bcrypt Hashing**: Passwords and quickconnect codes hashed in database
✅ **Token-Based**: Auth codes are 32-byte URL-safe tokens
✅ **Rate Limiting**: Authentication endpoint limited to 10 requests/minute
✅ **Session Management**: Server tracks player sessions and status
## Advantages Over Old System
### Old System (v1)
```
Player → [hostname + quickconnect on EVERY request] → Server
↓
Bcrypt verification on every API call (slow)
```
### New System (v2)
```
Player → [hostname + quickconnect ONCE] → Server
↓
Returns auth_code
↓
Player → [auth_code for all subsequent requests] → Server
↓
Fast token validation
```
**Benefits:**
- 🚀 **10x faster API calls** (no bcrypt on every request)
- 🔒 **More secure** (credentials only sent once)
- 📊 **Better tracking** (server knows player sessions)
- 🔄 **Easier management** (can revoke auth codes)
## Troubleshooting
### Authentication Fails
**Check:**
1. Player exists in DigiServer v2 with matching hostname
2. Quickconnect code matches exactly (case-sensitive)
3. Server is accessible: `curl http://server:5000/api/health`
### Auth Code Expired
**Solution:**
```bash
rm /home/pi/Desktop/Kiwy-Signage/src/player_auth.json
# Restart player - will auto-authenticate
```
### Old get_playlists.py Issues
**Keep both files:**
- `get_playlists.py` - Original (for DigiServer v1)
- `get_playlists_v2.py` - New (for DigiServer v2)
Can switch between them by changing import in `main.py`.
## Next Steps
1. ✅ **Test authentication** with `test_authentication.py`
2. ✅ **Update main.py** to use `get_playlists_v2`
3. ✅ **Run player** and verify playlist loading
4. ✅ **Monitor logs** for first 24 hours
5. ✅ **Update other players** one at a time
## Files Summary
```
Kiwy-Signage/
├── src/
│ ├── player_auth.py # ✨ NEW: Authentication module
│ ├── get_playlists_v2.py # ✨ NEW: Updated playlist fetcher
│ ├── get_playlists.py # OLD: Keep for v1 compatibility
│ ├── main.py # Update import to use v2
│ └── player_auth.json # ✨ AUTO-CREATED: Auth storage
├── test_authentication.py # ✨ NEW: Test script
├── MIGRATION_GUIDE.md # ✨ NEW: Migration docs
└── resources/
└── app_config.txt # Existing config (no changes needed)
digiserver-v2/
├── app/
│ ├── models/player.py # ✨ UPDATED: Added auth methods
│ └── blueprints/api.py # ✨ UPDATED: Added auth endpoints
├── player_auth_module.py # ✨ NEW: Standalone module
├── player_config_template.ini # ✨ NEW: Config template
├── PLAYER_AUTH.md # ✨ NEW: API documentation
└── reinit_db.sh # ✨ NEW: Database recreation script
```
---
## 🎉 Implementation Complete!
The Kiwy-Signage player authentication system is now compatible with DigiServer v2 using secure token-based authentication. Test with `test_authentication.py` before deploying to production.
@@ -0,0 +1,182 @@
# Investigation Results: Media File Skipping
## Diagnostic Summary
✅ **All 3 media files are present and valid:**
- music.jpg (36,481 bytes) - IMAGE
- 130414-746934884.mp4 (6,474,921 bytes) - VIDEO (H.264, 1920x1080, compatible)
- IMG_0386.jpeg (592,162 bytes) - IMAGE
✅ **No file system issues found:**
- All files exist
- Correct permissions
- No case sensitivity problems
- Supported file types
✅ **Video codec is compatible:**
- H.264 codec (fully supported by ffpyplayer)
- 1920x1080 @ 29.97fps
- Reasonable bitrate (2.3 Mbps)
## Potential Root Causes Identified
### 1. **Video Widget Not Properly Stopping** (Most Likely)
When transitioning from video to the next media, the video widget may not be properly stopped before removal. This could cause:
- The video to continue playing in background
- Race conditions with scheduling
- Next media appearing to "skip"
**Location**: `play_current_media()` line 417-420
```python
if self.current_widget:
self.ids.content_area.remove_widget(self.current_widget)
self.current_widget = None
```
**Fix**: Stop video before removing widget
### 2. **Multiple Scheduled Events**
The `Clock.schedule_once(self.next_media, duration)` could be called multiple times if widget loading triggers multiple events.
**Location**: Lines 510, 548
**Fix**: Add `Clock.unschedule()` before scheduling
### 3. **Video Loading Callback Issues**
The video `loaded` callback might not fire or might fire multiple times, causing state confusion.
**Location**: `_on_video_loaded()` line 516
### 4. **Pause State Not Properly Checked**
If the player gets paused/unpaused during media transition, scheduling could get confused.
**Location**: `next_media()` line 551
## What Enhanced Logging Will Show
With the new logging, you'll see patterns like:
### If Videos Are Being Skipped:
```
===== Playing item 2/3 =====
File: 130414-746934884.mp4
Extension: .mp4
Media type: VIDEO
Loading video...
Creating Video widget...
[SHORT PAUSE OR ERROR]
Transitioning to next media (was index 1)
===== Playing item 3/3 =====
```
### If Duration Is Too Short:
```
Creating Video widget...
Scheduled next media in 23s
[Only 1-2 seconds pass]
Transitioning to next media
```
## Recommended Fixes
I've added comprehensive logging. Here are additional fixes to try:
### Fix 1: Properly Stop Video Widget Before Removal
Add this to `play_current_media()` before removing widget:
```python
# Remove previous media widget
if self.current_widget:
# Stop video if it's playing
if isinstance(self.current_widget, Video):
self.current_widget.state = 'stop'
self.current_widget.unload()
self.ids.content_area.remove_widget(self.current_widget)
self.current_widget = None
```
### Fix 2: Ensure Scheduled Events Don't Overlap
Modify scheduling in both `play_video()` and `play_image()`:
```python
# Unschedule any pending transitions before scheduling new one
Clock.unschedule(self.next_media)
Clock.schedule_once(self.next_media, duration)
```
### Fix 3: Add Video State Monitoring
Track when video actually starts playing vs when widget is created.
## How to Test
### 1. Run with Enhanced Logging
```bash
cd /home/pi/Desktop/Kiwy-Signage
source .venv/bin/activate
cd src
python3 main.py 2>&1 | tee ../playback_debug.log
```
Watch the console output. You should see:
- Each media file being loaded
- Timing information
- Any errors or skips
### 2. Check Timing
If media skips, check the log for timing:
- Does "Scheduled next media in Xs" appear?
- How long until "Transitioning to next media" appears?
- Is it immediate (< 1 second) = scheduling bug
- Is it after full duration = normal operation
### 3. Look for Error Patterns
Search the log for:
```bash
grep "❌" playback_debug.log
grep "Error" playback_debug.log
grep "consecutive_errors" playback_debug.log
```
## Quick Test Scenario
Create a test with just one file to isolate the issue:
```json
{
"playlist": [
{
"file_name": "music.jpg",
"url": "media/music.jpg",
"duration": 10
}
],
"version": 99
}
```
If this single image repeats correctly every 10s, the issue is with video playback or transitions.
## What to Report
When you run the app, please capture:
1. **Console output** - especially the pattern around skipped files
2. **Which files skip?** - Is it always videos? Always after videos?
3. **Timing** - Do files play for full duration before skipping?
4. **Pattern** - First loop OK then skips? Always skips certain file?
## Tools Created
1. **diagnose_playlist.py** - Check file system issues
2. **test_playback_logging.py** - Simulate playback logic
3. **check_video_codecs.py** - Verify video compatibility
4. **Enhanced main.py** - Detailed logging throughout
## Next Actions
1. ✅ Run `diagnose_playlist.py` - **PASSED**
2. ✅ Run `check_video_codecs.py` - **PASSED**
3. ⏳ Run app with logging and observe pattern
4. ⏳ Apply video widget fixes if needed
5. ⏳ Report findings for further diagnosis
The enhanced logging will pinpoint exactly where and why files are being skipped!
+276
View File
@@ -0,0 +1,276 @@
# Kiwy-Signage Player Migration Guide
## Updating to DigiServer v2 Authentication
This guide explains how to update your Kiwy-Signage player to use the new secure authentication system with DigiServer v2.
## What Changed?
### Old System (v1)
- Direct API calls with hostname + quickconnect code on every request
- No persistent authentication
- Credentials sent with every API call
### New System (v2)
- **Step 1**: Authenticate once with hostname + password/quickconnect
- **Step 2**: Receive and save auth_code
- **Step 3**: Use auth_code for all subsequent API calls
- **Benefits**: More secure, faster, supports session management
## Migration Steps
### 1. Copy New Files
Copy the authentication modules to your Kiwy-Signage project:
```bash
cd /home/pi/Desktop/Kiwy-Signage/src/
# New files are already created:
# - player_auth.py (authentication module)
# - get_playlists_v2.py (updated playlist fetcher)
```
### 2. Update main.py Imports
In `main.py`, replace the old import:
```python
# OLD:
from get_playlists import (
update_playlist_if_needed,
send_playing_status_feedback,
send_playlist_restart_feedback,
send_player_error_feedback
)
# NEW:
from get_playlists_v2 import (
update_playlist_if_needed,
send_playing_status_feedback,
send_playlist_restart_feedback,
send_player_error_feedback
)
```
### 3. Add Authentication on Startup
In `main.py`, add authentication check in the `SignagePlayer` class:
```python
def build(self):
"""Build the application UI"""
# Load configuration
self.config = self.load_config()
# NEW: Authenticate with server
from player_auth import PlayerAuth
auth = PlayerAuth()
if not auth.is_authenticated():
Logger.info("First time setup - authenticating...")
from get_playlists_v2 import ensure_authenticated
if not ensure_authenticated(self.config):
Logger.error("❌ Failed to authenticate with server!")
# Show error popup or retry
else:
Logger.info(f"✅ Authenticated as: {auth.get_player_name()}")
# Continue with normal startup...
return SignagePlayerWidget(config=self.config)
```
### 4. Update Server Configuration
Your existing `app_config.txt` works as-is! The new system uses the same fields:
```json
{
"server_ip": "your-server-ip",
"port": "5000",
"screen_name": "player-001",
"quickconnect_key": "QUICK123",
...
}
```
**Note**: `screen_name` is now used as `hostname` for authentication.
### 5. Testing
1. **Stop the old player**:
```bash
pkill -f main.py
```
2. **Delete old authentication data** (first time only):
```bash
rm -f /home/pi/Desktop/Kiwy-Signage/src/player_auth.json
```
3. **Start the updated player**:
```bash
cd /home/pi/Desktop/Kiwy-Signage/src/
python3 main.py
```
4. **Check logs for authentication**:
- Look for: `✅ Authentication successful`
- Or: `❌ Authentication failed: [error message]`
## Troubleshooting
### Authentication Fails
**Problem**: `❌ Authentication failed: Invalid credentials`
**Solution**:
1. Verify player exists in DigiServer v2:
- Login to http://your-server:5000
- Go to Players → check if hostname exists
2. Verify quickconnect code:
- In DigiServer, check player's Quick Connect Code
- Update `app_config.txt` with correct code
3. Check server URL:
```python
# Test connection
import requests
response = requests.get('http://your-server:5000/api/health')
print(response.json()) # Should show: {'status': 'healthy'}
```
### Auth Code Expired
**Problem**: Player was working, now shows auth errors
**Solution**:
```bash
# Clear saved auth and re-authenticate
rm /home/pi/Desktop/Kiwy-Signage/src/player_auth.json
# Restart player - will auto-authenticate
```
### Can't Connect to Server
**Problem**: `Cannot connect to server`
**Solution**:
1. Check server is running:
```bash
curl http://your-server:5000/api/health
```
2. Check network connectivity:
```bash
ping your-server-ip
```
3. Verify server URL in `app_config.txt`
## Configuration Files
### player_auth.json (auto-created)
This file stores the authentication token:
```json
{
"hostname": "player-001",
"auth_code": "rrX4JtM99e4e6ni0VCsuIstjTVQQqILXeRmGu_Ek2Ks",
"player_id": 1,
"player_name": "Demo Player",
"group_id": 5,
"orientation": "Landscape",
"authenticated": true,
"server_url": "http://your-server:5000"
}
```
**Important**: Keep this file secure! It contains your player's access token.
## Advanced: Custom Authentication
If you need custom authentication logic:
```python
from player_auth import PlayerAuth
# Initialize
auth = PlayerAuth(config_file='custom_auth.json')
# Authenticate with password instead of quickconnect
success, error = auth.authenticate(
server_url='http://your-server:5000',
hostname='player-001',
password='your_secure_password' # Use password instead
)
if success:
print(f"✅ Authenticated as: {auth.get_player_name()}")
# Get playlist
playlist_data = auth.get_playlist()
# Send heartbeat
auth.send_heartbeat(status='playing')
# Send feedback
auth.send_feedback(
message="Playing video.mp4",
status="playing",
playlist_version=5
)
else:
print(f"❌ Failed: {error}")
```
## Rollback to Old System
If you need to rollback:
```bash
cd /home/pi/Desktop/Kiwy-Signage/src/
# Rename new files
mv get_playlists_v2.py get_playlists_v2.py.backup
mv player_auth.py player_auth.py.backup
# Use old get_playlists.py (keep as-is)
# Old system will continue working with DigiServer v1
```
## Benefits of New System
✅ **More Secure**: Auth tokens instead of passwords in every request
✅ **Better Performance**: No bcrypt verification on every API call
✅ **Session Management**: Server tracks player sessions
✅ **Easier Debugging**: Auth failures vs API failures are separate
✅ **Future-Proof**: Ready for token refresh, expiration, etc.
## Next Steps
Once migration is complete:
1. **Monitor player logs** for first 24 hours
2. **Verify playlist updates** are working
3. **Check feedback** is being received in DigiServer
4. **Update other players** one at a time
## Support
If you encounter issues:
1. **Check player logs**: `tail -f player.log`
2. **Check server logs**: DigiServer v2 logs in `instance/logs/`
3. **Test API manually**:
```bash
# Test authentication
curl -X POST http://your-server:5000/api/auth/player \
-H "Content-Type: application/json" \
-d '{"hostname":"player-001","quickconnect_code":"QUICK123"}'
```
---
**Migration completed!** Your Kiwy-Signage player now uses secure authentication with DigiServer v2. 🎉
@@ -0,0 +1,159 @@
# Offline Installation Guide
This guide explains how to set up and use offline installation for the Kiwy Signage Player.
## Overview
The offline installation system allows you to install the signage player on devices without internet access by pre-downloading all necessary packages.
## Directory Structure
```
Kiwy-Signage/
├── repo/ # Offline packages repository
│ ├── python-wheels/ # Python packages (.whl files)
│ ├── system-packages/ # System package information
│ │ ├── apt-packages.txt # List of required apt packages
│ │ └── debs/ # Downloaded .deb files (optional)
│ └── README.md
├── scripts/
│ ├── download_offline_packages.sh # Download Python packages
│ └── download_deb_packages.sh # Download system .deb packages
└── install.sh # Smart installer (online/offline)
```
## Setup for Offline Installation
### Step 1: Prepare on a Connected System
On a system with internet access (preferably Raspberry Pi OS):
```bash
# Clone the repository
git clone <repository-url>
cd Kiwy-Signage
# Download Python packages
bash scripts/download_offline_packages.sh
# (Optional) Download system .deb packages
bash scripts/download_deb_packages.sh
```
This will populate the `repo/` folder with all necessary packages.
### Step 2: Transfer to Offline System
Copy the entire `Kiwy-Signage` directory to your offline system:
```bash
# Using USB drive
cp -r Kiwy-Signage /media/usb/
# Or create a tarball
tar -czf kiwy-signage-offline.tar.gz Kiwy-Signage/
# On target system, extract:
tar -xzf kiwy-signage-offline.tar.gz
cd Kiwy-Signage
```
### Step 3: Install on Offline System
The installer automatically detects offline packages:
```bash
# Automatic detection
bash install.sh
# Or explicitly specify offline mode
bash install.sh --offline
```
## Package Information
### Python Packages (requirements.txt)
- **kivy==2.1.0** - UI framework
- **requests==2.32.4** - HTTP library
- **bcrypt==4.2.1** - Password hashing
- **aiohttp==3.9.1** - Async HTTP client
- **asyncio==3.4.3** - Async I/O framework
### System Packages (APT)
See `repo/system-packages/apt-packages.txt` for complete list:
- Python development tools
- SDL2 libraries (video/audio)
- FFmpeg and codecs
- GStreamer plugins
- Build dependencies
## Online Installation
If you have internet access, simply run:
```bash
bash install.sh
```
The installer will automatically download and install all packages from the internet.
## Updating Offline Packages
To update the offline package cache:
```bash
# On a connected system
bash scripts/download_offline_packages.sh
```
This will download the latest versions of all packages.
## Troubleshooting
### Problem: Missing Dependencies
If installation fails due to missing dependencies:
```bash
# Download .deb packages with dependencies
bash scripts/download_deb_packages.sh
# Install with dependency resolution
sudo apt install -f
```
### Problem: Wheel Not Found
If a specific Python package wheel is not found:
```bash
# Download specific package
pip3 download <package-name> -d repo/python-wheels/
```
### Problem: Architecture Mismatch
Ensure packages are downloaded on the same architecture (ARM for Raspberry Pi):
```bash
# Verify architecture
uname -m # Should show: armv7l or aarch64
# Force ARM downloads
pip3 download -r requirements.txt -d repo/python-wheels/ --platform linux_armv7l
```
## Storage Requirements
- **Python wheels**: ~50-100 MB
- **System .deb packages**: ~200-500 MB (if downloaded)
- **Total**: ~250-600 MB
## Notes
- The `repo/` folder is designed to be portable
- Downloaded packages are excluded from git (see `.gitignore`)
- The installer supports both online and offline modes seamlessly
- System packages list is maintained in `repo/system-packages/apt-packages.txt`
@@ -0,0 +1,43 @@
# Offline Installation Quick Start
## For Connected System (Preparation)
```bash
# 1. Download Python packages (required)
bash scripts/download_offline_packages.sh
# 2. Download system .deb packages (optional, for fully offline)
bash scripts/download_deb_packages.sh
```
## For Offline System (Installation)
```bash
# The installer auto-detects offline packages
bash install.sh
# Or explicitly use offline mode
bash install.sh --offline
```
## What's Included
### Python Packages
✅ Kivy 2.3.1
✅ Requests 2.34.2
✅ Bcrypt 4.2.1
✅ Aiohttp 3.11.16 (async HTTP)
✅ ffpyplayer 4.5.3 (video)
✅ evdev 1.9.1 (card reader)
✅ All dependencies
### System Packages
📋 See `repo/system-packages/apt-packages.txt`
## File Size
- Python wheels: ~50 MB
- System packages: ~200-500 MB (if .deb downloaded)
## See Also
- **OFFLINE_INSTALLATION.md** - Complete guide
- **repo/README.md** - Repository structure
+190
View File
@@ -0,0 +1,190 @@
# 🚀 Quick Start Guide - Player Authentication
## For DigiServer Admin
### 1. Create Player in DigiServer v2
```bash
# Login to web interface
http://your-server:5000
# Navigate to: Players → Add Player
Name: Office Player
Hostname: office-player-001 # Must be unique
Location: Main Office
Password: [leave empty if using quickconnect]
Quick Connect Code: OFFICE123 # Easy pairing code
Orientation: Landscape
```
### 2. Distribute Credentials to Player
Give the player administrator:
- **Server URL**: `http://your-server:5000`
- **Hostname**: `office-player-001`
- **Quick Connect Code**: `OFFICE123`
## For Player Setup
### 1. Update app_config.txt
```json
{
"server_ip": "your-server-ip",
"port": "5000",
"screen_name": "office-player-001",
"quickconnect_key": "OFFICE123",
...
}
```
### 2. Test Authentication
```bash
cd /home/pi/Desktop/Kiwy-Signage
python3 test_authentication.py
```
### 3. Update Player Code (One-Time)
In `src/main.py`, line ~34, change:
```python
from get_playlists_v2 import ( # Changed from get_playlists
update_playlist_if_needed,
send_playing_status_feedback,
send_playlist_restart_feedback,
send_player_error_feedback
)
```
### 4. Run Player
```bash
cd /home/pi/Desktop/Kiwy-Signage/src
python3 main.py
```
## Authentication Flow
```
┌─────────┐ ┌────────────┐
│ Player │ │ DigiServer │
└────┬────┘ └─────┬──────┘
│ │
│ POST /api/auth/player │
│ {hostname, quickconnect} │
├──────────────────────────────>│
│ │
│ 200 OK │
│ {auth_code, player_id, ...} │
│<──────────────────────────────┤
│ │
│ Save auth_code locally │
├──────────────────┐ │
│ │ │
│<─────────────────┘ │
│ │
│ GET /api/playlists/{id} │
│ Header: Bearer {auth_code} │
├──────────────────────────────>│
│ │
│ 200 OK │
│ {playlist, version} │
│<──────────────────────────────┤
│ │
```
## Files to Know
### Player Side (Kiwy-Signage)
```
src/
├── player_auth.json # Auto-created, stores auth_code
├── player_auth.py # Authentication module
├── get_playlists_v2.py # Updated playlist fetcher
└── app_config.txt # Your existing config
```
### Server Side (DigiServer v2)
```
app/
├── models/player.py # Player model with auth methods
└── blueprints/api.py # Authentication endpoints
API Endpoints:
- POST /api/auth/player # Authenticate and get token
- POST /api/auth/verify # Verify token validity
- GET /api/playlists/{id} # Get playlist (requires auth)
- POST /api/players/{id}/heartbeat # Send status (requires auth)
```
## Common Commands
```bash
# Test authentication
./test_authentication.py
# Clear saved auth (re-authenticate)
rm src/player_auth.json
# Check server health
curl http://your-server:5000/api/health
# Manual authentication test
curl -X POST http://your-server:5000/api/auth/player \
-H "Content-Type: application/json" \
-d '{"hostname":"player-001","quickconnect_code":"QUICK123"}'
# View player logs
tail -f player.log
# View server logs (if running Flask dev server)
# Logs appear in terminal where server is running
```
## Troubleshooting One-Liners
```bash
# Authentication fails → Check player exists
curl http://your-server:5000/api/health
# Auth expired → Clear and retry
rm src/player_auth.json && python3 main.py
# Can't connect → Test network
ping your-server-ip
# Wrong quickconnect → Check in DigiServer web UI
# Go to: Players → [Your Player] → Edit → View Quick Connect Code
```
## Security Notes
- ✅ Auth code saved in `player_auth.json` (keep secure!)
- ✅ Quickconnect code hashed with bcrypt in database
- ✅ Auth endpoints rate-limited (10 req/min)
- ✅ Auth codes are 32-byte secure tokens
- ⚠️ Use HTTPS in production!
- ⚠️ Rotate quickconnect codes periodically
## Quick Wins
### Before (Old System)
- Every API call = send hostname + quickconnect
- Server runs bcrypt check on every request
- Slow response times
- No session tracking
### After (New System)
- Authenticate once = get auth_code
- All subsequent calls use auth_code
- 10x faster API responses
- Server tracks player sessions
- Can revoke access instantly
---
**Ready to go!** 🎉 Test with `./test_authentication.py` then start your player!
+152
View File
@@ -0,0 +1,152 @@
# Kivy Signage Player
A modern digital signage player built with Kivy framework that displays content from DigiServer playlists.
## Features
- **Cross-platform**: Runs on Linux, Windows, and macOS
- **Modern UI**: Built with Kivy framework for smooth graphics and animations
- **Multiple Media Types**: Supports images (JPG, PNG, GIF, BMP) and videos (MP4, AVI, MKV, MOV, WEBM)
- **Server Integration**: Fetches playlists from DigiServer with automatic updates
- **Player Feedback**: Reports status and playback information to server
- **Fullscreen Display**: Optimized for digital signage displays
- **Touch Controls**: Mouse/touch-activated control panel
- **Auto-restart**: Continuous playlist looping
- **Error Handling**: Robust error handling with server feedback
## Installation
1. **Install system dependencies:**
```bash
chmod +x install.sh
./install.sh
```
2. **Configure the player:**
Edit `config/app_config.json` with your server details:
```json
{
"server_ip": "your-server-ip",
"port": "5000",
"screen_name": "your-player-name",
"quickconnect_key": "your-quickconnect-code"
}
```
## Usage
### Recommended: Using Start Script (with Virtual Environment)
```bash
chmod +x start.sh
./start.sh
```
This script automatically:
- Activates the Python virtual environment
- Checks for configuration
- Starts the player
### Alternative: Using Run Script
```bash
chmod +x run_player.sh
./run_player.sh
```
### Manual Start
```bash
# With virtual environment
source .venv/bin/activate
cd src
python3 main.py
# Without virtual environment
cd src
python3 main.py
```
## Controls
- **Mouse/Touch Movement**: Shows control panel for 3 seconds
- **Previous (⏮)**: Go to previous media item
- **Pause/Play (⏸/▶)**: Toggle playback
- **Next (⏭)**: Skip to next media item
- **Settings (⚙)**: View player configuration and status
- **Exit (⏻)**: Close the application
## Directory Structure
```
Kiwi-signage/
├── src/
│ ├── main.py # Main Kivy application
│ └── get_playlists.py # Playlist management and server communication
├── config/
│ └── app_config.json # Player configuration
├── media/ # Downloaded media files (auto-generated)
├── playlists/ # Playlist cache (auto-generated)
├── requirements.txt # Python dependencies
├── install.sh # Installation script
├── run_player.sh # Run script
└── README.md # This file
```
## Configuration Options
### app_config.json
- `server_ip`: IP address or domain of DigiServer
- `port`: Port number of DigiServer (default: 5000)
- `screen_name`: Unique identifier for this player
- `quickconnect_key`: Authentication key for server access
## Features Comparison with Tkinter Player
| Feature | Kivy Player | Tkinter Player |
|---------|-------------|----------------|
| Cross-platform | ✅ Better | ✅ Good |
| Modern UI | ✅ Excellent | ❌ Basic |
| Touch Support | ✅ Native | ❌ Limited |
| Video Playback | ✅ Built-in | ✅ VLC Required |
| Performance | ✅ GPU Accelerated | ❌ CPU Only |
| Animations | ✅ Smooth | ❌ None |
| Mobile Ready | ✅ Yes | ❌ No |
## Server Integration
The player communicates with DigiServer via REST API:
- **Playlist Fetch**: `GET /api/playlists`
- **Player Feedback**: `POST /api/player-feedback`
Status updates sent to server:
- Playlist check and update notifications
- Current playback status
- Error reports
- Playlist restart notifications
## Troubleshooting
### Installation Issues
- Make sure system dependencies are installed: `./install.sh`
- For ARM devices (Raspberry Pi), ensure proper SDL2 libraries
### Playback Issues
- Check media file formats are supported
- Verify network connection to DigiServer
- Check player configuration in settings
### Server Connection
- Verify server IP and port in configuration
- Check quickconnect key is correct
- Ensure DigiServer is running and accessible
## Development
Based on the proven architecture of the tkinter signage player with modern Kivy enhancements:
- **Playlist Management**: Inherited from `get_playlists.py`
- **Media Playback**: Kivy's built-in Video and AsyncImage widgets
- **Server Communication**: REST API calls with feedback system
- **Error Handling**: Comprehensive exception handling with server reporting
## License
This project is part of the DigiServer digital signage system.