Deze commit introduceert de overstap van traditionele threading naar async/await voor efficiëntere API-oproepen binnen de adhan-webapp. Dit omvat de vervanging van parallele executors met asyncio, waardoor de webapplicatie asynchroon weer- en Sonos-zonegegevens kan ophalen. Daarnaast zijn er prestatieverbeteringen in de gebruikersinterface doorgevoerd, zoals het cachen van DOM-elementen, debouncing van volume-updates en geoptimaliseerde tab-navigatie. Deze wijzigingen verbeteren de algehele snelheid en responsiviteit van de applicatie aanzienlijk.
911 lines
35 KiB
Python
911 lines
35 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
|
|
import asyncio
|
|
import aiohttp
|
|
from functools import wraps
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Cache configuratie
|
|
CACHE_DURATION = 300 # 5 minuten cache voor API calls
|
|
last_api_call = {}
|
|
cached_data = {}
|
|
executor = ThreadPoolExecutor(max_workers=3) # Voor parallelle API calls
|
|
|
|
def async_route(f):
|
|
@wraps(f)
|
|
def wrapped(*args, **kwargs):
|
|
return asyncio.run(f(*args, **kwargs))
|
|
return wrapped
|
|
|
|
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
|
|
|
|
async def fetch_weather_data_async():
|
|
"""Asynchrone versie van weather data ophalen"""
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
params = {
|
|
'q': WEATHER_LOCATION,
|
|
'appid': OPENWEATHER_API_KEY,
|
|
'units': 'metric',
|
|
'lang': 'nl'
|
|
}
|
|
async with session.get('https://api.openweathermap.org/data/2.5/weather', params=params, timeout=5) as response:
|
|
data = await 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),
|
|
'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'
|
|
}
|
|
|
|
async def fetch_sonos_zones_async():
|
|
"""Asynchrone versie van Sonos zones ophalen"""
|
|
try:
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(f'http://{SONOS_API_IP}:5005/zones', timeout=5) as response:
|
|
data = await response.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']
|
|
|
|
async def fetch_data_parallel_async():
|
|
"""Haal alle data parallel op met asyncio"""
|
|
weather_task = asyncio.create_task(fetch_weather_data_async())
|
|
sonos_task = asyncio.create_task(fetch_sonos_zones_async())
|
|
date_task = asyncio.create_task(asyncio.to_thread(get_date_info))
|
|
|
|
weather, sonos, date = await asyncio.gather(weather_task, sonos_task, date_task)
|
|
|
|
return {
|
|
'weather': weather,
|
|
'sonos': sonos,
|
|
'date': date
|
|
}
|
|
|
|
# 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()
|
|
next_time = "Onbekend"
|
|
next_name = "Onbekend"
|
|
debug_data = {}
|
|
gebedstijden = {}
|
|
hadith = load_hadith()
|
|
weather = fetch_weather_data()
|
|
date_info = get_date_info()
|
|
|
|
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)
|
|
|
|
now = datetime.now().strftime('%H:%M')
|
|
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]
|
|
|
|
debug_data = {
|
|
"api_response": data,
|
|
"gebedstijden": gebedstijden,
|
|
"now": now,
|
|
"next_time": next_time,
|
|
"next_name": next_name
|
|
}
|
|
|
|
except Exception as e:
|
|
print("❌ Fout bij ophalen/verwerken van gebedstijden:", e)
|
|
|
|
dua = "اللّهُمَّ اجْعَلْ صَلاتِي نُورًا"
|
|
return render_template('index.html',
|
|
next_time=next_time,
|
|
next_name=next_name,
|
|
dua=dua,
|
|
hadith=hadith,
|
|
gebedstijden=gebedstijden,
|
|
debug=debug_data,
|
|
settings=settings,
|
|
weather=weather,
|
|
date_info=date_info)
|
|
|
|
@app.route('/instellingen', methods=['GET', 'POST'])
|
|
@async_route
|
|
async 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 met asyncio
|
|
data = await fetch_data_parallel_async()
|
|
|
|
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) |