Skip to content

Commit

Permalink
Merge pull request #1 from etiennecollin/switch-feature
Browse files Browse the repository at this point in the history
Added switch feature
  • Loading branch information
etiennecollin authored Sep 23, 2024
2 parents 1617bbd + a29468c commit 960fae6
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ log = [
"dep:log",
"embassy-executor/log",
"embassy-futures/log",
"embassy-sync/log",
"esp-backtrace/println",
"esp-hal/log",
"esp-println/log",
Expand All @@ -28,6 +29,7 @@ defmt = [
"embassy-executor/defmt",
"embassy-futures/defmt",
"embassy-net/defmt",
"embassy-sync/defmt",
"esp-backtrace/defmt",
"esp-hal/defmt",
"esp-println/defmt-espflash",
Expand All @@ -41,6 +43,7 @@ defmt = { version = "0.3.8", optional = true }
embassy-executor = { version = "0.6.0", features=["nightly"] }
embassy-futures = "0.1.1"
embassy-net = { version = "0.4.0", features = ["tcp", "udp", "dns", "dhcpv4", "dhcpv4-hostname", "medium-ethernet"] }
embassy-sync = "0.6.0"
embassy-time = { version = "0.3.1", features=["generic-queue-8"] }
esp-backtrace = { version = "0.14.1", features = ["panic-handler", "exception-handler"] }
esp-hal = { version = "0.20.1" }
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ Set the following environment variables. These variables are used to configure t
- `WOL_ENABLE`: A flag to enable or disable the WOL feature of the HTTP server. Set to "true" or "1" to enable.
- `WOL_BROADCAST_ADDR`: The broadcast address to send Wake-on-LAN packets to. Typically set to "255.255.255.255" to broadcast to all devices on the local network.

**Switch Configuration**

- `SWITCH_ENABLE`: A flag to enable or disable the Switch feature of the HTTP server. This uses the ESP32 as a power switch. Set to "true" or "1" to enable.
- **Make sure to properly configure the GPIO pins as Pull Up or Pull Down in the `./src/main.rs` file depending on which device you want to switch ON and OFF.**
- Computer power switches often need a Pull Up configuration.
- **Make sure the pins on which you connect the Wakesp GPIO pins have a voltage of 3.3V. Use a level shifter if needed.**
- **Make sure the Wakesp and the devices it is connected to share the same ground.**
- **If you do not follow these last 3 points, the Wakesp and/or the devices it is connected to could be permanently damaged.**

Here is an example of setting these variables:

```bash
Expand All @@ -87,6 +96,9 @@ export HTTP_LISTEN_PORT="80"
# For WOL
export WOL_ENABLE="true"
export WOL_BROADCAST_ADDR="255.255.255.255"

# For Switch
export SWITCH_ENABLE="true"
```

Now, make sure that your current working directory (output of `pwd` command) is the root of the cloned repository.
Expand Down Expand Up @@ -130,6 +142,9 @@ HTTP_LISTEN_PORT="80"
WOL_ENABLE="true"
WOL_BROADCAST_ADDR="255.255.255.255"

# For Switch
SWITCH_ENABLE="true"

# ...
```

Expand Down
16 changes: 16 additions & 0 deletions src/http_server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod html_responses;
mod switch_utils;
mod wol_utils;

use crate::utils::{abort_connection, wait_for_connection, write_tcp_buf};
Expand All @@ -9,6 +10,7 @@ use esp_backtrace as _;
use esp_wifi::wifi::{WifiDevice, WifiStaDevice};
use heapless::FnvIndexMap;
use html_responses::{HTML_HEADER, HTML_MENU, HTML_TAIL};
use switch_utils::switch_command;
use wol_utils::wol_command;

/// The HTTP headers for the response.
Expand All @@ -23,6 +25,8 @@ const HTTP_LISTEN_PORT_FALLBACK: u16 = 8080;
const TCP_BUFFER_SIZE: usize = 4096;
/// The enable flag for the WOL feature.
const WOL_ENABLE: &str = env!("WOL_ENABLE");
/// The enable flag for the Switch feature.
const SWITCH_ENABLE: &str = env!("SWITCH_ENABLE");

/// The embassy task that handles the HTTP server.
#[embassy_executor::task]
Expand Down Expand Up @@ -151,6 +155,18 @@ async fn handle_http_query(
None => Ok(html_responses::WOL_INPUT),
}
}
"/switch" => {
if SWITCH_ENABLE != "true" && SWITCH_ENABLE != "1" {
return Ok(html_responses::NOT_ENABLED);
}
match args.get("gpio") {
Some(v) => {
switch_command(v).await?;
Ok(html_responses::SWITCH_SUCCESS)
}
None => Ok(html_responses::SWITCH_SELECT),
}
}
_ => Ok(html_responses::HOME),
}
}
Expand Down
55 changes: 52 additions & 3 deletions src/http_server/html_responses.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
pub const HOME: &[u8] = b"";

pub const SWITCH_SUCCESS: &[u8] = b"\
<h1>Switch</h1>
<p>Switch activated!</p>";

pub const SWITCH_SELECT: &[u8] = b"\
<h1>Switch</h1>
<p>Select the pin to use as a power switch</p>
<form method=\"get\">
<div>
<label for=\"gpio2\">GPIO 2</label>
<input type=\"radio\" id=\"gpio2\" name=\"gpio\" value=\"2\" />
<br />
<label for=\"gpio3\">GPIO 3</label>
<input type=\"radio\" id=\"gpio3\" name=\"gpio\" value=\"3\" />
<br />
<label for=\"gpio4\">GPIO 4</label>
<input type=\"radio\" id=\"gpio4\" name=\"gpio\" value=\"4\" />
<br />
<label for=\"gpio5\">GPIO 5</label>
<input type=\"radio\" id=\"gpio5\" name=\"gpio\" value=\"5\" />
<br />
<label for=\"gpio6\">GPIO 6</label>
<input type=\"radio\" id=\"gpio6\" name=\"gpio\" value=\"6\" />
<br />
<label for=\"gpio7\">GPIO 7</label>
<input type=\"radio\" id=\"gpio7\" name=\"gpio\" value=\"7\" />
<br />
<label for=\"gpio8\">GPIO 8</label>
<input type=\"radio\" id=\"gpio8\" name=\"gpio\" value=\"8\" />
<br />
<label for=\"gpio9\">GPIO 9</label>
<input type=\"radio\" id=\"gpio9\" name=\"gpio\" value=\"9\" />
</div>
<input type=\"submit\" value=\"Submit\" />
</form>";

pub const WOL_INPUT: &[u8] = b"\
<h1>WOL</h1>
<p>Insert the MAC address of the device to wake</p>
Expand Down Expand Up @@ -32,6 +68,11 @@ pub const HTML_MENU: &[u8] = b"\
><i class=\"fas fa-arrow-alt-right\"></i>WOL</a
>
</li>
<li>
<a class=\"arrow\" href=\"/switch\"
><i class=\"fas fa-arrow-alt-right\"></i>Switch</a
>
</li>
</ol>\r\n";

pub const HTML_HEADER: &[u8] = b"\
Expand Down Expand Up @@ -65,7 +106,7 @@ pub const HTML_HEADER: &[u8] = b"\
justify-content: center;
align-items: center;
text-align: center;
height: 100%;
height: 120%;
background-color: #221;
}
Expand Down Expand Up @@ -101,6 +142,11 @@ pub const HTML_HEADER: &[u8] = b"\
color: #fff090;
}
input[type=\"radio\"] {
width: 1.5em;
height: 1.5em;
}
form {
display: flex;
flex-direction: column;
Expand All @@ -125,7 +171,9 @@ pub const HTML_HEADER: &[u8] = b"\
}
.arrow:hover,
input[type=\"submit\"]:hover {
label:hover,
input[type=\"submit\"]:hover,
input[type=\"radio\"]:hover {
cursor: pointer;
background-color: transparent;
}
Expand All @@ -137,6 +185,7 @@ pub const HTML_HEADER: &[u8] = b"\
left: -6em;
}
</style>
</head>\r\n";
</head>
<body>\r\n";

pub const HTML_TAIL: &[u8] = b"\r\n</body>\r\n</html>\r\n";
98 changes: 98 additions & 0 deletions src/http_server/switch_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::pins::*;
use core::cell::RefCell;
use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex};
use embassy_time::{Duration, Timer};
use esp_hal::gpio::{InputPin, Level, OutputOpenDrain, OutputPin};

/// Triggers a GPIO pin based on the provided pin number.
pub async fn switch_command(pin_str: &str) -> Result<(), ()> {
// Parse the pin number as a u8
let pin = match pin_str.parse::<u8>() {
Ok(v) => v,
Err(_) => {
log::error!("Switch | Error parsing pin number");
return Err(());
}
};

// Toggle the pin based on the number
let result = match pin {
2 => toggle_pin(&GPIO2, false).await,
3 => toggle_pin(&GPIO3, false).await,
4 => toggle_pin(&GPIO4, false).await,
5 => toggle_pin(&GPIO5, false).await,
6 => toggle_pin(&GPIO6, false).await,
7 => toggle_pin(&GPIO7, false).await,
8 => toggle_pin(&GPIO8, false).await,
9 => toggle_pin(&GPIO9, false).await,
_ => {
log::warn!("Switch | Invalid pin number");
return Err(());
}
};

// Check if the pin was toggled successfully
if result.is_err() {
log::error!("Switch | Error toggling pin GPIO{}", pin_str);
return Err(());
}

log::info!("SWITCH | Triggered pin GPIO{}", pin_str);
Ok(())
}

/// Toggle a GPIO pin behind a Mutex and a RefCell.
/// This function will toggle the pin for 500ms, then toggle it back.
/// The parameter `toggle_high` determines how the pin will be toggled:
/// - `true`: High -> 500ms -> Low
/// - `false`: Low -> 500ms -> High
pub async fn toggle_pin<T>(
gpio: &Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, T>>>>,
toggle_high: bool,
) -> Result<(), ()>
where
T: InputPin + OutputPin,
{
let level_0;
let level_1;

if toggle_high {
level_0 = Level::High;
level_1 = Level::Low;
} else {
level_0 = Level::Low;
level_1 = Level::High;
}

set_pin(gpio, level_0)?;
Timer::after(Duration::from_millis(500)).await;
set_pin(gpio, level_1)?;

Ok(())
}

/// Sets a GPIO pin behind a Mutex and a RefCell to the provided level.
pub fn set_pin<T>(
gpio: &Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, T>>>>,
level: Level,
) -> Result<(), ()>
where
T: InputPin + OutputPin,
{
let mut triggered = false;

gpio.lock(|pin_locked| {
if let Ok(mut pin_option) = pin_locked.try_borrow_mut() {
if let Some(pin) = pin_option.as_mut() {
pin.set_level(level);
triggered = true;
}
}
});

if !triggered {
return Err(());
}

Ok(())
}
1 change: 0 additions & 1 deletion src/http_server/wol_utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::utils::{convert_mac_address, parse_ip_address};

use embassy_net::{
udp::{PacketMetadata, UdpSocket},
IpAddress, IpEndpoint, Stack,
Expand Down
22 changes: 22 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

mod dns;
mod http_server;
mod pins;
mod utils;

use core::str::FromStr;
Expand All @@ -14,6 +15,7 @@ use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
gpio::{Io, Level, OutputOpenDrain, Pull},
peripherals::Peripherals,
prelude::*,
riscv::singleton,
Expand All @@ -34,6 +36,7 @@ use esp_wifi::{
EspWifiInitFor,
};
use http_server::http_server_task;
use pins::*;

/// The hostname of the device.
const HOSTNAME: &str = env!("HOSTNAME");
Expand All @@ -58,10 +61,29 @@ async fn main(spawner: Spawner) {

// Initialize the peripherals
let peripherals = Peripherals::take();
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let system = SystemControl::new(peripherals.SYSTEM);
let clocks = ClockControl::max(system.clock_control).freeze();
let mut rng = Rng::new(peripherals.RNG);

// Initialize GPIO pins
let gpio2 = OutputOpenDrain::new(io.pins.gpio2, Level::High, Pull::Up);
let gpio3 = OutputOpenDrain::new(io.pins.gpio3, Level::High, Pull::Up);
let gpio4 = OutputOpenDrain::new(io.pins.gpio4, Level::High, Pull::Up);
let gpio5 = OutputOpenDrain::new(io.pins.gpio5, Level::High, Pull::Up);
let gpio6 = OutputOpenDrain::new(io.pins.gpio6, Level::High, Pull::Up);
let gpio7 = OutputOpenDrain::new(io.pins.gpio7, Level::High, Pull::Up);
let gpio8 = OutputOpenDrain::new(io.pins.gpio8, Level::High, Pull::Up);
let gpio9 = OutputOpenDrain::new(io.pins.gpio9, Level::High, Pull::Up);
GPIO2.lock(|x| x.borrow_mut().replace(gpio2));
GPIO3.lock(|x| x.borrow_mut().replace(gpio3));
GPIO4.lock(|x| x.borrow_mut().replace(gpio4));
GPIO5.lock(|x| x.borrow_mut().replace(gpio5));
GPIO6.lock(|x| x.borrow_mut().replace(gpio6));
GPIO7.lock(|x| x.borrow_mut().replace(gpio7));
GPIO8.lock(|x| x.borrow_mut().replace(gpio8));
GPIO9.lock(|x| x.borrow_mut().replace(gpio9));

// Generate a seed for the wifi stack
let mut seed_buf = [0u8; 8];
rng.read(&mut seed_buf);
Expand Down
20 changes: 20 additions & 0 deletions src/pins.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use core::cell::RefCell;
use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, CriticalSectionMutex, Mutex};
use esp_hal::gpio::{GpioPin, OutputOpenDrain};

pub static GPIO2: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<2>>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub static GPIO3: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<3>>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub static GPIO4: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<4>>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub static GPIO5: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<5>>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub static GPIO6: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<6>>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub static GPIO7: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<7>>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub static GPIO8: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<8>>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub static GPIO9: Mutex<CriticalSectionRawMutex, RefCell<Option<OutputOpenDrain<'_, GpioPin<9>>>>> =
CriticalSectionMutex::new(RefCell::new(None));

0 comments on commit 960fae6

Please sign in to comment.