NFC tag writer: local web page for ACR1252U (NDEF URL/text on Type 2 tags)
This commit is contained in:
commit
449cdb7af5
8 files changed
+1496
No files matched your search
+78
@@ -0,0 +1,78 @@
|
||||
mod ndef;
|
||||
mod reader;
|
||||
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Response},
|
||||
routing::{get, post},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
const INDEX: &str = include_str!("../static/index.html");
|
||||
|
||||
/// One reader, one operation at a time: the mutex serialises status polls and writes.
|
||||
type Shared = Arc<Mutex<reader::Cache>>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let port: u16 = std::env::var("NFC_PORT")
|
||||
.ok()
|
||||
.and_then(|p| p.parse().ok())
|
||||
.unwrap_or(7252);
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { Html(INDEX) }))
|
||||
.route("/api/status", get(status))
|
||||
.route("/api/program", post(program))
|
||||
.with_state(Shared::default());
|
||||
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("bind {addr}: {e}"));
|
||||
println!("NFC writer running on http://{addr}");
|
||||
axum::serve(listener, app).await.expect("server error");
|
||||
}
|
||||
|
||||
async fn with_reader<T: Send + 'static>(
|
||||
s: Shared,
|
||||
f: impl FnOnce(&mut reader::Cache) -> T + Send + 'static,
|
||||
) -> Result<T, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut cache = s.lock().unwrap_or_else(|e| e.into_inner());
|
||||
f(&mut cache)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("internal error: {e}"))
|
||||
}
|
||||
|
||||
async fn status(State(s): State<Shared>) -> Json<reader::Status> {
|
||||
Json(
|
||||
with_reader(s, reader::status)
|
||||
.await
|
||||
.unwrap_or_else(|message| reader::Status::Error { message }),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ProgramRequest {
|
||||
text: String,
|
||||
}
|
||||
|
||||
async fn program(State(s): State<Shared>, Json(req): Json<ProgramRequest>) -> Response {
|
||||
match with_reader(s, move |c| reader::program(c, &req.text)).await {
|
||||
Ok(Ok(done)) => Json(done).into_response(),
|
||||
Ok(Err(error)) => (
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
Json(json!({ "error": error })),
|
||||
)
|
||||
.into_response(),
|
||||
Err(error) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": error })),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user