#![allow(missing_docs)]
#![no_std]
#![no_main]
#![cfg(feature = "wifi")]
#![allow(clippy::future_not_send, reason = "single-threaded")]
use core::convert::{Infallible, TryFrom};
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,
servo::Servo as _,
servo::{AtEnd, Direction, ServoPlayer as _, combine, linear, servo_player},
wifi_auto::{
WifiAutoEvent, WifiAutoRp,
fields::{TimezoneField, TimezoneFieldStatic},
},
};
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use panic_probe as _;
button_watch! {
ButtonWatch13 {
pin: PIN_13,
}
}
servo_player! {
BottomServoPlayer {
pin: PIN_11,
direction: Direction::Reverse,
max_steps: 30,
}
}
servo_player! {
TopServoPlayer {
pin: PIN_12,
direction: Direction::Reverse,
max_steps: 30,
}
}
#[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 servo 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 bottom_servo = BottomServoPlayer::new(p.PIN_11, p.PWM_SLICE5, spawner)?;
let top_servo = TopServoPlayer::new(p.PIN_12, p.PWM_SLICE6, spawner)?;
let servo_display = ServoClockDisplay::new(bottom_servo, top_servo);
let servo_display_ref = &servo_display;
let stack = wifi_auto
.connect(&mut *button_watch13, |event| {
let servo_display_ref = servo_display_ref;
async move {
match event {
WifiAutoEvent::CaptivePortalReady => {
servo_display_ref.show_portal_ready().await;
}
WifiAutoEvent::Connecting { .. } => servo_display_ref.show_connecting().await,
WifiAutoEvent::ConnectionFailed => {
}
}
Ok(())
}
})
.await?;
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 servo_display_ref = &servo_display;
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 } => {
servo_display_ref.show_hours_minutes(hours, minutes).await;
}
ClockUiEvent::RenderMinutesSeconds { minutes, seconds } => {
servo_display_ref
.show_minutes_seconds(minutes, seconds)
.await;
}
ClockUiEvent::RenderHoursMinutesEdit { hours, minutes } => {
servo_display_ref
.show_hours_minutes_indicator(hours, minutes)
.await;
}
}
Ok(())
},
)
.await
}
struct ServoClockDisplay {
bottom: &'static BottomServoPlayer,
top: &'static TopServoPlayer,
}
impl ServoClockDisplay {
fn new(bottom: &'static BottomServoPlayer, top: &'static TopServoPlayer) -> Self {
Self { bottom, top }
}
async fn show_portal_ready(&self) {
self.bottom.set_degrees(90);
self.top.set_degrees(90);
}
async fn show_connecting(&self) {
const FIVE_SECONDS: Duration = Duration::from_secs(5);
const PHASE1: [(u16, Duration); 10] = linear(18, 0, FIVE_SECONDS);
const PHASE2: [(u16, Duration); 2] = linear(0, 180, FIVE_SECONDS);
self.top.animate(combine!(PHASE1, PHASE2), AtEnd::Loop);
self.bottom.animate(combine!(PHASE2, PHASE1), AtEnd::Loop);
}
async fn show_hours_minutes(&self, hours: u8, minutes: u8) {
let left_angle = hours_to_degrees(hours);
let right_angle = sixty_to_degrees(minutes);
self.set_angles(left_angle, right_angle).await;
Timer::after(Duration::from_millis(500)).await;
self.bottom.relax();
self.top.relax();
}
async fn show_hours_minutes_indicator(&self, hours: u8, minutes: u8) {
let left_angle = hours_to_degrees(hours);
let right_angle = sixty_to_degrees(minutes);
self.set_angles(left_angle, right_angle).await;
Timer::after(Duration::from_millis(500)).await;
self.bottom.relax();
self.top.relax();
}
async fn show_minutes_seconds(&self, minutes: u8, seconds: u8) {
let left_angle = sixty_to_degrees(minutes);
let right_angle = sixty_to_degrees(seconds);
self.set_angles(left_angle, right_angle).await;
}
async fn set_angles(&self, left_degrees: i32, right_degrees: i32) {
let left_angle =
u16::try_from(right_degrees).expect("servo angles must be between 0 and 180 degrees");
let right_angle =
u16::try_from(left_degrees).expect("servo angles must be between 0 and 180 degrees");
self.bottom.set_degrees(left_angle);
self.top.set_degrees(right_angle);
}
}
#[inline]
fn hours_to_degrees(hours: u8) -> i32 {
assert!((1..=12).contains(&hours));
let normalized_hour = hours % 12;
i32::from(normalized_hour) * 180 / 12
}
#[inline]
fn sixty_to_degrees(value: u8) -> i32 {
assert!(value < 60);
i32::from(value) * 180 / 60
}