1use std::time::Duration;
7
8use serde_json::Value;
9
10use crate::auth::{self, GatewayAuth};
11use crate::error::{Error, Result};
12use crate::nets::adc::AsyncAdc;
13use crate::nets::arm::AsyncArm;
14use crate::nets::battery::AsyncBattery;
15use crate::nets::ble::AsyncBle;
16use crate::nets::blufi::AsyncBlufi;
17use crate::nets::dac::AsyncDac;
18use crate::nets::debug::AsyncDebugNet;
19use crate::nets::dfu::AsyncDfu;
20use crate::nets::eload::AsyncEload;
21use crate::nets::energy::AsyncEnergyAnalyzer;
22use crate::nets::gpio::AsyncGpio;
23use crate::nets::i2c::AsyncI2c;
24use crate::nets::router::AsyncRouter;
25use crate::nets::scope::Scope;
26use crate::nets::solar::AsyncSolar;
27use crate::nets::spi::AsyncSpi;
28use crate::nets::supply::AsyncSupply;
29use crate::nets::thermocouple::AsyncThermocouple;
30use crate::nets::usb::AsyncUsbPort;
31use crate::nets::watt::AsyncWattMeter;
32use crate::nets::webcam::AsyncWebcam;
33use crate::nets::wifi::AsyncWifi;
34use crate::wire::{
35 self, BoxLock, BoxStatus, Health, HttpRequest, Method, NetRecord, Op, SafetyLimits, Timeout,
36 UsbDeviceFilter, UsbDeviceInfo,
37};
38
39pub struct AsyncLagerBox {
56 base: String,
57 debug_base: String,
58 http: reqwest::Client,
59 default_timeout: Duration,
60 auth: GatewayAuth,
61}
62
63pub struct AsyncLagerBoxBuilder {
66 host: String,
67 debug_url: Option<String>,
68 default_timeout: Duration,
69 bearer_token: Option<String>,
70}
71
72impl AsyncLagerBoxBuilder {
73 pub fn timeout(mut self, timeout: Duration) -> Self {
76 self.default_timeout = timeout;
77 self
78 }
79
80 pub fn debug_service_url(mut self, url: impl Into<String>) -> Self {
83 self.debug_url = Some(url.into());
84 self
85 }
86
87 pub fn bearer_token(mut self, token: impl Into<String>) -> Self {
97 self.bearer_token = Some(token.into());
98 self
99 }
100
101 pub fn build(self) -> Result<AsyncLagerBox> {
103 let base = wire::base_url(&self.host)?;
104 let debug_base = match self.debug_url {
105 Some(url) => wire::base_url_with_port(&url, wire::DEBUG_SERVICE_PORT)?,
106 None => wire::service_base(&base, wire::DEBUG_SERVICE_PORT),
107 };
108 let auth = GatewayAuth::new(&base, self.bearer_token);
109 Ok(AsyncLagerBox {
110 base,
111 debug_base,
112 http: reqwest::Client::new(),
113 default_timeout: self.default_timeout,
114 auth,
115 })
116 }
117}
118
119impl AsyncLagerBox {
120 pub fn connect(host: impl Into<String>) -> Result<Self> {
123 Self::builder(host).build()
124 }
125
126 pub fn from_env() -> Result<Self> {
129 let host = std::env::var(crate::BOX_HOST_ENV)
130 .map_err(|_| Error::Config(format!("{} is not set", crate::BOX_HOST_ENV)))?;
131 Self::connect(host)
132 }
133
134 pub fn builder(host: impl Into<String>) -> AsyncLagerBoxBuilder {
136 AsyncLagerBoxBuilder {
137 host: host.into(),
138 debug_url: std::env::var(crate::DEBUG_SERVICE_URL_ENV).ok(),
139 default_timeout: wire::DEFAULT_TIMEOUT,
140 bearer_token: None,
141 }
142 }
143
144 pub fn base_url(&self) -> &str {
146 &self.base
147 }
148
149 pub(crate) async fn execute(&self, req: &HttpRequest) -> Result<(u16, Value)> {
153 self.execute_at(&self.base, req).await
154 }
155
156 pub(crate) async fn execute_debug(&self, req: &HttpRequest) -> Result<(u16, Value)> {
158 self.execute_at(&self.debug_base, req).await
159 }
160
161 async fn execute_at(&self, base: &str, req: &HttpRequest) -> Result<(u16, Value)> {
166 let token = self.current_token().await;
167 let (status, gateway, resp_body) = self.send_once(base, req, token.as_deref()).await?;
168
169 let Some(auth_url) = gateway else {
170 return Ok((status, resp_body));
171 };
172 self.auth.learn_auth_server(&auth_url);
175 if status == 401 && !self.auth.has_static_token() {
176 if let Some(fresh) = self
177 .auth
178 .resolve_token_async(&auth_url, token.as_deref())
179 .await
180 {
181 let (status, gateway, resp_body) =
182 self.send_once(base, req, Some(&fresh)).await?;
183 let Some(auth_url) = gateway else {
184 return Ok((status, resp_body));
185 };
186 return Err(auth::denial_error(
187 status,
188 self.auth.box_host(),
189 &auth_url,
190 true,
191 ));
192 }
193 }
194 Err(auth::denial_error(
195 status,
196 self.auth.box_host(),
197 &auth_url,
198 token.is_some(),
199 ))
200 }
201
202 async fn current_token(&self) -> Option<String> {
205 if let Some(token) = self.auth.cached_token() {
206 return Some(token);
207 }
208 if self.auth.wants_store_token() {
209 let auth_url = self.auth.auth_url()?;
210 return self.auth.resolve_token_async(&auth_url, None).await;
211 }
212 None
213 }
214
215 async fn send_once(
219 &self,
220 base: &str,
221 req: &HttpRequest,
222 token: Option<&str>,
223 ) -> Result<(u16, Option<String>, Value)> {
224 let url = format!("{}{}", base, req.path);
225 let mut r = match req.method {
226 Method::Get => self.http.get(&url),
227 Method::Post => self.http.post(&url),
228 Method::Put => self.http.put(&url),
229 };
230 match req.timeout {
231 Timeout::Default => r = r.timeout(self.default_timeout),
232 Timeout::After(d) => r = r.timeout(d),
233 Timeout::Unbounded => {}
234 }
235 if let Some(token) = token {
236 r = r.header("Authorization", format!("Bearer {token}"));
237 }
238 if let Some(body) = &req.body {
239 r = r.json(body);
240 }
241 let resp = r.send().await.map_err(|e| {
242 if e.is_timeout() {
243 Error::Timeout(e.to_string())
244 } else {
245 Error::Connection(e.to_string())
246 }
247 })?;
248 let status = resp.status().as_u16();
249 let gateway = if auth::is_denial(status) {
250 resp.headers()
251 .get(auth::DISCOVERY_HEADER)
252 .and_then(|v| v.to_str().ok())
253 .map(str::to_string)
254 } else {
255 None
256 };
257 let body: Value = match resp.json().await {
258 Ok(body) => body,
259 Err(e) if e.is_timeout() => return Err(Error::Timeout(e.to_string())),
260 Err(e) if status < 400 => {
261 return Err(Error::Decode(format!("non-JSON response: {e}")))
262 }
263 Err(_) => Value::Null,
265 };
266 Ok((status, gateway, body))
267 }
268
269 pub(crate) async fn run<T>(&self, op: Op<T>) -> Result<T> {
271 let (status, body) = self.execute(&op.req).await?;
272 let resp = wire::parse_command(status, body)?;
273 (op.parse)(resp)
274 }
275
276 pub(crate) async fn debug_net_record(&self, name: &str) -> Result<Value> {
278 let records = self.nets_raw().await?;
279 crate::nets::debug::find_debug_record(records, name)
280 }
281
282 async fn nets_raw(&self) -> Result<Vec<Value>> {
285 let body = match self.get_json("/nets/list").await {
286 Ok(body) => body,
287 Err(primary) => self.get_json("/uart/nets/list").await.map_err(|_| primary)?,
288 };
289 Ok(wire::nets_list_values(body))
290 }
291
292 async fn get_json(&self, path: &str) -> Result<Value> {
293 let (status, body) = self.execute(&wire::get(path)).await?;
294 if status != 200 {
295 return Err(Error::Box {
296 status,
297 message: body
298 .get("error")
299 .and_then(Value::as_str)
300 .unwrap_or("request failed")
301 .to_string(),
302 });
303 }
304 Ok(body)
305 }
306
307 pub async fn nets(&self) -> Result<Vec<NetRecord>> {
314 match self.get_json("/nets/list").await {
315 Ok(body) => wire::nets_from_body(body),
316 Err(primary) => match self.get_json("/uart/nets/list").await {
317 Ok(body) => wire::nets_from_body(body),
318 Err(_) => Err(primary),
319 },
320 }
321 }
322
323 pub async fn health(&self) -> Result<Health> {
325 let body = self.get_json("/health").await?;
326 serde_json::from_value(body).map_err(Into::into)
327 }
328
329 pub async fn status(&self) -> Result<BoxStatus> {
331 let body = self.get_json("/status").await?;
332 serde_json::from_value(body).map_err(Into::into)
333 }
334
335 pub async fn usb_devices(&self) -> Result<Vec<UsbDeviceInfo>> {
344 self.usb_devices_matching(&UsbDeviceFilter::default()).await
345 }
346
347 pub async fn usb_devices_matching(
350 &self,
351 filter: &UsbDeviceFilter,
352 ) -> Result<Vec<UsbDeviceInfo>> {
353 match self.execute(&wire::usb_devices(filter)).await {
354 Ok((status, body)) => wire::parse_usb_devices(status, body),
355 Err(e) => Err(wire::map_route_missing(e, wire::usb_devices_unsupported)),
356 }
357 }
358
359 async fn lock_call(&self, req: &HttpRequest) -> Result<BoxLock> {
362 match self.execute(req).await {
363 Ok((status, body)) => wire::parse_lock(status, body),
364 Err(e) => Err(wire::map_route_missing(e, wire::lock_unsupported)),
365 }
366 }
367
368 pub async fn lock_status(&self) -> Result<BoxLock> {
370 self.lock_call(&wire::lock_status()).await
371 }
372
373 pub async fn lock(&self, user: &str) -> Result<BoxLock> {
378 self.lock_call(&wire::lock_acquire(user, "user", None)).await
379 }
380
381 pub async fn lock_with(
385 &self,
386 user: &str,
387 holder_type: &str,
388 ttl_seconds: Option<u64>,
389 ) -> Result<BoxLock> {
390 self.lock_call(&wire::lock_acquire(user, holder_type, ttl_seconds))
391 .await
392 }
393
394 pub async fn lock_heartbeat(&self, user: &str) -> Result<BoxLock> {
397 self.lock_call(&wire::lock_heartbeat(user)).await
398 }
399
400 pub async fn unlock(&self, user: &str) -> Result<()> {
404 self.lock_call(&wire::unlock(user, false)).await.map(|_| ())
405 }
406
407 pub async fn unlock_force(&self, user: &str) -> Result<()> {
410 self.lock_call(&wire::unlock(user, true)).await.map(|_| ())
411 }
412
413 pub async fn set_safety_limits(
430 &self,
431 name: &str,
432 limits: &SafetyLimits,
433 ) -> Result<Option<SafetyLimits>> {
434 match self.execute(&wire::safety_limits_set(name, limits)).await {
435 Ok((status, body)) => wire::parse_safety_limits(status, body),
436 Err(e) => Err(wire::map_route_missing(e, wire::safety_limits_unsupported)),
437 }
438 }
439
440 pub async fn clear_safety_limits(&self, name: &str) -> Result<()> {
442 self.set_safety_limits(name, &SafetyLimits::default())
443 .await
444 .map(|_| ())
445 }
446
447 pub async fn safety_limits(&self, name: &str) -> Result<Option<SafetyLimits>> {
451 let nets = self.nets().await?;
452 nets.iter()
453 .find(|rec| rec.name == name)
454 .map(|rec| rec.safety_limits)
455 .ok_or_else(|| Error::Box {
456 status: 404,
457 message: format!("no saved net named '{name}' on this box"),
458 })
459 }
460
461 pub fn supply(&self, name: impl Into<String>) -> AsyncSupply<'_> {
465 AsyncSupply { client: self, name: name.into() }
466 }
467
468 pub fn battery(&self, name: impl Into<String>) -> AsyncBattery<'_> {
470 AsyncBattery { client: self, name: name.into() }
471 }
472
473 pub fn eload(&self, name: impl Into<String>) -> AsyncEload<'_> {
475 AsyncEload { client: self, name: name.into() }
476 }
477
478 pub fn solar(&self, name: impl Into<String>) -> AsyncSolar<'_> {
480 AsyncSolar { client: self, name: name.into() }
481 }
482
483 pub fn gpio(&self, name: impl Into<String>) -> AsyncGpio<'_> {
485 AsyncGpio { client: self, name: name.into() }
486 }
487
488 pub fn adc(&self, name: impl Into<String>) -> AsyncAdc<'_> {
490 AsyncAdc { client: self, name: name.into() }
491 }
492
493 pub fn dac(&self, name: impl Into<String>) -> AsyncDac<'_> {
495 AsyncDac { client: self, name: name.into() }
496 }
497
498 pub fn thermocouple(&self, name: impl Into<String>) -> AsyncThermocouple<'_> {
500 AsyncThermocouple { client: self, name: name.into() }
501 }
502
503 pub fn watt_meter(&self, name: impl Into<String>) -> AsyncWattMeter<'_> {
505 AsyncWattMeter { client: self, name: name.into() }
506 }
507
508 pub fn energy_analyzer(&self, name: impl Into<String>) -> AsyncEnergyAnalyzer<'_> {
510 AsyncEnergyAnalyzer { client: self, name: name.into() }
511 }
512
513 pub fn spi(&self, name: impl Into<String>) -> AsyncSpi<'_> {
515 AsyncSpi { client: self, name: name.into() }
516 }
517
518 pub fn i2c(&self, name: impl Into<String>) -> AsyncI2c<'_> {
520 AsyncI2c { client: self, name: name.into() }
521 }
522
523 pub fn usb(&self, name: impl Into<String>) -> AsyncUsbPort<'_> {
525 AsyncUsbPort { client: self, name: name.into() }
526 }
527
528 pub fn arm(&self, name: impl Into<String>) -> AsyncArm<'_> {
530 AsyncArm { client: self, name: name.into() }
531 }
532
533 pub fn webcam(&self, name: impl Into<String>) -> AsyncWebcam<'_> {
535 AsyncWebcam { client: self, name: name.into() }
536 }
537
538 pub fn router(&self, name: impl Into<String>) -> AsyncRouter<'_> {
540 AsyncRouter { client: self, name: name.into() }
541 }
542
543 pub fn ble(&self) -> AsyncBle<'_> {
545 AsyncBle { client: self }
546 }
547
548 pub fn wifi(&self) -> AsyncWifi<'_> {
550 AsyncWifi { client: self }
551 }
552
553 pub fn blufi(&self) -> AsyncBlufi<'_> {
555 AsyncBlufi { client: self }
556 }
557
558 pub fn dfu(&self) -> AsyncDfu<'_> {
560 AsyncDfu { client: self }
561 }
562
563 pub fn debug(&self, name: impl Into<String>) -> AsyncDebugNet<'_> {
566 AsyncDebugNet { client: self, name: name.into(), record: Default::default() }
567 }
568
569 pub fn scope(&self, name: impl Into<String>) -> Scope {
571 Scope::new(name)
572 }
573}