diff --git a/SMTplugins/BlackJack/blackjack_route.py b/SMTplugins/BlackJack/blackjack_route.py index 8ed9659..83ec935 100644 --- a/SMTplugins/BlackJack/blackjack_route.py +++ b/SMTplugins/BlackJack/blackjack_route.py @@ -3,6 +3,9 @@ from SMTplugins.BlackJack.blackjack_widget import BlackjackWidget blackjack_bp = Blueprint('blackjack_bp', __name__) +def get_blueprint(): + return blackjack_bp + SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] VALUES = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] @@ -37,11 +40,11 @@ def stand(): data = request.json deck = data.get('deck', []) dealer_hand = data.get('dealer_hand', []) - + # Dealer must hit until score >= 17 while get_score(dealer_hand) < 17: dealer_hand.append(deck.pop()) - + return jsonify({ "dealer_hand": dealer_hand, "remaining_deck": deck, diff --git a/SMTplugins/Calendar/calendar_plugin.py b/SMTplugins/Calendar/calendar_plugin.py index 1bf0d23..5a27926 100644 --- a/SMTplugins/Calendar/calendar_plugin.py +++ b/SMTplugins/Calendar/calendar_plugin.py @@ -8,6 +8,9 @@ _widget = calendarWidget() _widget.update() +def get_blueprint(): + return calendar_bp + @calendar_bp.route("/widget/calendar") def calendar_view(): return render_template("calendar_widget.html", data=_widget.widgetData) diff --git a/SMTplugins/FakeLight/fakeLight_route.py b/SMTplugins/FakeLight/fakeLight_route.py index 75bf15f..450e467 100644 --- a/SMTplugins/FakeLight/fakeLight_route.py +++ b/SMTplugins/FakeLight/fakeLight_route.py @@ -3,6 +3,9 @@ fakeLight_bp = Blueprint('fakeLight', __name__) +def get_blueprint(): + return fakeLight_bp + light_instance = FakeLightWidget() @fakeLight_bp.route('/api/light/status', methods=['GET']) diff --git a/SMTplugins/GoogleTasks/googleTasks_route.py b/SMTplugins/GoogleTasks/googleTasks_route.py index bd2eb8c..85c538e 100644 --- a/SMTplugins/GoogleTasks/googleTasks_route.py +++ b/SMTplugins/GoogleTasks/googleTasks_route.py @@ -4,6 +4,9 @@ googleTasks_bp = Blueprint('googleTasks_bp', __name__) tasks = GoogleTasksWidget() +def get_blueprint(): + return googleTasks_bp + @googleTasks_bp.route("/api/google/tasks") def get_google_tasks(): data = tasks.update() @@ -16,7 +19,7 @@ def get_google_tasks(): def complete_task(): data = request.get_json() task_id = data.get('task_id') - + if not task_id: return jsonify({"error": "No task ID provided"}), 400 @@ -28,11 +31,11 @@ def complete_task(): # Use patch to update the status to 'completed' # Google requires the task ID and the '@default' list ID service.tasks().patch( - tasklist='@default', - task=task_id, + tasklist='@default', + task=task_id, body={'status': 'completed'} ).execute() - + return jsonify({"status": "success", "message": "Task marked as completed"}) except Exception as e: diff --git a/SMTplugins/Package/package_plugin.py b/SMTplugins/Package/package_plugin.py index 540c4ee..f7a1aed 100644 --- a/SMTplugins/Package/package_plugin.py +++ b/SMTplugins/Package/package_plugin.py @@ -6,6 +6,9 @@ package_bp = Blueprint('package', __name__) +def get_blueprint(): + return package_bp + _widget = packageWidget() diff --git a/SMTplugins/Spotify/spotify_route.py b/SMTplugins/Spotify/spotify_route.py index 872d7bc..d2923b7 100644 --- a/SMTplugins/Spotify/spotify_route.py +++ b/SMTplugins/Spotify/spotify_route.py @@ -5,7 +5,10 @@ # Blueprint for Spotify spotify_bp = Blueprint("spotify_bp", __name__) -# Spotify API credentials that are needed +def get_blueprint(): + return spotify_bp + +# Spotify API credentials that are needed CLIENT_ID = "f77a81fa784b46d6a3513a1fddbd3a2f" CLIENT_SECRET = "8ea40647f6c34ed7bae3afa7c5e1a267" REDIRECT_URI = "http://127.0.0.1:5000/callback" @@ -23,7 +26,7 @@ def get_auth_header(): return {"Authorization": f"Basic {auth_base64}"} -# Creates a bearer token header for the API calls +# Creates a bearer token header for the API calls def get_bearer_header(): return {"Authorization": f"Bearer {access_token}"} @@ -52,7 +55,7 @@ def refresh_access_token(): return True -# Route needed to redirect user to Spotify login page +# Route needed to redirect user to Spotify login page @spotify_bp.route("/login") def spotify_login(): scope = "user-read-currently-playing user-modify-playback-state" @@ -66,7 +69,7 @@ def spotify_login(): return redirect(auth_url) -# Call back route after Spotify login +# Call back route after Spotify login @spotify_bp.route("/callback") def spotify_callback(): global access_token, refresh_token @@ -113,13 +116,13 @@ def get_spotify(): "isPlaying": False }) - # Request current playback + # Request current playback response = requests.get( "https://api.spotify.com/v1/me/player/currently-playing", headers=get_bearer_header() ) - # This is when no song is playing + # This is when no song is playing if response.status_code == 204: return jsonify({ "track": "Nothing Playing", diff --git a/SMTplugins/TempSensor_Dummy/flaskTempSensorDummy.py b/SMTplugins/TempSensor_Dummy/flaskTempSensorDummy.py index 6e7fec6..1c3468d 100644 --- a/SMTplugins/TempSensor_Dummy/flaskTempSensorDummy.py +++ b/SMTplugins/TempSensor_Dummy/flaskTempSensorDummy.py @@ -5,12 +5,15 @@ fTemp_bp = Blueprint('FakeTeperature', __name__) +def get_blueprint(): + return fTemp_bp + DATA_FILE = "SMTplugins/TempSensor_Dummy/sensor_data.json" @fTemp_bp.route("/temperature") def get_temp(): current_val = "NAN " - + # Check if the file exists before trying to read it if os.path.exists(DATA_FILE): try: @@ -22,7 +25,7 @@ def get_temp(): # Pass the value to Widget Subsystem temp_widget = tempSensorWidget() - temp_widget.updateTemp(current_val) + temp_widget.updateTemp(current_val) return jsonify({"temp": temp_widget.update()}) #FOR HOME ASSISTANT UNTESTED diff --git a/SMTplugins/TimeZoneClock/timeZone_route.py b/SMTplugins/TimeZoneClock/timeZone_route.py index c43100a..b59e3e3 100644 --- a/SMTplugins/TimeZoneClock/timeZone_route.py +++ b/SMTplugins/TimeZoneClock/timeZone_route.py @@ -7,6 +7,9 @@ timeZone_bp = Blueprint('timeZone_bp', __name__) DATA_FILE = os.path.join("SMTplugins", "TimeZoneClock", "locations.json") +def get_blueprint(): + return timeZone_bp + @timeZone_bp.route("/api/timezone/data") def get_location_times(): output = [] @@ -27,5 +30,5 @@ def get_location_times(): }) except Exception as e: return jsonify({"error": str(e)}), 500 - + return jsonify(output) \ No newline at end of file diff --git a/SMTplugins/Weather/weather_route.py b/SMTplugins/Weather/weather_route.py index da9da29..b9c671a 100644 --- a/SMTplugins/Weather/weather_route.py +++ b/SMTplugins/Weather/weather_route.py @@ -7,6 +7,9 @@ weather_bp = Blueprint('weather_bp', __name__) geolocator = Nominatim(user_agent="SMT_Terminal_Project") +def get_blueprint(): + return weather_bp + # NWS requires a unique User-Agent header HEADERS = { 'User-Agent': '(SMT-Project, bschadoff@albany.edu)', @@ -32,21 +35,21 @@ def get_nws_weather(): location = geolocator.geocode(city) if not location: return jsonify({"error": "City not found"}), 404 - + lat, lon = location.latitude, location.longitude # 2) Resolve coordinates to NWS Grid Points points_url = f"https://api.weather.gov/points/{lat},{lon}" points_res = requests.get(points_url, headers=HEADERS) points_res.raise_for_status() - + # 3) Get the Hourly Forecast URL forecast_url = points_res.json()['properties']['forecastHourly'] forecast_res = requests.get(forecast_url, headers=HEADERS) forecast_res.raise_for_status() - + periods = forecast_res.json()['properties']['periods'] - + # Current data is the first period current = periods[0] # Calculate H/L by looking at the next 24 hourly periods diff --git a/SMTplugins/Weather/weather_widget.py b/SMTplugins/Weather/weather_widget.py index f013baf..1b06cc1 100644 --- a/SMTplugins/Weather/weather_widget.py +++ b/SMTplugins/Weather/weather_widget.py @@ -1,31 +1,31 @@ from widget import Widget -from weather_route import get_nws_weather +import weather_route class weatherrWidget(Widget): def widgetName(self): return "Weather" - + def widgetID(self): return "weather" - + def widgetHTML(self): return "
" - + def widgetData(self): return get_nws_weather() - + def widgetPreferences(self): return {} - + def widgetDefaultPreferences(self): return {} - + def updateTimer(self): return 15000 #Update every 15 secs for testing - + def handle_event(self, event, args): return None - + def update(self): - return get_nws_weather() \ No newline at end of file + return weather_route.get_nws_weather() \ No newline at end of file diff --git a/SMTplugins/clock/clock_route.py b/SMTplugins/clock/clock_route.py index 76d3017..ebc718a 100644 --- a/SMTplugins/clock/clock_route.py +++ b/SMTplugins/clock/clock_route.py @@ -3,6 +3,9 @@ clock_bp = Blueprint('clock', __name__) +def get_blueprint(): + return clock_bp + @clock_bp.route("/time") def get_time(): clock = clockWidget() diff --git a/SMTplugins/date/date_route.py b/SMTplugins/date/date_route.py index d26ceb3..4ace42e 100644 --- a/SMTplugins/date/date_route.py +++ b/SMTplugins/date/date_route.py @@ -3,6 +3,9 @@ date_bp = Blueprint("date_bp", __name__) +def get_blueprint(): + return date_bp + @date_bp.route("/api/date") def get_date(): now = datetime.now() diff --git a/SMTplugins/pluginImports.py b/SMTplugins/pluginImports.py deleted file mode 100644 index a2a9682..0000000 --- a/SMTplugins/pluginImports.py +++ /dev/null @@ -1,9 +0,0 @@ -from SMTplugins.clock.clock_route import clock_bp -from SMTplugins.TempSensor_Dummy.flaskTempSensorDummy import fTemp_bp -from SMTplugins.Weather.weather_route import weather_bp -from SMTplugins.TimeZoneClock.timeZone_route import timeZone_bp -from SMTplugins.date.date_route import date_bp -from SMTplugins.GoogleTasks.googleTasks_route import googleTasks_bp -from SMTplugins.Spotify.spotify_route import spotify_bp -from SMTplugins.FakeLight.fakeLight_route import fakeLight_bp -from SMTplugins.BlackJack.blackjack_route import blackjack_bp diff --git a/flaskServer.py b/flaskServer.py index 60d1714..c258768 100644 --- a/flaskServer.py +++ b/flaskServer.py @@ -1,6 +1,8 @@ from flask import Flask, render_template, request, jsonify import os +import sys import json +import importlib '''from SMTplugins.pluginImports import * from SMTplugins.Calendar.calendar_plugin import calendar_bp @@ -12,27 +14,51 @@ import os import json -from SMTplugins.pluginImports import * -from SMTplugins.Calendar.calendar_plugin import calendar_bp -from SMTplugins.Package.package_plugin import package_bp - app = Flask(__name__) #######PLUGINS####### -app.register_blueprint(clock_bp) -app.register_blueprint(fTemp_bp) -app.register_blueprint(weather_bp) -app.register_blueprint(timeZone_bp) -app.register_blueprint(date_bp) -app.register_blueprint(blackjack_bp) #Blackjack -app.register_blueprint(googleTasks_bp) -app.register_blueprint(spotify_bp) -app.register_blueprint(calendar_bp) -app.register_blueprint(package_bp) -app.register_blueprint(fakeLight_bp) +# IMPORTANT - Every bp file must have a function called get_blueprint() that returns the blueprint to be registered +# ALSO!!!! FOR SOME REASON YOU CANT USE FROM X IMPORT Y. ONLY USE "IMPORT Y" WHEN MAKING WIDGETS +# Recursive function that searches for all .py files in the filepath, including all subfolders in the path +def deep_search_for_module_blueprints(filepath): + returnList = [] + specList = [] + + for filename in os.listdir(filepath): + current_filepath = filepath + "\\" + filename + + # If we found a directory, recursively search it for plugins + if os.path.isdir(current_filepath) and filename != "__pycache__": + recur_returnList, recur_specList = deep_search_for_module_blueprints(current_filepath) + returnList.extend(recur_returnList) + specList.extend(recur_specList) + + # If we found a module, lazily import it + elif filename.endswith('.py'): + print("Importing " + current_filepath) + module_name = filename[:-3] + spec = importlib.util.spec_from_file_location(module_name, (current_filepath)) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module_name + returnList.append(module) + specList.append(spec) + + return returnList, specList + +# Retrieve our potential blueprints and specs +potential_bps, specList = deep_search_for_module_blueprints(os.getcwd() + "\\SMTplugins") + +# Actually perform the code executions in our imported modules +for i in range(0, len(potential_bps)): + specList[i].loader.exec_module(potential_bps[i]) + +for potential_bp in potential_bps: + bp_call = getattr(potential_bp, "get_blueprint", None) + if bp_call != None: + app.register_blueprint(bp_call()) #allows for easier starting of flask from start file @@ -47,11 +73,11 @@ def clientHome(): DATA_FILE = "layout_client.json" with open(DATA_FILE, 'r') as f: data = json.load(f) - + # Passes the list of widgets to the template return render_template('index.html', widgets=data['widgets']) #return render_template("index.html") - + @app.route("/settings") def settings(): return render_template("settingspage.html") diff --git a/start.py b/start.py index 9c39cb5..a145347 100644 --- a/start.py +++ b/start.py @@ -25,11 +25,6 @@ def start(): sqlThread.start() pluginsThread.start() flaskServer.run_flask() - # Flask thread has to be run on the main thread because of signal - #flaskServer.thread() - - # NOTE - Im gonna be kicking the can further down the road with this one but as it stands the way python threading works is - # really silly, so I'm gonna remove all the debug loops and figure out how to kill all processes cleanly on exit at a later date. # Perform setup operations def setup():