diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..d208eba
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,113 @@
+
+
+
+
+
+ Alarm Clock
+
+
+
+
+
Alarm Clock
+
--:--:--
+
Alarm: --:--
+
+
+
+
+
+
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index e985466..8f7925a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
astral
-requests
\ No newline at end of file
+requests
+flask
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index 23e83b3..b351cc2 100644
--- a/src/main.py
+++ b/src/main.py
@@ -3,7 +3,9 @@ from .shelly import ShellyDevice
from .audio import AudioPlayer
from .alarm_clock import AlarmClock
from .relay import Relay
+from .webserver import run_server
from os import path
+import threading
def main():
"""Main entry point for the alarm clock application."""
@@ -47,6 +49,15 @@ def main():
dismiss_action=lambda: relay.off()
)
+ # Start webserver in background thread
+ server_thread = threading.Thread(
+ target=run_server,
+ kwargs={'alarm_clock': alarm_clock, 'host': '0.0.0.0', 'port': 5000},
+ daemon=True
+ )
+ server_thread.start()
+ print("Webserver started on http://0.0.0.0:5000")
+
alarm_clock.start()
diff --git a/src/webserver.py b/src/webserver.py
new file mode 100644
index 0000000..4aabc56
--- /dev/null
+++ b/src/webserver.py
@@ -0,0 +1,117 @@
+from flask import Flask, jsonify, request, send_from_directory
+from datetime import datetime
+from zoneinfo import ZoneInfo
+from os import path
+
+from .config import CITY
+
+
+def create_app(alarm_clock=None):
+ """Create and configure the Flask application."""
+
+ # Get the absolute path to the public folder
+ public_folder = path.abspath(path.join(path.dirname(__file__), '..', 'public'))
+
+ app = Flask(__name__, static_folder=public_folder, static_url_path='')
+
+ # Store alarm_clock reference
+ app.alarm_clock = alarm_clock
+
+ # Serve static files from public folder
+ @app.route('/')
+ def serve_index():
+ return send_from_directory(public_folder, 'index.html')
+
+ @app.route('/')
+ def serve_static(filename):
+ return send_from_directory(public_folder, filename)
+
+ # API endpoints
+ @app.route('/api/status', methods=['GET'])
+ def get_status():
+ """Get current alarm clock status."""
+ now = datetime.now(ZoneInfo(CITY.timezone))
+
+ response = {
+ 'current_time': now.strftime('%H:%M:%S'),
+ 'timezone': CITY.timezone,
+ 'alarm_active': False,
+ 'alarm_time': None,
+ 'config_mode': 'normal'
+ }
+
+ if app.alarm_clock:
+ response['alarm_active'] = app.alarm_clock.alarm_active
+ response['alarm_time'] = app.alarm_clock.alarm_time.strftime('%H:%M')
+ response['config_mode'] = app.alarm_clock.config_mode
+
+ return jsonify(response)
+
+ @app.route('/api/alarm', methods=['GET'])
+ def get_alarm():
+ """Get current alarm time."""
+ if not app.alarm_clock:
+ return jsonify({'error': 'Alarm clock not initialized'}), 503
+
+ return jsonify({
+ 'hour': app.alarm_clock.alarm_time.hour,
+ 'minute': app.alarm_clock.alarm_time.minute,
+ 'formatted': app.alarm_clock.alarm_time.strftime('%H:%M')
+ })
+
+ @app.route('/api/alarm', methods=['POST'])
+ def set_alarm():
+ """Set alarm time. Expects JSON with 'hour' and 'minute'."""
+ if not app.alarm_clock:
+ return jsonify({'error': 'Alarm clock not initialized'}), 503
+
+ data = request.get_json()
+ if not data:
+ return jsonify({'error': 'JSON body required'}), 400
+
+ hour = data.get('hour')
+ minute = data.get('minute')
+
+ if hour is None or minute is None:
+ return jsonify({'error': 'Both hour and minute are required'}), 400
+
+ try:
+ hour = int(hour)
+ minute = int(minute)
+ except (ValueError, TypeError):
+ return jsonify({'error': 'Hour and minute must be integers'}), 400
+
+ if not (0 <= hour <= 23):
+ return jsonify({'error': 'Hour must be between 0 and 23'}), 400
+ if not (0 <= minute <= 59):
+ return jsonify({'error': 'Minute must be between 0 and 59'}), 400
+
+ app.alarm_clock.alarm_time = app.alarm_clock.alarm_time.replace(
+ hour=hour, minute=minute
+ )
+ app.alarm_clock.reset_wakeup_actions()
+
+ return jsonify({
+ 'success': True,
+ 'alarm_time': app.alarm_clock.alarm_time.strftime('%H:%M')
+ })
+
+ @app.route('/api/dismiss', methods=['POST'])
+ def dismiss_alarm():
+ """Dismiss the active alarm."""
+ if not app.alarm_clock:
+ return jsonify({'error': 'Alarm clock not initialized'}), 503
+
+ if not app.alarm_clock.alarm_active:
+ return jsonify({'message': 'No active alarm to dismiss'}), 200
+
+ app.alarm_clock.dismiss_alarm()
+ return jsonify({'success': True, 'message': 'Alarm dismissed'})
+
+ return app
+
+
+def run_server(alarm_clock=None, host='0.0.0.0', port=5000, debug=False):
+ """Run the webserver."""
+ app = create_app(alarm_clock)
+ app.run(host=host, port=port, debug=debug, threaded=True)