diff --git a/app.py b/app.py index 10041ce..748955f 100644 --- a/app.py +++ b/app.py @@ -1,13 +1,21 @@ #!/usr/bin/env python3 -from flask import Flask, render_template, jsonify +""" +Flask web UI for ultra-responsive fan control. +Works with the Arduino sketch above (pin 11 PWM). +""" + +from flask import Flask, render_template, jsonify, request import serial import serial.tools.list_ports import threading import time +import sys app = Flask(__name__) -# --- Auto-find Arduino --- +# ------------------------------------------------- +# Auto-detect Arduino +# ------------------------------------------------- def find_arduino(): for p in serial.tools.list_ports.comports(): if 'Arduino' in p.description or 'ACM' in p.device or 'USB' in p.device: @@ -17,57 +25,99 @@ def find_arduino(): PORT = find_arduino() BAUD = 115200 -# Global serial object +# Global serial ser = None serial_lock = threading.Lock() +# ------------------------------------------------- +# Serial init with retry on "busy" +# ------------------------------------------------- def init_serial(): global ser - try: - ser = serial.Serial(PORT, BAUD, timeout=0) - time.sleep(2) # wait for Arduino reset - print(f"Connected to {PORT}") - except Exception as e: - print(f"Serial error: {e}") - ser = None + for attempt in range(6): + try: + ser = serial.Serial(PORT, BAUD, timeout=0) + time.sleep(2) # Arduino reset + print(f"[OK] Connected to {PORT}") + return + except serial.SerialException as e: + if 'Device or resource busy' in str(e): + print(f"[BUSY] Port busy, retry {attempt+1}/6 in 1s...") + time.sleep(1) + else: + print(f"[ERROR] Serial: {e}") + sys.exit(1) + sys.exit("[FATAL] Could not open serial port after retries") init_serial() -# --- Send command safely --- -def send_command(cmd): +# ------------------------------------------------- +# Safe send +# ------------------------------------------------- +def send_command(cmd: str): with serial_lock: if ser and ser.is_open: try: ser.write(cmd.encode()) ser.flush() - except: - print("Serial write failed") - else: - print("Serial not open") + except Exception as e: + print(f"[WRITE FAIL] {e}") + +# ------------------------------------------------- +# Background reader – pushes feedback to browser via SSE +# ------------------------------------------------- +from collections import deque +feedback_buffer = deque(maxlen=50) # last 50 messages -# --- Background reader (optional: show live feedback) --- def serial_reader(): while True: if ser and ser.in_waiting: - line = ser.readline().decode(errors='ignore').strip() - if line: - print(f"Arduino: {line}") - time.sleep(0.01) + try: + line = ser.readline().decode(errors='ignore').strip() + if line: + feedback_buffer.append(line) + except: + pass + time.sleep(0.005) threading.Thread(target=serial_reader, daemon=True).start() -# --- Routes --- +# ------------------------------------------------- +# Routes +# ------------------------------------------------- @app.route('/') def index(): return render_template('index.html') @app.route('/cmd/') def command(key): - if len(key) == 1 and key in '0123456789fFsS': + if len(key) == 1 and key.lower() in '0123456789fs': send_command(key) return jsonify(status="ok", key=key) return jsonify(status="invalid"), 400 +# ------------------------------------------------- +# Server-Sent Events (SSE) – live Arduino feedback +# ------------------------------------------------- +from flask import Response + +@app.route('/stream') +def stream(): + def event_stream(): + last_len = 0 + while True: + if len(feedback_buffer) > last_len: + msg = feedback_buffer[-1] + last_len = len(feedback_buffer) + yield f"data: {msg}\n\n" + time.sleep(0.05) + return Response(event_stream(), mimetype="text/event-stream") + +# ------------------------------------------------- +# Run +# ------------------------------------------------- if __name__ == '__main__': - # Allow access from your phone/tablet on same WiFi - app.run(host='0.0.0.0', port=5000, debug=False) + # Accessible from phone/tablet on same WiFi + print(f"\nWeb UI: http://YOUR_IP:5000") + print(" (Find YOUR_IP with: ip addr show | grep inet)") + app.run(host='0.0.0.0', port=5000, debug=False, threaded=True) diff --git a/sketch_oct23a/sketch_oct23a.ino b/old/sketch_oct23a/sketch_oct23a.ino similarity index 100% rename from sketch_oct23a/sketch_oct23a.ino rename to old/sketch_oct23a/sketch_oct23a.ino diff --git a/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748/.theia/launch.json b/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748/.theia/launch.json new file mode 100644 index 0000000..7e4253b --- /dev/null +++ b/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748/.theia/launch.json @@ -0,0 +1,8 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + "version": "0.2.0", + "configurations": [ + + ] +} diff --git a/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748.ino b/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748.ino new file mode 100644 index 0000000..e5ebb12 --- /dev/null +++ b/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748/speed_fan_controller_dynamic_pythonkeys_copy_20251115184748.ino @@ -0,0 +1,42 @@ +// Fan control via Serial - works with ultra-responsive Python script +// Pin 11 = PWM output to fan (use transistor/MOSFET/driver for high current!) + +const int FAN_PIN = 11; + +void setup() { + Serial.begin(115200); + pinMode(FAN_PIN, OUTPUT); + analogWrite(FAN_PIN, 0); // Start stopped + Serial.println("Fan controller ready. Use 0-9, f=full, s=stop"); +} + +void loop() { + if (Serial.available() > 0) { + char cmd = Serial.read(); + + int speed = 0; // 0 to 255 for analogWrite + + if (cmd >= '0' && cmd <= '9') { + speed = map(cmd - '0', 0, 9, 0, 255); // 0-9 → 0-255 + } + else if (cmd == 'f' || cmd == 'F') { + speed = 255; + } + else if (cmd == 's' || cmd == 'S') { + speed = 0; + } + else { + // Ignore invalid commands, but optionally echo + Serial.print("?"); + return; + } + + // Apply speed + analogWrite(FAN_PIN, speed); + + // Send feedback: percentage + int percent = map(speed, 0, 255, 0, 100); + Serial.print(percent); + Serial.println("%"); + } +} \ No newline at end of file