Skip to content

Commit

Permalink
Merge pull request #93 from varoonp123/feature/detectPort
Browse files Browse the repository at this point in the history
Infer the arduino arcade serial port from available ports, falling back to an envvar. This port will be different on each arcade deployment.
  • Loading branch information
varoonp123 authored Sep 8, 2023
2 parents bcd1926 + 3e1c68a commit 0447952
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 229 deletions.
2 changes: 2 additions & 0 deletions crates/thetawave_arcade/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ bevy_serialport = {version = "0.4.0"}
bytes = "1.4.0"
bevy = {workspace = true}
thetawave_interface = {path = "../thetawave_interface"}
derive_more = {workspace = true}
serialport = { version = "4" }
9 changes: 9 additions & 0 deletions crates/thetawave_arcade/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Thetawave-arcade

This crate exposes Bevy plugins that implement features specific to the arcade cabinet build target/platform.


## Configuration

- `THETAWAVE_ARCADE_LIGHT_SERIAL_PORT_NAME` = The name of the serial port that controls the lights. We will probably
automate detecting this later, but this is currently related to a port used by Arduino.
294 changes: 294 additions & 0 deletions crates/thetawave_arcade/src/arduino.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
use bevy::prelude::*;
use bevy_serialport::{
DataBits, FlowControl, Parity, SerialPortPlugin, SerialPortRuntime, SerialPortSetting,
SerialResource, StopBits,
};

use derive_more::{Deref, DerefMut, From};
use serialport::{available_ports, SerialPortType};
use thetawave_interface::states;

/// Environment variable name of the serial port that handles lights.
const THETAWAVE_ARCADE_LIGHT_SERIAL_PORT_NAME: &'static str =
"THETAWAVE_ARCADE_LIGHT_SERIAL_PORT_NAME";

/// The port for the Arduino that controls the lights.
#[derive(Resource, Deref, DerefMut, From, Debug)]
struct ArduinoSerialPort(String);
impl ArduinoSerialPort {
fn first_port_matching_manufacturer_product() -> Option<Self> {
first_usb_port_name_matching_str("arduino").map(Self)
}
fn from_envvar() -> Option<Self> {
std::env::var(THETAWAVE_ARCADE_LIGHT_SERIAL_PORT_NAME)
.ok()
.map(Self)
}
}

/// Find a serial port from all available ports where the manufacturer or product match a string
/// pattern.
fn first_usb_port_name_matching_str(lowercase_pattern: &str) -> Option<String> {
match available_ports() {
Ok(ports) => ports
.into_iter()
.find(|x| match &x.port_type {
SerialPortType::UsbPort(x) => format!(
"{} {}",
x.manufacturer.clone().unwrap_or_default().to_lowercase(),
x.product.clone().unwrap_or_default().to_lowercase()
)
.contains(lowercase_pattern),
_ => false,
})
.map(|x| x.port_name.clone()),
Err(e) => {
error!("Failed to list serial ports. {}", e);
None
}
}
}
use bytes::Bytes;
use thetawave_interface::character_selection::PlayerJoinEvent;

/// Features specific to an Arduino that is only running on custom-made arcade machines. Mostly
/// lighting. The plugin no-ops if an Arduino serial port cannot be accessed.
pub struct ArcadeArduinoPlugin;

impl Plugin for ArcadeArduinoPlugin {
fn build(&self, app: &mut App) {
if let Some(res) = ArduinoSerialPort::first_port_matching_manufacturer_product()
.or_else(ArduinoSerialPort::from_envvar)
{
info!("arduino serial port: {:?}", &res);
app.add_plugins(SerialPortPlugin).insert_resource(res);

app.add_systems(
Startup,
(setup_serial_system, enter_main_menu_button_leds_system),
);

app.add_systems(
OnEnter(states::AppStates::MainMenu),
enter_main_menu_button_leds_system,
);

app.add_systems(
OnEnter(states::AppStates::CharacterSelection),
enter_character_selection_button_leds_system,
);

app.add_systems(
Update,
character_selection_button_leds_system
.run_if(in_state(states::AppStates::CharacterSelection)),
);

app.add_systems(
OnEnter(thetawave_interface::states::AppStates::Game),
enter_game_button_leds_system,
);

app.add_systems(
OnEnter(states::GameStates::Paused),
enter_pause_button_leds_system,
);

app.add_systems(
OnExit(states::GameStates::Paused),
enter_game_button_leds_system,
);

app.add_systems(
OnEnter(states::AppStates::Victory),
enter_victory_button_leds_system,
);

app.add_systems(
OnEnter(states::AppStates::GameOver),
enter_gameover_button_leds_system,
);
} else {
error!("Failed to find the arduino port for arcade lighting. no-op for the plugin. Enter the environment variable {} or compile without the --arcade feature", THETAWAVE_ARCADE_LIGHT_SERIAL_PORT_NAME);
}
}
}
fn setup_serial_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
runtime: Res<SerialPortRuntime>,
) {
let serial_setting = SerialPortSetting {
port_name: arduino_port.clone(),
baud_rate: 115200,
data_bits: DataBits::Eight,
flow_control: FlowControl::None,
parity: Parity::None,
stop_bits: StopBits::One,
timeout: Default::default(),
};
serial_resource
.open_with_setting(runtime.clone(), serial_setting)
.expect(&format!("Error opening serial port {:?}", &arduino_port));
}

enum ButtonLEDByte {
EndMarker = 255,
Off = 0,
On = 1,
Fade = 2,
}

impl ButtonLEDByte {
fn enter_main_menu() -> Bytes {
vec![
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}

fn enter_character_selection() -> Bytes {
vec![
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}

fn player_one_joined() -> Bytes {
vec![
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}

fn player_two_joined() -> Bytes {
vec![
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}

fn enter_game() -> Bytes {
vec![
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::On as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}

fn enter_pause() -> Bytes {
vec![
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}

fn enter_gameover() -> Bytes {
vec![
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}

fn enter_victory() -> Bytes {
vec![
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::Fade as u8,
ButtonLEDByte::Off as u8,
ButtonLEDByte::EndMarker as u8,
]
.into()
}
}

fn enter_main_menu_button_leds_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
) {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::enter_main_menu());
}

fn enter_character_selection_button_leds_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
) {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::enter_character_selection());
}

fn character_selection_button_leds_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
mut player_join_event: EventReader<PlayerJoinEvent>,
) {
for event in player_join_event.iter() {
if event.0 == 0 {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::player_one_joined());
} else if event.0 == 1 {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::player_two_joined());
}
}
}

fn enter_game_button_leds_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
) {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::enter_game());
}

fn enter_pause_button_leds_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
) {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::enter_pause());
}

fn enter_gameover_button_leds_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
) {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::enter_gameover());
}

fn enter_victory_button_leds_system(
mut serial_resource: ResMut<SerialResource>,
arduino_port: Res<ArduinoSerialPort>,
) {
serial_resource.send_message(&(*arduino_port), ButtonLEDByte::enter_victory());
}
2 changes: 1 addition & 1 deletion crates/thetawave_arcade/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/// Exposes a single Bevy plugin for integrating the arcade IO into the game systems.
pub mod plugin;
pub mod arduino;
Loading

0 comments on commit 0447952

Please sign in to comment.