#![allow(missing_docs)]
#![no_std]
#![no_main]
#![cfg(feature = "wifi")]
#![allow(clippy::future_not_send, reason = "single-threaded")]
use core::convert::Infallible;
use defmt::info;
use defmt_rtt as _;
use device_envoy_example_common::clock_ui::{ClockUiEvent, run_clock_ui};
use device_envoy_rp::{
Error, Result,
button::PressedTo,
button_watch,
clock_sync::{ClockSyncRp, ClockSyncStaticRp, ONE_MINUTE},
flash_block::FlashBlockRp,
led4::{BlinkState, Led4 as _, Led4Rp, Led4RpStatic, OutputArray, circular_outline_animation},
wifi_auto::{
WifiAutoEvent, WifiAutoRp,
fields::{TimezoneField, TimezoneFieldStatic},
},
};
use embassy_executor::Spawner;
use embassy_rp::gpio::{self, Level};
use panic_probe as _;
button_watch! {
ButtonWatch13 {
pin: PIN_13,
}
}
#[embassy_executor::main]
pub async fn main(spawner: Spawner) -> ! {
let err = inner_main(spawner).await.unwrap_err();
core::panic!("{err}");
}
async fn inner_main(spawner: Spawner) -> Result<Infallible> {
info!("Starting Wi-Fi 4-digit clock (WifiAutoRp)");
let p = embassy_rp::init(Default::default());
let [wifi_credentials_flash_block, mut timezone_flash_block] =
FlashBlockRp::new_array::<2>(p.FLASH)?;
static TIMEZONE_FIELD_STATIC: TimezoneFieldStatic = TimezoneField::new_static();
let timezone_field = TimezoneField::new(&TIMEZONE_FIELD_STATIC, timezone_flash_block);
let button_watch13 = ButtonWatch13::new(p.PIN_13, PressedTo::Ground, spawner).await?;
let wifi_auto = WifiAutoRp::new(
p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.PIO0, p.DMA_CH0, wifi_credentials_flash_block,
"DeviceEnvoyClock", [timezone_field], spawner,
)?;
let cell_pins = OutputArray::new([
gpio::Output::new(p.PIN_1, Level::High),
gpio::Output::new(p.PIN_2, Level::High),
gpio::Output::new(p.PIN_3, Level::High),
gpio::Output::new(p.PIN_4, Level::High),
]);
let segment_pins = OutputArray::new([
gpio::Output::new(p.PIN_5, Level::Low),
gpio::Output::new(p.PIN_6, Level::Low),
gpio::Output::new(p.PIN_7, Level::Low),
gpio::Output::new(p.PIN_8, Level::Low),
gpio::Output::new(p.PIN_9, Level::Low),
gpio::Output::new(p.PIN_10, Level::Low),
gpio::Output::new(p.PIN_11, Level::Low),
gpio::Output::new(p.PIN_12, Level::Low),
]);
static LED4_STATIC: Led4RpStatic = Led4Rp::new_static();
let led4 = Led4Rp::new(&LED4_STATIC, cell_pins, segment_pins, spawner)?;
let led4_ref = &led4;
let stack = wifi_auto
.connect(&mut *button_watch13, |event| async move {
match event {
WifiAutoEvent::CaptivePortalReady => {
led4_ref.write_text(['j', 'o', 'i', 'n'], BlinkState::BlinkingAndOn);
}
WifiAutoEvent::Connecting { .. } => {
led4_ref.animate_text(circular_outline_animation(true));
}
WifiAutoEvent::ConnectionFailed => {
led4_ref.write_text(['F', 'A', 'I', 'L'], BlinkState::BlinkingButOff);
}
}
Ok(())
})
.await?;
led4.write_text(['D', 'O', 'N', 'E'], BlinkState::Solid);
info!("WiFi connected");
let offset_minutes = timezone_field
.offset_minutes()?
.ok_or(Error::MissingCustomWifiAutoField)?;
static CLOCK_SYNC_STATIC: ClockSyncStaticRp = ClockSyncRp::new_static();
let clock_sync = ClockSyncRp::new(
&CLOCK_SYNC_STATIC,
stack,
offset_minutes,
Some(ONE_MINUTE),
spawner,
)?;
let led4_ref = &led4;
run_clock_ui(
&clock_sync,
&mut *button_watch13,
&mut timezone_flash_block,
|clock_ui_event| async move {
match clock_ui_event {
ClockUiEvent::RenderHoursMinutes { hours, minutes } => {
led4_ref.write_text(hours_minutes_text(hours, minutes), BlinkState::Solid);
}
ClockUiEvent::RenderMinutesSeconds { minutes, seconds } => {
led4_ref.write_text(minutes_seconds_text(minutes, seconds), BlinkState::Solid);
}
ClockUiEvent::RenderHoursMinutesEdit { hours, minutes } => {
led4_ref.write_text(
hours_minutes_text(hours, minutes),
BlinkState::BlinkingAndOn,
);
}
}
Ok(())
},
)
.await
}
fn hours_minutes_text(hours: u8, minutes: u8) -> [char; 4] {
[
tens_hours(hours),
ones_digit(hours),
tens_digit(minutes),
ones_digit(minutes),
]
}
fn minutes_seconds_text(minutes: u8, seconds: u8) -> [char; 4] {
[
tens_digit(minutes),
ones_digit(minutes),
tens_digit(seconds),
ones_digit(seconds),
]
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
clippy::integer_division_remainder_used,
reason = "Value < 60 ensures division is safe"
)]
const fn tens_digit(value: u8) -> char {
((value / 10) + b'0') as char
}
#[inline]
const fn tens_hours(value: u8) -> char {
if value >= 10 { '1' } else { ' ' }
}
#[inline]
#[expect(
clippy::arithmetic_side_effects,
clippy::integer_division_remainder_used,
reason = "Value < 60 ensures division is safe"
)]
const fn ones_digit(value: u8) -> char {
((value % 10) + b'0') as char
}