Added go/nogo
This commit is contained in:
@@ -1,46 +1,27 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
color: white;
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
justify-content: center;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
align-items: center;
|
color: white;
|
||||||
font-family: Consolas, monospace;
|
font-family: Consolas, monospace;
|
||||||
overflow: hidden;
|
}
|
||||||
}
|
#mission { font-size: 4vw; margin-bottom: 20px; }
|
||||||
#mission {
|
#timer { font-size: 8vw; margin-bottom: 40px; }
|
||||||
font-size: 4vw;
|
</style>
|
||||||
text-align: center;
|
<script>
|
||||||
margin-bottom: 10px;
|
setTimeout(() => location.reload(), 1000);
|
||||||
}
|
</script>
|
||||||
#timer {
|
</head>
|
||||||
font-size: 8vw;
|
<body>
|
||||||
text-align: center;
|
<div id="mission">Starship Flight 12</div>
|
||||||
margin-bottom: 20px;
|
<div id="timer">T-00:00:00</div>
|
||||||
}
|
</body>
|
||||||
</style>
|
</html>
|
||||||
<script>
|
|
||||||
// Auto-refresh every second to update the countdown and timeline if needed
|
|
||||||
setTimeout(() => location.reload(), 1000);
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="mission">Mission</div>
|
|
||||||
<div id="timer">T-00:00:00</div>
|
|
||||||
|
|
||||||
<iframe
|
|
||||||
id="timeline"
|
|
||||||
src="https://docs.google.com/spreadsheets/d/e/2PACX-1vQssCeiHQwlh-2SKHyQnw_KEoYwU9UY9sP0MLNVvW30cEUFVgr8lJ33fAz0SMWmRGfsVBuUqxJ1a4Ba/pubhtml?gid=855477916&single=true"
|
|
||||||
width="90%"
|
|
||||||
height="400"
|
|
||||||
style="border:none; border-radius:10px; overflow:hidden;">
|
|
||||||
</iframe>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
359
experiment.py
Normal file
359
experiment.py
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
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")
|
||||||
|
tk.Radiobutton(frame_mode, text="Duration", variable=self.mode_var, value="duration",
|
||||||
|
fg="white", bg="black", command=self.update_inputs).pack(side="left", padx=5)
|
||||||
|
tk.Radiobutton(frame_mode, text="Clock Time", variable=self.mode_var, value="clock",
|
||||||
|
fg="white", bg="black", command=self.update_inputs).pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 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()
|
||||||
41
gonogo.html
Normal file
41
gonogo.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!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">Range: GO</div>
|
||||||
|
<div class="status-box go">Vehicle: GO</div>
|
||||||
|
<div class="status-box go">Weather: GO</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
146
main.py
146
main.py
@@ -1,10 +1,38 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import requests
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
|
||||||
HTML_FILE = "countdown.html"
|
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()
|
||||||
|
|
||||||
def write_html(mission_name, timer_text):
|
# -------------------------
|
||||||
|
# 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 = f"""<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -17,25 +45,11 @@ body {{
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}}
|
|
||||||
#mission {{
|
|
||||||
color: white;
|
color: white;
|
||||||
font-family: Consolas, monospace;
|
font-family: Consolas, monospace;
|
||||||
font-size: 4vw;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 90vw;
|
|
||||||
text-align: center;
|
|
||||||
}}
|
|
||||||
#timer {{
|
|
||||||
color: white;
|
|
||||||
font-family: Consolas, monospace;
|
|
||||||
font-size: 8vw;
|
|
||||||
line-height: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: center;
|
|
||||||
}}
|
}}
|
||||||
|
#mission {{ font-size: 4vw; margin-bottom: 20px; }}
|
||||||
|
#timer {{ font-size: 8vw; margin-bottom: 40px; }}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
setTimeout(() => location.reload(), 1000);
|
setTimeout(() => location.reload(), 1000);
|
||||||
@@ -46,10 +60,62 @@ setTimeout(() => location.reload(), 1000);
|
|||||||
<div id="timer">{timer_text}</div>
|
<div id="timer">{timer_text}</div>
|
||||||
</body>
|
</body>
|
||||||
</html>"""
|
</html>"""
|
||||||
with open(HTML_FILE, "w", encoding="utf-8") as f:
|
with open(COUNTDOWN_HTML, "w", encoding="utf-8") as f:
|
||||||
f.write(html)
|
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:
|
class CountdownApp:
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
self.root = root
|
self.root = root
|
||||||
@@ -66,15 +132,11 @@ class CountdownApp:
|
|||||||
self.hold_start_time = None
|
self.hold_start_time = None
|
||||||
self.remaining_time = 0
|
self.remaining_time = 0
|
||||||
self.mission_name = "Mission"
|
self.mission_name = "Mission"
|
||||||
|
self.gonogo_values = fetch_gonogo()
|
||||||
|
self.last_gonogo_update = time.time()
|
||||||
|
|
||||||
# Display
|
# Display
|
||||||
self.text = tk.Label(
|
self.text = tk.Label(root, text="T-00:00:00", font=("Consolas", 80, "bold"), fg="white", bg="black")
|
||||||
root,
|
|
||||||
text="T-00:00:00",
|
|
||||||
font=("Consolas", 80, "bold"),
|
|
||||||
fg="white",
|
|
||||||
bg="black"
|
|
||||||
)
|
|
||||||
self.text.pack(padx=50, pady=20)
|
self.text.pack(padx=50, pady=20)
|
||||||
|
|
||||||
# Mission name input
|
# Mission name input
|
||||||
@@ -139,7 +201,6 @@ class CountdownApp:
|
|||||||
self.reset_btn = tk.Button(frame_buttons, text="⟳ Reset", command=self.reset, font=("Arial", 14))
|
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)
|
self.reset_btn.grid(row=0, column=3, padx=5)
|
||||||
|
|
||||||
|
|
||||||
self.update_inputs()
|
self.update_inputs()
|
||||||
self.update_clock()
|
self.update_clock()
|
||||||
|
|
||||||
@@ -186,7 +247,7 @@ class CountdownApp:
|
|||||||
total_seconds = (target_today - now).total_seconds()
|
total_seconds = (target_today - now).total_seconds()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.text.config(text="Invalid time")
|
self.text.config(text="Invalid time")
|
||||||
write_html(self.mission_name, "Invalid time")
|
write_countdown_html(self.mission_name, "Invalid time")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.target_time = time.time() + total_seconds
|
self.target_time = time.time() + total_seconds
|
||||||
@@ -213,11 +274,10 @@ class CountdownApp:
|
|||||||
self.hold_btn.grid_remove()
|
self.hold_btn.grid_remove()
|
||||||
self.resume_btn.grid()
|
self.resume_btn.grid()
|
||||||
|
|
||||||
|
|
||||||
def scrub(self):
|
def scrub(self):
|
||||||
self.scrubbed = True
|
self.scrubbed = True
|
||||||
self.running = False
|
self.running = False
|
||||||
write_html(self.mission_name, "SCRUB")
|
write_countdown_html(self.mission_name, "SCRUB")
|
||||||
self.text.config(text="SCRUB")
|
self.text.config(text="SCRUB")
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
@@ -226,7 +286,7 @@ class CountdownApp:
|
|||||||
self.scrubbed = False
|
self.scrubbed = False
|
||||||
self.counting_up = False
|
self.counting_up = False
|
||||||
self.text.config(text="T-00:00:00")
|
self.text.config(text="T-00:00:00")
|
||||||
write_html(self.mission_name, "T-00:00:00")
|
write_countdown_html(self.mission_name, "T-00:00:00")
|
||||||
self.show_hold_button()
|
self.show_hold_button()
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
@@ -239,19 +299,21 @@ class CountdownApp:
|
|||||||
return f"{prefix}{h:02}:{m:02}:{s:02}"
|
return f"{prefix}{h:02}:{m:02}:{s:02}"
|
||||||
|
|
||||||
def update_clock(self):
|
def update_clock(self):
|
||||||
|
now_time = time.time()
|
||||||
|
|
||||||
|
# Update timer
|
||||||
if self.running and not self.scrubbed:
|
if self.running and not self.scrubbed:
|
||||||
now = time.time()
|
|
||||||
if self.on_hold:
|
if self.on_hold:
|
||||||
elapsed = int(now - self.hold_start_time)
|
elapsed = int(now_time - self.hold_start_time)
|
||||||
timer_text = self.format_time(elapsed, "H+")
|
timer_text = self.format_time(elapsed, "H+")
|
||||||
elif self.target_time:
|
elif self.target_time:
|
||||||
diff = int(self.target_time - now)
|
diff = int(self.target_time - now_time)
|
||||||
if diff <= 0 and not self.counting_up:
|
if diff <= 0 and not self.counting_up:
|
||||||
self.counting_up = True
|
self.counting_up = True
|
||||||
self.target_time = now
|
self.target_time = now_time
|
||||||
diff = 0
|
diff = 0
|
||||||
if self.counting_up:
|
if self.counting_up:
|
||||||
elapsed = int(now - self.target_time)
|
elapsed = int(now_time - self.target_time)
|
||||||
timer_text = self.format_time(elapsed, "T+")
|
timer_text = self.format_time(elapsed, "T+")
|
||||||
else:
|
else:
|
||||||
timer_text = self.format_time(diff, "T-")
|
timer_text = self.format_time(diff, "T-")
|
||||||
@@ -261,12 +323,20 @@ class CountdownApp:
|
|||||||
timer_text = self.text.cget("text")
|
timer_text = self.text.cget("text")
|
||||||
|
|
||||||
self.text.config(text=timer_text)
|
self.text.config(text=timer_text)
|
||||||
write_html(self.mission_name, 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.5:
|
||||||
|
self.gonogo_values = fetch_gonogo()
|
||||||
|
write_gonogo_html(self.gonogo_values)
|
||||||
|
self.last_gonogo_update = now_time
|
||||||
|
|
||||||
self.root.after(200, self.update_clock)
|
self.root.after(200, self.update_clock)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
app = CountdownApp(root)
|
app = CountdownApp(root)
|
||||||
write_html("Mission", "T-00:00:00")
|
write_countdown_html("Mission", "T-00:00:00")
|
||||||
|
write_gonogo_html(fetch_gonogo())
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|||||||
Reference in New Issue
Block a user