//
// Space API HTTP Button ("dumbswitch") Main Entrypoint
// Copyright (C) 2025 memdmp
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License along with this program. If not, see .
//
//! Production ESP is built with `cargo r --bin dumbswitch-as-server --features led-as-busy-led -r`
//! Debug ESP is built with `ESP_LOG=debug cargo r --bin dumbswitch-as-server --features led-as-busy-led`
//! To get logs, use while true; do if ls /dev/ | grep ttyACM >/dev/null 2>/dev/null; then echo 'Found Device'; cat /dev/ttyACM*; else sleep 0.001; fi; done
//! To change networks, copy ../network_data.example.rs to ../network_data.rs and change it's contents
//% FEATURES: esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6
#![no_std]
#![no_main]
use core::cell::{OnceCell, RefCell};
use core::ops::Deref;
use core::str::FromStr;
use blocking_network_stack::Stack;
use critical_section::Mutex;
use dumbswitch::{network_data, network_type::Network};
use embedded_io::*;
use esp_alloc as _;
use esp_backtrace as _;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Event, Input, Io, Level, Output, Pull};
use esp_hal::handler;
use esp_hal::interrupt::InterruptConfigurable;
use esp_hal::{
clock::CpuClock,
main,
rng::Rng,
time::{self, Duration},
timer::timg::TimerGroup,
};
use esp_println::println;
use esp_wifi::wifi::ScanConfig;
use esp_wifi::{
init,
wifi::{
utils::create_network_interface, AccessPointInfo, ClientConfiguration, Configuration,
WifiError, WifiStaDevice,
},
};
use log::{debug, error, info, trace, warn};
use smoltcp::iface::{SocketSet, SocketStorage};
pub enum OurOption {
/// No value.
None,
/// Some value of type `T`.
Some(T),
}
static BUTTON: Mutex>> = Mutex::new(RefCell::new(None));
static mut LED: OurOption>> = OurOption::None;
fn set_open_led_state(val: bool) {
#[cfg(not(feature = "led-as-busy-led"))]
{
let v = unsafe {
#[allow(static_mut_refs)]
&LED
};
let v = match v {
OurOption::None => {
return;
}
OurOption::Some(v) => v,
};
let mut b = v.borrow_mut();
b.set_level(if val { Level::High } else { Level::Low });
}
}
// TODO: in future, add another LED for busy
fn set_busy_led_state(val: bool) {
#[cfg(feature = "led-as-busy-led")]
{
let v = unsafe {
#[allow(static_mut_refs)]
&LED
};
let v = match v {
OurOption::None => {
return;
}
OurOption::Some(v) => v,
};
let mut b = v.borrow_mut();
b.set_level(if val { Level::High } else { Level::Low });
}
}
#[main]
fn main() -> ! {
esp_println::logger::init_logger_from_env();
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
let delay = Delay::new();
esp_alloc::heap_allocator!(72 * 1024);
info!("Preparing GPIO");
let mut io = Io::new(peripherals.IO_MUX);
// Set GPIO0 as an output, and set its state low initially.
let led = RefCell::new(Output::new(peripherals.GPIO0, Level::Low));
let led = unsafe {
LED = OurOption::Some(led);
#[allow(static_mut_refs)]
match &LED {
OurOption::None => panic!(),
OurOption::Some(v) => v,
}
};
// Set GPIO5 as an input
let mut button = Input::new(peripherals.GPIO5, Pull::Down);
info!("Waiting 500ms pre-init");
delay.delay_millis(500);
led.borrow_mut().set_low();
let timg0 = TimerGroup::new(peripherals.TIMG0);
let mut rng = Rng::new(peripherals.RNG);
let init = init(timg0.timer0, rng.clone(), peripherals.RADIO_CLK).unwrap();
let mut wifi = peripherals.WIFI;
let (iface, device, mut controller) =
create_network_interface(&init, &mut wifi, WifiStaDevice).unwrap();
let mut socket_set_entries: [SocketStorage; 3] = Default::default();
let socket_set = SocketSet::new(&mut socket_set_entries[..]);
let now = || time::now().duration_since_epoch().to_millis();
let mut stack = Stack::new(iface, device, socket_set, now, rng.random());
let client_config = Configuration::Client(ClientConfiguration {
..Default::default()
});
let res = controller.set_configuration(&client_config);
debug!("wifi_set_configuration returned {:?}", res);
controller.start().unwrap();
debug!("is wifi started: {:?}", controller.is_started());
info!("Start Wifi Scan");
let mut net: Option = None;
let mut attempts = 0;
while (net.is_none() && attempts < 16) {
attempts += 1;
let res: Result<(heapless::Vec, usize), WifiError> = controller
.scan_with_config_sync(ScanConfig {
show_hidden: true,
..Default::default()
});
if let Ok((res, _count)) = res {
for ap in res {
info!("Saw AP: {:?}", ap);
for ap_comp in network_data::NETWORKS {
if ap_comp.ssid == ap.ssid.clone() {
let client_config = Configuration::Client(ClientConfiguration {
ssid: ap_comp.ssid.try_into().unwrap(),
password: ap_comp.password.try_into().unwrap(),
..Default::default()
});
controller.set_configuration(&client_config);
net = Some(ap_comp);
};
}
}
}
}
if net.is_none() {
loop {
error!("Failed to connect to wifi: No saved network found.");
let mut high = false;
loop {
delay.delay_millis(1000);
high = !high;
if high {
led.borrow_mut().set_high();
} else {
led.borrow_mut().set_low();
}
}
}
}
debug!("{:?}", controller.capabilities());
let connres = controller.connect();
debug!("wifi_connect {:?}", connres);
if !connres.is_ok() {
error!("Failed to connect to wifi! {:#?}", connres.unwrap_err());
let mut high = false;
loop {
delay.delay_millis(2000);
high = !high;
if high {
led.borrow_mut().set_high();
} else {
led.borrow_mut().set_low();
}
}
}
let net = net.unwrap();
// wait to get connected
info!("Wait to get connected");
loop {
match controller.is_connected() {
Ok(true) => break,
Ok(false) => {
set_busy_led_state(true);
delay.delay_millis(10);
set_busy_led_state(false);
delay.delay_millis(500);
}
Err(err) => {
error!("Failed to connect to wifi: {:?}", err);
let mut high = false;
loop {
delay.delay_millis(1000);
high = !high;
if high {
led.borrow_mut().set_high();
} else {
led.borrow_mut().set_low();
}
}
}
}
}
debug!("{:?}", controller.is_connected());
info!("Setting static IP {}", net.static_ip);
stack
.set_iface_configuration(&blocking_network_stack::ipv4::Configuration::Client(
blocking_network_stack::ipv4::ClientConfiguration::Fixed(
blocking_network_stack::ipv4::ClientSettings {
ip: blocking_network_stack::ipv4::Ipv4Addr::from(parse_ip(net.static_ip)),
subnet: blocking_network_stack::ipv4::Subnet {
gateway: blocking_network_stack::ipv4::Ipv4Addr::from(parse_ip(net.gateway_ip)),
mask: blocking_network_stack::ipv4::Mask(24),
},
dns: None,
secondary_dns: None,
},
),
))
.unwrap();
for i in 0..4 {
delay.delay_millis(100);
if i % 2 == 0 {
led.borrow_mut().set_high();
} else {
led.borrow_mut().set_low();
}
}
info!(
"Start busy loop on main. Point your browser to http://{}:8080/",
net.static_ip
);
let mut rx_buffer = [0u8; 1536];
let mut tx_buffer = [0u8; 1536];
let mut socket = stack.get_socket(&mut rx_buffer, &mut tx_buffer);
socket.listen(8080).unwrap();
// fn update_open(is_open: bool, button: &Input<'_>, led: &mut Output<'_>, delay: &Delay) -> bool {
// let mut is_open = if is_open { true } else { false };
// if button.is_low() {
// is_open = !is_open;
// if is_open {
// led.set_high();
// } else {
// led.set_low();
// }
// while button.is_low() {
// trace!("waiting for unpress btn");
// delay.delay_millis(10);
// }
// }
// is_open
// }
loop {
socket.work();
if !socket.is_open() {
socket.listen(8080).unwrap();
}
set_busy_led_state(true);
if socket.is_connected() {
let is_open = button.is_high();
unsafe {
set_open_led_state(is_open);
}
let r = socket.write_all(
// 0b01100001 - true (open)
(if is_open {
b"HTTP/1.0 200 OK\r\n\
\r\n\
a"
} else {
// 0b01111010 - false (closed)
b"HTTP/1.0 200 OK\r\n\
\r\n\
z"
}),
);
if !r.is_ok() {
error!("{:#?}", r.unwrap_err());
continue;
};
let r = socket.flush();
if !r.is_ok() {
error!("{:#?}", r.unwrap_err());
continue;
};
socket.work();
// }
socket.close();
}
set_busy_led_state(false);
// TODO: what
let deadline = time::now() + Duration::millis(100);
while time::now() < deadline && !socket.is_connected() {
socket.work();
}
let is_open = button.is_high();
set_open_led_state(is_open);
}
}
fn parse_ip(ip: &str) -> [u8; 4] {
let mut result = [0u8; 4];
for (idx, octet) in ip.split(".").into_iter().enumerate() {
result[idx] = u8::from_str_radix(octet, 10).unwrap();
}
result
}