mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32: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 full source and build project
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN cargo build --release
|
RUN cargo build --release && cargo install --locked statime
|
||||||
|
|
||||||
# (Statime installation removed; container runs only haci)
|
# (Statime installation removed; container runs only haci)
|
||||||
|
|
||||||
|
|
@ -37,6 +37,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=builder /app/target/release/get-haci /usr/local/bin/get-haci
|
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 static ./static
|
||||||
COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
COPY scripts/ltc_gen.sh /usr/local/bin/ltc-gen.sh
|
COPY scripts/ltc_gen.sh /usr/local/bin/ltc-gen.sh
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,15 @@ services:
|
||||||
- RUST_LOG=info
|
- RUST_LOG=info
|
||||||
- MOCK_TEENSY=1
|
- MOCK_TEENSY=1
|
||||||
- HACI_SERIAL_PORT=/dev/ttyACM0
|
- HACI_SERIAL_PORT=/dev/ttyACM0
|
||||||
|
- HACI_DEV=1
|
||||||
|
- RUN_STATIME=1
|
||||||
|
- PTP_INTERFACE=eth0
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./static:/app/static:ro
|
- ./static:/app/static:ro
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_NICE
|
||||||
|
- SYS_TIME
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
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 enabled, start a mock Teensy that exposes a PTY at /dev/ttyACM0 and streams LTC-like lines.
|
||||||
if [ "${MOCK_TEENSY:-0}" = "1" ]; then
|
if [ "${MOCK_TEENSY:-0}" = "1" ]; then
|
||||||
|
|
@ -26,5 +28,19 @@ if [ "${MOCK_TEENSY:-0}" = "1" ]; then
|
||||||
fi
|
fi
|
||||||
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)
|
# Run the requested command (default: get-haci)
|
||||||
exec "$@"
|
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::config::{self, Config};
|
||||||
use crate::sync_logic::{self, LtcState};
|
use crate::sync_logic::{self, LtcState};
|
||||||
use crate::system;
|
use crate::system;
|
||||||
|
use crate::ptp;
|
||||||
use num_rational::Ratio;
|
use num_rational::Ratio;
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
|
||||||
|
|
@ -30,6 +31,18 @@ struct ApiStatus {
|
||||||
ntp_active: bool,
|
ntp_active: bool,
|
||||||
interfaces: Vec<String>,
|
interfaces: Vec<String>,
|
||||||
hardware_offset_ms: i64,
|
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
|
// 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())
|
.map(|ifa| ifa.ip().to_string())
|
||||||
.collect();
|
.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 {
|
HttpResponse::Ok().json(ApiStatus {
|
||||||
ltc_status,
|
ltc_status,
|
||||||
ltc_timecode,
|
ltc_timecode,
|
||||||
|
|
@ -101,6 +144,11 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
||||||
ntp_active,
|
ntp_active,
|
||||||
interfaces,
|
interfaces,
|
||||||
hardware_offset_ms: hw_offset_ms,
|
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 {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Option<Command>,
|
command: Option<Command>,
|
||||||
|
|
||||||
|
#[arg(long, help = "Enable development mode (also respected via HACI_DEV=1)")]
|
||||||
|
dev: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Subcommand, Debug)]
|
#[derive(clap::Subcommand, Debug)]
|
||||||
|
|
@ -116,6 +119,19 @@ async fn main() {
|
||||||
let log_buffer = logger::setup_logger();
|
let log_buffer = logger::setup_logger();
|
||||||
let args = Args::parse();
|
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 {
|
if let Some(command) = &args.command {
|
||||||
match command {
|
match command {
|
||||||
Command::Daemon => {
|
Command::Daemon => {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@
|
||||||
deltaText: document.getElementById('delta-text'),
|
deltaText: document.getElementById('delta-text'),
|
||||||
interfaces: document.getElementById('interfaces'),
|
interfaces: document.getElementById('interfaces'),
|
||||||
logs: document.getElementById('logs'),
|
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');
|
const hwOffsetInput = document.getElementById('hw-offset');
|
||||||
|
|
@ -150,6 +155,27 @@
|
||||||
} else {
|
} else {
|
||||||
statusElements.interfaces.textContent = 'No active interfaces found.';
|
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() {
|
function animateClocks() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue