diff --git a/.licenserc.yaml b/.licenserc.yaml index 2fbbafe..41ed9bc 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -61,6 +61,7 @@ header: # `header` section is configurations for source codes license header. - "**/*.lds" - "**/lib64" - ".github/**" + - "app.conf" comment: on-failure # on what condition license-eye will comment on the pull request, `on-failure`, `always`, `never`. # license-location-threshold specifies the index threshold where the license header can be located, # after all, a "header" cannot be TOO far from the file start. diff --git a/framebuffer/BUILD.gn b/framebuffer/BUILD.gn new file mode 100644 index 0000000..2389472 --- /dev/null +++ b/framebuffer/BUILD.gn @@ -0,0 +1,35 @@ +# Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/boards/${board}.gni") +import("//build/templates/reload_autoconf_and_build.gni") +import("//build/templates/rust.gni") +import("//build/toolchain/blueos.gni") + +reload_autoconf_and_build("fb_example") { + app_conf = [ "app.conf" ] + crate_name = "fb_example" + crate_type = "bin" + + sources = [ "src/main.rs" ] + deps = [ + "//external/vendor/embedded-graphics-0.8.2:embedded_graphics", + "//external/vendor/embedded-graphics-core-0.4.1:embedded_graphics_core", + "//kernel/rsrt:rsrt_std", + "//libc", + "//librs", + ] + linker_script = default_linker_script + configs = [ "//build/boards/${board}:kernel_config" ] +} diff --git a/framebuffer/app.conf b/framebuffer/app.conf new file mode 100644 index 0000000..1345598 --- /dev/null +++ b/framebuffer/app.conf @@ -0,0 +1,2 @@ +CONFIG_ENABLE_VFS=y +CONFIG_SPI_CORE=y diff --git a/framebuffer/src/main.rs b/framebuffer/src/main.rs new file mode 100644 index 0000000..6d1d68c --- /dev/null +++ b/framebuffer/src/main.rs @@ -0,0 +1,454 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate embedded_graphics; +extern crate librs; +extern crate rsrt; + +use embedded_graphics::{ + prelude::*, + primitives::{PrimitiveStyle, Rectangle}, +}; +use embedded_graphics_core::{ + draw_target::DrawTarget, + geometry::OriginDimensions, + pixelcolor::{raw::ToBytes, Rgb565, RgbColor}, + Pixel, +}; +use librs::{c_str::CStr, syscall::Syscall}; +use std::io::{Error, ErrorKind, Result}; + +#[derive(Clone, Copy)] +enum PixelFormat { + Rgb565, + Bgra8888, +} + +impl PixelFormat { + fn bytes_per_pixel(self) -> u32 { + match self { + Self::Rgb565 => 2, + Self::Bgra8888 => 4, + } + } +} + +struct FbFile { + fd: libc::c_int, + fixed_info: libc::fb_fix_screeninfo, + variable_info: libc::fb_var_screeninfo, + pixel_format: PixelFormat, +} + +impl FbFile { + fn open() -> Result { + let path = CStr::from_bytes_with_nul(b"/dev/fb0\0") + .map_err(|_| Error::from_raw_os_error(libc::EINVAL))?; + let fd = librs::syscall::sys::Sys::open(path, libc::O_RDWR, 0); + if fd < 0 { + return Err(syscall_error(fd)); + } + + let mut fb = Self { + fd, + fixed_info: unsafe { core::mem::zeroed() }, + variable_info: unsafe { core::mem::zeroed() }, + pixel_format: PixelFormat::Rgb565, + }; + + if let Err(err) = fb.load_info().and_then(|_| fb.validate_format()) { + let _ = librs::syscall::sys::Sys::close(fd); + return Err(err); + } + + fb.print_info(); + Ok(fb) + } + + fn print_info(&self) { + let fixed = &self.fixed_info; + let variable = &self.variable_info; + + println!("FBIOGET_FSCREENINFO:"); + println!(" id: {}", fb_id_to_str(&fixed.id)); + println!(" smem_start: {:#x}", fixed.smem_start); + println!(" smem_len: {}", fixed.smem_len); + println!(" type: {}", fixed.type_); + println!(" type_aux: {}", fixed.type_aux); + println!(" visual: {}", fixed.visual); + println!(" xpanstep: {}", fixed.xpanstep); + println!(" ypanstep: {}", fixed.ypanstep); + println!(" ywrapstep: {}", fixed.ywrapstep); + println!(" line_length: {}", fixed.line_length); + println!(" mmio_start: {:#x}", fixed.mmio_start); + println!(" mmio_len: {}", fixed.mmio_len); + println!(" accel: {}", fixed.accel); + println!(" capabilities: {}", fixed.capabilities); + + println!("FBIOGET_VSCREENINFO:"); + println!(" xres: {}", variable.xres); + println!(" yres: {}", variable.yres); + println!(" xres_virtual: {}", variable.xres_virtual); + println!(" yres_virtual: {}", variable.yres_virtual); + println!(" xoffset: {}", variable.xoffset); + println!(" yoffset: {}", variable.yoffset); + println!(" bits_per_pixel: {}", variable.bits_per_pixel); + println!(" grayscale: {}", variable.grayscale); + print_bitfield("red", &variable.red); + print_bitfield("green", &variable.green); + print_bitfield("blue", &variable.blue); + print_bitfield("transp", &variable.transp); + println!(" nonstd: {}", variable.nonstd); + println!(" activate: {}", variable.activate); + println!(" height: {}", variable.height); + println!(" width: {}", variable.width); + println!(" accel_flags: {}", variable.accel_flags); + println!(" pixclock: {}", variable.pixclock); + println!(" left_margin: {}", variable.left_margin); + println!(" right_margin: {}", variable.right_margin); + println!(" upper_margin: {}", variable.upper_margin); + println!(" lower_margin: {}", variable.lower_margin); + println!(" hsync_len: {}", variable.hsync_len); + println!(" vsync_len: {}", variable.vsync_len); + println!(" sync: {}", variable.sync); + println!(" vmode: {}", variable.vmode); + println!(" rotate: {}", variable.rotate); + println!(" colorspace: {}", variable.colorspace); + } + + fn load_info(&mut self) -> Result<()> { + unsafe { + ioctl( + self.fd, + libc::FBIOGET_FSCREENINFO, + &mut self.fixed_info as *mut libc::fb_fix_screeninfo as *mut libc::c_void, + )?; + ioctl( + self.fd, + libc::FBIOGET_VSCREENINFO, + &mut self.variable_info as *mut libc::fb_var_screeninfo as *mut libc::c_void, + )?; + } + Ok(()) + } + + fn validate_format(&mut self) -> Result<()> { + let info = &self.variable_info; + let fixed = &self.fixed_info; + let pixel_format = if is_rgb565(info) { + PixelFormat::Rgb565 + } else if is_bgra8888(info) { + PixelFormat::Bgra8888 + } else { + return Err(Error::new( + ErrorKind::InvalidData, + "unsupported framebuffer format", + )); + }; + let min_line_length = info + .xres + .checked_mul(pixel_format.bytes_per_pixel()) + .ok_or_else(|| Error::from_raw_os_error(libc::EINVAL))?; + let min_size = fixed + .line_length + .checked_mul(info.yres) + .ok_or_else(|| Error::from_raw_os_error(libc::EINVAL))?; + + if fixed.line_length < min_line_length || fixed.smem_len < min_size { + return Err(Error::new( + ErrorKind::InvalidData, + "unsupported framebuffer format", + )); + } + + self.pixel_format = pixel_format; + Ok(()) + } + + fn draw_pixel(&mut self, x: u32, y: u32, color: Rgb565) -> Result<()> { + let offset = y as u64 * self.fixed_info.line_length as u64 + + x as u64 * self.pixel_format.bytes_per_pixel() as u64; + if offset > libc::off_t::MAX as u64 { + return Err(Error::from_raw_os_error(libc::EINVAL)); + } + + let offset = + librs::syscall::sys::Sys::lseek(self.fd, offset as libc::off_t, libc::SEEK_SET); + if offset < 0 { + return Err(syscall_error(offset as libc::c_int)); + } + + match self.pixel_format { + PixelFormat::Rgb565 => write_all(self.fd, &color.to_be_bytes()), + PixelFormat::Bgra8888 => write_all(self.fd, &rgb565_to_bgra8888(color)), + } + } +} + +impl Drop for FbFile { + fn drop(&mut self) { + let _ = librs::syscall::sys::Sys::close(self.fd); + } +} + +impl OriginDimensions for FbFile { + fn size(&self) -> Size { + Size::new(self.variable_info.xres, self.variable_info.yres) + } +} + +impl DrawTarget for FbFile { + type Error = Error; + type Color = Rgb565; + + fn draw_iter(&mut self, pixels: I) -> Result<()> + where + I: IntoIterator>, + { + for Pixel(point, color) in pixels { + if point.x < 0 || point.y < 0 { + continue; + } + + let x = point.x as u32; + let y = point.y as u32; + if x >= self.variable_info.xres || y >= self.variable_info.yres { + continue; + } + + self.draw_pixel(x, y, color)?; + } + + Ok(()) + } +} + +unsafe fn ioctl(fd: libc::c_int, request: libc::c_ulong, arg: *mut libc::c_void) -> Result<()> { + match librs::syscall::sys::Sys::ioctl(fd, request, arg) { + Ok(ret) if ret < 0 => Err(syscall_error(ret)), + Ok(_) => Ok(()), + Err(librs::errno::Errno(errno)) => Err(Error::from_raw_os_error(errno)), + } +} + +fn write_all(fd: libc::c_int, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + match librs::syscall::sys::Sys::write(fd, buf) { + Ok(0) => { + return Err(Error::new( + ErrorKind::WriteZero, + "failed to write framebuffer", + )) + } + Ok(size) => buf = &buf[size..], + Err(librs::errno::Errno(errno)) => return Err(Error::from_raw_os_error(errno)), + } + } + + Ok(()) +} + +fn fb_id_to_str(id: &[libc::c_char; 16]) -> &str { + let len = id.iter().position(|&ch| ch == 0).unwrap_or(id.len()); + let bytes = unsafe { core::slice::from_raw_parts(id.as_ptr() as *const u8, len) }; + + core::str::from_utf8(bytes).unwrap_or("") +} + +fn print_bitfield(name: &str, bitfield: &libc::fb_bitfield) { + println!( + " {name}: offset={}, length={}, msb_right={}", + bitfield.offset, bitfield.length, bitfield.msb_right + ); +} + +fn is_rgb565(info: &libc::fb_var_screeninfo) -> bool { + info.bits_per_pixel == 16 + && info.red.offset == 11 + && info.red.length == 5 + && info.green.offset == 5 + && info.green.length == 6 + && info.blue.offset == 0 + && info.blue.length == 5 +} + +fn is_bgra8888(info: &libc::fb_var_screeninfo) -> bool { + info.bits_per_pixel == 32 + && info.red.offset == 16 + && info.red.length == 8 + && info.green.offset == 8 + && info.green.length == 8 + && info.blue.offset == 0 + && info.blue.length == 8 +} + +fn rgb565_to_bgra8888(color: Rgb565) -> [u8; 4] { + [color.b(), color.g(), color.r(), 0xff] +} + +fn syscall_error(ret: libc::c_int) -> Error { + if ret == -1 { + Error::last_os_error() + } else { + Error::from_raw_os_error(-ret) + } +} + +const CELL_SIZE: u32 = 8; +const FRAME_DELAY_MS: libc::c_uint = 80; +const INITIAL_SNAKE_LEN: usize = 5; +const MAX_SNAKE_LEN: usize = 48; + +#[derive(Clone, Copy)] +struct Cell { + x: i32, + y: i32, +} + +fn draw_cell(fb: &mut FbFile, cell: Cell, color: Rgb565) -> Result<()> { + Rectangle::new( + Point::new(cell.x * CELL_SIZE as i32, cell.y * CELL_SIZE as i32), + Size::new(CELL_SIZE, CELL_SIZE), + ) + .into_styled(PrimitiveStyle::with_fill(color)) + .draw(fb) +} + +fn loop_path_len(grid_width: usize, grid_height: usize) -> usize { + grid_width * 2 + grid_height * 2 - 4 +} + +fn loop_cell(index: usize, grid_width: usize, grid_height: usize) -> Cell { + let right = grid_width - 1; + let bottom = grid_height - 1; + + if index < grid_width { + Cell { + x: index as i32, + y: 0, + } + } else if index < grid_width + bottom { + Cell { + x: right as i32, + y: (index - grid_width + 1) as i32, + } + } else if index < grid_width + bottom + right { + Cell { + x: (right - (index - grid_width - bottom + 1)) as i32, + y: bottom as i32, + } + } else { + Cell { + x: 0, + y: (bottom - (index - grid_width - bottom - right + 1)) as i32, + } + } +} + +fn snake_contains(index: usize, head_index: usize, length: usize, path_len: usize) -> bool { + (0..length).any(|offset| (head_index + path_len - offset) % path_len == index) +} + +fn next_food_index(head_index: usize, length: usize, path_len: usize, seed: usize) -> usize { + for offset in 1..path_len { + let index = (head_index + seed * 11 + offset) % path_len; + if !snake_contains(index, head_index, length, path_len) { + return index; + } + } + + head_index +} + +fn run_snake(fb: &mut FbFile) -> Result<()> { + let size = fb.size(); + let grid_width = (size.width / CELL_SIZE) as usize; + let grid_height = (size.height / CELL_SIZE) as usize; + if grid_width < 2 || grid_height < 2 { + return Err(Error::new( + ErrorKind::InvalidData, + "framebuffer is too small for snake", + )); + } + + let path_len = loop_path_len(grid_width, grid_height); + let max_len = MAX_SNAKE_LEN.min(path_len - 1); + let mut length = INITIAL_SNAKE_LEN.min(max_len); + let mut head_index = length - 1; + let mut food_seed = 1; + let mut food_index = next_food_index(head_index, length, path_len, food_seed); + + fb.clear(Rgb565::BLACK)?; + draw_cell( + fb, + loop_cell(food_index, grid_width, grid_height), + Rgb565::RED, + )?; + for offset in (0..length).rev() { + let index = (head_index + path_len - offset) % path_len; + let color = if offset == 0 { + Rgb565::YELLOW + } else { + Rgb565::GREEN + }; + draw_cell(fb, loop_cell(index, grid_width, grid_height), color)?; + } + + loop { + let _ = librs::time::msleep(FRAME_DELAY_MS); + + let old_head_index = head_index; + let old_tail_index = (head_index + path_len - length + 1) % path_len; + head_index = (head_index + 1) % path_len; + + let ate_food = head_index == food_index; + if ate_food && length < max_len { + length += 1; + } else { + draw_cell( + fb, + loop_cell(old_tail_index, grid_width, grid_height), + Rgb565::BLACK, + )?; + } + + draw_cell( + fb, + loop_cell(old_head_index, grid_width, grid_height), + Rgb565::GREEN, + )?; + draw_cell( + fb, + loop_cell(head_index, grid_width, grid_height), + Rgb565::YELLOW, + )?; + + if ate_food { + food_seed += 1; + food_index = next_food_index(head_index, length, path_len, food_seed); + draw_cell( + fb, + loop_cell(food_index, grid_width, grid_height), + Rgb565::RED, + )?; + } + } +} + +fn main() -> Result<()> { + println!("Framebuffer example application started"); + let mut fb = FbFile::open()?; + + run_snake(&mut fb) +} diff --git a/wifi/BUILD.gn b/wifi/BUILD.gn new file mode 100644 index 0000000..a04f198 --- /dev/null +++ b/wifi/BUILD.gn @@ -0,0 +1,64 @@ +# Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/boards/${board}.gni") +import("//build/templates/reload_autoconf_and_build.gni") +import("//build/templates/rust.gni") +import("//build/toolchain/blueos.gni") + +reload_autoconf_and_build("wifi_example") { + app_conf = [ "app.conf" ] + crate_name = "wifi_example" + crate_type = "bin" + + sources = [ "src/main.rs" ] + deps = [ + "//kernel/adapter/esp_radio_sys", + "//kernel/kconfig:blueos_kconfig", + "//kernel/rsrt:rsrt_std", + "//libc", + "//librs", + ] + linker_script = default_linker_script + configs = [ "//build/boards/${board}:kernel_config" ] + + wifi_ldflags = [ + "-L" + rebase_path("${root_build_dir}/obj/external/vendor/esp-phy-0.2.0"), + "-L" + + rebase_path("${root_build_dir}/obj/external/vendor/esp-radio-0.18.0"), + "-L" + rebase_path("//external/vendor/esp-wifi-sys-esp32c3-0.2.0/libs"), + "-L" + rebase_path("//external/vendor/esp-rom-sys-0.1.4/ld/esp32c3"), + "-L" + rebase_path("//external/vendor/esp-radio-0.18.0"), + "-L" + rebase_path("//external/vendor/esp-phy-0.2.0"), + "-lesp-phy", + "-lesp_rom_sys", + "-lbtbb", + "-lbtdm_app", + "-lcoexist", + "-lespnow", + "-lmesh", + "-lnet80211", + "-lcore", + "-lsmartconfig", + "-lwapi", + "-lwpa_supplicant", + "-lregulatory", + "-lphy", + "-lpp", + ] + rustflags = [] + foreach(item, wifi_ldflags) { + rustflags += [ "-Clink-arg=${item}" ] + } +} diff --git a/wifi/app.conf b/wifi/app.conf new file mode 100644 index 0000000..e0d2b71 --- /dev/null +++ b/wifi/app.conf @@ -0,0 +1,9 @@ +CONFIG_ENABLE_NET=y +CONFIG_ENABLE_VFS=y +CONFIG_KERNEL_ASYNC=y +CONFIG_NET_ROUTER_IP="10.58.218.198" +CONFIG_NET_STATIC_IP="10.58.218.197" +CONFIG_ENABLE_WLAN=y +# Change the following two lines to your Wi-Fi SSID and password. +CONFIG_WLAN_SSID="test_esp" +CONFIG_WLAN_PASSWORD="88888888" diff --git a/wifi/src/main.rs b/wifi/src/main.rs new file mode 100644 index 0000000..9a99cd5 --- /dev/null +++ b/wifi/src/main.rs @@ -0,0 +1,358 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate esp_radio_sys; +use librs::syscall::Syscall; +use std::{ + io::{Read, Write}, + net::{Ipv4Addr, SocketAddrV4, TcpStream}, +}; + +const SCAN_POLL_ATTEMPTS: usize = 25; +const SCAN_POLL_INTERVAL_MS: u32 = 200; +const WIFI_CONNECT_WAIT_MS: u32 = 15000; +const WIFI_SSID: &[u8] = blueos_kconfig::CONFIG_WLAN_SSID; +const WIFI_PASSWORD: &[u8] = blueos_kconfig::CONFIG_WLAN_PASSWORD; +const PHONE_TCP_SERVER_PORT: u16 = 34228; +const TCP_TEST_PAYLOAD: &[u8] = b"blueos-wifi-tcp-link-test"; +const TCP_RECV_EXPECTED_MESSAGES: usize = 2; +const TCP_RECV_BUF_SIZE: usize = 512; + +extern crate librs; +extern crate rsrt; + +fn security_name(sec: u8) -> &'static str { + match sec { + 0 => "Open", + 1 => "WEP", + 2 => "WPA", + 3 => "WPA2", + 4 => "WPA3", + _ => "Unknown", + } +} + +fn wlan0_name() -> [libc::c_char; 16] { + let mut name = [0 as libc::c_char; 16]; + let bytes = b"wlan0\0"; + let mut i = 0; + while i < bytes.len() && i < name.len() { + name[i] = bytes[i] as libc::c_char; + i += 1; + } + name +} + +fn parse_router_ip() -> std::io::Result { + let ip_bytes = blueos_kconfig::CONFIG_NET_ROUTER_IP; + let len = ip_bytes + .iter() + .position(|&byte| byte == b'\0') + .unwrap_or(ip_bytes.len()); + let ip = core::str::from_utf8(&ip_bytes[..len]) + .map_err(|_| std::io::Error::from_raw_os_error(libc::EINVAL))?; + let mut parts = ip.split('.'); + + let a = parts + .next() + .and_then(|part| part.parse::().ok()) + .ok_or_else(|| std::io::Error::from_raw_os_error(libc::EINVAL))?; + let b = parts + .next() + .and_then(|part| part.parse::().ok()) + .ok_or_else(|| std::io::Error::from_raw_os_error(libc::EINVAL))?; + let c = parts + .next() + .and_then(|part| part.parse::().ok()) + .ok_or_else(|| std::io::Error::from_raw_os_error(libc::EINVAL))?; + let d = parts + .next() + .and_then(|part| part.parse::().ok()) + .ok_or_else(|| std::io::Error::from_raw_os_error(libc::EINVAL))?; + + if parts.next().is_some() { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + + Ok(Ipv4Addr::new(a, b, c, d)) +} + +fn run_phone_tcp_check() -> std::io::Result<()> { + let phone_tcp_server = SocketAddrV4::new(parse_router_ip()?, PHONE_TCP_SERVER_PORT); + println!("TCP link check: connecting to {}", phone_tcp_server); + let mut stream = TcpStream::connect(phone_tcp_server)?; + stream.write_all(TCP_TEST_PAYLOAD)?; + println!( + "WiFi phone TCP TX OK: sent {} bytes to {}: {:?}", + TCP_TEST_PAYLOAD.len(), + phone_tcp_server, + TCP_TEST_PAYLOAD + ); + + println!( + "TCP link check: waiting for response from {}", + phone_tcp_server + ); + let mut rx_buf = [0u8; TCP_RECV_BUF_SIZE]; + for message_index in 0..TCP_RECV_EXPECTED_MESSAGES { + let size = stream.read(&mut rx_buf)?; + if size == 0 { + println!( + "WiFi phone TCP RX closed after {} message(s)", + message_index + ); + return Ok(()); + } + + println!( + "WiFi phone TCP RX OK: message {}/{} received {} bytes from {}: {:?}", + message_index + 1, + TCP_RECV_EXPECTED_MESSAGES, + size, + phone_tcp_server, + &rx_buf[..size] + ); + if let Ok(text) = core::str::from_utf8(&rx_buf[..size]) { + println!("WiFi phone TCP RX text: {}", text); + } + } + + println!( + "WiFi phone TCP RX check done: received {} message(s)", + TCP_RECV_EXPECTED_MESSAGES + ); + Ok(()) +} + +fn main() -> std::io::Result<()> { + let _d = librs::time::msleep(1000); + + println!("Wifi example"); + let fd = librs::net::socket::socket(libc::AF_INET, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 0); + if fd < 0 { + eprintln!( + "Failed to create socket: {}", + std::io::Error::last_os_error() + ); + return Err(std::io::Error::last_os_error()); + } + + println!("Scanning for WiFi networks..."); + + // SIOCSIWSCAN: trigger scan on wlan0 with struct iw_scan_req + let scan_req = libc::iw_scan_req { + scan_type: libc::IW_SCAN_TYPE_ACTIVE as u8, + essid_len: 0, + num_channels: 0, + flags: 0, + bssid: unsafe { core::mem::zeroed() }, + essid: [0u8; libc::IW_ESSID_MAX_SIZE], + min_channel_time: 0, + max_channel_time: 0, + channel_list: [libc::iw_freq { + m: 0, + e: 0, + i: 0, + flags: 0, + }; libc::IW_MAX_FREQUENCIES], + }; + let mut iwreq = libc::iwreq { + ifr_ifrn: libc::__c_anonymous_iwreq { + ifrn_name: wlan0_name(), + }, + u: libc::iwreq_data { + essid: libc::iw_point { + pointer: &scan_req as *const libc::iw_scan_req as *mut libc::c_void, + length: core::mem::size_of::() as u16, + flags: 0, + }, + }, + }; + let ret = unsafe { + librs::syscall::sys::Sys::ioctl( + fd, + libc::SIOCSIWSCAN, + &mut iwreq as *mut _ as *mut libc::c_void, + ) + }; + if let Err(librs::errno::Errno(errno)) = ret { + eprintln!("SIOCSIWSCAN failed: errno={}", errno); + return Err(std::io::Error::from_raw_os_error(errno)); + } + println!("Scan triggered."); + + // SIOCGIWSCAN: retrieve results into a 4 KiB buffer + let mut buf = [0u8; 4096]; + let data = libc::iw_point { + pointer: buf.as_mut_ptr() as *mut libc::c_void, + length: buf.len() as u16, + flags: 0, + }; + let mut iwreq = libc::iwreq { + ifr_ifrn: libc::__c_anonymous_iwreq { + ifrn_name: wlan0_name(), + }, + u: libc::iwreq_data { data }, + }; + let mut total = None; + for attempt in 0..SCAN_POLL_ATTEMPTS { + let ret = unsafe { + librs::syscall::sys::Sys::ioctl( + fd, + libc::SIOCGIWSCAN, + &mut iwreq as *mut _ as *mut libc::c_void, + ) + }; + match ret { + Ok(n) if n >= 0 => { + total = Some(n as usize); + break; + } + Err(librs::errno::Errno(errno)) if attempt + 1 < SCAN_POLL_ATTEMPTS => { + if errno != libc::EAGAIN { + eprintln!("SIOCGIWSCAN failed: errno={}", errno); + return Err(std::io::Error::from_raw_os_error(errno)); + } + let _ = librs::time::msleep(SCAN_POLL_INTERVAL_MS); + } + Err(librs::errno::Errno(errno)) => { + eprintln!( + "SIOCGIWSCAN failed or scan results are not ready: errno={}", + errno + ); + return Err(std::io::Error::from_raw_os_error(errno)); + } + _ => { + eprintln!("SIOCGIWSCAN returned an invalid success value"); + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + } + } + let total = total.unwrap_or(0); + + // Decode: [u32 count][entry]* + if total < 4 { + println!("No scan results ({} bytes returned)", total); + return Ok(()); + } + let count = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]); + println!("Found {} networks:\n", count); + + let mut off = 4usize; + for i in 0..count { + if off + 4 > total { + break; + } + let ssid_len = u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]); + off += 4; + + if off + ssid_len as usize > total { + break; + } + let ssid = + core::str::from_utf8(&buf[off..off + ssid_len as usize]).unwrap_or(""); + off += ssid_len as usize; + + if off + 6 > total { + break; + } + let bssid = &buf[off..off + 6]; + off += 6; + + if off + 1 > total { + break; + } + let signal = buf[off] as i8; + off += 1; + + if off + 2 > total { + break; + } + let channel = u16::from_le_bytes([buf[off], buf[off + 1]]); + off += 2; + + if off + 1 > total { + break; + } + let security = buf[off]; + off += 1; + + println!(" {}. SSID: {}", i + 1, ssid); + println!( + " BSSID: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5] + ); + println!(" Signal: {} dBm", signal); + println!(" Channel: {}", channel); + println!(" Security: {}", security_name(security)); + println!(); + } + + println!("Connecting to WiFi SSID: test_esp (WPA2)..."); + + // SIOCSIWENCODE — cache passphrase for WPA2 network + let mut iwreq = libc::iwreq { + ifr_ifrn: libc::__c_anonymous_iwreq { + ifrn_name: wlan0_name(), + }, + u: libc::iwreq_data { + encoding: libc::iw_point { + pointer: WIFI_PASSWORD.as_ptr() as *mut libc::c_void, + length: WIFI_PASSWORD.len() as u16, + flags: 0, + }, + }, + }; + let ret = unsafe { + librs::syscall::sys::Sys::ioctl( + fd, + libc::SIOCSIWENCODE, + &mut iwreq as *mut _ as *mut libc::c_void, + ) + }; + if let Err(librs::errno::Errno(errno)) = ret { + eprintln!("SIOCSIWENCODE failed: errno={}", errno); + } + + // SIOCSIWESSID — trigger connect + let mut iwreq = libc::iwreq { + ifr_ifrn: libc::__c_anonymous_iwreq { + ifrn_name: wlan0_name(), + }, + u: libc::iwreq_data { + essid: libc::iw_point { + pointer: WIFI_SSID.as_ptr() as *mut libc::c_void, + // The length should not include the null terminator, so we subtract 1. + length: (WIFI_SSID.len() - 1) as u16, + flags: 1, + }, + }, + }; + let ret = unsafe { + librs::syscall::sys::Sys::ioctl( + fd, + libc::SIOCSIWESSID, + &mut iwreq as *mut _ as *mut libc::c_void, + ) + }; + if let Err(librs::errno::Errno(errno)) = ret { + eprintln!("SIOCSIWESSID failed: errno={}", errno); + return Err(std::io::Error::from_raw_os_error(errno)); + } + + println!("WiFi connect triggered, waiting for station connection..."); + let _ = librs::time::msleep(WIFI_CONNECT_WAIT_MS); + run_phone_tcp_check(); + Ok(()) +}