Het aantal workers voor de ThreadPoolExecutor is verhoogd van 3 naar 5 om de uitvoering te verbeteren. Nieuwe data-ophaalfuncties zijn toegevoegd, inclusief error handling met fallback-opties. De index methode gebruikt nu fetch_data_parallel voor efficiëntere API-calls en fouttolerantie. Dit verbetert de algehele responsiviteit en stabiliteit van de applicatie.
879 lines
33 KiB
Python
879 lines
33 KiB
Python
from flask import Flask, render_template, request, redirect, send_from_directory, jsonify, request as flask_request
|
|
import requests, json, os, random, subprocess
|
|
from datetime import datetime, timedelta
|
|
from config import *
|
|
from hijridate import Gregorian
|
|
from functools import lru_cache
|
|
import time
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Cache configuratie
|
|
CACHE_DURATION = 300 # 5 minuten cache voor API calls
|
|
last_api_call = {}
|
|
cached_data = {}
|
|
executor = ThreadPoolExecutor(max_workers=5) # Verhoogd naar 5 workers voor betere parallelle uitvoering
|
|
|
|
def get_cached_data(key, fetch_func, duration=CACHE_DURATION):
|
|
"""Haal data uit cache of voer fetch_func uit als cache verlopen is"""
|
|
current_time = time.time()
|
|
|
|
if key in cached_data and key in last_api_call:
|
|
if current_time - last_api_call[key] < duration:
|
|
return cached_data[key]
|
|
|
|
data = fetch_func()
|
|
cached_data[key] = data
|
|
last_api_call[key] = current_time
|
|
return data
|
|
|
|
def fetch_data_parallel():
|
|
"""Haal alle data parallel op met verbeterde error handling"""
|
|
futures = {
|
|
'weather': executor.submit(fetch_weather_data),
|
|
'sonos': executor.submit(fetch_sonos_zones),
|
|
'date': executor.submit(get_date_info),
|
|
'hadith': executor.submit(load_hadith),
|
|
'prayer_times': executor.submit(fetch_prayer_times)
|
|
}
|
|
|
|
results = {}
|
|
for key, future in futures.items():
|
|
try:
|
|
results[key] = future.result(timeout=5) # Timeout van 5 seconden per request
|
|
except Exception as e:
|
|
print(f"⚠️ Fout bij ophalen {key} data: {e}")
|
|
results[key] = get_fallback_data(key)
|
|
|
|
return results
|
|
|
|
def get_fallback_data(key):
|
|
"""Geef fallback data terug als API calls falen"""
|
|
fallbacks = {
|
|
'weather': {
|
|
'temperature': '--',
|
|
'feels_like': '--',
|
|
'description': 'Weer niet beschikbaar',
|
|
'humidity': '--',
|
|
'wind_speed': '--',
|
|
'icon': '01d'
|
|
},
|
|
'sonos': ['Woonkamer'],
|
|
'date': get_date_info(), # Gebruik lokale functie als fallback
|
|
'hadith': {
|
|
"text": "De daden die Allah het meest liefheeft zijn degenen die regelmatig worden verricht, zelfs als ze klein zijn.",
|
|
"bron": "Sahih al-Bukhari"
|
|
},
|
|
'prayer_times': {
|
|
"Fajr": "06:00",
|
|
"Zuhr": "12:30",
|
|
"Asr": "15:00",
|
|
"Maghrib": "17:30",
|
|
"Isha": "19:00"
|
|
}
|
|
}
|
|
return fallbacks.get(key, {})
|
|
|
|
@lru_cache(maxsize=1)
|
|
def fetch_prayer_times():
|
|
"""Haal gebedstijden op met caching"""
|
|
try:
|
|
res = requests.get(VUMG_API, timeout=5)
|
|
res.raise_for_status()
|
|
data = res.json()[0]
|
|
|
|
return {
|
|
"Fajr": data.get("fajr_jamah", "00:00")[:5],
|
|
"Zuhr": data.get("zuhr_jamah", "00:00")[:5],
|
|
"Asr": data.get("asr_jamah", "00:00")[:5],
|
|
"Maghrib": data.get("maghrib_jamah", "00:00")[:5],
|
|
"Isha": data.get("isha_jamah", "00:00")[:5]
|
|
}
|
|
except Exception as e:
|
|
print(f"⚠️ Fout bij ophalen gebedstijden: {e}")
|
|
return get_fallback_data('prayer_times')
|
|
|
|
# Voeg cache-control headers toe voor statische bestanden
|
|
@app.after_request
|
|
def add_header(response):
|
|
if 'Cache-Control' not in response.headers:
|
|
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
|
return response
|
|
|
|
# Specifieke route voor statische bestanden met cache-control
|
|
@app.route('/static/<path:filename>')
|
|
def serve_static(filename):
|
|
return send_from_directory('static', filename, cache_timeout=0)
|
|
|
|
SETTINGS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.json')
|
|
HADITHS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'hadiths.json')
|
|
VUMG_API = 'https://www.vumg.nl/?rest_route=/dpt/v1/prayertime&filter=today'
|
|
CLIPS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'clips')
|
|
# SONOS_API_IP wordt nu geïmporteerd uit config.py
|
|
|
|
# Globale variabelen
|
|
debug_time_offset = 0 # Offset in seconden voor debug mode
|
|
debug_mode_active = False
|
|
|
|
@lru_cache(maxsize=1)
|
|
def load_settings():
|
|
try:
|
|
if os.path.exists(SETTINGS_PATH) and os.path.getsize(SETTINGS_PATH) > 0:
|
|
with open(SETTINGS_PATH) as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
print(f"⚠️ Fout bij laden van settings: {e}")
|
|
return {"volume": 30, "zones": ["Woonkamer"], "audio_clip": "adhan1.mp3"}
|
|
|
|
def fetch_weather_data():
|
|
"""Haalt weersinformatie op voor de geconfigureerde locatie"""
|
|
return get_cached_data('weather', lambda: _fetch_weather_data_impl())
|
|
|
|
def _fetch_weather_data_impl():
|
|
try:
|
|
params = {
|
|
'q': WEATHER_LOCATION,
|
|
'appid': OPENWEATHER_API_KEY,
|
|
'units': 'metric',
|
|
'lang': 'nl'
|
|
}
|
|
response = requests.get('https://api.openweathermap.org/data/2.5/weather', params=params, timeout=5)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
weather_info = {
|
|
'temperature': round(data['main']['temp']),
|
|
'feels_like': round(data['main']['feels_like']),
|
|
'description': data['weather'][0]['description'].capitalize(),
|
|
'humidity': data['main']['humidity'],
|
|
'wind_speed': round(data['wind']['speed'] * 3.6), # m/s naar km/h
|
|
'icon': data['weather'][0]['icon']
|
|
}
|
|
return weather_info
|
|
except Exception as e:
|
|
print(f"⚠️ Fout bij ophalen weerdata: {e}")
|
|
return {
|
|
'temperature': '--',
|
|
'feels_like': '--',
|
|
'description': 'Weer niet beschikbaar',
|
|
'humidity': '--',
|
|
'wind_speed': '--',
|
|
'icon': '01d'
|
|
}
|
|
|
|
def load_hadith():
|
|
try:
|
|
with open(HADITHS_PATH) as f:
|
|
hadiths = json.load(f)['hadiths']
|
|
return random.choice(hadiths)
|
|
except Exception as e:
|
|
print(f"⚠️ Fout bij laden van hadith: {e}")
|
|
return {"text": "De daden die Allah het meest liefheeft zijn degenen die regelmatig worden verricht, zelfs als ze klein zijn.", "bron": "Sahih al-Bukhari"}
|
|
|
|
def get_current_volume(settings):
|
|
"""Bepaal het juiste volume op basis van de huidige tijd"""
|
|
from datetime import datetime, timedelta
|
|
|
|
# Fallback naar oude volume als nieuwe instellingen niet bestaan
|
|
if 'volume_day' not in settings or 'volume_night' not in settings:
|
|
return settings.get('volume', 30)
|
|
|
|
# Gebruik debug tijd als actief
|
|
global debug_mode_active, debug_time_offset
|
|
if debug_mode_active:
|
|
current_time = (datetime.now() + timedelta(seconds=debug_time_offset)).strftime('%H:%M')
|
|
print(f"🔧 Volume bepaling gebruikt debug tijd: {current_time}")
|
|
else:
|
|
current_time = datetime.now().strftime('%H:%M')
|
|
|
|
night_start = settings.get('night_start', '20:00')
|
|
day_start = settings.get('day_start', '07:00')
|
|
|
|
# Converteer tijden naar vergelijkbare format
|
|
current_minutes = int(current_time[:2]) * 60 + int(current_time[3:5])
|
|
night_minutes = int(night_start[:2]) * 60 + int(night_start[3:5])
|
|
day_minutes = int(day_start[:2]) * 60 + int(day_start[3:5])
|
|
|
|
# Bepaal of het avond is
|
|
if night_minutes > day_minutes: # Normale dag/nacht cyclus (bijv. dag 07:00-20:00)
|
|
is_night = current_minutes >= night_minutes or current_minutes < day_minutes
|
|
else: # Avond gaat over middernacht (bijv. avond 20:00-07:00)
|
|
is_night = current_minutes >= night_minutes and current_minutes < day_minutes
|
|
|
|
if is_night:
|
|
volume = settings.get('volume_night', settings.get('volume', 30) // 2)
|
|
print(f"🌙 Avond volume gebruikt: {volume} (tijd: {current_time})")
|
|
return volume
|
|
else:
|
|
volume = settings.get('volume_day', settings.get('volume', 30))
|
|
print(f"☀️ Dag volume gebruikt: {volume} (tijd: {current_time})")
|
|
return volume
|
|
|
|
def apply_prayer_offsets(gebedstijden, settings):
|
|
"""Pas offsets toe op gebedstijden"""
|
|
from datetime import datetime, timedelta
|
|
|
|
offsets = {
|
|
'Fajr': settings.get('fajr_offset', 0),
|
|
'Zuhr': settings.get('zuhr_offset', 0),
|
|
'Asr': settings.get('asr_offset', 0),
|
|
'Maghrib': settings.get('maghrib_offset', 0),
|
|
'Isha': settings.get('isha_offset', 0)
|
|
}
|
|
|
|
adjusted_times = {}
|
|
|
|
for prayer, original_time in gebedstijden.items():
|
|
offset_minutes = offsets.get(prayer, 0)
|
|
|
|
if offset_minutes == 0:
|
|
adjusted_times[prayer] = original_time
|
|
continue
|
|
|
|
try:
|
|
# Parse de originele tijd
|
|
time_obj = datetime.strptime(original_time, '%H:%M')
|
|
|
|
# Voeg offset toe
|
|
adjusted_time = time_obj + timedelta(minutes=offset_minutes)
|
|
|
|
# Converteer terug naar string
|
|
adjusted_times[prayer] = adjusted_time.strftime('%H:%M')
|
|
|
|
if offset_minutes != 0:
|
|
print(f"📅 {prayer}: {original_time} → {adjusted_times[prayer]} ({offset_minutes:+d} min)")
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Fout bij aanpassen {prayer} tijd: {e}")
|
|
adjusted_times[prayer] = original_time
|
|
|
|
return adjusted_times
|
|
|
|
@lru_cache(maxsize=1)
|
|
def fetch_sonos_zones():
|
|
return get_cached_data('sonos_zones', lambda: _fetch_sonos_zones_impl())
|
|
|
|
def _fetch_sonos_zones_impl():
|
|
try:
|
|
res = requests.get(f'http://{SONOS_API_IP}:5005/zones', timeout=5)
|
|
res.raise_for_status()
|
|
data = res.json()
|
|
zones = []
|
|
for group in data:
|
|
for player in group['members']:
|
|
zones.append(player['roomName'])
|
|
return sorted(set(zones))
|
|
except Exception as e:
|
|
print(f'Fout bij ophalen Sonos-zones: {e}')
|
|
return ['Woonkamer', 'Slaapkamer', 'Keuken']
|
|
|
|
def get_date_info():
|
|
"""Genereert zowel Gregoriaanse als Hijri datum informatie"""
|
|
now = datetime.now()
|
|
|
|
# Gregoriaanse datum
|
|
gregorian_date = now.strftime('%A %d %B %Y')
|
|
gregorian_short = now.strftime('%d-%m-%Y')
|
|
|
|
# Nederlandse maandnamen
|
|
dutch_months = {
|
|
'January': 'januari', 'February': 'februari', 'March': 'maart',
|
|
'April': 'april', 'May': 'mei', 'June': 'juni',
|
|
'July': 'juli', 'August': 'augustus', 'September': 'september',
|
|
'October': 'oktober', 'November': 'november', 'December': 'december'
|
|
}
|
|
|
|
# Nederlandse dagnamen
|
|
dutch_days = {
|
|
'Monday': 'maandag', 'Tuesday': 'dinsdag', 'Wednesday': 'woensdag',
|
|
'Thursday': 'donderdag', 'Friday': 'vrijdag', 'Saturday': 'zaterdag', 'Sunday': 'zondag'
|
|
}
|
|
|
|
# Vervang Engelse namen door Nederlandse
|
|
for eng, nl in dutch_months.items():
|
|
gregorian_date = gregorian_date.replace(eng, nl)
|
|
for eng, nl in dutch_days.items():
|
|
gregorian_date = gregorian_date.replace(eng, nl)
|
|
|
|
try:
|
|
# Converteer naar Hijri datum
|
|
hijri_date = Gregorian(now.year, now.month, now.day).to_hijri()
|
|
|
|
# Arabische maandnamen
|
|
hijri_months = [
|
|
'محرم', 'صفر', 'ربيع الأول', 'ربيع الثاني', 'جمادى الأولى', 'جمادى الثانية',
|
|
'رجب', 'شعبان', 'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة'
|
|
]
|
|
|
|
# Nederlandse maandnamen voor Hijri
|
|
hijri_months_nl = [
|
|
'Muharram', 'Safar', 'Rabi al-Awwal', 'Rabi al-Thani', 'Jumada al-Awwal', 'Jumada al-Thani',
|
|
'Rajab', 'Sha\'ban', 'Ramadan', 'Shawwal', 'Dhu al-Qi\'dah', 'Dhu al-Hijjah'
|
|
]
|
|
|
|
hijri_arabic = f"{hijri_date.day} {hijri_months[hijri_date.month - 1]} {hijri_date.year} هـ"
|
|
hijri_dutch = f"{hijri_date.day} {hijri_months_nl[hijri_date.month - 1]} {hijri_date.year} AH"
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Fout bij Hijri conversie: {e}")
|
|
hijri_arabic = "التاريخ الهجري غير متاح"
|
|
hijri_dutch = "Hijri datum niet beschikbaar"
|
|
|
|
return {
|
|
'gregorian_full': gregorian_date,
|
|
'gregorian_short': gregorian_short,
|
|
'hijri_arabic': hijri_arabic,
|
|
'hijri_dutch': hijri_dutch,
|
|
'current_time': now.strftime('%H:%M')
|
|
}
|
|
|
|
@app.route('/')
|
|
def index():
|
|
settings = load_settings()
|
|
|
|
# Haal alle data parallel op
|
|
data = fetch_data_parallel()
|
|
|
|
# Verwerk gebedstijden
|
|
gebedstijden = apply_prayer_offsets(data['prayer_times'], settings)
|
|
|
|
# Bepaal volgende gebed
|
|
now = datetime.now().strftime('%H:%M')
|
|
next_time = "Onbekend"
|
|
next_name = "Onbekend"
|
|
|
|
for naam, tijd in gebedstijden.items():
|
|
if tijd > now:
|
|
next_time = tijd
|
|
next_name = naam
|
|
break
|
|
else:
|
|
next_time = list(gebedstijden.values())[0]
|
|
next_name = list(gebedstijden.keys())[0]
|
|
|
|
return render_template('index.html',
|
|
next_time=next_time,
|
|
next_name=next_name,
|
|
dua="اللّهُمَّ اجْعَلْ صَلاتِي نُورًا",
|
|
hadith=data['hadith'],
|
|
gebedstijden=gebedstijden,
|
|
settings=settings,
|
|
weather=data['weather'],
|
|
date_info=data['date'])
|
|
|
|
@app.route('/instellingen', methods=['GET', 'POST'])
|
|
def instellingen():
|
|
if request.method == 'POST':
|
|
try:
|
|
# Nieuwe volume instellingen
|
|
if 'volume_day' in request.form and 'volume_night' in request.form:
|
|
settings = load_settings()
|
|
settings['volume_day'] = int(request.form['volume_day'])
|
|
settings['volume_night'] = int(request.form['volume_night'])
|
|
settings['night_start'] = request.form['night_start']
|
|
settings['day_start'] = request.form['day_start']
|
|
# Update oude volume voor backward compatibility
|
|
settings['volume'] = settings['volume_day']
|
|
else:
|
|
# Fallback: gebruik default waarden als velden ontbreken
|
|
settings = load_settings()
|
|
settings['volume_day'] = settings.get('volume_day', 15)
|
|
settings['volume_night'] = settings.get('volume_night', 8)
|
|
settings['night_start'] = settings.get('night_start', '20:00')
|
|
settings['day_start'] = settings.get('day_start', '07:00')
|
|
settings['volume'] = settings['volume_day']
|
|
|
|
# Gebedstijd offsets
|
|
settings['fajr_offset'] = int(request.form.get('fajr_offset', 0))
|
|
settings['zuhr_offset'] = int(request.form.get('zuhr_offset', 0))
|
|
settings['asr_offset'] = int(request.form.get('asr_offset', 0))
|
|
settings['maghrib_offset'] = int(request.form.get('maghrib_offset', 0))
|
|
settings['isha_offset'] = int(request.form.get('isha_offset', 0))
|
|
|
|
# Adzkaar instellingen
|
|
settings['adzkaar_enabled'] = 'adzkaar_enabled' in request.form
|
|
settings['adzkaar_duration'] = int(request.form.get('adzkaar_duration', 5))
|
|
|
|
# Hadith instellingen
|
|
settings['hadith_interval_seconds'] = int(request.form.get('hadith_interval_seconds', 30))
|
|
|
|
# Pi HDMI volume instelling
|
|
if 'pi_hdmi_volume' in request.form:
|
|
pi_volume = int(request.form.get('pi_hdmi_volume', 70))
|
|
settings['pi_hdmi_volume'] = pi_volume
|
|
|
|
# Probeer Pi volume in te stellen via amixer (alleen als we niet in Docker draaien)
|
|
try:
|
|
# Check of we in een Docker container draaien
|
|
if os.path.exists('/.dockerenv'):
|
|
print(f"🔊 Pi HDMI volume opgeslagen: {pi_volume}% (Docker container - amixer niet beschikbaar)")
|
|
else:
|
|
subprocess.run(['amixer', 'set', 'PCM', f'{pi_volume}%'],
|
|
check=True, capture_output=True, text=True)
|
|
print(f"🔊 Pi HDMI volume ingesteld op {pi_volume}%")
|
|
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
print(f"⚠️ Kon Pi volume niet direct instellen: {e} (volume wel opgeslagen)")
|
|
except Exception as e:
|
|
print(f"❌ Onverwachte fout bij volume instelling: {e}")
|
|
|
|
settings['zones'] = request.form.getlist('zones')
|
|
settings['audio_clip'] = request.form['audio_clip']
|
|
|
|
with open(SETTINGS_PATH, 'w') as f:
|
|
json.dump(settings, f, indent=2)
|
|
|
|
if 'test_clip' in request.form:
|
|
# Gebruik het juiste volume voor de test
|
|
test_volume = get_current_volume(settings)
|
|
for zone in settings['zones']:
|
|
url = f"http://{SONOS_API_IP}:5005/{zone}/clip/{settings['audio_clip']}/{test_volume}"
|
|
try:
|
|
r = requests.get(url, timeout=3)
|
|
print(f"🎵 Test clip verzonden naar {zone}, status: {r.status_code}")
|
|
except Exception as e:
|
|
print(f"❌ Fout bij afspelen op zone {zone}: {e}")
|
|
|
|
return redirect('/instellingen')
|
|
|
|
except Exception as e:
|
|
print(f"❌ Fout bij opslaan instellingen: {e}")
|
|
# Probeer alsnog door te gaan zonder de Pi volume instelling
|
|
return redirect('/instellingen')
|
|
|
|
# Haal alle data parallel op
|
|
data = fetch_data_parallel()
|
|
|
|
return render_template('settings.html',
|
|
settings=load_settings(),
|
|
alle_zones=data['sonos'],
|
|
date_info=data['date'],
|
|
weather=data['weather'])
|
|
|
|
@app.route('/api/hadith')
|
|
def api_hadith():
|
|
hadith = load_hadith()
|
|
return jsonify(hadith)
|
|
|
|
@app.route('/api/mute', methods=['POST'])
|
|
def toggle_mute():
|
|
try:
|
|
data = flask_request.get_json()
|
|
mute = data.get('mute', False)
|
|
with open(SETTINGS_PATH, 'r+') as f:
|
|
settings = json.load(f)
|
|
settings['mute'] = mute
|
|
f.seek(0)
|
|
json.dump(settings, f, indent=2)
|
|
f.truncate()
|
|
return jsonify({'success': True, 'mute': mute})
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
@app.route('/api/weather')
|
|
def api_weather():
|
|
"""API endpoint voor het ophalen van actuele weerdata"""
|
|
weather = fetch_weather_data()
|
|
return jsonify(weather)
|
|
|
|
@app.route('/api/date-info')
|
|
def api_date_info():
|
|
"""API endpoint voor het ophalen van actuele datum informatie"""
|
|
date_info = get_date_info()
|
|
return jsonify(date_info)
|
|
|
|
@app.route('/api/test-audio', methods=['POST'])
|
|
def test_audio():
|
|
"""Test een audio bestand lokaal in de browser"""
|
|
try:
|
|
data = flask_request.get_json()
|
|
audio_file = data.get('audio_file', 'adhan1.mp3')
|
|
|
|
# Controleer of het bestand bestaat
|
|
audio_path = os.path.join(CLIPS_PATH, audio_file)
|
|
if not os.path.exists(audio_path):
|
|
return jsonify({'success': False, 'error': 'Audio bestand niet gevonden'}), 404
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Audio bestand {audio_file} is beschikbaar',
|
|
'url': f'/static/clips/{audio_file}'
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
@app.route('/debug')
|
|
def debug():
|
|
"""Debug pagina voor het testen van adhaan functionaliteit"""
|
|
settings = load_settings()
|
|
|
|
# Check of debug mode is ingeschakeld
|
|
if not settings.get('debug_mode', False):
|
|
return redirect('/')
|
|
|
|
gebedstijden = {}
|
|
|
|
try:
|
|
res = requests.get(VUMG_API)
|
|
res.raise_for_status()
|
|
data = res.json()[0]
|
|
|
|
gebedstijden = {
|
|
"Fajr": data.get("fajr_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Zuhr": data.get("zuhr_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Asr": data.get("asr_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Maghrib": data.get("maghrib_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Isha": data.get("isha_jamah", "00:00")[:5] # Haal seconden weg
|
|
}
|
|
|
|
# Pas offsets toe op gebedstijden
|
|
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
|
|
except Exception as e:
|
|
print("❌ Fout bij ophalen gebedstijden voor debug:", e)
|
|
# Fallback tijden voor testing
|
|
gebedstijden = {
|
|
"Fajr": "06:00",
|
|
"Zuhr": "12:30",
|
|
"Asr": "15:00",
|
|
"Maghrib": "17:30",
|
|
"Isha": "19:00"
|
|
}
|
|
|
|
# Pas ook offsets toe op fallback tijden
|
|
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
|
|
|
|
return render_template('debug.html',
|
|
gebedstijden=gebedstijden,
|
|
settings=settings)
|
|
|
|
@app.route('/api/debug-adhaan', methods=['POST'])
|
|
def debug_adhaan():
|
|
"""API endpoint voor debug adhaan testing"""
|
|
import time
|
|
|
|
# Check of debug mode is ingeschakeld
|
|
settings = load_settings()
|
|
if not settings.get('debug_mode', False):
|
|
return jsonify({'success': False, 'error': 'Debug mode is niet ingeschakeld'}), 403
|
|
|
|
try:
|
|
data = flask_request.get_json()
|
|
prayer_name = data.get('prayer', 'Test')
|
|
test_time = data.get('time', 'Onbekend')
|
|
|
|
# Check mute status
|
|
if settings.get('mute', False):
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Adhaan niet afgespeeld: mute staat aan',
|
|
'muted': True
|
|
})
|
|
|
|
# Verbeterde Sonos afspeling
|
|
success_count = 0
|
|
error_messages = []
|
|
zones = settings.get('zones', ['Woonkamer'])
|
|
audio_clip = settings.get('audio_clip', 'adhan1.mp3')
|
|
volume = get_current_volume(settings)
|
|
|
|
for zone in zones:
|
|
try:
|
|
# Stop eerst alle audio op deze zone
|
|
stop_url = f"http://{SONOS_API_IP}:5005/{zone}/pause"
|
|
requests.get(stop_url, timeout=2)
|
|
time.sleep(0.5) # Korte pauze
|
|
|
|
# Speel de clip af
|
|
clip_url = f"http://{SONOS_API_IP}:5005/{zone}/clip/{audio_clip}/{volume}"
|
|
response = requests.get(clip_url, timeout=10)
|
|
|
|
if response.status_code == 200:
|
|
success_count += 1
|
|
print(f"🔧 DEBUG: Adhaan afgespeeld op {zone} voor {prayer_name} om {test_time}")
|
|
else:
|
|
error_messages.append(f"{zone}: HTTP {response.status_code}")
|
|
|
|
except requests.exceptions.Timeout:
|
|
error_messages.append(f"{zone}: Timeout")
|
|
except Exception as e:
|
|
error_messages.append(f"{zone}: {str(e)}")
|
|
|
|
# Pauze tussen zones
|
|
if len(zones) > 1:
|
|
time.sleep(1)
|
|
|
|
if success_count > 0:
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Adhaan afgespeeld op {success_count}/{len(zones)} zone(s)',
|
|
'errors': error_messages if error_messages else None
|
|
})
|
|
else:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': f'Geen zones bereikbaar: {", ".join(error_messages)}'
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
@app.route('/api/sonos-status')
|
|
def sonos_status():
|
|
"""API endpoint voor Sonos status controle"""
|
|
try:
|
|
settings = load_settings()
|
|
zones = settings.get('zones', ['Woonkamer'])
|
|
status_info = {}
|
|
|
|
for zone in zones:
|
|
try:
|
|
# Haal zone status op
|
|
status_url = f"http://{SONOS_API_IP}:5005/{zone}/state"
|
|
response = requests.get(status_url, timeout=3)
|
|
|
|
if response.status_code == 200:
|
|
zone_data = response.json()
|
|
status_info[zone] = {
|
|
'status': 'online',
|
|
'playbackState': zone_data.get('playbackState', 'unknown'),
|
|
'currentTrack': zone_data.get('currentTrack', {}).get('title', 'Geen track'),
|
|
'volume': zone_data.get('volume', 0)
|
|
}
|
|
else:
|
|
status_info[zone] = {'status': 'error', 'error': f'HTTP {response.status_code}'}
|
|
|
|
except Exception as e:
|
|
status_info[zone] = {'status': 'offline', 'error': str(e)}
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'zones': status_info,
|
|
'api_ip': SONOS_API_IP
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
@app.route('/api/sonos-stop', methods=['POST'])
|
|
def sonos_stop():
|
|
"""API endpoint om alle Sonos zones te stoppen"""
|
|
try:
|
|
settings = load_settings()
|
|
zones = settings.get('zones', ['Woonkamer'])
|
|
stopped_zones = []
|
|
|
|
for zone in zones:
|
|
try:
|
|
stop_url = f"http://{SONOS_API_IP}:5005/{zone}/pause"
|
|
response = requests.get(stop_url, timeout=3)
|
|
|
|
if response.status_code == 200:
|
|
stopped_zones.append(zone)
|
|
|
|
except Exception as e:
|
|
print(f"Fout bij stoppen {zone}: {e}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Audio gestopt op {len(stopped_zones)} zone(s)',
|
|
'stopped_zones': stopped_zones
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
@app.route('/quran')
|
|
def quran():
|
|
"""Quran speler pagina"""
|
|
settings = load_settings()
|
|
gebedstijden = {}
|
|
|
|
try:
|
|
res = requests.get(VUMG_API)
|
|
res.raise_for_status()
|
|
data = res.json()[0]
|
|
|
|
gebedstijden = {
|
|
"Fajr": data.get("fajr_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Zuhr": data.get("zuhr_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Asr": data.get("asr_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Maghrib": data.get("maghrib_jamah", "00:00")[:5], # Haal seconden weg
|
|
"Isha": data.get("isha_jamah", "00:00")[:5] # Haal seconden weg
|
|
}
|
|
|
|
# Pas offsets toe op gebedstijden
|
|
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
|
|
except Exception as e:
|
|
print("❌ Fout bij ophalen gebedstijden voor quran pagina:", e)
|
|
# Fallback tijden
|
|
gebedstijden = {
|
|
"Fajr": "06:00",
|
|
"Zuhr": "12:30",
|
|
"Asr": "15:00",
|
|
"Maghrib": "17:30",
|
|
"Isha": "19:00"
|
|
}
|
|
|
|
# Pas ook offsets toe op fallback tijden
|
|
gebedstijden = apply_prayer_offsets(gebedstijden, settings)
|
|
|
|
return render_template('quran.html',
|
|
gebedstijden=gebedstijden,
|
|
settings=settings)
|
|
|
|
@app.route('/adzkaar')
|
|
def adzkaar():
|
|
"""Adzkaar pagina na gebed"""
|
|
settings = load_settings()
|
|
duration_minutes = settings.get('adzkaar_duration', 5) # Default 5 minuten
|
|
|
|
return render_template('adzkaar.html',
|
|
duration_minutes=duration_minutes,
|
|
settings=settings)
|
|
|
|
@app.route('/api/trigger-adzkaar', methods=['POST'])
|
|
def trigger_adzkaar():
|
|
"""API endpoint om Adzkaar scherm te triggeren (voor debug)"""
|
|
try:
|
|
settings = load_settings()
|
|
|
|
# Check of Adzkaar is ingeschakeld
|
|
if not settings.get('adzkaar_enabled', True):
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Adzkaar is uitgeschakeld in instellingen'
|
|
})
|
|
|
|
duration = settings.get('adzkaar_duration', 5)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Adzkaar scherm wordt getoond voor {duration} minuten',
|
|
'duration': duration,
|
|
'url': '/adzkaar'
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
@app.route('/api/debug-time', methods=['GET', 'POST'])
|
|
def debug_time():
|
|
"""API endpoint voor debug tijd beheer"""
|
|
global debug_time_offset, debug_mode_active
|
|
|
|
if flask_request.method == 'POST':
|
|
# Check of debug mode is ingeschakeld voor POST requests
|
|
settings = load_settings()
|
|
if not settings.get('debug_mode', False):
|
|
return jsonify({'success': False, 'error': 'Debug mode is niet ingeschakeld'}), 403
|
|
|
|
try:
|
|
data = flask_request.get_json()
|
|
action = data.get('action')
|
|
|
|
if action == 'set_offset':
|
|
debug_time_offset = data.get('offset', 0)
|
|
debug_mode_active = True
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Debug tijd offset ingesteld op {debug_time_offset} seconden',
|
|
'offset': debug_time_offset,
|
|
'debug_active': debug_mode_active
|
|
})
|
|
|
|
elif action == 'reset':
|
|
debug_time_offset = 0
|
|
debug_mode_active = False
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'Debug tijd gereset naar echte tijd',
|
|
'offset': debug_time_offset,
|
|
'debug_active': debug_mode_active
|
|
})
|
|
|
|
elif action == 'adjust':
|
|
adjustment = data.get('seconds', 0)
|
|
debug_time_offset += adjustment
|
|
debug_mode_active = True
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Debug tijd aangepast met {adjustment} seconden',
|
|
'offset': debug_time_offset,
|
|
'debug_active': debug_mode_active
|
|
})
|
|
|
|
else:
|
|
return jsonify({'success': False, 'error': 'Onbekende actie'}), 400
|
|
|
|
except Exception as e:
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
else: # GET request - altijd beschikbaar voor cron script
|
|
from datetime import datetime, timedelta
|
|
|
|
current_time = datetime.now()
|
|
if debug_mode_active:
|
|
current_time += timedelta(seconds=debug_time_offset)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'current_time': current_time.strftime('%H:%M:%S'),
|
|
'current_time_display': current_time.strftime('%H:%M'),
|
|
'offset': debug_time_offset,
|
|
'debug_active': debug_mode_active,
|
|
'real_time': datetime.now().strftime('%H:%M:%S')
|
|
})
|
|
|
|
@app.route('/api/set-pi-volume', methods=['POST'])
|
|
def set_pi_volume():
|
|
"""API endpoint om Pi HDMI volume in te stellen"""
|
|
try:
|
|
data = flask_request.get_json()
|
|
volume = data.get('volume', 70)
|
|
|
|
# Valideer volume (0-100)
|
|
if not isinstance(volume, int) or volume < 0 or volume > 100:
|
|
return jsonify({'success': False, 'error': 'Volume moet tussen 0 en 100 zijn'}), 400
|
|
|
|
# Sla volume op in settings voor persistentie
|
|
settings = load_settings()
|
|
settings['pi_hdmi_volume'] = volume
|
|
|
|
with open(SETTINGS_PATH, 'w') as f:
|
|
json.dump(settings, f, indent=2)
|
|
|
|
# Check of we in een Docker container draaien
|
|
if os.path.exists('/.dockerenv'):
|
|
print(f"🔊 Pi HDMI volume opgeslagen: {volume}% (Docker container)")
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Pi HDMI volume opgeslagen op {volume}%. Volume wordt toegepast bij browser herstart.',
|
|
'volume': volume,
|
|
'info': 'Docker container - volume opgeslagen voor browser audio'
|
|
})
|
|
else:
|
|
# Direct op Pi - probeer amixer
|
|
try:
|
|
subprocess.run(['amixer', 'set', 'PCM', f'{volume}%'],
|
|
check=True, capture_output=True, text=True)
|
|
print(f"🔊 Pi HDMI volume direct ingesteld op {volume}%")
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Pi HDMI volume ingesteld op {volume}%',
|
|
'volume': volume
|
|
})
|
|
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
print(f"⚠️ Amixer fout: {e}")
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Volume opgeslagen op {volume}%. Amixer niet beschikbaar.',
|
|
'volume': volume,
|
|
'warning': str(e)
|
|
})
|
|
|
|
except Exception as e:
|
|
print(f"❌ Volume API fout: {e}")
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=80) |