chore: initial commit
This commit is contained in:
parent
69c93a960f
commit
dd74d36bbf
7
.gitignore
vendored
7
.gitignore
vendored
@ -18,4 +18,9 @@ Cargo.lock
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
#.idea/
|
||||
|
||||
|
||||
*.mp3
|
||||
*.flac
|
||||
*.wav
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "marlinbox-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.5"
|
||||
rodio = "0.19.0"
|
||||
rusb = "0.9.4"
|
||||
serde = { version = "1.0.210", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.132"
|
96
flake.lock
generated
Normal file
96
flake.lock
generated
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1729265718,
|
||||
"narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ccc0c2126893dd20963580b6478d1a10a4512185",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1728538411,
|
||||
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729477859,
|
||||
"narHash": "sha256-r0VyeJxy4O4CgTB/PNtfQft9fPfN1VuGvnZiCxDArvg=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "ada8266712449c4c0e6ee6fcbc442b3c217c79e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
57
flake.nix
Normal file
57
flake.nix
Normal file
@ -0,0 +1,57 @@
|
||||
{
|
||||
description = "Example Rust development environment for Zero to Nix";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
overlays = [ (import rust-overlay) ];
|
||||
pkgs = import nixpkgs {
|
||||
inherit system overlays;
|
||||
};
|
||||
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ];
|
||||
targets = [ "x86_64-unknown-linux-gnu" "wasm32-unknown-unknown" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
rustToolchain
|
||||
trunk
|
||||
clippy
|
||||
# tailwindcss
|
||||
|
||||
# dioxus-cli
|
||||
# wasm-bindgen-cli
|
||||
|
||||
# cargo-shuttle
|
||||
cargo-edit
|
||||
cargo-binstall
|
||||
bacon
|
||||
|
||||
alsa-lib.dev
|
||||
|
||||
openssl
|
||||
pkg-config
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
export PATH=${rustToolchain}/bin:$PATH
|
||||
export RUSTC_VERSION=$(rustc --version)
|
||||
export RUST_SRC_PATH="${rustToolchain}/lib/rustlib/src/rust/library"
|
||||
export OPENSSL_DIR="${pkgs.openssl.dev}"
|
||||
export OPENSSL_LIB_DIR="${pkgs.openssl.out}/lib"
|
||||
export OPENSSL_INCLUDE_DIR="${pkgs.openssl.dev}/include"
|
||||
'';
|
||||
|
||||
packages = pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv ]);
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
11
music.json
Normal file
11
music.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"music": {
|
||||
"27271E24211E1E26": {
|
||||
"Play": "02 - Disturbed - Immortalized.mp3"
|
||||
},
|
||||
"27271E242121221F": "Shuffle",
|
||||
"27271E2420222321": {
|
||||
"Play": "01-13 33 RPM.flac"
|
||||
}
|
||||
}
|
||||
}
|
25
src/card.rs
Normal file
25
src/card.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum Card {
|
||||
Play(Arc<str>),
|
||||
Pause,
|
||||
Resume,
|
||||
Next,
|
||||
Previous,
|
||||
Shuffle,
|
||||
}
|
||||
|
||||
impl From<&Card> for Option<Card> {
|
||||
fn from(card: &Card) -> Self {
|
||||
Some(card.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> From<T> for Card {
|
||||
fn from(music_file: T) -> Self {
|
||||
Self::Play(Arc::from(music_file.as_ref()))
|
||||
}
|
||||
}
|
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod card;
|
||||
pub mod library;
|
||||
pub mod portal;
|
47
src/library.rs
Normal file
47
src/library.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::card::Card;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Library {
|
||||
music: HashMap<Arc<str>, Option<Card>>,
|
||||
}
|
||||
|
||||
impl Library {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
music: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, card_id: &str) {
|
||||
self.music.insert(card_id.into(), None);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, card_id: &str, music_file: Option<Card>) {
|
||||
self.music
|
||||
.insert(card_id.into(), music_file.map(Into::into));
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, card_id: &str) -> Option<&Card> {
|
||||
self.music.get::<str>(card_id).unwrap_or(&None).as_ref()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_random(&self) -> Option<&Card> {
|
||||
let play_cards: Vec<&Card> = self.music.values().flatten().collect();
|
||||
|
||||
play_cards.choose(&mut rand::thread_rng()).copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Library {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
111
src/main.rs
Normal file
111
src/main.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use marlinbox_rs::{card::Card, library::Library};
|
||||
use rodio::{Decoder, OutputStream, Sink};
|
||||
use rusb::{Context, DeviceHandle, UsbContext};
|
||||
use std::{fs::File, io::BufReader, time::Duration};
|
||||
|
||||
const VID: u16 = 0xffff; // Replace with your device's Vendor ID
|
||||
const PID: u16 = 0x0035; // Replace with your device's Product ID
|
||||
|
||||
fn extract_card_id(buf: &[u8]) -> Option<String> {
|
||||
let significant_indices = [2, 18, 34, 50, 66, 82, 98, 114];
|
||||
let extracted: Vec<u8> = significant_indices
|
||||
.iter()
|
||||
.filter_map(|&i| buf.get(i).copied())
|
||||
.collect();
|
||||
|
||||
if extracted.len() == 8 && extracted.iter().all(|&b| b != 0) {
|
||||
Some(extracted.iter().fold(String::new(), |mut acc, &b| {
|
||||
acc.push_str(&format!("{b:02X}"));
|
||||
acc
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
|
||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||
|
||||
let music: Library = serde_json::from_reader(File::open("music.json")?)?;
|
||||
// let mut music = Library::new();
|
||||
// music.update(
|
||||
// "27271E24211E1E26",
|
||||
// Some(Card::Play("02 - Disturbed - Immortalized.mp3".into())),
|
||||
// );
|
||||
// music.update(
|
||||
// "27271E242121221F",
|
||||
// Some(Card::Play(
|
||||
// "11 - Disturbed - The Sound Of Silence.mp3".into(),
|
||||
// )),
|
||||
// );
|
||||
// music.update("27271E2420222321", Card::Shuffle.into());
|
||||
|
||||
// let music_json = serde_json::to_string_pretty(&music)?;
|
||||
// fs::write("music.json", music_json)?;
|
||||
|
||||
let context = Context::new()?;
|
||||
let handle: DeviceHandle<Context> = context
|
||||
.open_device_with_vid_pid(VID, PID)
|
||||
.ok_or("Device not found")?;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if handle.kernel_driver_active(0)? {
|
||||
handle.detach_kernel_driver(0)?;
|
||||
}
|
||||
}
|
||||
|
||||
handle.claim_interface(0)?;
|
||||
|
||||
println!("Starting to read RFID cards...");
|
||||
|
||||
let mut buf = [0u8; 120];
|
||||
// let mut last_id: Option<String> = None;
|
||||
|
||||
loop {
|
||||
match handle.read_interrupt(0x81, &mut buf, Duration::from_millis(1000)) {
|
||||
Ok(len) => {
|
||||
if len == 120 {
|
||||
if let Some(card_id) = extract_card_id(&buf) {
|
||||
// if last_id.as_ref() != Some(&card_id) {
|
||||
println!("Card ID: {card_id}");
|
||||
if let Some(music_file) = music.get(card_id.as_ref()) {
|
||||
println!("Playing: {music_file:?}");
|
||||
|
||||
match music_file {
|
||||
Card::Play(music_file) => {
|
||||
sink.stop();
|
||||
let file =
|
||||
BufReader::new(File::open(music_file.as_ref()).unwrap());
|
||||
let source = Decoder::new(file).unwrap();
|
||||
sink.append(source);
|
||||
}
|
||||
Card::Pause => sink.pause(),
|
||||
Card::Resume => sink.play(),
|
||||
Card::Next | Card::Previous => sink.stop(),
|
||||
Card::Shuffle => {
|
||||
if let Some(Card::Play(music_file)) = music.get_random() {
|
||||
sink.stop();
|
||||
let file = BufReader::new(
|
||||
File::open(music_file.as_ref()).unwrap(),
|
||||
);
|
||||
let source = Decoder::new(file).unwrap();
|
||||
sink.append(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("No music file found for this card");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(rusb::Error::Timeout) => {
|
||||
// last_id = None;
|
||||
}
|
||||
Err(e) => eprintln!("Error: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
1
src/portal/mod.rs
Normal file
1
src/portal/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod server;
|
0
src/portal/server.rs
Normal file
0
src/portal/server.rs
Normal file
Loading…
x
Reference in New Issue
Block a user