Skip to content

Instantly share code, notes, and snippets.

@bczhc
Last active June 22, 2024 14:29
Show Gist options
  • Save bczhc/5024933a9a21a00356418ed68925aa02 to your computer and use it in GitHub Desktop.
Save bczhc/5024933a9a21a00356418ed68925aa02 to your computer and use it in GitHub Desktop.
用Raspberry PI4当手动电键连接器

说明

先在树莓派上运行rpi,此时它会在5001端口上监听TCP连接。

当GPIO23和VCC相连时,为电键接通,PWR_LED(红色)会亮起,并向连接的客户端socket发送字节1;反之断开时,为电键断开,PWR_LED熄灭,并向连接的客户端socket发送字节0

在电脑(使用X11的Linux)上运行host,把IP地址改成树莓派的地址,就可成功连接。电键触发与断开就会分别发送mousedownmouseup

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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment