Repository: andrewdavidmackenzie/pigg
Pull request #38 (2024-05-22 08:19:52)

More HW configuration on Pi HW


Patch:
diff --git a/src/gpio/mod.rs b/src/gpio/mod.rs
index 32ba4106..a8f1a418 100644
--- a/src/gpio/mod.rs
+++ b/src/gpio/mod.rs
@@ -1,11 +1,20 @@
-mod pin_descriptions;
-
-use pin_descriptions::*;
-use serde::{Deserialize, Serialize};
 use std::fs::File;
 use std::io;
 use std::io::{BufReader, Write};
 
+use serde::{Deserialize, Serialize};
+
+use pin_descriptions::*;
+
+mod pin_descriptions;
+
+// An input can be configured to have an optional pull-up or pull-down
+#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
+pub enum InputPull {
+    PullUp,
+    PullDown,
+}
+
 // All the possible functions a pin can be given
 #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
 #[allow(non_camel_case_types)]
@@ -14,8 +23,8 @@ pub enum PinFunction {
     Power3V3,
     Power5V,
     Ground,
-    Input,
-    Output,
+    Input(Option<InputPull>),
+    Output(Option<bool>),
     SDA1,
     I2C,
     SCL1,
@@ -97,13 +106,15 @@ pub struct GPIOState {
 
 #[cfg(test)]
 mod test {
-    use crate::gpio::{GPIOConfig, PinFunction};
     use std::fs;
     use std::fs::File;
     use std::io::Write;
     use std::path::PathBuf;
+
     use tempfile::tempdir;
 
+    use crate::gpio::{GPIOConfig, PinFunction};
+
     #[test]
     fn create_a_config() {
         let config = GPIOConfig::default();
@@ -111,8 +122,24 @@ mod test {
     }
 
     #[test]
-    fn load_one_pin_config() {
-        let pin_config = r#"{"configured_pins":[[1,"Input"]]}"#;
+    fn save_one_pin_config_input_no_pullup() {
+        let config = GPIOConfig {
+            configured_pins: vec![(1, PinFunction::Input(None))],
+        };
+
+        let output_dir = tempdir().expect("Could not create a tempdir").into_path();
+        let test_file = output_dir.join("test.piggui");
+
+        config.save(test_file.to_str().unwrap()).unwrap();
+
+        let pin_config = r#"{"configured_pins":[[1,{"Input":null}]]}"#;
+        let contents = fs::read_to_string(test_file).expect("Could not read test file");
+        assert_eq!(contents, pin_config);
+    }
+
+    #[test]
+    fn load_one_pin_config_input_no_pull() {
+        let pin_config = r#"{"configured_pins":[[1,{"Input":null}]]}"#;
         let output_dir = tempdir().expect("Could not create a tempdir").into_path();
         let test_file = output_dir.join("test.piggui");
         let mut file = File::create(&test_file).expect("Could not create test file");
@@ -121,7 +148,7 @@ mod test {
         let config = GPIOConfig::load(test_file.to_str().unwrap()).unwrap();
         assert_eq!(config.configured_pins.len(), 1);
         assert_eq!(config.configured_pins[0].0, 1);
-        assert_eq!(config.configured_pins[0].1, PinFunction::Input);
+        assert_eq!(config.configured_pins[0].1, PinFunction::Input(None));
     }
 
     #[test]
@@ -131,14 +158,14 @@ mod test {
         path = path.join("tests/one_pin_config.piggui");
         let config = GPIOConfig::load(path.to_str().unwrap()).unwrap();
         assert_eq!(config.configured_pins.len(), 1);
-        assert_eq!(config.configured_pins[0].0, 1);
-        assert_eq!(config.configured_pins[0].1, PinFunction::Input);
+        assert_eq!(config.configured_pins[0].0, 17); // GPIO17
+        assert_eq!(config.configured_pins[0].1, PinFunction::Output(Some(true)));
     }
 
     #[test]
-    fn save_one_pin_config() {
+    fn save_one_pin_config_output_with_level() {
         let config = GPIOConfig {
-            configured_pins: vec![(1, PinFunction::Input)],
+            configured_pins: vec![(7, PinFunction::Output(Some(true)))], // GPIO7 output set to 1
         };
 
         let output_dir = tempdir().expect("Could not create a tempdir").into_path();
@@ -146,7 +173,7 @@ mod test {
 
         config.save(test_file.to_str().unwrap()).unwrap();
 
-        let pin_config = r#"{"configured_pins":[[1,"Input"]]}"#;
+        let pin_config = r#"{"configured_pins":[[7,{"Output":true}]]}"#;
         let contents = fs::read_to_string(test_file).expect("Could not read test file");
         assert_eq!(contents, pin_config);
     }
diff --git a/src/gpio/pin_descriptions.rs b/src/gpio/pin_descriptions.rs
index 76a0bd8a..48d8849a 100644
--- a/src/gpio/pin_descriptions.rs
+++ b/src/gpio/pin_descriptions.rs
@@ -20,7 +20,12 @@ pub const PIN_3: PinDescription = PinDescription {
     board_pin_number: 3,
     bcm_pin_number: Some(2),
     name: "GPIO2",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::SDA1, PinFunction::I2C],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::SDA1,
+        PinFunction::I2C,
+    ],
 };
 
 pub const PIN_4: PinDescription = PinDescription {
@@ -34,7 +39,12 @@ pub const PIN_5: PinDescription = PinDescription {
     board_pin_number: 5,
     bcm_pin_number: Some(3),
     name: "GPIO3",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::SCL1, PinFunction::I2C],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::SCL1,
+        PinFunction::I2C,
+    ],
 };
 
 pub const PIN_6: PinDescription = PinDescription {
@@ -48,14 +58,18 @@ pub const PIN_7: PinDescription = PinDescription {
     board_pin_number: 7,
     bcm_pin_number: Some(4),
     name: "GPIO4",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_8: PinDescription = PinDescription {
     board_pin_number: 8,
     bcm_pin_number: Some(14),
     name: "GPIO14",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::UART0_TXD],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::UART0_TXD,
+    ],
 };
 
 pub const PIN_9: PinDescription = PinDescription {
@@ -69,28 +83,36 @@ pub const PIN_10: PinDescription = PinDescription {
     board_pin_number: 10,
     bcm_pin_number: Some(15),
     name: "GPIO15",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::UART0_RXD],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::UART0_RXD,
+    ],
 };
 
 pub const PIN_11: PinDescription = PinDescription {
     board_pin_number: 11,
     bcm_pin_number: Some(17),
     name: "GPIO17",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_12: PinDescription = PinDescription {
     board_pin_number: 12,
     bcm_pin_number: Some(18),
     name: "GPIO18",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::PCM_CLK],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::PCM_CLK,
+    ],
 };
 
 pub const PIN_13: PinDescription = PinDescription {
     board_pin_number: 13,
     bcm_pin_number: Some(27),
     name: "GPIO27",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_14: PinDescription = PinDescription {
@@ -104,14 +126,14 @@ pub const PIN_15: PinDescription = PinDescription {
     board_pin_number: 15,
     bcm_pin_number: Some(22),
     name: "GPIO22",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_16: PinDescription = PinDescription {
     board_pin_number: 16,
     bcm_pin_number: Some(23),
     name: "GPIO23",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_17: PinDescription = PinDescription {
@@ -125,14 +147,18 @@ pub const PIN_18: PinDescription = PinDescription {
     board_pin_number: 18,
     bcm_pin_number: Some(24),
     name: "GPIO24",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_19: PinDescription = PinDescription {
     board_pin_number: 19,
     bcm_pin_number: Some(10),
     name: "GPIO10",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::SPIO_MOSI],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::SPIO_MOSI,
+    ],
 };
 
 pub const PIN_20: PinDescription = PinDescription {
@@ -146,28 +172,40 @@ pub const PIN_21: PinDescription = PinDescription {
     board_pin_number: 21,
     bcm_pin_number: Some(9),
     name: "GPIO9",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::SPIO_MISO],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::SPIO_MISO,
+    ],
 };
 
 pub const PIN_22: PinDescription = PinDescription {
     board_pin_number: 22,
     bcm_pin_number: Some(25),
     name: "GPIO25",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_23: PinDescription = PinDescription {
     board_pin_number: 23,
     bcm_pin_number: Some(11),
     name: "GPIO11",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::SPIO_SCLK],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::SPIO_SCLK,
+    ],
 };
 
 pub const PIN_24: PinDescription = PinDescription {
     board_pin_number: 24,
     bcm_pin_number: Some(8),
     name: "GPIO8",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::SPIO_CE0_N],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::SPIO_CE0_N,
+    ],
 };
 
 pub const PIN_25: PinDescription = PinDescription {
@@ -181,28 +219,42 @@ pub const PIN_26: PinDescription = PinDescription {
     board_pin_number: 26,
     bcm_pin_number: Some(7),
     name: "GPIO7",
-    options: &[PinFunction::Input, PinFunction::Output, PinFunction::SPIO_CE1_N],
+    options: &[
+        PinFunction::Input(None),
+        PinFunction::Output(None),
+        PinFunction::SPIO_CE1_N,
+    ],
 };
 
 pub const PIN_27: PinDescription = PinDescription {
     board_pin_number: 27,
     bcm_pin_number: None,
     name: "ID_SD",
-    options: &[PinFunction::ID_SD, PinFunction::I2C, PinFunction::ID, PinFunction::EEPROM],
+    options: &[
+        PinFunction::ID_SD,
+        PinFunction::I2C,
+        PinFunction::ID,
+        PinFunction::EEPROM,
+    ],
 };
 
 pub const PIN_28: PinDescription = PinDescription {
     board_pin_number: 28,
     bcm_pin_number: None,
     name: "ID_SC",
-    options: &[PinFunction::ID_SC, PinFunction::I2C, PinFunction::ID, PinFunction::EEPROM],
+    options: &[
+        PinFunction::ID_SC,
+        PinFunction::I2C,
+        PinFunction::ID,
+        PinFunction::EEPROM,
+    ],
 };
 
 pub const PIN_29: PinDescription = PinDescription {
     board_pin_number: 29,
     bcm_pin_number: Some(5),
     name: "GPIO5",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_30: PinDescription = PinDescription {
@@ -216,21 +268,21 @@ pub const PIN_31: PinDescription = PinDescription {
     board_pin_number: 31,
     bcm_pin_number: Some(6),
     name: "GPIO6",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_32: PinDescription = PinDescription {
     board_pin_number: 32,
     bcm_pin_number: Some(12),
     name: "GPIO12",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_33: PinDescription = PinDescription {
     board_pin_number: 33,
     bcm_pin_number: Some(13),
     name: "GPIO13",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_34: PinDescription = PinDescription {
@@ -244,28 +296,28 @@ pub const PIN_35: PinDescription = PinDescription {
     board_pin_number: 35,
     bcm_pin_number: Some(19),
     name: "GPIO19",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_36: PinDescription = PinDescription {
     board_pin_number: 36,
     bcm_pin_number: Some(16),
     name: "GPIO16",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_37: PinDescription = PinDescription {
     board_pin_number: 37,
     bcm_pin_number: Some(26),
     name: "GPIO26",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_38: PinDescription = PinDescription {
     board_pin_number: 38,
     bcm_pin_number: Some(20),
     name: "GPIO20",
-    options: &[PinFunction::Input, PinFunction::Output],
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
 };
 
 pub const PIN_39: PinDescription = PinDescription {
@@ -279,5 +331,5 @@ pub const PIN_40: PinDescription = PinDescription {
     board_pin_number: 40,
     bcm_pin_number: Some(21),
     name: "GPIO21",
-    options: &[PinFunction::Input, PinFunction::Output],
-};
\ No newline at end of file
+    options: &[PinFunction::Input(None), PinFunction::Output(None)],
+};
diff --git a/src/hw/mod.rs b/src/hw/mod.rs
index 4bf29371..475023cd 100644
--- a/src/hw/mod.rs
+++ b/src/hw/mod.rs
@@ -1,18 +1,25 @@
+use std::io;
+
 use crate::gpio::{GPIOConfig, GPIOState};
 
+/// There are three implementations of [`Hardware`] trait:
+/// * None - used on host (macOS, Linux, etc.) to show and develop GUI without real HW
+/// * Pi - Raspberry Pi using "rppal" crate: Should support most Pi hardware from Model B
+/// * Pico - Raspberry Pi Pico Microcontroller (To Be done)
 #[cfg_attr(all(feature = "pico", not(feature = "pi")), path = "pico.rs")]
 #[cfg_attr(all(feature = "pi", not(feature = "pico")), path = "pi.rs")]
 #[cfg_attr(not(any(feature = "pico", feature = "pi")), path = "none.rs")]
 mod implementation;
 
-#[allow(dead_code)] // TODO remove later when used
 pub fn get() -> impl Hardware {
     implementation::get()
 }
 
-// TODO placeholder until I figure out what this trait needs to implement
-#[allow(dead_code)] // TODO remove later when used
+/// [`Hardware`] is a trait to be implemented depending on the hardware we are running on, to
+/// interact with any possible GPIO hardware on the device to set config and get state
+#[must_use]
 pub trait Hardware {
-    fn apply_config(&mut self, config: &GPIOConfig);
+    fn apply_config(&mut self, config: &GPIOConfig) -> io::Result<()>;
+    #[allow(dead_code)] // TODO remove later when used
     fn get_state(&self) -> GPIOState;
-}
\ No newline at end of file
+}
diff --git a/src/hw/none.rs b/src/hw/none.rs
index 429255ab..7f7ffe35 100644
--- a/src/hw/none.rs
+++ b/src/hw/none.rs
@@ -1,7 +1,10 @@
 // Implementation of GPIO for hosts that don't have GPIO (Linux, macOS, etc.)
 
+use std::io;
+
+use crate::gpio::{GPIOConfig, GPIOState};
+
 use super::Hardware;
-use crate::gpio::{GPIOState, GPIOConfig};
 
 pub struct NoneHW;
 
@@ -10,13 +13,14 @@ pub fn get() -> impl Hardware {
 }
 
 impl Hardware for NoneHW {
-    fn apply_config(&mut self, _config: &GPIOConfig) {
+    fn apply_config(&mut self, _config: &GPIOConfig) -> io::Result<()> {
         println!("GPIO Config has been applied to fake hardware");
+        Ok(())
     }
 
     fn get_state(&self) -> GPIOState {
         GPIOState {
-            pin_state: [None; 40]
+            pin_state: [None; 40],
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/hw/pi.rs b/src/hw/pi.rs
index c53b4d5d..c4be28f5 100644
--- a/src/hw/pi.rs
+++ b/src/hw/pi.rs
@@ -1,3 +1,5 @@
+use std::io;
+
 /// Implementation of GPIO for raspberry pi - uses rrpal
 #[allow(unused_imports)] // just checking builds work for now...
 use rppal::gpio::{InputPin, Level, Trigger};
@@ -5,7 +7,7 @@ use rppal::gpio::Gpio;
 use rppal::gpio::OutputPin;
 
 use crate::gpio::{GPIOConfig, GPIOState};
-use crate::gpio::PinFunction;
+use crate::gpio::{InputPull, PinFunction};
 
 use super::Hardware;
 
@@ -18,6 +20,7 @@ enum Pin {
 
 // Tuples of pin board number and Pin
 pub struct PiHW {
+    // TODO not sure if this is useful/needed, if we can read the config from the pins via rppal
     configured_pins: Vec<(u8, Pin)>,
 }
 
@@ -32,24 +35,35 @@ pub fn get() -> impl Hardware {
 impl Hardware for PiHW {
     /// This takes the "virtual" configuration of GPIO from a GPIOConfig struct and uses rppal to
     /// configure the Pi GPIO hardware to correspond to it
-    // TODO maybe change trait to allow errors
-
-    // TODO FIXME looks like get() uses BCM pin numbering...
 
-    fn apply_config(&mut self, config: &GPIOConfig) {
-        for (pin_number, pin_config) in &config.configured_pins {
+    fn apply_config(&mut self, config: &GPIOConfig) -> io::Result<()> {
+        for (bcm_pin_number, pin_config) in &config.configured_pins {
             match pin_config {
-                PinFunction::Input => {
-                    // TODO handle pull-up/down options
-                    let input = Gpio::new().unwrap().get(*pin_number).unwrap().into_input();
-                    self.configured_pins.push((*pin_number, Pin::Input(input)))
+                PinFunction::Input(pull) => {
+                    let pin = Gpio::new()
+                        .unwrap()
+                        .get(*bcm_pin_number)
+                        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+                    let input = match pull {
+                        None => pin.into_input(),
+                        Some(InputPull::PullUp) => pin.into_input_pullup(),
+                        Some(InputPull::PullDown) => pin.into_input_pulldown(),
+                    };
+                    self.configured_pins
+                        .push((*bcm_pin_number, Pin::Input(input)))
                 }
-                PinFunction::Output => {
-                    // TODO check if there are any options on Output pins
-                    // TODO consider config being able to "save" the output value to set?
-                    let output = Gpio::new().unwrap().get(*pin_number).unwrap().into_output();
+                PinFunction::Output(value) => {
+                    let pin = Gpio::new()
+                        .unwrap()
+                        .get(*bcm_pin_number)
+                        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+                    let output = match value {
+                        None => pin.into_output(),
+                        Some(true) => pin.into_output_high(),
+                        Some(false) => pin.into_output_low(),
+                    };
                     self.configured_pins
-                        .push((*pin_number, Pin::Output(output)))
+                        .push((*bcm_pin_number, Pin::Output(output)))
                 }
                 // TODO implement all of these
                 PinFunction::SDA1 => {}
@@ -70,14 +84,19 @@ impl Hardware for PiHW {
                 PinFunction::SPIO_CE1_N => {}
                 PinFunction::ID_SC => {}
                 PinFunction::Ground | PinFunction::Power3V3 | PinFunction::Power5V => {
-                    eprintln!("Ground, 3V3 or 5V pins cannot be configured");
+                    return Err(io::Error::new(
+                        io::ErrorKind::Other,
+                        "Ground, 3V3 or 5V pins cannot be configured",
+                    ));
                 }
             }
         }
+
         println!("GPIO Config has been applied to Pi hardware");
+        Ok(())
     }
 
-    /// Return the state of the Input pins and other pins who's state is read from GPIO hardware
+    /// Return the state of the Input pins and other pins whose state is read from GPIO hardware
     // TODO might deprecate this in favor of some sort of message or callback when an input changes
     // its value, to trigger a UI update...
     // messages will need to be able to capture other types of input, Image (SPIO), value from ADC
diff --git a/src/hw/pico.rs b/src/hw/pico.rs
index d26928cf..bfa24997 100644
--- a/src/hw/pico.rs
+++ b/src/hw/pico.rs
@@ -1,9 +1,12 @@
+use std::io;
+
 /// Implementation of GPIO for pi pico targets
 #[cfg(feature = "rppal")]
 use rppal::gpio::{InputPin, Level, Trigger};
 
+use crate::gpio::{GPIOConfig, GPIOState};
+
 use super::Hardware;
-use crate::gpio::{GPIOState, GPIOConfig};
 
 pub struct PicoHW;
 
@@ -12,13 +15,14 @@ pub fn get() -> impl Hardware {
 }
 
 impl Hardware for PicoHW {
-    fn apply_config(&mut self, _config: &GPIOConfig) {
+    fn apply_config(&mut self, _config: &GPIOConfig) -> io::Result<()> {
         println!("GPIO Config has been applied to Pico hardware");
+        Ok(())
     }
 
     fn get_state(&self) -> GPIOState {
         GPIOState {
-            pin_state: [None; 40]
+            pin_state: [None; 40],
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/piggui.rs b/src/piggui.rs
index 78f81160..d7c32c47 100644
--- a/src/piggui.rs
+++ b/src/piggui.rs
@@ -9,6 +9,7 @@ use custom_widgets::{circle::circle, line::line};
 // This binary will only be built with the "iced" feature enabled, by use of "required-features"
 // in Cargo.toml so no need for the feature to be used here for conditional compiling
 use crate::gpio::{GPIO_DESCRIPTION, GPIOConfig, PinDescription, PinFunction};
+use crate::hw::Hardware;
 
 mod gpio;
 mod hw;
@@ -27,10 +28,6 @@ fn main() -> Result<(), iced::Error> {
         ..Default::default()
     };
 
-    // Will need an "Apply" button in the UI to apply config changes to the HW, or apply on each change
-    //let mut hw = hw::get();
-    //hw.apply_config(&config);
-
     Gpio::run(Settings {
         window,
         ..Default::default()
@@ -74,6 +71,10 @@ impl Sandbox for Gpio {
         let (config_filename, gpio_config) =
             Self::get_config(env::args().nth(1)).unwrap_or((None, GPIOConfig::default()));
 
+        // Will need an "Apply" button in the UI to apply config changes to the HW, or apply on each change
+        let mut hw = hw::get();
+        hw.apply_config(&gpio_config).unwrap(); // TODO handle error
+
         let num_pins = GPIO_DESCRIPTION.len();
         let pin_function_selected = vec![None; num_pins];
 
diff --git a/src/piglet.rs b/src/piglet.rs
index e77b7b55..853ffe1f 100644
--- a/src/piglet.rs
+++ b/src/piglet.rs
@@ -1,10 +1,11 @@
-mod gpio;
-mod hw;
+use hw::Hardware;
 
 // Use Hardware via trait
-use hw::Hardware;
 use crate::gpio::GPIO_DESCRIPTION;
 
+mod gpio;
+mod hw;
+
 fn main() {
     println!("Description: {:?}", GPIO_DESCRIPTION);
 
@@ -13,6 +14,6 @@ fn main() {
     println!("Pin configs: {:?}", config);
 
     let mut hw = hw::get();
-    hw.apply_config(&config);
+    let _ = hw.apply_config(&config); // TODO handle error
     println!("Oink: {:?}", hw.get_state());
-}
\ No newline at end of file
+}


Test patch:
diff --git a/tests/one_pin_config.piggui b/tests/one_pin_config.piggui
index 1e1150fc..bf7e2498 100644
--- a/tests/one_pin_config.piggui
+++ b/tests/one_pin_config.piggui
@@ -1,3 +1,3 @@
 {
-    "configured_pins":[[1,"Input"]]
+    "configured_pins":[[17,{"Output":true}]]
 }