use rmcp::handler::server::wrapper::Parameters;
use rmcp::model::CallToolResult;
use rmcp::{ErrorData, tool, tool_router};
use crate::encoding::{Bytes, parse_bytes};
use crate::error::{invalid_arg, map_pdg_err};
use crate::{GalloMcp, ok_json};
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct OneWireReadParams {
pub len: u16,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct OneWireWriteParams {
pub data: String,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct OneWireWritePullupParams {
pub data: String,
#[serde(default = "default_pullup_ms")]
pub duration_ms: u16,
}
fn default_pullup_ms() -> u16 {
750
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct OneWireSearchParams {
#[serde(default)]
pub continue_search: bool,
}
#[tool_router(router = onewire_router, vis = "pub(crate)")]
impl GalloMcp {
#[tool(
description = "1-Wire reset + presence detect",
annotations(destructive_hint = true, read_only_hint = false)
)]
async fn onewire_reset(&self) -> Result<CallToolResult, ErrorData> {
let dev = self.connect().await?;
let present = dev.onewire_reset().await.map_err(map_pdg_err)?;
ok_json(&serde_json::json!({ "presence": present }))
}
#[tool(
description = "Read bytes over 1-Wire",
annotations(read_only_hint = true)
)]
async fn onewire_read(
&self,
Parameters(p): Parameters<OneWireReadParams>,
) -> Result<CallToolResult, ErrorData> {
let dev = self.connect().await?;
let data = dev.onewire_read(p.len).await.map_err(map_pdg_err)?;
ok_json(&Bytes::from_slice(&data))
}
#[tool(
description = "Write bytes over 1-Wire",
annotations(destructive_hint = true, read_only_hint = false)
)]
async fn onewire_write(
&self,
Parameters(p): Parameters<OneWireWriteParams>,
) -> Result<CallToolResult, ErrorData> {
let bytes = parse_bytes(&p.data).map_err(invalid_arg)?;
let dev = self.connect().await?;
dev.onewire_write(&bytes).await.map_err(map_pdg_err)?;
ok_json(&"ok")
}
#[tool(
description = "Write bytes over 1-Wire then apply strong pullup",
annotations(destructive_hint = true, read_only_hint = false)
)]
async fn onewire_write_pullup(
&self,
Parameters(p): Parameters<OneWireWritePullupParams>,
) -> Result<CallToolResult, ErrorData> {
let bytes = parse_bytes(&p.data).map_err(invalid_arg)?;
let dev = self.connect().await?;
dev.onewire_write_pullup(&bytes, p.duration_ms)
.await
.map_err(map_pdg_err)?;
ok_json(&"ok")
}
#[tool(
description = "Search for a 1-Wire ROM code (set continue_search to advance)",
annotations(read_only_hint = true)
)]
async fn onewire_search(
&self,
Parameters(p): Parameters<OneWireSearchParams>,
) -> Result<CallToolResult, ErrorData> {
let dev = self.connect().await?;
let rom = if p.continue_search {
dev.onewire_search_next().await.map_err(map_pdg_err)?
} else {
dev.onewire_search().await.map_err(map_pdg_err)?
};
match rom {
Some(id) => ok_json(&serde_json::json!({ "rom": format!("0x{id:016X}"), "raw": id })),
None => ok_json(&serde_json::json!({ "rom": null })),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn search_params_default_is_new_search() {
let p: OneWireSearchParams = serde_json::from_str("{}").unwrap();
assert!(!p.continue_search);
}
#[test]
fn write_pullup_params_default_duration() {
let p: OneWireWritePullupParams = serde_json::from_str(r#"{"data":"0x44"}"#).unwrap();
assert_eq!(p.duration_ms, 750);
}
#[test]
fn onewire_tools_registered() {
let names: Vec<String> = crate::GalloMcp::router_for_test()
.list_all()
.iter()
.map(|t| t.name.to_string())
.collect();
for e in [
"onewire_reset",
"onewire_read",
"onewire_write",
"onewire_write_pullup",
"onewire_search",
] {
assert!(names.contains(&e.to_string()), "missing {e}");
}
}
}