先在树莓派上运行rpi
,此时它会在5001端口上监听TCP连接。
当GPIO23和VCC相连时,为电键接通,PWR_LED(红色)会亮起,并向连接的客户端socket发送字节1
;反之断开时,为电键断开,PWR_LED熄灭,并向连接的客户端socket发送字节0
。
在电脑(使用X11的Linux)上运行host
,把IP地址改成树莓派的地址,就可成功连接。电键触发与断开就会分别发送mousedown
和mouseup
。
use std::io::Read; | |
use std::net::{SocketAddr, TcpStream}; | |
#[derive(Copy, Clone)] | |
enum InputType { | |
Key, | |
Mouse, | |
} | |
fn main() -> anyhow::Result<()> { | |
let input_type = InputType::Mouse; | |
let xdo = libxdo::XDo::new(None)?; | |
let stream = TcpStream::connect("192.168.4.11:5001".parse::<SocketAddr>()?)?; | |
for byte in stream.bytes() { | |
let byte = byte?; | |
println!("{}", byte); | |
match (byte, input_type) { | |
(1, InputType::Key) => xdo.send_keysequence_down("space", 0)?, | |
(0, InputType::Key) => xdo.send_keysequence_up("space", 0)?, | |
(1, InputType::Mouse) => xdo.mouse_down(1)?, | |
(0, InputType::Mouse) => xdo.mouse_up(1)?, | |
_ => {} | |
} | |
} | |
Ok(()) | |
} |
#![feature(try_blocks)] | |
use std::fs::File; | |
use std::io::Write; | |
use std::net::{SocketAddr, TcpListener}; | |
use std::sync::atomic::{AtomicBool, Ordering}; | |
use std::sync::mpsc::{channel, Receiver}; | |
use std::sync::{Arc, Mutex}; | |
use std::thread::{sleep, spawn}; | |
use std::time::{Duration, SystemTime, UNIX_EPOCH}; | |
use clap::Parser; | |
use gpio_cdev::{Chip, EventRequestFlags, EventType, LineRequestFlags}; | |
use once_cell::sync::Lazy; | |
static IDLE_FLAG: Lazy<AtomicBool> = Lazy::new(|| AtomicBool::new(true)); | |
static ARGS: Lazy<Mutex<Args>> = Lazy::new(|| Mutex::new(Default::default())); | |
#[derive(clap::Parser, Default, Debug)] | |
struct Args { | |
/// TCP listening port | |
#[arg(default_value = "5001", short, long)] | |
port: u16, | |
/// Line of the high-active GPIO | |
#[arg(default_value = "23")] | |
gpio: u16, | |
} | |
fn main() -> anyhow::Result<()> { | |
let args = Args::parse(); | |
println!("{:?}", args); | |
*ARGS.lock().unwrap() = args; | |
let events = start_gpio_monitor()?; | |
start_tcp_listener(events)?; | |
Ok(()) | |
} | |
#[allow(unreachable_code)] | |
fn start_tcp_listener(events: Receiver<bool>) -> anyhow::Result<()> { | |
let tcp_port = ARGS.lock().unwrap().port; | |
let streams = Arc::new(Mutex::new(Vec::new())); | |
let streams_arc = Arc::clone(&streams); | |
spawn(move || { | |
let result: anyhow::Result<()> = try { | |
let listener = | |
TcpListener::bind(format!("0.0.0.0:{}", tcp_port).parse::<SocketAddr>()?)?; | |
loop { | |
let client = listener.accept()?; | |
println!("Accepted: {}", client.1); | |
streams_arc.lock().unwrap().push(client.0); | |
} | |
}; | |
result.unwrap(); | |
}); | |
for state in events { | |
let byte: u8 = match state { | |
true => 1, | |
false => 0, | |
}; | |
let mut guard = streams.lock().unwrap(); | |
for (i, stream) in guard.iter_mut().enumerate() { | |
let result: anyhow::Result<()> = try { | |
println!("Write to client #{}: {}", i, byte); | |
stream.write_all(&[byte])?; | |
stream.flush()?; | |
}; | |
if result.is_err() { | |
println!("Failed to write to client #{}", i); | |
} | |
} | |
} | |
// `events` will be infinity | |
unreachable!() | |
} | |
fn start_gpio_monitor() -> anyhow::Result<Receiver<bool>> { | |
spawn(idle_blink_led); | |
let (tx, rx) = channel(); | |
let chip = "/dev/gpiochip0"; | |
// GPIO 23 | |
let line = ARGS.lock().unwrap().gpio as u32; | |
spawn(move || { | |
let mut chip = Chip::new(chip).unwrap(); | |
let line = chip.get_line(line).unwrap(); | |
let events = line | |
.events( | |
LineRequestFlags::INPUT, | |
EventRequestFlags::BOTH_EDGES, | |
"gpioevents", | |
) | |
.unwrap(); | |
let mut previous = false; | |
let mut last_time = 0_u64; | |
for event in events { | |
let event = event.unwrap(); | |
let state = event.event_type() == EventType::RisingEdge; | |
// remove duplicated events | |
if state == previous { | |
continue; | |
} | |
let timestamp = SystemTime::now() | |
.duration_since(UNIX_EPOCH) | |
.unwrap() | |
.as_millis() as u64; | |
// remove highly oscillated interference | |
if timestamp - last_time < 10 | |
/* 10ms */ | |
{ | |
last_time = timestamp; | |
continue; | |
} | |
println!("{} State: {}", timestamp, state); | |
IDLE_FLAG.store(false, Ordering::SeqCst); | |
tx.send(state).unwrap(); | |
Led.set(state); | |
previous = state; | |
last_time = timestamp; | |
} | |
}); | |
Ok(rx) | |
} | |
fn idle_blink_led() { | |
loop { | |
if !IDLE_FLAG.load(Ordering::SeqCst) { | |
break; | |
} | |
Led.set_high(); | |
sleep(Duration::from_secs_f64(0.5)); | |
Led.set_low(); | |
sleep(Duration::from_secs_f64(0.5)); | |
} | |
} | |
struct Led; | |
impl Led { | |
fn set(&self, level: bool) { | |
let brightness = match level { | |
true => 255, | |
false => 0, | |
}; | |
// I don't know but controlling gpiochip1 throws EBUSY. | |
// So instead directly write to the class file. | |
let mut led_handle = File::options() | |
.truncate(true) | |
.read(true) | |
.write(true) | |
.open("/sys/class/leds/PWR/brightness") | |
.unwrap(); | |
write!(&mut led_handle, "{}", brightness).unwrap(); | |
led_handle.flush().unwrap(); | |
} | |
fn set_high(&self) { | |
self.set(true); | |
} | |
fn set_low(&self) { | |
self.set(false); | |
} | |
} |