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 config: Arc<Mutex<Config>>,
|
||||
pub log_buffer: Arc<Mutex<VecDeque<String>>>,
|
||||
pub ptp_daemon: Arc<Mutex<Option<ptp::StatimeDaemon>>>,
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
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"] {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
async fn update_config(
|
||||
data: web::Data<AppState>,
|
||||
|
|
@ -253,9 +348,10 @@ pub async fn start_api_server(
|
|||
ltc_state: state,
|
||||
config: config,
|
||||
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 || {
|
||||
App::new()
|
||||
|
|
@ -267,6 +363,8 @@ pub async fn start_api_server(
|
|||
.service(get_logs)
|
||||
.service(nudge_clock)
|
||||
.service(set_date)
|
||||
.service(start_ptp)
|
||||
.service(stop_ptp)
|
||||
// Serve frontend static files
|
||||
.service(fs::Files::new("/", "static/").index_file("index.html"))
|
||||
})
|
||||
|
|
@ -322,6 +420,7 @@ mod tests {
|
|||
ltc_state,
|
||||
config,
|
||||
log_buffer,
|
||||
ptp_daemon: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue