-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhal_c6_connect_async_led.rs
More file actions
199 lines (175 loc) · 6.92 KB
/
Copy pathhal_c6_connect_async_led.rs
File metadata and controls
199 lines (175 loc) · 6.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! Async Wi-Fi connect with WS2812 RGB LED feedback for ESP32-C6.
//!
//! Spawns a `led_task` that pulses the onboard WS2812 LED (GPIO8) blue
//! via [`PulseEffect`] while Wi-Fi is connecting, then holds a steady
//! dim green once an IP address is acquired.
//!
//! LED phases:
//!
//! * **Connecting** — blue pulse (~1.4 s cycle via `PulseEffect`)
//! * **Connected** — steady dim green (0, 20, 0)
//! * **Disconnected** — resumes blue pulse automatically
//!
//! `WIFI_SSID` and `WIFI_PASS` must be set as environment variables **at build
//! time**. Requires the `embassy` and `rustyfarian-esp-hal-ws2812` features.
//!
//! # Build and flash
//!
//! ```sh
//! WIFI_SSID="MyNetwork" WIFI_PASS="secret" just build-example hal_c6_connect_async_led
//! just flash hal_c6_connect_async_led
//! ```
#![no_std]
#![no_main]
extern crate alloc;
use core::sync::atomic::{AtomicBool, Ordering};
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::gpio::Level;
use esp_hal::rmt::{Rmt, TxChannelConfig, TxChannelCreator};
use esp_hal::time::Rate;
use esp_hal::Blocking;
use esp_println::println;
use esp_radio::wifi::{Interface, WifiController};
use pennant::{PulseEffect, StatusLed};
use rgb::RGB8;
use rustyfarian_esp_hal_wifi::{AsyncWifiHandle, WiFiConfig, WiFiConfigExt, WiFiManager};
use rustyfarian_esp_hal_ws2812::{buffer_size, Ws2812Rmt, RMT_CLK_DIV};
esp_bootloader_esp_idf::esp_app_desc!();
const SSID: &str = match option_env!("WIFI_SSID") {
Some(s) => s,
None => "",
};
const PASSWORD: &str = match option_env!("WIFI_PASS") {
Some(s) => s,
None => "",
};
const NUM_LEDS: usize = 1;
const N: usize = buffer_size(NUM_LEDS);
/// Shared flag: `false` = no IPv4 config (LED pulses blue), `true` = config up (LED steady green).
/// Owned by `link_status_task`, which watches `embassy_net::Stack::wait_config_up`
/// and `wait_config_down` so the LED tracks the current DHCP state — including
/// after the first disconnect/reconnect cycle.
static CONNECTED: AtomicBool = AtomicBool::new(false);
#[esp_rtos::main]
async fn main(spawner: Spawner) {
esp_println::logger::init_logger(log::LevelFilter::Info);
let peripherals = esp_hal::init(esp_hal::Config::default());
// ESP32-C6 requires two heap regions: reclaimed IRAM for Wi-Fi DMA
// buffers, and regular DRAM for general allocations.
// (ESP32-C3 has contiguous SRAM and uses a single 72 KiB region instead.)
esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 64 * 1024);
esp_alloc::heap_allocator!(size: 36 * 1024);
// Set up the onboard WS2812 RGB LED on GPIO8 via RMT.
let rmt = Rmt::new(peripherals.RMT, Rate::from_mhz(80)).unwrap();
let rmt_config = TxChannelConfig::default()
.with_clk_divider(RMT_CLK_DIV)
.with_idle_output_level(Level::Low)
.with_idle_output(true)
.with_carrier_modulation(false);
// esp-hal 1.1.0 split RMT TX setup: `configure_tx(pin, config)` is gone —
// the new pattern is `configure_tx(&config).unwrap().with_pin(pin)`.
let channel = rmt
.channel0
.configure_tx(&rmt_config)
.unwrap()
.with_pin(peripherals.GPIO8);
let led = Ws2812Rmt::<Blocking, N>::new(channel);
println!("LED ready");
// WiFiManager::init_async() is synchronous (heap + RTOS + radio init).
// In embassy's cooperative model the LED task cannot run until init
// returns and main hits its first .await. Radio init is fast (~100 ms)
// once the heap is correctly configured with reclaimed IRAM.
println!("Initializing Wi-Fi...");
let config = WiFiConfig::new(SSID, PASSWORD).with_peripherals(
peripherals.TIMG0,
peripherals.SW_INTERRUPT,
peripherals.WIFI,
);
let handle = match WiFiManager::init_async(config) {
Ok(h) => h,
Err(e) => {
println!("FATAL: Wi-Fi init failed: {}", e);
loop {}
}
};
println!("Wi-Fi radio ready");
let AsyncWifiHandle {
controller,
stack,
runner,
} = handle;
spawner.spawn(wifi_task(controller).unwrap());
spawner.spawn(net_task(runner).unwrap());
spawner.spawn(led_task(led).unwrap());
spawner.spawn(link_status_task(stack).unwrap());
println!("Waiting for DHCPv4 lease (LED pulsing blue)...");
stack.wait_config_up().await;
let v4 = stack
.config_v4()
.expect("stack reports config up but has no IPv4 config");
println!(
"Wi-Fi connected — IP: {} gateway: {:?}",
v4.address, v4.gateway
);
loop {
Timer::after(Duration::from_secs(10)).await;
}
}
/// Owns the `CONNECTED` flag. Toggling it on association events alone is not
/// enough — a brief disconnect/reconnect race can leave the controller saying
/// "connected" while the embassy-net stack is still waiting for a new DHCP
/// lease, so the user-visible signal is whether the IP-layer config is up.
/// Watches both edges (`wait_config_up` and `wait_config_down`) and updates
/// the flag every time DHCP comes back up after a drop, not just on first boot.
#[embassy_executor::task]
async fn link_status_task(stack: embassy_net::Stack<'static>) {
loop {
stack.wait_config_up().await;
CONNECTED.store(true, Ordering::Relaxed);
stack.wait_config_down().await;
CONNECTED.store(false, Ordering::Relaxed);
}
}
/// Pulses the WS2812 LED blue while connecting; holds dim green once connected.
///
/// Uses [`PulseEffect`] for smooth brightness animation at ~20 fps.
/// When `CONNECTED` transitions back to `false` (e.g. after disconnect),
/// the blue pulse resumes automatically.
#[embassy_executor::task]
async fn led_task(mut led: Ws2812Rmt<'static, Blocking, N>) {
let mut pulse = PulseEffect::new();
loop {
if CONNECTED.load(Ordering::Relaxed) {
let _ = led.set_color(RGB8::new(0, 20, 0));
Timer::after(Duration::from_millis(100)).await;
} else {
let _ = led.set_color(pulse.update((0, 0, 255)));
Timer::after(Duration::from_millis(50)).await;
}
}
}
// Handles both the initial association and subsequent reconnects.
// The LED `CONNECTED` flag is owned by `link_status_task` watching
// `embassy_net::Stack` config-up/config-down edges — not toggled here,
// because a successful L2 reconnect does not yet imply a new DHCP lease.
#[embassy_executor::task]
async fn wifi_task(mut controller: WifiController<'static>) {
loop {
match controller.connect_async().await {
Ok(_) => {
let _ = controller.wait_for_disconnect_async().await;
println!("Wi-Fi disconnected — reconnecting...");
}
Err(e) => {
println!("connect failed: {:?}", e);
}
}
Timer::after(Duration::from_millis(500)).await;
}
}
#[embassy_executor::task]
async fn net_task(mut runner: embassy_net::Runner<'static, Interface<'static>>) -> ! {
runner.run().await
}