Made exemaker and test hidden
This commit is contained in:
243
background/exemaker.py
Normal file
243
background/exemaker.py
Normal file
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Tkinter GUI to run PyInstaller and make an exe from a .py file.
|
||||
|
||||
Features:
|
||||
- choose script
|
||||
- choose onefile / dir
|
||||
- choose windowed (noconsole)
|
||||
- add icon
|
||||
- add additional data (file or folder; multiple, separated by semicolons)
|
||||
- set output folder
|
||||
- show live PyInstaller output
|
||||
- cancel build
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox, scrolledtext
|
||||
import subprocess
|
||||
import threading
|
||||
import sys
|
||||
import shlex
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
class BuilderGUI(tk.Tk):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.title("Python → EXE (PyInstaller GUI)")
|
||||
self.geometry("800x600")
|
||||
self.create_widgets()
|
||||
self.proc = None # subprocess handle
|
||||
self.stop_requested = False
|
||||
|
||||
def create_widgets(self):
|
||||
frame_top = tk.Frame(self)
|
||||
frame_top.pack(fill="x", padx=10, pady=8)
|
||||
|
||||
tk.Label(frame_top, text="Script:").grid(row=0, column=0, sticky="e")
|
||||
self.script_entry = tk.Entry(frame_top, width=70)
|
||||
self.script_entry.grid(row=0, column=1, padx=6)
|
||||
tk.Button(frame_top, text="Browse...", command=self.browse_script).grid(row=0, column=2)
|
||||
|
||||
tk.Label(frame_top, text="Output folder:").grid(row=1, column=0, sticky="e")
|
||||
self.out_entry = tk.Entry(frame_top, width=70)
|
||||
self.out_entry.grid(row=1, column=1, padx=6)
|
||||
tk.Button(frame_top, text="Choose...", command=self.choose_output).grid(row=1, column=2)
|
||||
|
||||
opts_frame = tk.LabelFrame(self, text="Options", padx=8, pady=8)
|
||||
opts_frame.pack(fill="x", padx=10)
|
||||
|
||||
self.onefile_var = tk.BooleanVar(value=True)
|
||||
tk.Checkbutton(opts_frame, text="Onefile (single exe)", variable=self.onefile_var).grid(row=0, column=0, sticky="w", padx=6, pady=2)
|
||||
|
||||
self.windowed_var = tk.BooleanVar(value=False)
|
||||
tk.Checkbutton(opts_frame, text="Windowed (no console) / --noconsole", variable=self.windowed_var).grid(row=0, column=1, sticky="w", padx=6, pady=2)
|
||||
|
||||
tk.Label(opts_frame, text="Icon (.ico):").grid(row=1, column=0, sticky="e")
|
||||
self.icon_entry = tk.Entry(opts_frame, width=50)
|
||||
self.icon_entry.grid(row=1, column=1, sticky="w", padx=6)
|
||||
tk.Button(opts_frame, text="Browse", command=self.browse_icon).grid(row=1, column=2)
|
||||
|
||||
tk.Label(opts_frame, text="Additional data (src;dest pairs separated by ';', e.g. resources;resources):").grid(row=2, column=0, columnspan=3, sticky="w", pady=(6,0))
|
||||
self.data_entry = tk.Entry(opts_frame, width=110)
|
||||
self.data_entry.grid(row=3, column=0, columnspan=3, padx=6, pady=4)
|
||||
|
||||
tk.Label(opts_frame, text="Extra PyInstaller args:").grid(row=4, column=0, sticky="w")
|
||||
self.extra_entry = tk.Entry(opts_frame, width=80)
|
||||
self.extra_entry.grid(row=4, column=1, columnspan=2, padx=6, pady=4, sticky="w")
|
||||
|
||||
run_frame = tk.Frame(self)
|
||||
run_frame.pack(fill="x", padx=10, pady=8)
|
||||
|
||||
self.build_btn = tk.Button(run_frame, text="Build EXE", command=self.start_build, bg="#2b7a78", fg="white")
|
||||
self.build_btn.pack(side="left", padx=(0,6))
|
||||
|
||||
self.cancel_btn = tk.Button(run_frame, text="Cancel", command=self.request_cancel, state="disabled", bg="#b00020", fg="white")
|
||||
self.cancel_btn.pack(side="left")
|
||||
|
||||
clear_btn = tk.Button(run_frame, text="Clear Log", command=self.clear_log)
|
||||
clear_btn.pack(side="left", padx=6)
|
||||
|
||||
open_out_btn = tk.Button(run_frame, text="Open Output Folder", command=self.open_output)
|
||||
open_out_btn.pack(side="right")
|
||||
|
||||
self.log = scrolledtext.ScrolledText(self, height=18, font=("Consolas", 10))
|
||||
self.log.pack(fill="both", expand=True, padx=10, pady=(0,10))
|
||||
|
||||
def browse_script(self):
|
||||
path = filedialog.askopenfilename(filetypes=[("Python files", "*.py")])
|
||||
if path:
|
||||
self.script_entry.delete(0, tk.END)
|
||||
self.script_entry.insert(0, path)
|
||||
# default output to script parent /dist
|
||||
parent = os.path.dirname(path)
|
||||
default_out = os.path.join(parent, "dist")
|
||||
self.out_entry.delete(0, tk.END)
|
||||
self.out_entry.insert(0, default_out)
|
||||
|
||||
def choose_output(self):
|
||||
path = filedialog.askdirectory()
|
||||
if path:
|
||||
self.out_entry.delete(0, tk.END)
|
||||
self.out_entry.insert(0, path)
|
||||
|
||||
def browse_icon(self):
|
||||
path = filedialog.askopenfilename(filetypes=[("Icon files", "*.ico")])
|
||||
if path:
|
||||
self.icon_entry.delete(0, tk.END)
|
||||
self.icon_entry.insert(0, path)
|
||||
|
||||
def clear_log(self):
|
||||
self.log.delete("1.0", tk.END)
|
||||
|
||||
def open_output(self):
|
||||
out = self.out_entry.get().strip()
|
||||
if not out:
|
||||
messagebox.showinfo("Output folder", "No output folder set.")
|
||||
return
|
||||
os.startfile(out) if os.name == "nt" else subprocess.run(["xdg-open", out])
|
||||
|
||||
def request_cancel(self):
|
||||
if self.proc and self.proc.poll() is None:
|
||||
self.stop_requested = True
|
||||
# try terminate politely
|
||||
try:
|
||||
self.proc.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
self.log_insert("\nCancellation requested...\n")
|
||||
self.cancel_btn.config(state="disabled")
|
||||
|
||||
def log_insert(self, text):
|
||||
self.log.insert(tk.END, text)
|
||||
self.log.see(tk.END)
|
||||
|
||||
def start_build(self):
|
||||
script = self.script_entry.get().strip()
|
||||
if not script or not os.path.isfile(script):
|
||||
messagebox.showerror("Error", "Please choose a valid Python script to build.")
|
||||
return
|
||||
|
||||
out_dir = self.out_entry.get().strip() or os.path.dirname(script)
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
self.build_btn.config(state="disabled")
|
||||
self.cancel_btn.config(state="normal")
|
||||
self.stop_requested = False
|
||||
self.clear_log()
|
||||
|
||||
# Start build on a thread to keep UI responsive
|
||||
thread = threading.Thread(target=self.run_pyinstaller, args=(script, out_dir), daemon=True)
|
||||
thread.start()
|
||||
|
||||
def run_pyinstaller(self, script, out_dir):
|
||||
# Build the PyInstaller command
|
||||
cmd = [sys.executable, "-m", "PyInstaller"]
|
||||
|
||||
if self.onefile_var.get():
|
||||
cmd.append("--onefile")
|
||||
else:
|
||||
cmd.append("--onedir")
|
||||
|
||||
if self.windowed_var.get():
|
||||
cmd.append("--noconsole")
|
||||
|
||||
icon = self.icon_entry.get().strip()
|
||||
if icon:
|
||||
cmd.extend(["--icon", icon])
|
||||
|
||||
# add additional data: user can provide pairs like "data;data" or "assets;assets"
|
||||
data_spec = self.data_entry.get().strip()
|
||||
if data_spec:
|
||||
# support multiple separated by semicolons or vertical bars
|
||||
pairs = [p for p in (data_spec.split(";") + data_spec.split("|")) if p.strip()]
|
||||
# normalize pairs to PyInstaller format: src;dest (on Windows use ';' in CLI but PyInstaller expects src;dest as single argument)
|
||||
for p in pairs:
|
||||
# if user typed "src:dest" or "src->dest", replace with semicolon
|
||||
p_fixed = p.replace(":", ";").replace("->", ";")
|
||||
cmd.extend(["--add-data", p_fixed])
|
||||
|
||||
# user extra args (raw)
|
||||
extra = self.extra_entry.get().strip()
|
||||
if extra:
|
||||
# split carefully
|
||||
cmd.extend(shlex.split(extra))
|
||||
|
||||
# ensure output path goes to chosen dir: use --distpath
|
||||
cmd.extend(["--distpath", out_dir])
|
||||
|
||||
# entry script
|
||||
cmd.append(script)
|
||||
|
||||
self.log_insert("Running PyInstaller with command:\n" + " ".join(shlex.quote(c) for c in cmd) + "\n\n")
|
||||
|
||||
# spawn the process
|
||||
try:
|
||||
self.proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
bufsize=1,
|
||||
universal_newlines=True,
|
||||
)
|
||||
except Exception as e:
|
||||
self.log_insert(f"Failed to start PyInstaller: {e}\n")
|
||||
self.build_btn.config(state="normal")
|
||||
self.cancel_btn.config(state="disabled")
|
||||
return
|
||||
|
||||
# Stream output line by line
|
||||
try:
|
||||
for line in self.proc.stdout:
|
||||
if line:
|
||||
self.log_insert(line)
|
||||
if self.stop_requested:
|
||||
try:
|
||||
self.proc.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
self.proc.wait(timeout=30)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.log_insert("Process did not exit in time after termination.\n")
|
||||
except Exception as e:
|
||||
self.log_insert(f"Error while running PyInstaller: {e}\n")
|
||||
|
||||
retcode = self.proc.returncode if self.proc else None
|
||||
if self.stop_requested:
|
||||
self.log_insert("\nBuild cancelled by user.\n")
|
||||
elif retcode == 0:
|
||||
self.log_insert("\nBuild finished successfully.\n")
|
||||
else:
|
||||
self.log_insert(f"\nBuild finished with return code {retcode}.\n")
|
||||
|
||||
# Re-enable buttons
|
||||
self.build_btn.config(state="normal")
|
||||
self.cancel_btn.config(state="disabled")
|
||||
self.proc = None
|
||||
self.stop_requested = False
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = BuilderGUI()
|
||||
app.mainloop()
|
||||
375
background/experiment.py
Normal file
375
background/experiment.py
Normal file
@@ -0,0 +1,375 @@
|
||||
import tkinter as tk
|
||||
import time
|
||||
from datetime import datetime
|
||||
import requests
|
||||
import csv
|
||||
import io
|
||||
|
||||
COUNTDOWN_HTML = "countdown.html"
|
||||
GONOGO_HTML = "gonogo.html"
|
||||
SHEET_LINK = "https://docs.google.com/spreadsheets/d/1UPJTW8vH2mgEzispjg_Y_zSqYTFaLoxuoZnqleVlSZ0/export?format=csv&gid=855477916"
|
||||
session = requests.Session()
|
||||
|
||||
# -------------------------
|
||||
# Fetch Go/No-Go Data
|
||||
# -------------------------
|
||||
def fetch_gonogo():
|
||||
"""Fetch Go/No-Go parameters from L2, L3, L4 (rows 2,3,4; col 12)"""
|
||||
try:
|
||||
resp = session.get(SHEET_LINK, timeout=2) # timeout for faster failure if network is slow
|
||||
resp.raise_for_status()
|
||||
reader = csv.reader(io.StringIO(resp.text))
|
||||
data = list(reader)
|
||||
gonogo = []
|
||||
for i in [1, 2, 3]:
|
||||
value = data[i][11] if len(data[i]) > 11 else "N/A"
|
||||
gonogo.append(value.strip().upper()) # <-- always uppercase
|
||||
return gonogo
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to fetch Go/No-Go: {e}")
|
||||
return ["ERROR"] * 3
|
||||
|
||||
# -------------------------
|
||||
# Write Countdown HTML
|
||||
# -------------------------
|
||||
def write_countdown_html(mission_name, timer_text):
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {{
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-family: Consolas, monospace;
|
||||
}}
|
||||
#mission {{ font-size: 4vw; margin-bottom: 20px; }}
|
||||
#timer {{ font-size: 8vw; margin-bottom: 40px; }}
|
||||
</style>
|
||||
<script>
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mission">{mission_name}</div>
|
||||
<div id="timer">{timer_text}</div>
|
||||
</body>
|
||||
</html>"""
|
||||
with open(COUNTDOWN_HTML, "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
|
||||
# -------------------------
|
||||
# Write Go/No-Go HTML
|
||||
# -------------------------
|
||||
def write_gonogo_html(gonogo_values=None):
|
||||
if gonogo_values is None:
|
||||
gonogo_values = ["N/A", "N/A", "N/A"]
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {{
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
color: white;
|
||||
font-family: Consolas, monospace;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}}
|
||||
#gonogo {{
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
}}
|
||||
.status-box {{
|
||||
border: 2px solid white;
|
||||
padding: 20px 40px;
|
||||
font-size: 2.5vw;
|
||||
text-align: center;
|
||||
background-color: #111;
|
||||
}}
|
||||
.go {{ color: #0f0; }}
|
||||
.nogo {{ color: #f00; }}
|
||||
</style>
|
||||
<script>
|
||||
setTimeout(() => location.reload(), 5000);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="gonogo">
|
||||
<div class="status-box {'go' if gonogo_values[0].lower()=='go' else 'nogo'}">Range: {gonogo_values[0]}</div>
|
||||
<div class="status-box {'go' if gonogo_values[2].lower()=='go' else 'nogo'}">Vehicle: {gonogo_values[2]}</div>
|
||||
<div class="status-box {'go' if gonogo_values[1].lower()=='go' else 'nogo'}">Weather: {gonogo_values[1]}</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
with open(GONOGO_HTML, "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
|
||||
# -------------------------
|
||||
# Countdown App
|
||||
# -------------------------
|
||||
class CountdownApp:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("RocketLaunchCountdown")
|
||||
self.root.config(bg="black")
|
||||
self.root.attributes("-topmost", True)
|
||||
|
||||
# State
|
||||
self.running = False
|
||||
self.on_hold = False
|
||||
self.scrubbed = False
|
||||
self.counting_up = False
|
||||
self.target_time = None
|
||||
self.hold_start_time = None
|
||||
self.remaining_time = 0
|
||||
self.mission_name = "Placeholder Mission"
|
||||
# fetch_gonogo() returns [Range, Weather, Vehicle] to match gonogo.html writer
|
||||
self.gonogo_values = fetch_gonogo()
|
||||
self.last_gonogo_update = time.time()
|
||||
|
||||
# Display
|
||||
self.text = tk.Label(root, text="T-00:00:00", font=("Consolas", 80, "bold"), fg="white", bg="black")
|
||||
self.text.pack(padx=50, pady=20)
|
||||
|
||||
# Mission name input
|
||||
frame_top = tk.Frame(root, bg="black")
|
||||
frame_top.pack(pady=5)
|
||||
tk.Label(frame_top, text="Mission Name:", fg="white", bg="black").pack(side="left")
|
||||
self.mission_entry = tk.Entry(frame_top, width=20, font=("Arial", 18))
|
||||
self.mission_entry.insert(0, self.mission_name)
|
||||
self.mission_entry.pack(side="left")
|
||||
|
||||
# Mode toggle
|
||||
frame_mode = tk.Frame(root, bg="black")
|
||||
frame_mode.pack(pady=5)
|
||||
self.mode_var = tk.StringVar(value="duration")
|
||||
self.rb_duration = tk.Radiobutton(frame_mode, text="Duration", variable=self.mode_var, value="duration",
|
||||
fg="white", bg="black", indicatoron=0, width=12,
|
||||
command=self.update_inputs)
|
||||
self.rb_duration.pack(side="left", padx=5)
|
||||
self.rb_clock = tk.Radiobutton(frame_mode, text="Clock Time", variable=self.mode_var, value="clock",
|
||||
fg="black", bg="white", indicatoron=0, width=12,
|
||||
command=self.update_inputs)
|
||||
self.rb_clock.pack(side="left", padx=5)
|
||||
|
||||
def update_mode_buttons(*args):
|
||||
val = self.mode_var.get()
|
||||
if val == 'duration':
|
||||
self.rb_duration.config(bg='black', fg='white', relief='sunken')
|
||||
self.rb_clock.config(bg='white', fg='black', relief='raised')
|
||||
else:
|
||||
self.rb_duration.config(bg='white', fg='black', relief='raised')
|
||||
self.rb_clock.config(bg='black', fg='white', relief='sunken')
|
||||
|
||||
self.mode_var.trace_add('write', update_mode_buttons)
|
||||
update_mode_buttons()
|
||||
|
||||
# Duration inputs
|
||||
frame_duration = tk.Frame(root, bg="black")
|
||||
frame_duration.pack(pady=5)
|
||||
tk.Label(frame_duration, text="H", fg="white", bg="black").pack(side="left")
|
||||
self.hours_entry = tk.Entry(frame_duration, width=3, font=("Arial", 18))
|
||||
self.hours_entry.insert(0, "0")
|
||||
self.hours_entry.pack(side="left", padx=2)
|
||||
tk.Label(frame_duration, text="M", fg="white", bg="black").pack(side="left")
|
||||
self.minutes_entry = tk.Entry(frame_duration, width=3, font=("Arial", 18))
|
||||
self.minutes_entry.insert(0, "5")
|
||||
self.minutes_entry.pack(side="left", padx=2)
|
||||
tk.Label(frame_duration, text="S", fg="white", bg="black").pack(side="left")
|
||||
self.seconds_entry = tk.Entry(frame_duration, width=3, font=("Arial", 18))
|
||||
self.seconds_entry.insert(0, "0")
|
||||
self.seconds_entry.pack(side="left", padx=2)
|
||||
|
||||
# Clock time input
|
||||
frame_clock = tk.Frame(root, bg="black")
|
||||
frame_clock.pack(pady=5)
|
||||
tk.Label(frame_clock, text="HH:MM", fg="white", bg="black").pack(side="left")
|
||||
self.clock_entry = tk.Entry(frame_clock, width=7, font=("Arial", 18))
|
||||
self.clock_entry.insert(0, "14:00")
|
||||
self.clock_entry.pack(side="left", padx=5)
|
||||
|
||||
# Control buttons
|
||||
frame_buttons = tk.Frame(root, bg="black")
|
||||
frame_buttons.pack(pady=10)
|
||||
|
||||
self.start_btn = tk.Button(frame_buttons, text="▶ Start", command=self.start, font=("Arial", 14))
|
||||
self.start_btn.grid(row=0, column=0, padx=5)
|
||||
|
||||
# Hold and resume share the same position
|
||||
self.hold_btn = tk.Button(frame_buttons, text="⏸ Hold", command=self.hold, font=("Arial", 14))
|
||||
self.hold_btn.grid(row=0, column=1, padx=5)
|
||||
|
||||
self.resume_btn = tk.Button(frame_buttons, text="⏵ Resume", command=self.resume, font=("Arial", 14))
|
||||
self.resume_btn.grid(row=0, column=1, padx=5)
|
||||
self.resume_btn.grid_remove() # hidden at start
|
||||
|
||||
self.scrub_btn = tk.Button(frame_buttons, text="🚫 Scrub", command=self.scrub, font=("Arial", 14), fg="red")
|
||||
self.scrub_btn.grid(row=0, column=2, padx=5)
|
||||
|
||||
self.reset_btn = tk.Button(frame_buttons, text="⟳ Reset", command=self.reset, font=("Arial", 14))
|
||||
self.reset_btn.grid(row=0, column=3, padx=5)
|
||||
|
||||
frame_gn = tk.Frame(root, bg="black")
|
||||
frame_gn.pack(pady=10)
|
||||
# Labels displayed: Range, Weather, Vehicle — match write_gonogo_html ordering
|
||||
self.range_label = tk.Label(frame_gn, text="RANGE: N/A", font=("Consolas", 20), fg="white", bg="black")
|
||||
self.range_label.pack()
|
||||
self.weather_label = tk.Label(frame_gn, text="WEATHER: N/A", font=("Consolas", 20), fg="white", bg="black")
|
||||
self.weather_label.pack()
|
||||
self.vehicle_label = tk.Label(frame_gn, text="VEHICLE: N/A", font=("Consolas", 20), fg="white", bg="black")
|
||||
self.vehicle_label.pack()
|
||||
|
||||
|
||||
self.update_inputs()
|
||||
self.update_clock()
|
||||
|
||||
# ----------------------------
|
||||
# Update input visibility based on mode
|
||||
# ----------------------------
|
||||
def update_inputs(self):
|
||||
if self.mode_var.get() == "duration":
|
||||
self.hours_entry.config(state="normal")
|
||||
self.minutes_entry.config(state="normal")
|
||||
self.seconds_entry.config(state="normal")
|
||||
self.clock_entry.config(state="disabled")
|
||||
else:
|
||||
self.hours_entry.config(state="disabled")
|
||||
self.minutes_entry.config(state="disabled")
|
||||
self.seconds_entry.config(state="disabled")
|
||||
self.clock_entry.config(state="normal")
|
||||
|
||||
# ----------------------------
|
||||
# Control logic
|
||||
# ----------------------------
|
||||
def start(self):
|
||||
self.mission_name = self.mission_entry.get().strip() or "Placeholder Mission"
|
||||
self.running = True
|
||||
self.on_hold = False
|
||||
self.scrubbed = False
|
||||
self.counting_up = False
|
||||
self.show_hold_button()
|
||||
|
||||
try:
|
||||
if self.mode_var.get() == "duration":
|
||||
h = int(self.hours_entry.get())
|
||||
m = int(self.minutes_entry.get())
|
||||
s = int(self.seconds_entry.get())
|
||||
total_seconds = h * 3600 + m * 60 + s
|
||||
else:
|
||||
now = datetime.now()
|
||||
parts = [int(p) for p in self.clock_entry.get().split(":")]
|
||||
h, m = parts[0], parts[1]
|
||||
s = parts[2] if len(parts) == 3 else 0
|
||||
target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
|
||||
if target_today < now:
|
||||
target_today = target_today.replace(day=now.day + 1)
|
||||
total_seconds = (target_today - now).total_seconds()
|
||||
except Exception:
|
||||
self.text.config(text="Invalid time")
|
||||
write_countdown_html(self.mission_name, "Invalid time")
|
||||
return
|
||||
|
||||
self.target_time = time.time() + total_seconds
|
||||
self.remaining_time = total_seconds
|
||||
|
||||
def hold(self):
|
||||
if self.running and not self.on_hold and not self.scrubbed:
|
||||
self.on_hold = True
|
||||
self.hold_start_time = time.time()
|
||||
self.remaining_time = max(0, self.target_time - self.hold_start_time)
|
||||
self.show_resume_button()
|
||||
|
||||
def resume(self):
|
||||
if self.running and self.on_hold and not self.scrubbed:
|
||||
self.on_hold = False
|
||||
self.target_time = time.time() + self.remaining_time
|
||||
self.show_hold_button()
|
||||
|
||||
def show_hold_button(self):
|
||||
self.resume_btn.grid_remove()
|
||||
self.hold_btn.grid()
|
||||
|
||||
def show_resume_button(self):
|
||||
self.hold_btn.grid_remove()
|
||||
self.resume_btn.grid()
|
||||
|
||||
def scrub(self):
|
||||
self.scrubbed = True
|
||||
self.running = False
|
||||
write_countdown_html(self.mission_name, "SCRUB")
|
||||
self.text.config(text="SCRUB")
|
||||
|
||||
def reset(self):
|
||||
self.running = False
|
||||
self.on_hold = False
|
||||
self.scrubbed = False
|
||||
self.counting_up = False
|
||||
self.text.config(text="T-00:00:00")
|
||||
write_countdown_html(self.mission_name, "T-00:00:00")
|
||||
self.show_hold_button()
|
||||
|
||||
# ----------------------------
|
||||
# Clock updating
|
||||
# ----------------------------
|
||||
def format_time(self, seconds, prefix="T-"):
|
||||
h = int(seconds // 3600)
|
||||
m = int((seconds % 3600) // 60)
|
||||
s = int(seconds % 60)
|
||||
return f"{prefix}{h:02}:{m:02}:{s:02}"
|
||||
|
||||
def update_clock(self):
|
||||
now_time = time.time()
|
||||
|
||||
# Update timer
|
||||
if self.running and not self.scrubbed:
|
||||
if self.on_hold:
|
||||
elapsed = int(now_time - self.hold_start_time)
|
||||
timer_text = self.format_time(elapsed, "H+")
|
||||
elif self.target_time:
|
||||
diff = int(self.target_time - now_time)
|
||||
if diff <= 0 and not self.counting_up:
|
||||
self.counting_up = True
|
||||
self.target_time = now_time
|
||||
diff = 0
|
||||
if self.counting_up:
|
||||
elapsed = int(now_time - self.target_time)
|
||||
timer_text = self.format_time(elapsed, "T+")
|
||||
else:
|
||||
timer_text = self.format_time(diff, "T-")
|
||||
else:
|
||||
timer_text = "T-00:00:00"
|
||||
else:
|
||||
timer_text = self.text.cget("text")
|
||||
|
||||
self.text.config(text=timer_text)
|
||||
write_countdown_html(self.mission_name, timer_text)
|
||||
|
||||
# Update Go/No-Go every 10 seconds
|
||||
if now_time - self.last_gonogo_update > 0.1:
|
||||
# fetch_gonogo returns [Range, Weather, Vehicle]
|
||||
self.range_status, self.weather, self.vehicle = fetch_gonogo()
|
||||
self.range_label.config(text=f"RANGE: {self.range_status}")
|
||||
self.weather_label.config(text=f"WEATHER: {self.weather}")
|
||||
self.vehicle_label.config(text=f"VEHICLE: {self.vehicle}")
|
||||
self.gonogo_values = [self.range_status, self.weather, self.vehicle]
|
||||
write_gonogo_html(self.gonogo_values)
|
||||
self.last_gonogo_update = now_time
|
||||
|
||||
self.root.after(200, self.update_clock)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = CountdownApp(root)
|
||||
write_countdown_html("Placeholder Mission", "T-00:00:00")
|
||||
write_gonogo_html(fetch_gonogo())
|
||||
root.mainloop()
|
||||
Reference in New Issue
Block a user