508 lines
19 KiB
Python
508 lines
19 KiB
Python
from flask import Flask, render_template, request, redirect, send_from_directory, jsonify, request as flask_request
|
|
import requests, json, os, random
|
|
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 = '192.168.0.114' # IP van host waar Sonos API draait
|
|
|
|
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
|
|
|
|
# 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)
|
|
|
|
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:
|
|
return settings.get('volume_night', settings.get('volume', 30) // 2)
|
|
else:
|
|
return settings.get('volume_day', settings.get('volume', 30))
|
|
|
|
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"),
|
|
"Zuhr": data.get("zuhr_jamah", "00:00"),
|
|
"Asr": data.get("asr_jamah", "00:00"),
|
|
"Maghrib": data.get("maghrib_jamah", "00:00"),
|
|
"Isha": data.get("isha_jamah", "00:00")
|
|
}
|
|
|
|
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':
|
|
# 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']
|
|
|
|
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')
|
|
|
|
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()
|
|
gebedstijden = {}
|
|
|
|
try:
|
|
res = requests.get(VUMG_API)
|
|
res.raise_for_status()
|
|
data = res.json()[0]
|
|
|
|
gebedstijden = {
|
|
"Fajr": data.get("fajr_jamah", "00:00"),
|
|
"Zuhr": data.get("zuhr_jamah", "00:00"),
|
|
"Asr": data.get("asr_jamah", "00:00"),
|
|
"Maghrib": data.get("maghrib_jamah", "00:00"),
|
|
"Isha": data.get("isha_jamah", "00:00")
|
|
}
|
|
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"
|
|
}
|
|
|
|
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
|
|
|
|
try:
|
|
data = flask_request.get_json()
|
|
prayer_name = data.get('prayer', 'Test')
|
|
test_time = data.get('time', 'Onbekend')
|
|
|
|
settings = load_settings()
|
|
|
|
# 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"""
|
|
return render_template('quran.html')
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=80) |