diff --git a/main.py b/main.py index 08970da1f05b7f56a04c888b93ac36b18a8e4c8c..09b520555790dbaa24ea0d7b7bc68e0791e82d88 100644 --- a/main.py +++ b/main.py @@ -16,6 +16,7 @@ from config import PROP_AVAILABLE_WIDGETS, PROP_AVAILABLE_PATTERNS, PROP_WINDOW_ PROP_FLASK_DEBUG, PROP_FLASK_HOST, PROP_FLASK_PORT, hw_config from utils.jsonencoding import get_cfg_from_request, decode_dict, encode_dict from utils.net import json_response, verify_json_request +from utils.sequencing import validate_sequence app = Flask(__name__) @@ -133,7 +134,7 @@ def wr_set_widget(): # Return success return json_response({ "status": "OK", - "pattern": widget_name + "widget": widget_name }) @app.route("/api/set_pattern", methods=["POST"]) @@ -169,6 +170,48 @@ def wr_set_pattern(): "pattern": pattern_name }) +@app.route("/api/set_sequence", methods=["POST"]) +def wr_set_sequence(): + # Make sure the request is valid + valid, validation_response = verify_json_request(request, ["widget", "pattern"]) + if not valid: + return validation_response + + # Make sure the widget and pattern sequences are valid + widget_seq = request.json.get("widget") + pattern_seq = request.json.get("pattern") + if ( + (not isinstance(widget_seq, list)) or + (not isinstance(pattern_seq, list)) or + len(widget_seq) == 0 or + len(pattern_seq) == 0 + ): + return json_response({ + "error": "Invalid sequence provided" + }, 400) + + # Make sure each widget is valid + for w_index in range(len(widget_seq)): + widget = widget_seq[w_index] + if not validate_sequence(widget, PROP_AVAILABLE_WIDGETS): + return json_response({ + "error": "Invalid widget in sequence, index " + str(w_index) + }, 400) + + # Make sure each pattern is valid + for p_index in range(len(pattern_seq)): + pattern = pattern_seq[p_index] + if not validate_sequence(pattern, PROP_AVAILABLE_PATTERNS): + return json_response({ + "error": "Invalid pattern in sequence, index " + str(p_index) + }, 400) + + # Set the sequences + mainthread_queue.put("SAVE_SEQUENCE_CFG:" + encode_dict(widget_seq) + ":" + encode_dict(pattern_seq)) + return json_response({ + "status": "OK" + }) + @app.route("/api/get_config") def wr_get_config(): return json_response(module_config) @@ -347,6 +390,9 @@ if __name__ == "__main__": current_widget_delta = 0 current_pattern_delta = 0 + force_change_widget = False + force_change_pattern = False + # Fork off pygame pygame_thread = threading.Thread( target=lambda: run_screen(initial_widget), @@ -382,51 +428,52 @@ if __name__ == "__main__": elif parts[0] == "UPDATE_ALL_MODULE_CFG" and len(parts) == 2: module_config = decode_dict(parts[1]) save_module_config() - elif parts[0] == "SAVE_WIDGET_SEQUENCE_CFG" and len(parts) == 2: + elif parts[0] == "SAVE_SEQUENCE_CFG" and len(parts) == 3: widget_sequence = decode_dict(parts[1]) - with open("sequence_config.json", "w") as f: - json.dump({ - "widget": widget_sequence, - "pattern": pattern_sequence - }, f, indent=4) - elif parts[0] == "SAVE_PATTERN_SEQUENCE_CFG" and len(parts) == 2: - pattern_sequence = decode_dict(parts[1]) - with open("sequence_config.json", "w") as f: - json.dump({ - "widget": widget_sequence, - "pattern": pattern_sequence - }, f, indent=4) + pattern_sequence = decode_dict(parts[2]) + if len(widget_sequence) <= current_widget_index: + current_widget_index = 0 + if len(pattern_sequence) <= current_pattern_index: + current_pattern_index = 0 + save_sequences() + elif message == "CYCLE_WIDGET": + force_change_widget = True + elif message == "CYCLE_PATTERN": + force_change_pattern = True + except queue.Empty: pass # Check if time delta has passed # For Widgets curr_widget = widget_sequence[current_widget_index] - if curr_widget.get("duration", -1) != -1: + if curr_widget.get("duration", -1) != -1 or force_change_widget: # Check if the widget has existed for too long - if curr_widget.get("duration") * 1000 <= current_widget_delta: + if curr_widget.get("duration") * 1000 <= current_widget_delta or force_change_widget: # Set the widget to the next widget current_widget_index += 1 if current_widget_index >= len(widget_sequence): current_widget_index = 0 display_queue.put("SET_WIDGET:" + widget_sequence[current_widget_index].get("type") + ":" + - encode_dict(widget_sequence[current_widget_index].get("config", {})) + encode_dict(widget_sequence[current_widget_index].get("config_override", {})) ) current_widget_delta = 0 + force_change_widget = False # For patterns curr_pattern = pattern_sequence[current_pattern_index] - if curr_pattern.get("duration", -1) != -1: + if curr_pattern.get("duration", -1) != -1 or force_change_pattern: # Check if the pattern has existed for too long - if curr_pattern.get("duration") * 1000 <= current_pattern_delta: + if curr_pattern.get("duration") * 1000 <= current_pattern_delta or force_change_pattern: # Set the pattern to the next pattern current_pattern_index += 1 if current_pattern_index >= len(pattern_sequence): current_pattern_index = 0 led_queue.put("SET_PATTERN:" + pattern_sequence[current_pattern_index].get("type") + ":" + - encode_dict(pattern_sequence[current_pattern_index].get("config", {})) + encode_dict(pattern_sequence[current_pattern_index].get("config_override", {})) ) current_pattern_delta = 0 + force_change_pattern = False # Keep main thread alive current_widget_delta += hw_config["main_update_frequency"] time.sleep(hw_config["main_update_frequency"] / 1000) diff --git a/utils/sequencing.py b/utils/sequencing.py new file mode 100644 index 0000000000000000000000000000000000000000..4e904bc4210ae4d8415c6fd36999939a82fcbd05 --- /dev/null +++ b/utils/sequencing.py @@ -0,0 +1,23 @@ +def validate_sequence(seq: dict, available_seq_type: list[str]) -> bool: + """ + Validates the entry sequence + + All of these conditions must be met: + - seq is a dictionary + - seq has a "type" key + - seq["type"] is in available_seq_type + - seq has a "duration" key + - seq["duration"] is an integer or float + - seq["config_override"] is a dictionary or is not set + :param seq: the entry sequence + :param available_seq_type: the available sequence types + :return: True or False + """ + return all([ + isinstance(seq, dict), + "type" in seq, + seq.get("type") in available_seq_type, + "duration" in seq, + isinstance(seq.get("duration"), (int, float)), + isinstance(seq.get("config_override", {}), dict) + ]) \ No newline at end of file