rtc-hal 0.4.0

Platform-agnostic hardware abstraction for Real Time Clock peripherals.
//! # RTC Alarm
//!
//! This module provides a generic interface for configuring RTC alarms.
//!
//! An [`AlarmConfig`] describes which date and time fields should
//! match. Individual RTC drivers translate this configuration to their
//! hardware.
//!
//! # Examples
//!
//! An alarm every day at 07:30:
//!
//! ```
//! use rtc_hal::alarm::AlarmBuilder;
//!
//! let alarm = AlarmBuilder::at(7, 30).build()?;
//! ```
//!
//! An alarm every Monday at 07:30:
//!
//! ```
//! use rtc_hal::alarm::AlarmBuilder;
//! use rtc_hal::datetime::Weekday;
//!
//! let alarm = AlarmBuilder::at(7, 30)
//!     .on(Weekday::Monday)
//!     .build()?;
//! ```
//!
//! An alarm on the 15th of every month at 07:30:
//!
//! ```
//! use rtc_hal::alarm::AlarmBuilder;
//!
//! let alarm = AlarmBuilder::at(7, 30)
//!     .on_day_of_month(15)
//!     .build()?;
//! ```

use crate::datetime::Weekday;
use crate::error::ErrorType;

/// An RTC alarm configuration.
///
/// A set field must match. A field set to `None` is ignored.
///
/// The hour uses 24-hour format (`0..=23`).
///
/// Individual RTC drivers are responsible for determining whether the
/// requested alarm configuration is supported by their hardware.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AlarmConfig {
    second: Option<u8>,
    minute: Option<u8>,
    hour: Option<u8>,
    weekday: Option<Weekday>,
    day_of_month: Option<u8>,
    month: Option<u8>,
}

/// Errors that can occur when building an alarm configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AlarmConfigError {
    /// The seconds value is outside the valid range of `0..=59`.
    InvalidSecond,

    /// The minutes value is outside the valid range of `0..=59`.
    InvalidMinute,

    /// The hour value is outside the valid range of `0..=23`.
    InvalidHour,

    /// The day of the month is outside the valid range of `1..=31`.
    InvalidDayOfMonth,

    /// The month value is outside the valid range of `1..=12`.
    InvalidMonth,
}

impl AlarmConfig {
    fn validate(&self) -> Result<(), AlarmConfigError> {
        if let Some(second) = self.second
            && second > 59
        {
            return Err(AlarmConfigError::InvalidSecond);
        }

        if let Some(minute) = self.minute
            && minute > 59
        {
            return Err(AlarmConfigError::InvalidMinute);
        }

        if let Some(hour) = self.hour
            && hour > 23
        {
            return Err(AlarmConfigError::InvalidHour);
        }

        if let Some(day) = self.day_of_month
            && !(1..=31).contains(&day)
        {
            return Err(AlarmConfigError::InvalidDayOfMonth);
        }

        if let Some(month) = self.month
            && !(1..=12).contains(&month)
        {
            return Err(AlarmConfigError::InvalidMonth);
        }

        Ok(())
    }

    /// Returns the configured second, if any.
    pub const fn second(&self) -> Option<u8> {
        self.second
    }

    /// Returns the configured minute, if any.
    pub const fn minute(&self) -> Option<u8> {
        self.minute
    }

    /// Returns the configured hour, if any.
    pub const fn hour(&self) -> Option<u8> {
        self.hour
    }

    /// Returns the configured weekday, if any.
    pub const fn weekday(&self) -> Option<Weekday> {
        self.weekday
    }

    /// Returns the configured day of the month, if any.
    pub const fn day_of_month(&self) -> Option<u8> {
        self.day_of_month
    }

    /// Returns the configured month, if any.
    pub const fn month(&self) -> Option<u8> {
        self.month
    }
}

/// Builder for creating an [`AlarmConfig`].
///
/// Use the builder methods to configure the alarm, then call `build`
/// to validate the configuration and obtain an [`AlarmConfig`].
pub struct AlarmBuilder {
    config: AlarmConfig,
}

impl AlarmBuilder {
    /// Creates an alarm matching the specified hour and minute.
    ///
    /// The hour is specified in 24-hour format (`0..=23`).
    ///
    /// # Example
    ///
    /// ```
    /// # use rtc_hal::alarm::AlarmBuilder;
    /// let alarm = AlarmBuilder::at(7, 30).build()?;
    /// ```
    pub const fn at(hour: u8, minute: u8) -> Self {
        let config = AlarmConfig {
            hour: Some(hour),
            minute: Some(minute),
            second: None,
            weekday: None,
            day_of_month: None,
            month: None,
        };
        Self { config }
    }

    /// Creates an alarm matching the specified hour, minute, and second.
    ///
    /// The hour is specified in 24-hour format (`0..=23`).
    ///
    /// # Example
    ///
    /// ```
    /// # use rtc_hal::alarm::AlarmBuilder;
    /// let alarm = AlarmBuilder::at_hms(7, 30, 15).build()?;
    /// ```
    pub const fn at_hms(hour: u8, minute: u8, second: u8) -> Self {
        let config = AlarmConfig {
            hour: Some(hour),
            minute: Some(minute),
            second: Some(second),
            weekday: None,
            day_of_month: None,
            month: None,
        };
        Self { config }
    }

    /// Creates an alarm matching only the specified hour.
    ///
    /// The hour is specified in 24-hour format (`0..=23`).
    pub const fn at_hour(hour: u8) -> Self {
        let config = AlarmConfig {
            hour: Some(hour),
            minute: None,
            second: None,
            weekday: None,
            day_of_month: None,
            month: None,
        };
        Self { config }
    }

    /// Creates an alarm matching only the specified minute.
    pub const fn at_minute(minute: u8) -> Self {
        let config = AlarmConfig {
            hour: None,
            minute: Some(minute),
            second: None,
            weekday: None,
            day_of_month: None,
            month: None,
        };
        Self { config }
    }

    /// Creates an alarm matching only the specified second.
    pub const fn at_second(second: u8) -> Self {
        let config = AlarmConfig {
            hour: None,
            minute: None,
            second: Some(second),
            weekday: None,
            day_of_month: None,
            month: None,
        };
        Self { config }
    }

    /// Makes the alarm match on the specified weekday.
    pub const fn on(mut self, weekday: Weekday) -> Self {
        self.config.weekday = Some(weekday);
        self
    }

    /// Makes the alarm match on the specified day of the month.
    pub const fn on_day_of_month(mut self, day: u8) -> Self {
        self.config.day_of_month = Some(day);
        self
    }

    /// Makes the alarm match in the specified month.
    ///
    /// The month uses the same `1..=12` representation as
    /// [`crate::datetime::DateTime`].
    pub const fn on_month(mut self, month: u8) -> Self {
        self.config.month = Some(month);
        self
    }

    /// Builds and validates the alarm configuration.
    pub fn build(self) -> Result<AlarmConfig, AlarmConfigError> {
        self.config.validate()?;
        Ok(self.config)
    }
}

/// Provides alarm functionality for an RTC.
pub trait Alarm: ErrorType {
    /// Identifies an alarm supported by the RTC.
    type AlarmId;

    /// Configures an alarm without enabling it.
    ///
    /// Returns an error if the configuration is not supported by the RTC.
    fn set_alarm(&mut self, id: Self::AlarmId, alarm: AlarmConfig) -> Result<(), Self::Error>;

    /// Enables a configured alarm.
    fn enable_alarm(&mut self, id: Self::AlarmId) -> Result<(), Self::Error>;

    /// Disables an alarm without removing its configuration.
    fn disable_alarm(&mut self, id: Self::AlarmId) -> Result<(), Self::Error>;

    /// Returns whether the alarm flag is set.
    fn alarm_triggered(&mut self, id: Self::AlarmId) -> Result<bool, Self::Error>;

    /// Clears the alarm flag without disabling the alarm.
    fn clear_alarm(&mut self, id: Self::AlarmId) -> Result<(), Self::Error>;
}

/// Blanket implementation for `&mut T`.
impl<T: Alarm + ?Sized> Alarm for &mut T {
    type AlarmId = T::AlarmId;

    #[inline]
    fn set_alarm(&mut self, id: Self::AlarmId, alarm: AlarmConfig) -> Result<(), Self::Error> {
        T::set_alarm(self, id, alarm)
    }

    #[inline]
    fn enable_alarm(&mut self, id: Self::AlarmId) -> Result<(), Self::Error> {
        T::enable_alarm(self, id)
    }

    #[inline]
    fn disable_alarm(&mut self, id: Self::AlarmId) -> Result<(), Self::Error> {
        T::disable_alarm(self, id)
    }

    #[inline]
    fn alarm_triggered(&mut self, id: Self::AlarmId) -> Result<bool, Self::Error> {
        T::alarm_triggered(self, id)
    }

    #[inline]
    fn clear_alarm(&mut self, id: Self::AlarmId) -> Result<(), Self::Error> {
        T::clear_alarm(self, id)
    }
}