mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
feat: add PTP daemon and start/stop API; rename API server
Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
This commit is contained in:
parent
252ea15486
commit
8eb4c6f2da
1 changed files with 104 additions and 5 deletions
107
src/api.rs
107
src/api.rs
|
|
@ -50,6 +50,7 @@ pub struct AppState {
|
||||||
pub ltc_state: Arc<Mutex<LtcState>>,
|
pub ltc_state: Arc<Mutex<LtcState>>,
|
||||||
pub config: Arc<Mutex<Config>>,
|
pub config: Arc<Mutex<Config>>,
|
||||||
pub log_buffer: Arc<Mutex<VecDeque<String>>>,
|
pub log_buffer: Arc<Mutex<VecDeque<String>>>,
|
||||||
|
pub ptp_daemon: Arc<Mutex<Option<ptp::StatimeDaemon>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/status")]
|
#[get("/api/status")]
|
||||||
|
|
@ -115,14 +116,19 @@ async fn get_status(data: web::Data<AppState>) -> impl Responder {
|
||||||
let mut ptp_offset_ns: Option<i128> = None;
|
let mut ptp_offset_ns: Option<i128> = None;
|
||||||
|
|
||||||
if ptp_supported {
|
if ptp_supported {
|
||||||
// Check a few common PHC device nodes
|
// Check managed daemon handle first (if any)
|
||||||
|
if let Ok(mut guard) = data.ptp_daemon.lock() {
|
||||||
|
if let Some(ref mut d) = *guard {
|
||||||
|
ptp_daemon_running = d.is_running();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probe PHC devices to read current offset
|
||||||
for dev in ["/dev/ptp0", "/dev/ptp1", "/dev/ptp2"] {
|
for dev in ["/dev/ptp0", "/dev/ptp1", "/dev/ptp2"] {
|
||||||
if std::path::Path::new(dev).exists() {
|
if std::path::Path::new(dev).exists() {
|
||||||
if let Ok(clock) = ptp::PtpClock::open(dev) {
|
if let Ok(clock) = ptp::PtpClock::open(dev) {
|
||||||
if let Ok(offset) = clock.offset_from_system_ns() {
|
if let Ok(offset) = clock.offset_from_system_ns() {
|
||||||
ptp_offset_ns = Some(offset);
|
ptp_offset_ns = Some(offset);
|
||||||
// Consider daemon "running" if we can read a PHC offset
|
|
||||||
ptp_daemon_running = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,6 +215,95 @@ async fn set_date(req: web::Json<SetDateRequest>) -> impl Responder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PTP control endpoints
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct StartPtpRequest {
|
||||||
|
interface: Option<String>,
|
||||||
|
phc_index: Option<u32>,
|
||||||
|
args: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/api/ptp/start")]
|
||||||
|
async fn start_ptp(
|
||||||
|
data: web::Data<AppState>,
|
||||||
|
req: web::Json<StartPtpRequest>,
|
||||||
|
) -> impl Responder {
|
||||||
|
if !ptp::is_supported() {
|
||||||
|
return HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": "PTP is unsupported on this platform"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut guard = data.ptp_daemon.lock().unwrap();
|
||||||
|
// Stop existing daemon if running
|
||||||
|
if let Some(ref mut d) = *guard {
|
||||||
|
let _ = d.stop();
|
||||||
|
*guard = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let iface = req
|
||||||
|
.interface
|
||||||
|
.clone()
|
||||||
|
.or_else(|| std::env::var("PTP_INTERFACE").ok())
|
||||||
|
.unwrap_or_else(|| "lo".to_string());
|
||||||
|
|
||||||
|
let mut opts = ptp::StatimeOptions::new(iface);
|
||||||
|
if let Some(idx) = req.phc_index {
|
||||||
|
opts = opts.with_phc_index(idx);
|
||||||
|
}
|
||||||
|
if let Some(ref extra) = req.args {
|
||||||
|
opts = opts.with_args(extra.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
match ptp::spawn_statime(opts) {
|
||||||
|
Ok(d) => {
|
||||||
|
*guard = Some(d);
|
||||||
|
HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "success",
|
||||||
|
"message": "PTP daemon started"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(e) => HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": e.to_string()
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/api/ptp/stop")]
|
||||||
|
async fn stop_ptp(data: web::Data<AppState>) -> impl Responder {
|
||||||
|
if !ptp::is_supported() {
|
||||||
|
return HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": "PTP is unsupported on this platform"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut guard = data.ptp_daemon.lock().unwrap();
|
||||||
|
if let Some(ref mut d) = *guard {
|
||||||
|
match d.stop() {
|
||||||
|
Ok(_) => {
|
||||||
|
*guard = None;
|
||||||
|
HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "success",
|
||||||
|
"message": "PTP daemon stopped"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(e) => HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": e.to_string()
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"status": "success",
|
||||||
|
"message": "PTP daemon was not running"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/api/config")]
|
#[post("/api/config")]
|
||||||
async fn update_config(
|
async fn update_config(
|
||||||
data: web::Data<AppState>,
|
data: web::Data<AppState>,
|
||||||
|
|
@ -253,9 +348,10 @@ pub async fn start_api_server(
|
||||||
ltc_state: state,
|
ltc_state: state,
|
||||||
config: config,
|
config: config,
|
||||||
log_buffer: log_buffer,
|
log_buffer: log_buffer,
|
||||||
|
ptp_daemon: Arc::new(Mutex::new(None)),
|
||||||
});
|
});
|
||||||
|
|
||||||
log::info!("🚀 Starting API server at http://0.0.0.0:8080");
|
log::info!("🚀 Starting Hachi-TimeTransformer API server at http://0.0.0.0:8080");
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
@ -267,6 +363,8 @@ pub async fn start_api_server(
|
||||||
.service(get_logs)
|
.service(get_logs)
|
||||||
.service(nudge_clock)
|
.service(nudge_clock)
|
||||||
.service(set_date)
|
.service(set_date)
|
||||||
|
.service(start_ptp)
|
||||||
|
.service(stop_ptp)
|
||||||
// Serve frontend static files
|
// Serve frontend static files
|
||||||
.service(fs::Files::new("/", "static/").index_file("index.html"))
|
.service(fs::Files::new("/", "static/").index_file("index.html"))
|
||||||
})
|
})
|
||||||
|
|
@ -322,6 +420,7 @@ mod tests {
|
||||||
ltc_state,
|
ltc_state,
|
||||||
config,
|
config,
|
||||||
log_buffer,
|
log_buffer,
|
||||||
|
ptp_daemon: Arc::new(Mutex::new(None)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue