Loading noctua/config/devices.ini +7 −7 Original line number Diff line number Diff line Loading @@ -125,14 +125,14 @@ module = ipcam class = Webcam node = IPCAM ###################################### [tel_temp] module = astelco class = Sensor node = CABINET outlet1 = 1 outlet2 = 2 # [tel_temp] # module = astelco # class = Sensor # node = CABINET # outlet1 = 1 # outlet2 = 2 ###################################### # [met] # module = meteo Loading noctua/web/__init__.py +14 −2 Original line number Diff line number Diff line Loading @@ -70,12 +70,10 @@ async def start_background_tasks(): if parts: subsystems.add(parts[0]) print("---") for subsys in subsystems: if subsys not in ("blocks", "templates"): asyncio.create_task(streamer.subsystem_poll_loop(subsys)) print(subsys) print("---") @web_blueprint.websocket('/socket') Loading Loading @@ -203,6 +201,20 @@ async def sequencer_page(): return await render_template('sequencer.html') @web_blueprint.route('/control') async def control_page(): """ Render the control page. Returns ------- str The rendered HTML content. """ return await render_template('control.html') @web_blueprint.route('/subsystem') @web_blueprint.route('/subsystem/') async def subsystems(): Loading noctua/web/pages/base.html +9 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,15 @@ Init </a> </li> <li class="nav-item"> <a class="nav-link {% if request.endpoint == 'web.control' %}active{% endif %}" href="{{ url_for('web.control_page') }}"> Control </a> </li> <li class="nav-item"> <a class="nav-link {% if request.endpoint == 'web.status_monitor' %}active{% endif %}" href="{{ url_for('web.status_monitor') }}"> Loading noctua/web/pages/control.html 0 → 100644 +83 −0 Original line number Diff line number Diff line {% extends "base.html" %} {% import "macros/widgets.html" as w %} {% import "macros/control_elements.html" as ctrl %} {% block content %} <div class="row g-3"> <!-- LEFT: Telescope & Dome Controls --> <section class="col-xl-4 col-lg-6"> {{ ctrl.mount_pointing() }} <div class="bg-dark p-3 rounded border border-secondary shadow-sm mb-3"> {{ w.widget_input({ "label": "Focus", "info": "/telescope/focuser", "inputs": [{"value": 0}], "unit": "µm", "buttons": [{"label": "Set", "endpoint": "/telescope/focuser", "method": "PUT"}], "info_list": [{"label": "status", "status": "telescope-focuser-movement"}] }) }} </div> <div class="bg-dark p-3 rounded border border-secondary shadow-sm"> <h6 class="text-info mb-3">Dome & Shutter</h6> {{ w.widget_shutter("/dome/shutter", "/dome/shutter/movement", extra_flags=False) }} <aside class="row g-0 mt-2 small font-monospace"> <div class="col-4 text-success">Az: <var data-status="dome-position-azimuth">N/A</var>°</div> <div class="col-4 text-success">T: <var data-status="environment-external-Temperature_meteo_station">N/A</var>°</div> <div class="col-4 text-success text-end">Hum: <var data-status="environment-external-Humidity_meteo_station">N/A</var>%</div> </aside> </div> </section> <!-- CENTER: Camera & Sequencer Expose --> <section class="col-xl-4 col-lg-6"> {{ ctrl.camera_exposure() }} <!-- Quick status of the camera --> <div class="card bg-black border-secondary p-2 small font-monospace text-success"> <div>State: <var data-status="camera-snapshot-state">N/A</var></div> <div>Cooler: <var data-status="camera-cooler">N/A</var> (<var data-status="camera-temperature">N/A</var>°C)</div> </div> </section> <!-- RIGHT: Monitors (FITS, Webcam, Output) --> <section class="col-xl-4 col-12"> <div class="card bg-dark border-secondary h-100 shadow-sm"> <div class="card-header p-0 border-secondary"> <ul class="nav nav-tabs border-0" role="tablist"> <li class="nav-item"><button class="nav-link active py-2" data-bs-toggle="tab" data-bs-target="#mon-fits">FITS</button></li> <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-webcam">Webcam</button></li> <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-output">Output</button></li> </ul> </div> <div class="card-body p-2 bg-black overflow-auto" style="min-height: 400px;"> <div class="tab-content"> <div class="tab-pane fade show active" id="mon-fits"> <!-- Placeholder for FITS Viewer component --> <div class="text-center text-muted mt-5">FITS Monitor active</div> </div> <div class="tab-pane fade" id="mon-webcam"> <img id="webcam-snapshot" class="img-fluid w-100" alt="Webcam"> </div> <div class="tab-pane fade" id="mon-output"> <div id="sequencer-output-display" class="p-3 text-info font-monospace small"> Waiting for template output data... </div> </div> </div> </div> </div> </section> </div> {% endblock %} {% block scripts %} <script src="{{ url_for('web.static', filename='js/control.js') }}"></script> <script src="{{ url_for('web.static', filename='js/webcam.js') }}"></script> <script src="{{ url_for('web.static', filename='js/status-stream.js') }}"></script> <script src="{{ url_for('web.static', filename='js/actions.js') }}"></script> {% endblock %} noctua/web/pages/macros/control_elements.html 0 → 100644 +143 −0 Original line number Diff line number Diff line {# control_elements.html --------------------- Specialized components for the operational control page. #} {% macro mount_pointing() %} <div class="card bg-dark border-secondary shadow-sm mb-3"> <div class="card-body"> <h6 class="card-title text-info mb-3">Mount Pointing</h6> <ul class="nav nav-tabs nav-fill mb-3" id="pointing-tabs" role="tablist"> <li class="nav-item"> <button class="nav-link py-1 active" data-bs-toggle="tab" data-bs-target="#tab-radec">Ra / Dec</button> </li> <li class="nav-item"> <button class="nav-link py-1" data-bs-toggle="tab" data-bs-target="#tab-altaz">Alt / Az</button> </li> </ul> <div class="tab-content"> <!-- RaDec Tab --> <div class="tab-pane fade show active" id="tab-radec"> <div class="input-group input-group-sm mb-2"> <span class="input-group-text bg-dark border-secondary text-muted" style="width: 80px;"> α,δ / ID </span> <input type="text" class="form-control bg-black text-info border-secondary" id="in-radec" placeholder="Object or HH MM SS ±DD MM SS" data-parameter="mount-target"> <span class="input-group-text bg-dark border-secondary text-muted"> hms ±dms / id </span> </div> </div> <!-- AltAz Tab --> <div class="tab-pane fade" id="tab-altaz"> <div class="input-group input-group-sm mb-2"> <span class="input-group-text bg-dark border-secondary text-muted">Alt</span> <input type="number" class="form-control bg-black text-info border-secondary" id="in-alt" placeholder="45.0"> <span class="input-group-text bg-dark border-secondary text-muted">Az</span> <input type="number" class="form-control bg-black text-info border-secondary" id="in-az" placeholder="180.0"> <span class="input-group-text bg-dark border-secondary text-muted"> ° </span> </div> </div> </div> <div class="d-flex gap-2 mt-3"> <button class="btn btn-primary btn-sm flex-fill btn-universal" id="btn-mount-slew" data-method="POST" data-url="/telescope/coordinates/movement/radec">SLEW</button> <button class="btn btn-outline-danger btn-sm flex-fill btn-universal" data-method="DELETE" data-url="/telescope/coordinates/movement">STOP</button> </div> <!-- Real-time feedback bar --> <div class="row g-0 mt-3 pt-2 border-top border-secondary small font-monospace"> <div class="col-6 text-success">RA: <var data-status="telescope-coordinates-radec">N/A</var></div> <div class="col-6 text-success text-end">HA: <var data-status="telescope-coordinates-ha">N/A</var></div> <div class="col-6 text-success">Alt: <var data-status="telescope-coordinates-altaz">N/A</var></div> <div class="col-6 text-success text-end">LST: <var data-status="telescope-coordinates-lst">N/A</var></div> </div> </div> </div> {% endmacro %} {% macro camera_exposure() %} <div class="card bg-dark border-secondary shadow-sm mb-3"> <div class="card-body"> <h6 class="card-title text-info mb-3">Observation (Expose)</h6> <div class="row g-2 mb-2"> <div class="col-4"> <label class="small text-muted">Type</label> <select class="form-select form-select-sm bg-black text-light border-secondary" data-parameter="observation-frametype"> <option value="Light">Light</option> <option value="Bias">Bias</option> <option value="Flat">Flat</option> <option value="Dark">Dark</option> </select> </div> <div class="col-4"> <label class="small text-muted">Filter</label> <select class="form-select form-select-sm bg-black text-light border-secondary" data-parameter="observation-filter"> <option value="SDSS u">SDSS u</option> <option value="SDSS g">SDSS g</option> <option value="SDSS r">SDSS r</option> <option value="SDSS i">SDSS i</option> <option value="SDSS z">SDSS z</option> <option value="SDSS y">SDSS y</option> <option value="SDSS FREE">Free</option> </select> </div> <div class="col-4"> <label class="small text-muted">Binning</label> <select class="form-select form-select-sm bg-black text-light border-secondary" data-parameter="observation-binning"> <option value="1">1x1</option><option value="2">2x2</option><option value="3">3x3</option> </select> </div> </div> <div class="row g-2 mb-3"> <div class="col-6"> <label class="small text-muted">Exptime (s)</label> <input type="number" class="form-control form-control-sm bg-black text-info border-secondary" value="10.0" data-parameter="observation-exptime"> </div> <div class="col-6"> <label class="small text-muted">Repeat (#)</label> <input type="number" class="form-control form-control-sm bg-black text-info border-secondary" value="1" data-parameter="observation-repeat"> </div> <div class="col-12"> <label class="small text-muted">Object Name</label> <input type="text" class="form-control form-control-sm bg-black text-info border-secondary" value="Test" data-parameter="observation-object"> </div> </div> <div class="d-flex gap-2 mb-3 align-items-center bg-black bg-opacity-25 p-2 rounded"> <div class="form-check form-switch mb-0"> <input class="form-check-input" type="checkbox" id="check-recenter" data-parameter="observation-recenter"> <label class="form-check-label small text-muted" for="check-recenter">Recenter</label> </div> <div class="input-group input-group-sm ms-auto" style="max-width: 120px;"> <input type="number" class="form-control bg-black text-info border-secondary" value="300" data-parameter="observation-box"> <span class="input-group-text bg-dark border-secondary small">px</span> </div> </div> <div class="d-flex gap-2"> <!-- This button triggers a sequencer run with the 'observation' template --> <button class="btn btn-success flex-fill btn-universal" id="btn-camera-expose" data-method="POST" data-url="/sequencer/run"> EXPOSE </button> <button class="btn btn-outline-danger btn-universal" data-method="DELETE" data-url="/sequencer/run"> STOP </button> </div> </div> </div> {% endmacro %} Loading
noctua/config/devices.ini +7 −7 Original line number Diff line number Diff line Loading @@ -125,14 +125,14 @@ module = ipcam class = Webcam node = IPCAM ###################################### [tel_temp] module = astelco class = Sensor node = CABINET outlet1 = 1 outlet2 = 2 # [tel_temp] # module = astelco # class = Sensor # node = CABINET # outlet1 = 1 # outlet2 = 2 ###################################### # [met] # module = meteo Loading
noctua/web/__init__.py +14 −2 Original line number Diff line number Diff line Loading @@ -70,12 +70,10 @@ async def start_background_tasks(): if parts: subsystems.add(parts[0]) print("---") for subsys in subsystems: if subsys not in ("blocks", "templates"): asyncio.create_task(streamer.subsystem_poll_loop(subsys)) print(subsys) print("---") @web_blueprint.websocket('/socket') Loading Loading @@ -203,6 +201,20 @@ async def sequencer_page(): return await render_template('sequencer.html') @web_blueprint.route('/control') async def control_page(): """ Render the control page. Returns ------- str The rendered HTML content. """ return await render_template('control.html') @web_blueprint.route('/subsystem') @web_blueprint.route('/subsystem/') async def subsystems(): Loading
noctua/web/pages/base.html +9 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,15 @@ Init </a> </li> <li class="nav-item"> <a class="nav-link {% if request.endpoint == 'web.control' %}active{% endif %}" href="{{ url_for('web.control_page') }}"> Control </a> </li> <li class="nav-item"> <a class="nav-link {% if request.endpoint == 'web.status_monitor' %}active{% endif %}" href="{{ url_for('web.status_monitor') }}"> Loading
noctua/web/pages/control.html 0 → 100644 +83 −0 Original line number Diff line number Diff line {% extends "base.html" %} {% import "macros/widgets.html" as w %} {% import "macros/control_elements.html" as ctrl %} {% block content %} <div class="row g-3"> <!-- LEFT: Telescope & Dome Controls --> <section class="col-xl-4 col-lg-6"> {{ ctrl.mount_pointing() }} <div class="bg-dark p-3 rounded border border-secondary shadow-sm mb-3"> {{ w.widget_input({ "label": "Focus", "info": "/telescope/focuser", "inputs": [{"value": 0}], "unit": "µm", "buttons": [{"label": "Set", "endpoint": "/telescope/focuser", "method": "PUT"}], "info_list": [{"label": "status", "status": "telescope-focuser-movement"}] }) }} </div> <div class="bg-dark p-3 rounded border border-secondary shadow-sm"> <h6 class="text-info mb-3">Dome & Shutter</h6> {{ w.widget_shutter("/dome/shutter", "/dome/shutter/movement", extra_flags=False) }} <aside class="row g-0 mt-2 small font-monospace"> <div class="col-4 text-success">Az: <var data-status="dome-position-azimuth">N/A</var>°</div> <div class="col-4 text-success">T: <var data-status="environment-external-Temperature_meteo_station">N/A</var>°</div> <div class="col-4 text-success text-end">Hum: <var data-status="environment-external-Humidity_meteo_station">N/A</var>%</div> </aside> </div> </section> <!-- CENTER: Camera & Sequencer Expose --> <section class="col-xl-4 col-lg-6"> {{ ctrl.camera_exposure() }} <!-- Quick status of the camera --> <div class="card bg-black border-secondary p-2 small font-monospace text-success"> <div>State: <var data-status="camera-snapshot-state">N/A</var></div> <div>Cooler: <var data-status="camera-cooler">N/A</var> (<var data-status="camera-temperature">N/A</var>°C)</div> </div> </section> <!-- RIGHT: Monitors (FITS, Webcam, Output) --> <section class="col-xl-4 col-12"> <div class="card bg-dark border-secondary h-100 shadow-sm"> <div class="card-header p-0 border-secondary"> <ul class="nav nav-tabs border-0" role="tablist"> <li class="nav-item"><button class="nav-link active py-2" data-bs-toggle="tab" data-bs-target="#mon-fits">FITS</button></li> <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-webcam">Webcam</button></li> <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-output">Output</button></li> </ul> </div> <div class="card-body p-2 bg-black overflow-auto" style="min-height: 400px;"> <div class="tab-content"> <div class="tab-pane fade show active" id="mon-fits"> <!-- Placeholder for FITS Viewer component --> <div class="text-center text-muted mt-5">FITS Monitor active</div> </div> <div class="tab-pane fade" id="mon-webcam"> <img id="webcam-snapshot" class="img-fluid w-100" alt="Webcam"> </div> <div class="tab-pane fade" id="mon-output"> <div id="sequencer-output-display" class="p-3 text-info font-monospace small"> Waiting for template output data... </div> </div> </div> </div> </div> </section> </div> {% endblock %} {% block scripts %} <script src="{{ url_for('web.static', filename='js/control.js') }}"></script> <script src="{{ url_for('web.static', filename='js/webcam.js') }}"></script> <script src="{{ url_for('web.static', filename='js/status-stream.js') }}"></script> <script src="{{ url_for('web.static', filename='js/actions.js') }}"></script> {% endblock %}
noctua/web/pages/macros/control_elements.html 0 → 100644 +143 −0 Original line number Diff line number Diff line {# control_elements.html --------------------- Specialized components for the operational control page. #} {% macro mount_pointing() %} <div class="card bg-dark border-secondary shadow-sm mb-3"> <div class="card-body"> <h6 class="card-title text-info mb-3">Mount Pointing</h6> <ul class="nav nav-tabs nav-fill mb-3" id="pointing-tabs" role="tablist"> <li class="nav-item"> <button class="nav-link py-1 active" data-bs-toggle="tab" data-bs-target="#tab-radec">Ra / Dec</button> </li> <li class="nav-item"> <button class="nav-link py-1" data-bs-toggle="tab" data-bs-target="#tab-altaz">Alt / Az</button> </li> </ul> <div class="tab-content"> <!-- RaDec Tab --> <div class="tab-pane fade show active" id="tab-radec"> <div class="input-group input-group-sm mb-2"> <span class="input-group-text bg-dark border-secondary text-muted" style="width: 80px;"> α,δ / ID </span> <input type="text" class="form-control bg-black text-info border-secondary" id="in-radec" placeholder="Object or HH MM SS ±DD MM SS" data-parameter="mount-target"> <span class="input-group-text bg-dark border-secondary text-muted"> hms ±dms / id </span> </div> </div> <!-- AltAz Tab --> <div class="tab-pane fade" id="tab-altaz"> <div class="input-group input-group-sm mb-2"> <span class="input-group-text bg-dark border-secondary text-muted">Alt</span> <input type="number" class="form-control bg-black text-info border-secondary" id="in-alt" placeholder="45.0"> <span class="input-group-text bg-dark border-secondary text-muted">Az</span> <input type="number" class="form-control bg-black text-info border-secondary" id="in-az" placeholder="180.0"> <span class="input-group-text bg-dark border-secondary text-muted"> ° </span> </div> </div> </div> <div class="d-flex gap-2 mt-3"> <button class="btn btn-primary btn-sm flex-fill btn-universal" id="btn-mount-slew" data-method="POST" data-url="/telescope/coordinates/movement/radec">SLEW</button> <button class="btn btn-outline-danger btn-sm flex-fill btn-universal" data-method="DELETE" data-url="/telescope/coordinates/movement">STOP</button> </div> <!-- Real-time feedback bar --> <div class="row g-0 mt-3 pt-2 border-top border-secondary small font-monospace"> <div class="col-6 text-success">RA: <var data-status="telescope-coordinates-radec">N/A</var></div> <div class="col-6 text-success text-end">HA: <var data-status="telescope-coordinates-ha">N/A</var></div> <div class="col-6 text-success">Alt: <var data-status="telescope-coordinates-altaz">N/A</var></div> <div class="col-6 text-success text-end">LST: <var data-status="telescope-coordinates-lst">N/A</var></div> </div> </div> </div> {% endmacro %} {% macro camera_exposure() %} <div class="card bg-dark border-secondary shadow-sm mb-3"> <div class="card-body"> <h6 class="card-title text-info mb-3">Observation (Expose)</h6> <div class="row g-2 mb-2"> <div class="col-4"> <label class="small text-muted">Type</label> <select class="form-select form-select-sm bg-black text-light border-secondary" data-parameter="observation-frametype"> <option value="Light">Light</option> <option value="Bias">Bias</option> <option value="Flat">Flat</option> <option value="Dark">Dark</option> </select> </div> <div class="col-4"> <label class="small text-muted">Filter</label> <select class="form-select form-select-sm bg-black text-light border-secondary" data-parameter="observation-filter"> <option value="SDSS u">SDSS u</option> <option value="SDSS g">SDSS g</option> <option value="SDSS r">SDSS r</option> <option value="SDSS i">SDSS i</option> <option value="SDSS z">SDSS z</option> <option value="SDSS y">SDSS y</option> <option value="SDSS FREE">Free</option> </select> </div> <div class="col-4"> <label class="small text-muted">Binning</label> <select class="form-select form-select-sm bg-black text-light border-secondary" data-parameter="observation-binning"> <option value="1">1x1</option><option value="2">2x2</option><option value="3">3x3</option> </select> </div> </div> <div class="row g-2 mb-3"> <div class="col-6"> <label class="small text-muted">Exptime (s)</label> <input type="number" class="form-control form-control-sm bg-black text-info border-secondary" value="10.0" data-parameter="observation-exptime"> </div> <div class="col-6"> <label class="small text-muted">Repeat (#)</label> <input type="number" class="form-control form-control-sm bg-black text-info border-secondary" value="1" data-parameter="observation-repeat"> </div> <div class="col-12"> <label class="small text-muted">Object Name</label> <input type="text" class="form-control form-control-sm bg-black text-info border-secondary" value="Test" data-parameter="observation-object"> </div> </div> <div class="d-flex gap-2 mb-3 align-items-center bg-black bg-opacity-25 p-2 rounded"> <div class="form-check form-switch mb-0"> <input class="form-check-input" type="checkbox" id="check-recenter" data-parameter="observation-recenter"> <label class="form-check-label small text-muted" for="check-recenter">Recenter</label> </div> <div class="input-group input-group-sm ms-auto" style="max-width: 120px;"> <input type="number" class="form-control bg-black text-info border-secondary" value="300" data-parameter="observation-box"> <span class="input-group-text bg-dark border-secondary small">px</span> </div> </div> <div class="d-flex gap-2"> <!-- This button triggers a sequencer run with the 'observation' template --> <button class="btn btn-success flex-fill btn-universal" id="btn-camera-expose" data-method="POST" data-url="/sequencer/run"> EXPOSE </button> <button class="btn btn-outline-danger btn-universal" data-method="DELETE" data-url="/sequencer/run"> STOP </button> </div> </div> </div> {% endmacro %}