De functie voor het opslaan van instellingen is verbeterd met uitgebreide foutafhandeling door gebruik te maken van try-except blokken. Er is nu een controle toegevoegd om te bepalen of de code in een Docker-container draait voordat 'amixer' commando's worden uitgevoerd, wat ongewenste fouten voorkomt als 'amixer' niet beschikbaar is. Dit verhoogt de robuustheid bij het instellen van Pi HDMI volume. Onverwachte fouten worden gelogd en het proces gaat verder zonder onderbreking.
799 lines
30 KiB
Python
799 lines
30 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
|
|
from config import *
|
|
from hijridate import Gregorian
|
|
|
|
app = Flask(__name__)
|
|
|
|
# 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
|
|
|
|
def fetch_weather_data():
|
|
"""Haalt weersinformatie op voor de geconfigureerde locatie"""
|
|
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 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 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
|
|
|
|
def fetch_sonos_zones():
|
|
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'])
|
|
def instellingen():
|
|
settings = load_settings()
|
|
clips = [f for f in os.listdir(CLIPS_PATH) if f.endswith('.mp3') or f.endswith('.wav')]
|
|
alle_zones = fetch_sonos_zones()
|
|
|
|
if request.method == 'POST':
|
|
try:
|
|
# Nieuwe volume instellingen
|
|
if 'volume_day' in request.form and 'volume_night' in request.form:
|
|
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['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')
|
|
|
|
return render_template('settings.html',
|
|
settings=settings,
|
|
alle_zones=alle_zones,
|
|
clips=clips)
|
|
|
|
@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
|
|
|
|
# Stel Pi volume in via amixer
|
|
try:
|
|
# Stel PCM volume in (HDMI audio)
|
|
subprocess.run(['amixer', 'set', 'PCM', f'{volume}%'],
|
|
check=True, capture_output=True, text=True)
|
|
|
|
# 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)
|
|
|
|
print(f"🔊 Pi HDMI volume ingesteld op {volume}%")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Pi HDMI volume ingesteld op {volume}%',
|
|
'volume': volume
|
|
})
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"❌ Amixer fout: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': f'Kon volume niet instellen: {e.stderr if e.stderr else str(e)}'
|
|
}), 500
|
|
|
|
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) |