refactor to 'lights', add diva protocol

This commit is contained in:
4yn 2022-02-13 23:22:31 +08:00
parent 287c9c63f6
commit f356624502
18 changed files with 401 additions and 123 deletions

View File

@ -3,7 +3,7 @@ use serde_json::Value;
use std::fs;
use crate::{
input::config::DeviceMode, lighting::config::LedMode, output::config::OutputMode, system,
device::config::DeviceMode, lighting::config::LightsMode, output::config::OutputMode, system,
};
#[derive(Debug, Clone)]
@ -11,7 +11,7 @@ pub struct Config {
pub raw: String,
pub device_mode: DeviceMode,
pub output_mode: OutputMode,
pub led_mode: LedMode,
pub lights_mode: LightsMode,
}
impl Config {
@ -22,7 +22,7 @@ impl Config {
raw: s.to_string(),
device_mode: DeviceMode::from_serde_value(&v)?,
output_mode: OutputMode::from_serde_value(&v)?,
led_mode: LedMode::from_serde_value(&v)?,
lights_mode: LightsMode::from_serde_value(&v)?,
})
}

View File

@ -4,8 +4,8 @@ use std::sync::{atomic::Ordering, Arc};
use crate::{
config::Config,
input::{brokenithm::BrokenithmJob, config::DeviceMode, device::HidDeviceJob},
lighting::{config::LedMode, lighting::LedJob},
device::{brokenithm::BrokenithmJob, config::DeviceMode, diva::DivaSliderJob, hid::HidJob},
lighting::{config::LightsMode, lighting::LightsJob},
output::{config::OutputMode, output::OutputJob},
shared::{
utils::LoopTimer,
@ -18,10 +18,10 @@ use crate::{
pub struct Context {
state: SliderState,
config: Config,
device_worker: Option<ThreadWorker>,
brokenithm_worker: Option<AsyncHaltableWorker>,
device_thread_worker: Option<ThreadWorker>,
device_async_haltable_worker: Option<AsyncHaltableWorker>,
output_worker: Option<AsyncWorker>,
led_worker: Option<AsyncWorker>,
lights_worker: Option<AsyncWorker>,
timers: Vec<(&'static str, Arc<AtomicF64>)>,
}
@ -30,7 +30,7 @@ impl Context {
info!("Context creating");
info!("Device config {:?}", config.device_mode);
info!("Output config {:?}", config.output_mode);
info!("LED config {:?}", config.led_mode);
info!("Lights config {:?}", config.lights_mode);
let state = SliderState::new();
let mut timers = vec![];
@ -39,12 +39,12 @@ impl Context {
DeviceMode::None => (None, None),
DeviceMode::Brokenithm {
ground_only,
led_enabled,
lights_enabled,
} => (
None,
Some(AsyncHaltableWorker::new(
"brokenithm",
BrokenithmJob::new(&state, ground_only, led_enabled),
BrokenithmJob::new(&state, ground_only, lights_enabled),
)),
),
DeviceMode::Hardware { spec } => (
@ -53,7 +53,19 @@ impl Context {
timers.push(("d", timer.fork()));
Some(ThreadWorker::new(
"device",
HidDeviceJob::from_config(&state, spec),
HidJob::from_config(&state, spec),
timer,
))
},
None,
),
DeviceMode::DivaSlider { port } => (
{
let timer = LoopTimer::new();
timers.push(("d", timer.fork()));
Some(ThreadWorker::new(
"diva",
DivaSliderJob::new(&state, port),
timer,
))
},
@ -72,14 +84,14 @@ impl Context {
))
}
};
let led_worker = match &config.led_mode {
LedMode::None => None,
let lights_worker = match &config.lights_mode {
LightsMode::None => None,
_ => {
let timer = LoopTimer::new();
timers.push(("l", timer.fork()));
Some(AsyncWorker::new(
"led",
LedJob::new(&state, &config.led_mode),
"lights",
LightsJob::new(&state, &config.lights_mode),
timer,
))
}
@ -88,10 +100,10 @@ impl Context {
Self {
state,
config,
device_worker,
brokenithm_worker,
device_thread_worker: device_worker,
device_async_haltable_worker: brokenithm_worker,
output_worker,
led_worker,
lights_worker,
timers,
}
}

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -69,7 +69,7 @@ async fn serve_file(path: &str) -> Result<Response<Body>, Infallible> {
async fn handle_brokenithm(
ws_stream: WebSocketStream<Upgraded>,
state: SliderState,
led_enabled: bool,
lights_enabled: bool,
) {
let (mut ws_write, mut ws_read) = ws_stream.split();
@ -153,7 +153,7 @@ async fn handle_brokenithm(
// info!("Websocket read task done");
};
match led_enabled {
match lights_enabled {
false => {
select! {
_ = read_task => {}
@ -163,14 +163,14 @@ async fn handle_brokenithm(
true => {
let msg_write_handle = msg_write.clone();
let state_handle = state.clone();
let led_task = async move {
let lights_task = async move {
loop {
let mut led_data = vec![0; 93];
let mut lights_data = vec![0; 93];
{
let lights_handle = state_handle.lights.lock();
(&mut led_data).copy_from_slice(&lights_handle.ground);
(&mut lights_data).copy_from_slice(&lights_handle.ground);
}
msg_write_handle.send(Message::Binary(led_data)).ok();
msg_write_handle.send(Message::Binary(lights_data)).ok();
sleep(Duration::from_millis(50)).await;
}
@ -179,7 +179,7 @@ async fn handle_brokenithm(
select! {
_ = read_task => {}
_ = write_task => {}
_ = led_task => {}
_ = lights_task => {}
};
}
}
@ -188,7 +188,7 @@ async fn handle_brokenithm(
async fn handle_websocket(
mut request: Request<Body>,
state: SliderState,
led_enabled: bool,
lights_enabled: bool,
) -> Result<Response<Body>, Infallible> {
let res = match handshake::server::create_response_with_body(&request, || Body::empty()) {
Ok(res) => {
@ -202,7 +202,7 @@ async fn handle_websocket(
)
.await;
handle_brokenithm(ws_stream, state, led_enabled).await;
handle_brokenithm(ws_stream, state, lights_enabled).await;
}
Err(e) => {
@ -229,7 +229,7 @@ async fn handle_request(
remote_addr: SocketAddr,
state: SliderState,
ground_only: bool,
led_enabled: bool,
lights_enabled: bool,
) -> Result<Response<Body>, Infallible> {
let method = request.method();
let path = request.uri().path();
@ -251,7 +251,7 @@ async fn handle_request(
true => serve_file("index-go.html").await,
},
(filename, false) => serve_file(&filename[1..]).await,
("/ws", true) => handle_websocket(request, state, led_enabled).await,
("/ws", true) => handle_websocket(request, state, lights_enabled).await,
_ => error_response().await,
}
}
@ -259,15 +259,15 @@ async fn handle_request(
pub struct BrokenithmJob {
state: SliderState,
ground_only: bool,
led_enabled: bool,
lights_enabled: bool,
}
impl BrokenithmJob {
pub fn new(state: &SliderState, ground_only: &bool, led_enabled: &bool) -> Self {
pub fn new(state: &SliderState, ground_only: &bool, lights_enabled: &bool) -> Self {
Self {
state: state.clone(),
ground_only: *ground_only,
led_enabled: *led_enabled,
lights_enabled: *lights_enabled,
}
}
}
@ -277,14 +277,14 @@ impl AsyncHaltableJob for BrokenithmJob {
async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F) {
let state = self.state.clone();
let ground_only = self.ground_only;
let led_enabled = self.led_enabled;
let lights_enabled = self.lights_enabled;
let make_svc = make_service_fn(|conn: &AddrStream| {
let remote_addr = conn.remote_addr();
let make_svc_state = state.clone();
async move {
Ok::<_, Infallible>(service_fn(move |request: Request<Body>| {
let svc_state = make_svc_state.clone();
handle_request(request, remote_addr, svc_state, ground_only, led_enabled)
handle_request(request, remote_addr, svc_state, ground_only, lights_enabled)
}))
}
});

View File

@ -15,7 +15,10 @@ pub enum DeviceMode {
},
Brokenithm {
ground_only: bool,
led_enabled: bool,
lights_enabled: bool,
},
DivaSlider {
port: String,
},
}
@ -32,21 +35,24 @@ impl DeviceMode {
"yuancon" => DeviceMode::Hardware {
spec: HardwareSpec::Yuancon,
},
"diva" => DeviceMode::DivaSlider {
port: v["divaSerialPort"].as_str()?.to_string(),
},
"brokenithm" => DeviceMode::Brokenithm {
ground_only: false,
led_enabled: false,
lights_enabled: false,
},
"brokenithm-led" => DeviceMode::Brokenithm {
ground_only: false,
led_enabled: true,
lights_enabled: true,
},
"brokenithm-ground" => DeviceMode::Brokenithm {
ground_only: true,
led_enabled: false,
lights_enabled: false,
},
"brokenithm-ground-led" => DeviceMode::Brokenithm {
ground_only: true,
led_enabled: true,
lights_enabled: true,
},
_ => return None,
})

View File

@ -0,0 +1,313 @@
use log::{error, info};
use serialport::SerialPort;
use std::{collections::VecDeque, num::Wrapping, thread::sleep, time::Duration};
use crate::{shared::worker::ThreadJob, state::SliderState};
struct DivaPacket {
command: u8,
len: u8,
data: Vec<u8>,
checksum: Wrapping<u8>,
raw: Option<Vec<u8>>,
}
impl DivaPacket {
fn new() -> Self {
Self {
command: 0,
len: 0,
data: Vec::with_capacity(256),
checksum: Wrapping(0),
raw: None,
}
}
fn from_bytes(command: u8, data: &[u8]) -> Self {
Self {
command,
len: data.len() as u8,
data: data.iter().copied().collect(),
checksum: Wrapping(0),
raw: None,
}
}
fn push_raw_escaped(byte: u8, raw: &mut Vec<u8>) {
match byte {
0xfd => {
raw.push(0xfd);
raw.push(0xfc);
}
0xff => {
raw.push(0xfd);
raw.push(0xfe);
}
_ => {
raw.push(byte);
}
}
}
fn serialize(&mut self) -> &[u8] {
let mut raw: Vec<u8> = Vec::with_capacity(512);
let mut checksum = Wrapping(0);
raw.push(0xff);
checksum += Wrapping(0xffu8);
Self::push_raw_escaped(self.command, &mut raw);
checksum += Wrapping(self.command);
Self::push_raw_escaped(self.len, &mut raw);
checksum += Wrapping(self.len);
for i in &self.data {
Self::push_raw_escaped(*i, &mut raw);
checksum += Wrapping(*i);
}
checksum = -checksum;
Self::push_raw_escaped(checksum.0, &mut raw);
self.raw = Some(raw);
return self.raw.as_ref().unwrap();
}
}
enum DivaDeserializerState {
ExpectCommand,
ExpectLen,
ExpectData,
ExpectChecksum,
Done,
}
struct DivaDeserializer {
state: DivaDeserializerState,
escape: u8,
len: u8,
packet: DivaPacket,
}
impl DivaDeserializer {
fn new() -> Self {
Self {
state: DivaDeserializerState::Done,
escape: 1,
len: 0,
packet: DivaPacket::new(),
}
}
fn deserialize(&mut self, data: &[u8], out: &mut VecDeque<DivaPacket>) {
for c in data {
match c {
0xff => {
self.packet = DivaPacket::new();
self.packet.checksum = Wrapping(0xff);
self.state = DivaDeserializerState::ExpectCommand;
}
0xfd => {
self.escape = 1;
}
c => {
let c = c + self.escape;
self.escape = 0;
self.packet.checksum += Wrapping(c);
match self.state {
DivaDeserializerState::ExpectCommand => {
self.packet.command = c;
self.state = DivaDeserializerState::ExpectLen;
}
DivaDeserializerState::ExpectLen => {
self.len = c;
self.packet.len = c;
self.state = match c {
0 => DivaDeserializerState::ExpectChecksum,
_ => DivaDeserializerState::ExpectData,
};
}
DivaDeserializerState::ExpectData => {
self.packet.data.push(c);
self.len -= 1;
if self.len == 0 {
self.state = DivaDeserializerState::ExpectChecksum;
}
}
DivaDeserializerState::ExpectChecksum => {
debug_assert!(self.packet.checksum == Wrapping(0));
if self.packet.checksum == Wrapping(0) {
out.push_back(DivaPacket::new());
std::mem::swap(&mut self.packet, out.back_mut().unwrap());
}
self.state = DivaDeserializerState::Done;
}
_ => {}
}
}
}
}
}
}
enum DivaSliderBootstrap {
Init,
AwaitReset,
AwaitInfo,
AwaitStart,
ReadLoop,
}
pub struct DivaSliderJob {
state: SliderState,
port: String,
packets: VecDeque<DivaPacket>,
deserializer: DivaDeserializer,
serial_port: Option<Box<dyn SerialPort>>,
bootstrap: DivaSliderBootstrap,
}
impl DivaSliderJob {
pub fn new(state: &SliderState, port: &String) -> Self {
Self {
state: state.clone(),
port: port.clone(),
packets: VecDeque::with_capacity(100),
deserializer: DivaDeserializer::new(),
serial_port: None,
bootstrap: DivaSliderBootstrap::Init,
}
}
}
impl ThreadJob for DivaSliderJob {
fn setup(&mut self) -> bool {
info!(
"Serial port for diva slider opening at {} {:?}",
self.port.as_str(),
115200
);
match serialport::new(&self.port, 152000).open() {
Ok(serial_port_buf) => {
info!("Serial port opened");
self.serial_port = Some(serial_port_buf);
true
}
Err(e) => {
error!("Serial port could not open: {}", e);
false
}
}
}
fn tick(&mut self) -> bool {
let mut work = false;
let serial_port = self.serial_port.as_mut().unwrap();
let bytes_avail = serial_port.bytes_to_read().unwrap_or(0);
if bytes_avail > 0 {
let mut read_buf = vec![0 as u8; bytes_avail as usize];
serial_port.read_exact(&mut read_buf).ok();
self.deserializer.deserialize(&read_buf, &mut self.packets);
work = true;
}
match self.bootstrap {
DivaSliderBootstrap::Init => {
let mut reset_packet = DivaPacket::from_bytes(0x10, &[]);
serial_port.write(reset_packet.serialize()).ok();
self.bootstrap = DivaSliderBootstrap::AwaitReset;
work = true;
}
DivaSliderBootstrap::AwaitReset => {
if let Some(ack_packet) = self.packets.pop_front() {
info!(
"Diva slider ack reset {:?} {:?}",
ack_packet.command, ack_packet.data
);
let mut info_packet = DivaPacket::from_bytes(0xf0, &[]);
serial_port.write(info_packet.serialize()).ok();
self.bootstrap = DivaSliderBootstrap::AwaitInfo;
work = true;
}
}
DivaSliderBootstrap::AwaitInfo => {
if let Some(ack_packet) = self.packets.pop_front() {
info!(
"Diva slider ack info {:?} {:?}",
ack_packet.command, ack_packet.data
);
let mut start_packet = DivaPacket::from_bytes(0x03, &[]);
serial_port.write(start_packet.serialize()).ok();
self.bootstrap = DivaSliderBootstrap::AwaitStart;
work = true;
}
}
DivaSliderBootstrap::AwaitStart => {
if let Some(ack_packet) = self.packets.pop_front() {
info!(
"Diva slider ack start {:?} {:?}",
ack_packet.command, ack_packet.data
);
self.bootstrap = DivaSliderBootstrap::ReadLoop;
work = true;
}
}
DivaSliderBootstrap::ReadLoop => {
while let Some(data_packet) = self.packets.pop_front() {
if data_packet.command == 0x01 && data_packet.len == 32 {
let mut input_handle = self.state.input.lock();
input_handle
.ground
.copy_from_slice(&data_packet.data[0..32]);
work = true;
}
}
let mut send_lights = false;
let mut lights_buf = [0; 97];
{
let mut lights_handle = self.state.lights.lock();
if lights_handle.dirty {
send_lights = true;
lights_buf[0] = 0x3f;
lights_buf[1..97].copy_from_slice(&lights_handle.ground[0..96]);
lights_handle.dirty = false;
}
}
if send_lights {
let mut lights_packet = DivaPacket::from_bytes(0x02, &lights_buf);
serial_port.write(lights_packet.serialize()).ok();
}
}
};
// TODO: async worker?
sleep(Duration::from_millis(10));
work
}
}
impl Drop for DivaSliderJob {
fn drop(&mut self) {
match self.bootstrap {
DivaSliderBootstrap::AwaitStart | DivaSliderBootstrap::ReadLoop => {
info!("Diva slider sending stop");
let serial_port = self.serial_port.as_mut().unwrap();
let mut stop_packet = DivaPacket::from_bytes(0x04, &[]);
serial_port.write(stop_packet.serialize()).ok();
}
_ => {}
};
}
}

View File

@ -25,7 +25,7 @@ enum WriteType {
Interrupt,
}
pub struct HidDeviceJob {
pub struct HidJob {
state: SliderState,
vid: u16,
@ -44,7 +44,7 @@ pub struct HidDeviceJob {
handle: Option<DeviceHandle<GlobalContext>>,
}
impl HidDeviceJob {
impl HidJob {
fn new(
state: SliderState,
vid: u16,
@ -212,7 +212,7 @@ impl HidDeviceJob {
const TIMEOUT: Duration = Duration::from_millis(20);
impl ThreadJob for HidDeviceJob {
impl ThreadJob for HidJob {
fn setup(&mut self) -> bool {
match self.get_handle() {
Ok(_) => {
@ -283,7 +283,7 @@ impl ThreadJob for HidDeviceJob {
}
}
impl Drop for HidDeviceJob {
impl Drop for HidJob {
fn drop(&mut self) {
if let Some(handle) = self.handle.as_mut() {
handle.release_interface(0).ok();

View File

@ -1,6 +1,5 @@
pub mod config;
mod acio;
pub mod diva;
pub mod brokenithm;
pub mod device;
pub mod hid;

View File

@ -1,52 +0,0 @@
// use serialport::SerialPort;
// use std::io::{BufRead, BufReader};
// struct ArcadeSlider {
// serial_port: BufReader<Box<dyn SerialPort>>,
// }
// impl ArcadeSlider {
// fn new() -> Self {
// let serial_port = serialport::new("COM1", 152000).open().unwrap();
// let serial_port_buf = BufReader::new(serial_port);
// Self {
// serial_port: serial_port_buf,
// }
// }
// fn recv(&mut self) {
// let mut consumed = 0;
// {
// let d = self.serial_port.fill_buf().unwrap();
// let mut packets = vec![];
// let mut packet = vec![];
// let mut bytes_taken = 0;
// let mut in_escape = 0;
// let mut checksum = 0;
// for b in d.iter() {
// bytes_taken += 1;
// match b {
// 0xff => {
// consumed += bytes_taken;
// bytes_taken = 0;
// in_escape = 0;
// }
// 0xfd => {
// in_escape = 1;
// }
// _ => {
// let b = b + in_escape;
// in_escape = 0;
// packet.push(b);
// }
// }
// }
// }
// self.serial_port.consume(consumed);
// }
// fn send(&mut self) {}
// }

View File

@ -9,7 +9,7 @@ mod config;
mod shared;
mod state;
mod input;
mod device;
mod lighting;
mod output;

View File

@ -7,7 +7,7 @@ pub enum ReactiveLayout {
}
#[derive(Debug, Clone)]
pub enum LedMode {
pub enum LightsMode {
None,
Reactive {
layout: ReactiveLayout,
@ -23,32 +23,32 @@ pub enum LedMode {
},
}
impl LedMode {
impl LightsMode {
pub fn from_serde_value(v: &Value) -> Option<Self> {
Some(match v["ledMode"].as_str()? {
"none" => LedMode::None,
"reactive-4" => LedMode::Reactive {
"none" => LightsMode::None,
"reactive-4" => LightsMode::Reactive {
layout: ReactiveLayout::Even { splits: 4 },
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
},
"reactive-8" => LedMode::Reactive {
"reactive-8" => LightsMode::Reactive {
layout: ReactiveLayout::Even { splits: 8 },
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
},
"reactive-16" => LedMode::Reactive {
"reactive-16" => LightsMode::Reactive {
layout: ReactiveLayout::Even { splits: 16 },
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
},
"reactive-voltex" => LedMode::Reactive {
"reactive-voltex" => LightsMode::Reactive {
layout: ReactiveLayout::Voltex,
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
},
"attract" => LedMode::Attract,
"test" => LedMode::Test,
"websocket" => LedMode::Websocket {
"attract" => LightsMode::Attract,
"test" => LightsMode::Test,
"websocket" => LightsMode::Websocket {
url: v["ledWebsocketUrl"].as_str()?.to_string(),
},
"serial" => LedMode::Serial {
"serial" => LightsMode::Serial {
port: v["ledSerialPort"].as_str()?.to_string(),
},
_ => return None,

View File

@ -13,18 +13,18 @@ use crate::{
state::{SliderLights, SliderState},
};
use super::config::{LedMode, ReactiveLayout};
use super::config::{LightsMode, ReactiveLayout};
pub struct LedJob {
pub struct LightsJob {
state: SliderState,
mode: LedMode,
mode: LightsMode,
serial_port: Option<Box<dyn SerialPort>>,
started: Instant,
timer: Interval,
}
impl LedJob {
pub fn new(state: &SliderState, mode: &LedMode) -> Self {
impl LightsJob {
pub fn new(state: &SliderState, mode: &LightsMode) -> Self {
Self {
state: state.clone(),
mode: mode.clone(),
@ -41,7 +41,7 @@ impl LedJob {
lights: &mut SliderLights,
) {
match self.mode {
LedMode::Reactive { layout, .. } => {
LightsMode::Reactive { layout, .. } => {
let flat_input = flat_input.unwrap();
match layout {
@ -116,7 +116,7 @@ impl LedJob {
}
}
}
LedMode::Attract => {
LightsMode::Attract => {
let theta = self
.started
.elapsed()
@ -128,7 +128,7 @@ impl LedJob {
lights.paint(idx, &[color.red, color.green, color.blue]);
}
}
LedMode::Serial { .. } => {
LightsMode::Serial { .. } => {
// https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialProcessor.h
// https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialProcessor.cpp
// https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialLeds.h
@ -155,10 +155,10 @@ impl LedJob {
}
#[async_trait]
impl AsyncJob for LedJob {
impl AsyncJob for LightsJob {
async fn setup(&mut self) -> bool {
match &self.mode {
LedMode::Serial { port } => {
LightsMode::Serial { port } => {
info!(
"Serial port for led opening at {} {:?}",
port.as_str(),
@ -187,11 +187,11 @@ impl AsyncJob for LedJob {
// Do the IO here
match self.mode {
LedMode::Reactive { sensitivity, .. } => {
LightsMode::Reactive { sensitivity, .. } => {
let input_handle = self.state.input.lock();
flat_input = Some(input_handle.to_flat(&sensitivity));
}
LedMode::Serial { .. } => {
LightsMode::Serial { .. } => {
if let Some(serial_port) = self.serial_port.as_mut() {
let mut serial_data_avail = serial_port.bytes_to_read().unwrap_or(0);
if serial_data_avail >= 100 {