mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2025-07-01 16:54:17 +00:00
implemented mocking of rpi hardware
buzzer,led & hotspot got traits and a mock version of it. Based on the flag the real or mock version is used.
This commit is contained in:
parent
31f65261df
commit
64a50d434b
@ -3,6 +3,10 @@ name = "fw-anwesenheit"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
mock_pi = [] # Enable mocking of the rpi hardware
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
gpio = "0.4.1"
|
||||
|
@ -1,6 +1,12 @@
|
||||
use rppal::pwm::{Channel, Error, Polarity, Pwm};
|
||||
use std::{future::Future, time::Duration};
|
||||
use tokio::time::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
pub trait Buzzer {
|
||||
fn beep_ack(&mut self) -> impl Future<Output = Result<(), Error>> + std::marker::Send;
|
||||
|
||||
fn beep_nak(&mut self) -> impl Future<Output = Result<(), Error>> + std::marker::Send;
|
||||
}
|
||||
|
||||
pub struct GPIOBuzzer {
|
||||
pwm: Pwm,
|
||||
@ -8,19 +14,19 @@ pub struct GPIOBuzzer {
|
||||
|
||||
impl GPIOBuzzer {
|
||||
/// Create a new GPIOBuzzer instance.
|
||||
/// 0.5 duty cyle
|
||||
/// 0.5 duty cyle
|
||||
/// # Arguments
|
||||
/// * "channel" - PWM channel for buzzer PWM0 = GPIO 12 / PWM1 = GPIO 13
|
||||
pub fn new(channel: Channel) -> Result<Self, Error> {
|
||||
// Enable with dummy values; we'll set frequency/duty in the tone method
|
||||
let duty_cycle:f64 = 0.5;
|
||||
let duty_cycle: f64 = 0.5;
|
||||
let pwm = Pwm::with_frequency(channel, 1000.0, duty_cycle, Polarity::Normal, true)?;
|
||||
pwm.disable()?; // Start disabled
|
||||
|
||||
Ok(GPIOBuzzer { pwm })
|
||||
}
|
||||
|
||||
/// Play a tone using hardware PWM on supported GPIO pins.
|
||||
/// Play a tone using hardware PWM on supported GPIO pins.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `frequency` - Frequency in Hz.
|
||||
@ -32,33 +38,24 @@ impl GPIOBuzzer {
|
||||
self.pwm.disable()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn beep_ack(&mut self) -> Result<(), Error>{
|
||||
impl Buzzer for GPIOBuzzer {
|
||||
async fn beep_ack(&mut self) -> Result<(), Error> {
|
||||
let sleep_ms: u64 = 10;
|
||||
|
||||
self.modulated_tone(1200.0, 100).await?;
|
||||
sleep(Duration::from_millis(sleep_ms)).await;
|
||||
self.modulated_tone(2000.0,50).await?;
|
||||
self.modulated_tone(2000.0, 50).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn beep_nak(&mut self) -> Result<(), Error>{
|
||||
async fn beep_nak(&mut self) -> Result<(), Error> {
|
||||
let sleep_ms: u64 = 100;
|
||||
|
||||
self.modulated_tone(600.0, 150).await?;
|
||||
sleep(Duration::from_millis(sleep_ms)).await;
|
||||
self.modulated_tone(600.0,150).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn beep_unnkown(&mut self) -> Result<(), Error>{
|
||||
let sleep_ms: u64 = 100;
|
||||
|
||||
self.modulated_tone(750.0, 100).await?;
|
||||
sleep(Duration::from_millis(sleep_ms)).await;
|
||||
self.modulated_tone(1200.0,100).await?;
|
||||
sleep(Duration::from_millis(sleep_ms)).await;
|
||||
self.modulated_tone(2300.0,100).await?;
|
||||
self.modulated_tone(600.0, 150).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
199
src/hotspot.rs
199
src/hotspot.rs
@ -1,5 +1,6 @@
|
||||
use log::trace;
|
||||
use log::{error, trace, warn};
|
||||
use std::{
|
||||
env,
|
||||
fmt::{self},
|
||||
process::Output,
|
||||
};
|
||||
@ -14,6 +15,7 @@ const IPV4_ADDRES: &str = "192.168.4.1/24";
|
||||
pub enum HotspotError {
|
||||
IoError(std::io::Error),
|
||||
NonZeroExit(Output),
|
||||
PasswordToShort,
|
||||
}
|
||||
|
||||
impl fmt::Display for HotspotError {
|
||||
@ -30,104 +32,145 @@ impl fmt::Display for HotspotError {
|
||||
"Failed to run hotspot command.\nStdout: {stdout}\nStderr: {stderr}",
|
||||
)
|
||||
}
|
||||
HotspotError::PasswordToShort => {
|
||||
write!(f, "The password must be at leat 8 characters long")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for HotspotError {}
|
||||
|
||||
/// Create the connection in NM
|
||||
/// Will fail if already exists
|
||||
async fn create_hotspot() -> Result<(), HotspotError> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["device", "wifi", "hotspot"])
|
||||
.arg("con-name")
|
||||
.arg(CON_NAME)
|
||||
.arg("ssid")
|
||||
.arg(SSID)
|
||||
.arg("password")
|
||||
.arg(PASSWORD)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
pub trait Hotspot {
|
||||
fn enable_hotspot(
|
||||
&self,
|
||||
) -> impl std::future::Future<Output = Result<(), HotspotError>> + std::marker::Send;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
}
|
||||
|
||||
let cmd = Command::new("nmcli")
|
||||
.arg("connection")
|
||||
.arg("modify")
|
||||
.arg(CON_NAME)
|
||||
.arg("ipv4.method")
|
||||
.arg("shared")
|
||||
.arg("ipv4.addresses")
|
||||
.arg(IPV4_ADDRES)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
async fn disable_hotspot(&self) -> Result<(), HotspotError>;
|
||||
}
|
||||
|
||||
/// Checks if the connection already exists
|
||||
async fn exists() -> Result<bool, HotspotError> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "show"])
|
||||
.arg(CON_NAME)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
Ok(cmd.status.success())
|
||||
/// NetworkManager Hotspot
|
||||
pub struct NMHotspot {
|
||||
ssid: String,
|
||||
con_name: String,
|
||||
password: String,
|
||||
ipv4: String,
|
||||
}
|
||||
|
||||
pub async fn enable_hotspot() -> Result<(), HotspotError> {
|
||||
if !exists().await? {
|
||||
create_hotspot().await?;
|
||||
impl NMHotspot {
|
||||
pub fn new_from_env() -> Result<Self, HotspotError> {
|
||||
let ssid = env::var("HOTSPOT_SSID").unwrap_or(SSID.to_owned());
|
||||
let password = env::var("HOTSPOT_PW").unwrap_or_else(|_| {
|
||||
warn!("HOTSPOT_PW not set. Using default password");
|
||||
PASSWORD.to_owned()
|
||||
});
|
||||
|
||||
if password.len() < 8 {
|
||||
error!("Hotspot PW is to short");
|
||||
return Err(HotspotError::PasswordToShort);
|
||||
}
|
||||
|
||||
Ok(NMHotspot {
|
||||
ssid,
|
||||
con_name: CON_NAME.to_owned(),
|
||||
password,
|
||||
ipv4: IPV4_ADDRES.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "up"])
|
||||
.arg(CON_NAME)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
async fn create_hotspot(&self) -> Result<(), HotspotError> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["device", "wifi", "hotspot"])
|
||||
.arg("con-name")
|
||||
.arg(&self.con_name)
|
||||
.arg("ssid")
|
||||
.arg(&self.ssid)
|
||||
.arg("password")
|
||||
.arg(&self.password)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
}
|
||||
|
||||
let cmd = Command::new("nmcli")
|
||||
.arg("connection")
|
||||
.arg("modify")
|
||||
.arg(&self.con_name)
|
||||
.arg("ipv4.method")
|
||||
.arg("shared")
|
||||
.arg("ipv4.addresses")
|
||||
.arg(&self.ipv4)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// Checks if the connection already exists
|
||||
async fn exists(&self) -> Result<bool, HotspotError> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "show"])
|
||||
.arg(&self.con_name)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
Ok(cmd.status.success())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn disable_hotspot() -> Result<(), HotspotError> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "down"])
|
||||
.arg(CON_NAME)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
impl Hotspot for NMHotspot {
|
||||
async fn enable_hotspot(&self) -> Result<(), HotspotError> {
|
||||
if !self.exists().await? {
|
||||
self.create_hotspot().await?;
|
||||
}
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "up"])
|
||||
.arg(&self.con_name)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
async fn disable_hotspot(&self) -> Result<(), HotspotError> {
|
||||
let cmd = Command::new("nmcli")
|
||||
.args(["connection", "down"])
|
||||
.arg(&self.con_name)
|
||||
.output()
|
||||
.await
|
||||
.map_err(HotspotError::IoError)?;
|
||||
|
||||
trace!("nmcli (std): {}", String::from_utf8_lossy(&cmd.stdout));
|
||||
trace!("nmcli (err): {}", String::from_utf8_lossy(&cmd.stderr));
|
||||
|
||||
if !cmd.status.success() {
|
||||
return Err(HotspotError::NonZeroExit(cmd));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
36
src/led.rs
36
src/led.rs
@ -7,35 +7,47 @@ use ws2812_spi::Ws2812;
|
||||
|
||||
use crate::color::NamedColor;
|
||||
|
||||
pub struct Led {
|
||||
const STATUS_DURATION: Duration = Duration::from_secs(1); // 1s sleep for all status led signals
|
||||
|
||||
pub trait StatusLed {
|
||||
fn turn_green_on_1s(
|
||||
&mut self,
|
||||
) -> impl std::future::Future<Output = Result<(), Error>> + std::marker::Send;
|
||||
|
||||
fn turn_red_on_1s(
|
||||
&mut self,
|
||||
) -> impl std::future::Future<Output = Result<(), Error>> + std::marker::Send;
|
||||
}
|
||||
|
||||
pub struct SpiLed {
|
||||
controller: Ws2812<Spi>,
|
||||
}
|
||||
|
||||
const STATUS_DURATION: Duration = Duration::from_secs(1); // 1s sleep for all status led signals
|
||||
|
||||
impl Led {
|
||||
impl SpiLed {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 3_800_000, Mode::Mode0)?;
|
||||
let controller = Ws2812::new(spi);
|
||||
Ok(Led { controller })
|
||||
Ok(SpiLed { controller })
|
||||
}
|
||||
|
||||
pub async fn turn_green_on_1s(&mut self) -> Result<(), Error> {
|
||||
fn turn_off(&mut self) -> Result<(), Error> {
|
||||
self.controller.write(NamedColor::Off.into_iter())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusLed for SpiLed {
|
||||
async fn turn_green_on_1s(&mut self) -> Result<(), Error> {
|
||||
self.controller.write(NamedColor::Green.into_iter())?;
|
||||
sleep(STATUS_DURATION).await;
|
||||
self.turn_off()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn turn_red_on_1s(&mut self) -> Result<(), Error> {
|
||||
async fn turn_red_on_1s(&mut self) -> Result<(), Error> {
|
||||
self.controller.write(NamedColor::Red.into_iter())?;
|
||||
sleep(STATUS_DURATION).await;
|
||||
self.turn_off()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn turn_off(&mut self) -> Result<(), Error> {
|
||||
self.controller.write(NamedColor::Off.into_iter())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
69
src/main.rs
69
src/main.rs
@ -1,6 +1,7 @@
|
||||
use buzzer::GPIOBuzzer;
|
||||
use buzzer::{Buzzer, GPIOBuzzer};
|
||||
use hotspot::{Hotspot, HotspotError, NMHotspot};
|
||||
use id_store::IDStore;
|
||||
use led::Led;
|
||||
use led::{SpiLed, StatusLed};
|
||||
use log::{LevelFilter, debug, error, info, warn};
|
||||
use pm3::run_pm3;
|
||||
use rppal::pwm::Channel;
|
||||
@ -13,11 +14,15 @@ use tokio::{
|
||||
};
|
||||
use webserver::start_webserver;
|
||||
|
||||
#[cfg(feature = "mock_pi")]
|
||||
use mock::{MockBuzzer, MockHotspot, MockLed};
|
||||
|
||||
mod buzzer;
|
||||
mod color;
|
||||
mod hotspot;
|
||||
mod id_store;
|
||||
mod led;
|
||||
mod mock;
|
||||
mod parser;
|
||||
mod pm3;
|
||||
mod tally_id;
|
||||
@ -48,7 +53,10 @@ fn setup_logger() {
|
||||
}
|
||||
|
||||
/// Signal the user success via buzzer and led
|
||||
async fn feedback_success(gpio_buzzer: &Arc<Mutex<GPIOBuzzer>>, status_led: &Arc<Mutex<Led>>) {
|
||||
async fn feedback_success<T: Buzzer, I: StatusLed>(
|
||||
gpio_buzzer: &Arc<Mutex<T>>,
|
||||
status_led: &Arc<Mutex<I>>,
|
||||
) {
|
||||
let mut buzzer_guard = gpio_buzzer.lock().await;
|
||||
let mut led_guard = status_led.lock().await;
|
||||
|
||||
@ -64,7 +72,10 @@ async fn feedback_success(gpio_buzzer: &Arc<Mutex<GPIOBuzzer>>, status_led: &Arc
|
||||
}
|
||||
|
||||
/// Signal the user failure via buzzer and led
|
||||
async fn feedback_failure(gpio_buzzer: &Arc<Mutex<GPIOBuzzer>>, status_led: &Arc<Mutex<Led>>) {
|
||||
async fn feedback_failure<T: Buzzer, I: StatusLed>(
|
||||
gpio_buzzer: &Arc<Mutex<T>>,
|
||||
status_led: &Arc<Mutex<I>>,
|
||||
) {
|
||||
let mut buzzer_guard = gpio_buzzer.lock().await;
|
||||
let mut led_guard = status_led.lock().await;
|
||||
|
||||
@ -79,6 +90,48 @@ async fn feedback_failure(gpio_buzzer: &Arc<Mutex<GPIOBuzzer>>, status_led: &Arc
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a buzzer
|
||||
/// Respects the `mock_pi` flag.
|
||||
fn create_buzzer() -> Result<Arc<Mutex<impl Buzzer>>, rppal::pwm::Error> {
|
||||
#[cfg(feature = "mock_pi")]
|
||||
{
|
||||
Ok(Arc::new(Mutex::new(MockBuzzer {})))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock_pi"))]
|
||||
{
|
||||
Ok(Arc::new(Mutex::new(GPIOBuzzer::new(PWM_CHANNEL_BUZZER)?)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a status led.
|
||||
/// Respects the `mock_pi` flag.
|
||||
fn create_status_led() -> Result<Arc<Mutex<impl StatusLed>>, rppal::spi::Error> {
|
||||
#[cfg(feature = "mock_pi")]
|
||||
{
|
||||
Ok(Arc::new(Mutex::new(MockLed {})))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock_pi"))]
|
||||
{
|
||||
Ok(Arc::new(Mutex::new(SpiLed::new()?)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a struct to manage the hotspot
|
||||
/// Respects the `mock_pi` flag.
|
||||
fn create_hotspot() -> Result<impl Hotspot, HotspotError> {
|
||||
#[cfg(feature = "mock_pi")]
|
||||
{
|
||||
Ok(MockHotspot {})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mock_pi"))]
|
||||
{
|
||||
NMHotspot::new_from_env()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
setup_logger();
|
||||
@ -109,9 +162,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
debug!("created store sucessfully");
|
||||
|
||||
let store: Arc<Mutex<IDStore>> = Arc::new(Mutex::new(raw_store));
|
||||
let gpio_buzzer: Arc<Mutex<GPIOBuzzer>> =
|
||||
Arc::new(Mutex::new(GPIOBuzzer::new(PWM_CHANNEL_BUZZER)?));
|
||||
let status_led: Arc<Mutex<Led>> = Arc::new(Mutex::new(Led::new()?));
|
||||
let gpio_buzzer = create_buzzer()?;
|
||||
let status_led = create_status_led()?;
|
||||
let hotspot = create_hotspot()?;
|
||||
|
||||
let hotspot_ids: Vec<TallyID> = env::var("HOTSPOT_IDS")
|
||||
.map(|ids| ids.split(";").map(|id| TallyID(id.to_owned())).collect())
|
||||
@ -130,7 +183,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
if hotspot_ids.contains(&tally_id) {
|
||||
info!("Enableing hotspot");
|
||||
hotspot::enable_hotspot().await.unwrap_or_else(|err| {
|
||||
hotspot.enable_hotspot().await.unwrap_or_else(|err| {
|
||||
error!("Hotspot: {err}");
|
||||
});
|
||||
// TODO: Should the ID be added anyway or ignored ?
|
||||
|
45
src/mock.rs
Normal file
45
src/mock.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use log::debug;
|
||||
|
||||
use crate::{buzzer::Buzzer, hotspot::Hotspot, led::StatusLed};
|
||||
|
||||
pub struct MockBuzzer {}
|
||||
|
||||
impl Buzzer for MockBuzzer {
|
||||
async fn beep_ack(&mut self) -> Result<(), rppal::pwm::Error> {
|
||||
debug!("Mockbuzzer: ACK");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn beep_nak(&mut self) -> Result<(), rppal::pwm::Error> {
|
||||
debug!("Mockbuzzer: NAK");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockLed {}
|
||||
|
||||
impl StatusLed for MockLed {
|
||||
async fn turn_green_on_1s(&mut self) -> Result<(), rppal::spi::Error> {
|
||||
debug!("Mockled: Turn LED green for 1 sec");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn turn_red_on_1s(&mut self) -> Result<(), rppal::spi::Error> {
|
||||
debug!("Mockled: Turn LED red for 1 sec");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockHotspot {}
|
||||
|
||||
impl Hotspot for MockHotspot {
|
||||
async fn enable_hotspot(&self) -> Result<(), crate::hotspot::HotspotError> {
|
||||
debug!("Mockhotspot: Enable hotspot");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disable_hotspot(&self) -> Result<(), crate::hotspot::HotspotError> {
|
||||
debug!("Mockhotspot: Disable hotspot");
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user