mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 10:22:02 +00:00
feat: add development mode flag HACI_DEV and PTP status fields in API/UI
Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
This commit is contained in:
parent
ee4a5a3630
commit
d8eb1f9824
6 changed files with 115 additions and 1 deletions
|
|
@ -20,7 +20,7 @@ RUN rm -rf src
|
|||
|
||||
# Copy full source and build project
|
||||
COPY . .
|
||||
RUN cargo build --release
|
||||
RUN cargo build --release && cargo install --locked statime
|
||||
|
||||
# (Statime installation removed; container runs only haci)
|
||||
|
||||
|
|
@ -37,6 +37,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/target/release/get-haci /usr/local/bin/get-haci
|
||||
COPY --from=builder /usr/local/cargo/bin/statime /usr/local/bin/statime
|
||||
COPY static ./static
|
||||
COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
COPY scripts/ltc_gen.sh /usr/local/bin/ltc-gen.sh
|
||||
|
|
|
|||
|
|
@ -8,8 +8,15 @@ services:
|
|||
- RUST_LOG=info
|
||||
- MOCK_TEENSY=1
|
||||
- HACI_SERIAL_PORT=/dev/ttyACM0
|
||||
- HACI_DEV=1
|
||||
- RUN_STATIME=1
|
||||
- PTP_INTERFACE=eth0
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./static:/app/static:ro
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_NICE
|
||||
- SYS_TIME
|
||||
restart: unless-stopped
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
# Mark container as development unless explicitly disabled
|
||||
export HACI_DEV="${HACI_DEV:-1}"
|
||||
|
||||
# If enabled, start a mock Teensy that exposes a PTY at /dev/ttyACM0 and streams LTC-like lines.
|
||||
if [ "${MOCK_TEENSY:-0}" = "1" ]; then
|
||||
|
|
@ -26,5 +28,19 @@ if [ "${MOCK_TEENSY:-0}" = "1" ]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
# Optionally start the PTP daemon (statime) for development
|
||||
if [ "${RUN_STATIME:-0}" = "1" ]; then
|
||||
echo "[entrypoint] Starting statime PTP daemon..." >&2
|
||||
if command -v statime >/dev/null 2>&1; then
|
||||
IFACE="${PTP_INTERFACE:-eth0}"
|
||||
echo "[entrypoint] statime interface: ${IFACE}" >&2
|
||||
# Run statime in background; logs to container stderr/stdout
|
||||
statime -i "${IFACE}" &
|
||||
STATIME_PID=$!
|
||||
else
|
||||
echo "[entrypoint] statime not found on PATH" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run the requested command (default: get-haci)
|
||||
exec "$@"
|
||||
|
|
|
|||
48
src/api.rs
48
src/api.rs
|
|
@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex};
|
|||
use crate::config::{self, Config};
|
||||
use crate::sync_logic::{self, LtcState};
|
||||
use crate::system;
|
||||
use crate::ptp;
|
||||
use num_rational::Ratio;
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
|
|
@ -30,6 +31,18 @@ struct ApiStatus {
|
|||
ntp_active: bool,
|
||||
interfaces: Vec<String>,
|
||||
hardware_offset_ms: i64,
|
||||
|
||||
// Development/PTP fields (optional; defaulted for backward compatibility)
|
||||
#[serde(default)]
|
||||
dev_mode: bool,
|
||||
#[serde(default)]
|
||||
ptp_supported: bool,
|
||||
#[serde(default)]
|
||||
ptp_daemon_running: bool,
|
||||
#[serde(default)]
|
||||
ptp_interface: Option<String>,
|
||||
#[serde(default)]
|
||||
ptp_offset_ns: Option<i128>,
|
||||
}
|
||||
|
||||
// AppState to hold shared data
|
||||
|
|
@ -87,6 +100,36 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
|||
.map(|ifa| ifa.ip().to_string())
|
||||
.collect();
|
||||
|
||||
// Development mode flag
|
||||
let dev_mode = std::env::var("HACI_DEV")
|
||||
.map(|v| {
|
||||
let v = v.to_lowercase();
|
||||
v == "1" || v == "true" || v == "yes"
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
// PTP status (best-effort; safe on non-Linux)
|
||||
let ptp_supported = ptp::is_supported();
|
||||
let ptp_interface = std::env::var("PTP_INTERFACE").ok();
|
||||
let mut ptp_daemon_running = false;
|
||||
let mut ptp_offset_ns: Option<i128> = None;
|
||||
|
||||
if ptp_supported {
|
||||
// Check a few common PHC device nodes
|
||||
for dev in ["/dev/ptp0", "/dev/ptp1", "/dev/ptp2"] {
|
||||
if std::path::Path::new(dev).exists() {
|
||||
if let Ok(clock) = ptp::PtpClock::open(dev) {
|
||||
if let Ok(offset) = clock.offset_from_system_ns() {
|
||||
ptp_offset_ns = Some(offset);
|
||||
// Consider daemon "running" if we can read a PHC offset
|
||||
ptp_daemon_running = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok().json(ApiStatus {
|
||||
ltc_status,
|
||||
ltc_timecode,
|
||||
|
|
@ -101,6 +144,11 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
|||
ntp_active,
|
||||
interfaces,
|
||||
hardware_offset_ms: hw_offset_ms,
|
||||
dev_mode,
|
||||
ptp_supported,
|
||||
ptp_daemon_running,
|
||||
ptp_interface,
|
||||
ptp_offset_ns,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
16
src/main.rs
16
src/main.rs
|
|
@ -31,6 +31,9 @@ use tokio::task::{self, LocalSet};
|
|||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Option<Command>,
|
||||
|
||||
#[arg(long, help = "Enable development mode (also respected via HACI_DEV=1)")]
|
||||
dev: bool,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
|
|
@ -116,6 +119,19 @@ async fn main() {
|
|||
let log_buffer = logger::setup_logger();
|
||||
let args = Args::parse();
|
||||
|
||||
// Determine development mode early and announce
|
||||
let dev_mode = args.dev
|
||||
|| std::env::var("HACI_DEV")
|
||||
.map(|v| {
|
||||
let v = v.to_lowercase();
|
||||
v == "1" || v == "true" || v == "yes"
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if dev_mode {
|
||||
std::env::set_var("HACI_DEV", "1");
|
||||
log::info!("🧪 Development mode enabled");
|
||||
}
|
||||
|
||||
if let Some(command) = &args.command {
|
||||
match command {
|
||||
Command::Daemon => {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@
|
|||
deltaText: document.getElementById('delta-text'),
|
||||
interfaces: document.getElementById('interfaces'),
|
||||
logs: document.getElementById('logs'),
|
||||
ptpSupported: document.getElementById('ptp-supported'),
|
||||
ptpDaemon: document.getElementById('ptp-daemon'),
|
||||
ptpOffset: document.getElementById('ptp-offset'),
|
||||
ptpInterface: document.getElementById('ptp-interface'),
|
||||
devMode: document.getElementById('dev-mode'),
|
||||
};
|
||||
|
||||
const hwOffsetInput = document.getElementById('hw-offset');
|
||||
|
|
@ -150,6 +155,27 @@
|
|||
} else {
|
||||
statusElements.interfaces.textContent = 'No active interfaces found.';
|
||||
}
|
||||
|
||||
// Optional: Development/PTP Status (only if elements exist in DOM)
|
||||
if (statusElements.devMode) {
|
||||
statusElements.devMode.textContent = data.dev_mode ? 'DEV' : '';
|
||||
}
|
||||
if (statusElements.ptpSupported) {
|
||||
statusElements.ptpSupported.textContent = data.ptp_supported ? 'PTP supported' : 'PTP not supported';
|
||||
}
|
||||
if (statusElements.ptpDaemon) {
|
||||
statusElements.ptpDaemon.textContent = data.ptp_daemon_running ? 'PTP daemon: running' : 'PTP daemon: not running';
|
||||
}
|
||||
if (statusElements.ptpInterface) {
|
||||
statusElements.ptpInterface.textContent = data.ptp_interface || '';
|
||||
}
|
||||
if (statusElements.ptpOffset) {
|
||||
if (data.ptp_offset_ns !== null && data.ptp_offset_ns !== undefined) {
|
||||
statusElements.ptpOffset.textContent = `${data.ptp_offset_ns} ns`;
|
||||
} else {
|
||||
statusElements.ptpOffset.textContent = '—';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function animateClocks() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue