powerpic

Replacement board for a Casio CA-53W

/** @file clock.c
 * 
 * This mode implements a clock face that displays the current time.
*/

#include <xc.h>

#include "lib/mode.h"
#include "lib/events.h"
#include "lib/tick.h"
#include "lib/display.h"
#include "lib/buttons.h"
#include "lib/keypad.h"
#include "lib/datetime.h"
#include "lib/backlight.h"

#include "lib/settings.h"
#include "modes/mode_settings.h"

#include "modes/clock.h"


// Alternative mode functions for editing date/time
void clock_edit_start (void);
signed char clock_edit  (unsigned int event);

// Draw functions
void clock_draw_time (time_t *draw_time);
void clock_draw_date (date_t *draw_date);
void clock_draw_weekday (unsigned char weekday);

// Edit helper functions
static void clock_draw_edit (void);
static void clock_edit_next (void);

// Needed variables
static volatile unsigned char clock_fmt = 0; // 24/12 HR time format flag
static datetime_t now;              // Current date and time
static unsigned char date_looksie = 0;     // Flag is set when divde button is down

//
// Main Mode functions
//

void
clock_init (void)
{
    // Default state
    date_looksie = 0;
}

void
clock_start (void)
{
    // Load clock_fmt from settings
    clock_fmt = settings_get(SETTING_CLOCK_FMT);

    // Verify value is in range 0-1
    if (1 < clock_fmt)
    {
        // We default to 24-hour time with unknown values.
        clock_fmt = 0;

        // And reset setting value to 0
        settings_set(SETTING_CLOCK_FMT, 0);
    }

    // Get current time
    datetime_now(&now);

    // Draw time
    clock_draw_time(&now.time);

    // Draw day of week
    clock_draw_weekday(now.date.weekday);

    // Update display
    display_update();

    // Set tickrate to 1 second
    tick_rate_set_sec(1);

    // Wait for current second to elapse
    while (SECONDS == now.time.second)
    {
        tick_counter_reset();
    }
    
    // Increment seconds
    now.time.second = (unsigned char)DEC2BCD((BCD2DEC(now.time.second)+1) % 60);

    // Draw time
    clock_draw_time(&now.time);
}

signed char
clock_run (unsigned int event)
{
    switch (EVENT_TYPE(event))
    {
    case EVENT_TICK:
        // Increment seconds
        now.time.second = (unsigned char)DEC2BCD((BCD2DEC(now.time.second)+1) % 60);
        
        // Sync current time with rtc every minute
        if (0 == now.time.second)
        {
            datetime_time_now(&now.time);

            // We update the date every hour on the hour
            if (0 == now.time.minute)
            {
                datetime_today(&now.date);

                // Also update the weekday display
                clock_draw_weekday(now.date.weekday);
            }
        }

        // If the divide key isn't down, we draw the time
        if (!date_looksie)
        {
            // Draw time
            clock_draw_time(&now.time);
        }
    break;

    case KEYPAD_EVENT_PRESS:
        if (EVENT_DATA(event) == '/')
        {
            // Divide key shows full date
            clock_draw_date(&now.date);

            if (clock_fmt)
            {
                // Clear AM/PM sign if in 12-hour fmt
                display_misc_clear(DISPLAY_MISC_AM);
                display_misc_clear(DISPLAY_MISC_PM);
            }

            // Flag that divide key is down
            date_looksie = 1;
        }

        if (EVENT_DATA(event) == '+')
        {
            backlight_set(BACKLIGHT_ON);
        }
    break;

    case KEYPAD_EVENT_RELEASE:
        if (EVENT_DATA(event) == '/')
        {
            // Draw time on key release
            clock_draw_time(&now.time);

            // Divide key is up
            date_looksie = 0;
        }

        if (EVENT_DATA(event) == '+')
        {
            backlight_set(BACKLIGHT_OFF);
        }
    break;

    case EVENT_BUTTON:
        if (EVENT_DATA(event) == BUTTON_MODE_PRESS)
        {
            // Switch to next mode
            return 1;
        }

        if (EVENT_DATA(event) == BUTTON_ADJ_PRESS)
        {
            // Go into edit mode

            // Set the edit function to be our run function
            clock_mode.run = &clock_edit;

            // Start the edit procedure
            clock_edit_start();
        }
    break;
    
    default:
    break;
    }
    return 0;
}

void
clock_stop (void)
{
    // Clear AM/PM for next mode
    display_misc_clear(DISPLAY_MISC_AM);
    display_misc_clear(DISPLAY_MISC_PM);
}


static datetime_t edit_now;
static unsigned char edit_blink = 0;
static unsigned char edit_position = 0;

//
// Mode edit functions
//

enum {
    POS_TIME,
    // This holds the positions for every character on the time edit screen
    POS_HOUR_TENS,
    POS_HOUR_ONES,
    POS_COLON,
    POS_MIN_TENS,
    POS_MIN_ONES,
    POS_TSPACE,
    POS_SECONDS,     // Second tens and ones are 'edited'(cleared) in one block
    POS_SECONDS_ONES,

    POS_DATE,
    // This holds the positions for every character on the date edit screen
    POS_YEAR_TENS,
    POS_YEAR_ONES,
    POS_DSPACE,
    POS_MONTH_TENS,
    POS_MONTH_ONES,
    POS_DASH,
    POS_DAY_TENS,
    POS_DAY_ONES,

    POS_WEEKDAY // The weekday is represented by a string on the secondary

} EDIT_POSITIONS;



/** 
 * Start editing the clock.
*/
void
clock_edit_start (void)
{
    // Blink off the bat
    edit_blink = 1;

    // Always start editing the time seconds place
    edit_position = POS_SECONDS;

    // Get the current time
    datetime_now(&edit_now);

    // Set our edit tickrate to 1/2 a sec for blinking the current character.
    tick_rate_set_ms(500);

    // Draw time
    clock_draw_edit();
    
    // Clear seconday display of weekday
    display_secondary_clear(0);
}

/**
 * Main clock editing thread.
*/
signed char
clock_edit (unsigned int event)
{
    if (EVENT_TYPE(event) == EVENT_TICK)
    {
        if (edit_blink)
        {
            // We are currently blinking the selected character position.

            if (edit_position == POS_WEEKDAY)
            {
                // When we are editing the weekday, flash the numerical value
                display_secondary_clear(0);
                display_secondary_character(1, (edit_now.date.weekday >> 4));
                display_secondary_character(2, (edit_now.date.weekday & 0x0F));
            }
            else if (edit_position > POS_DATE)
            {
                // We need to subtract POS_DATE from the edit position to get
                // the actual display character position.
                display_primary_character((signed char)
                    (edit_position - POS_DATE), 
                    '_'
                );
            }
            else
            {
                // If we are editing time the positions work out correctly.
                display_primary_character((signed char)edit_position, '_');

                if (POS_SECONDS == edit_position)
                {
                    // Blink both second characters
                    display_primary_character(POS_SECONDS_ONES, '_');
                }
            }

            // Update display next tick
            edit_blink = 0;
        }
        else
        {
            // Update seconds
            edit_now.time.second = SECONDS;

            // Draw edited time or date
            clock_draw_edit();

            // Blink next tick
            edit_blink = 1;
        }
    }

    if (EVENT_TYPE(event) == EVENT_KEYPAD)
    {
        unsigned char keypress = EVENT_DATA(event);

        if (keypress >= '0' && keypress <= '9')
        {
            // Subtracting 48 gives us a numerical value corresponding to the
            // ascii number character.
            keypress -= 48;

            // Set alarm digit based on edit_position
            switch (edit_position)
            {
                case POS_HOUR_TENS:     // Hour Tens
                    edit_now.time.hour = (unsigned char)((edit_now.time.hour & 0x0F) | (keypress << 4));
                break;
                case POS_HOUR_ONES:     // Hour Ones
                    edit_now.time.hour = ((edit_now.time.hour & 0xF0) | (keypress));
                break;

                case POS_MIN_TENS:     // Minute Tens
                    edit_now.time.minute = (unsigned char)((edit_now.time.minute & 0x0F) | (keypress << 4));
                break;
                case POS_MIN_ONES:     // Minute Ones
                    edit_now.time.minute = ((edit_now.time.minute & 0xF0) | (keypress));
                break;

                case POS_YEAR_TENS:
                    edit_now.date.year = (unsigned char)((edit_now.date.year & 0x0F) | (keypress << 4));
                break;
                case POS_YEAR_ONES:
                    edit_now.date.year = ((edit_now.date.year & 0xF0) | keypress);
                break;

                case POS_MONTH_TENS:
                    edit_now.date.month = (unsigned char)((edit_now.date.month & 0x0F) | (keypress << 4));
                break;
                case POS_MONTH_ONES:
                    edit_now.date.month = ((edit_now.date.month & 0xF0) | keypress);
                break;

                case POS_DAY_TENS:
                    edit_now.date.day = (unsigned char)((edit_now.date.day & 0x0F) | (keypress << 4));
                break;
                case POS_DAY_ONES:
                    edit_now.date.day = ((edit_now.date.day & 0xF0) | keypress);
                break;

                case POS_WEEKDAY:
                    edit_now.date.weekday = keypress;
                break;

                case POS_SECONDS:     // Seconds
                    if (0 == keypress)
                    {
                        // Clear seconds to 00 if 0 is pressed.
                        datetime_time_now(&now.time);
                        now.time.second = 0;
                        datetime_time_set(&now.time);
                    }
                break;
            }

            if (POS_SECONDS != edit_position)
            {
                // Advance to the next position,
                // except when the seconds are cleared
                clock_edit_next();
            }

            // Redraw time for every press
            clock_draw_edit();
        }

        if (keypress == '+')
        {
            // Plus sign toggles 12/24 hour fmt
            if (clock_fmt)
            {
                clock_fmt = 0;
            }
            else
            {
                clock_fmt = 1;
            }
        }
    }

    if (EVENT_TYPE(event) == EVENT_BUTTON)
    {
        if (EVENT_DATA(event) == BUTTON_MODE_PRESS)
        {
            // Press of the mode button advances the edit position
            clock_edit_next();
        }

        if (EVENT_DATA(event) == BUTTON_ADJ_PRESS)
        {
            // Press of the adj button saves the time and returns to the clock.

            // Set new time with current seconds
            edit_now.time.second = SECONDS;
            datetime_set(&edit_now);

            // Set to 0 to signify we aren't editing any position.
            // We use this in the clock_draw_time function to determine whether
            // to draw a 0 in the tens place if the hour is < 10 AND we are in 12HR mode
            edit_position = 0;

            // Save clock_fmt to EEPROM if its changed.
            if (settings_get(SETTING_CLOCK_FMT) != clock_fmt)
            {
                settings_set(SETTING_CLOCK_FMT, clock_fmt);
            }

            // Set main run thread function
            clock_mode.run = &clock_run;

            // Start the clock
            clock_start();
        }
    }

    return 0;
}


//
// Draw functions
//

/**
 * Draw the given time to the display.
*/
void
clock_draw_time (time_t *draw_time)
{
    // Draw second
    display_primary_character(8, (draw_time->second & 0x0F));
    display_primary_character(7, (draw_time->second >> 4));

    // Clear space between minutes and seconds
    display_primary_clear(6);

    // Draw minute
    display_primary_character(5, (draw_time->minute & 0x0F));
    display_primary_character(4, (draw_time->minute >> 4));

    // Colon
    display_period(DISPLAY_PERIOD_COLON);

    // Draw hour
    if (clock_fmt)
    {
        // 12-hour format

        if (0x00 == draw_time->hour)
        {
            // Midnght - 12:00 AM
            display_primary_character(1, 1);
            display_primary_character(2, 2);
            display_misc(DISPLAY_MISC_AM);
            display_misc_clear(DISPLAY_MISC_PM);
        }
        else if (0x12 == draw_time->hour)
        {
            // Noon - 12:00 PM
            display_primary_character(1, 1);
            display_primary_character(2, 2);
            display_misc(DISPLAY_MISC_PM);
            display_misc_clear(DISPLAY_MISC_AM);
        }
        else if (0x12 < draw_time->hour)
        {
            // PM

            // Convert hour to 24-hour numerical format.
            unsigned char hour_num = BCD2DEC(draw_time->hour);

            // Convert hour back to 12-hour bcd format.
            hour_num = (unsigned char)DEC2BCD(hour_num - 12);

            if ((hour_num >> 4) || edit_position)
            {
                // Only display digits > 0 OR if we are editing a position
                display_primary_character(1, (hour_num >> 4));
            }
            else
            {
                // Clear digit instead of displaying 0
                display_primary_clear(1);
            }
            display_primary_character(2, (hour_num & 0x0F));
            display_misc(DISPLAY_MISC_PM);
            display_misc_clear(DISPLAY_MISC_AM);
        }
        else
        {
            // AM
            if ((draw_time->hour >> 4) || edit_position)
            {
                // Only display digits > 0 OR if we are editing a position
                display_primary_character(1, (draw_time->hour >> 4));
            }
            else
            {
                // Clear digit instead of displaying 0
                display_primary_clear(1);
            }
            display_primary_character(2, (draw_time->hour & 0x0F));
            display_misc(DISPLAY_MISC_AM);
            display_misc_clear(DISPLAY_MISC_PM);
        }
    }
    else
    {
        // 24-hour format
        display_primary_character(2, (draw_time->hour & 0x0F));
        display_primary_character(1, (draw_time->hour >> 4));

        // Clear AM/PM
        display_misc_clear(DISPLAY_MISC_AM);
        display_misc_clear(DISPLAY_MISC_PM);
    }
}


/**
 * Draw the given date to the display.
*/
void
clock_draw_date (date_t *draw_date)
{
    // Draw year (20XX)
    display_primary_character(1, (draw_date->year >> 4));
    display_primary_character(2, (draw_date->year & 0x0F));

    // Clear space between year and month
    display_primary_clear(3);
    // Also clear colon in case a time was being displayed
    display_period_clear(DISPLAY_PERIOD_COLON);

    // Draw month
    display_primary_character(4, (draw_date->month >> 4));
    display_primary_character(5, (draw_date->month & 0x0F));

    // Draw dash between month and day
    display_primary_character(6, '-');

    // Draw day
    display_primary_character(7, (draw_date->day >> 4));
    display_primary_character(8, (draw_date->day & 0x0F));

    // Draw weekday
    clock_draw_weekday(draw_date->weekday);
}

/**
 * Draw the given day of the week to the display.
 * 
 * This draws a two character string to the secondary display
 * representing the DOW.
*/
void
clock_draw_weekday (unsigned char weekday)
{
    // Clear secondary display
    display_secondary_clear(0);

    switch (weekday)
    {
    case 0:
        // Sunday
        display_secondary_string(1, "SU");
    break;

    case 1:
        // Monday
        display_secondary_segments(1, 0b1010110111);
        display_secondary_character(2, 'O');
    break;

    case 2:
        // Tuesday
        display_secondary_segments(1, 0b0010110001);
        display_secondary_character(2, 'U');
    break;

    case 3:
        // Wednesday
        display_secondary_segments(1, 0b1100111110);
        display_secondary_character(2, 'E');
    break;

    case 4:
        // Thursday
        display_secondary_segments(1, 0b0010110001);
        display_secondary_character(2, 'H');
    break;

    case 5:
        // Friday
        display_secondary_string(1, "Fr");
        // display_secondary_character(1, 'F');
        // display_secondary_segments(2, 0b0011110111);
    break;

    case 6:
        // Saturday
        display_secondary_string(1, "SA");
    break;
    }
}


//
// Edit helper functions
//

/**
 * Draw the time or date that's being edited.
*/
static void
clock_draw_edit (void)
{
    if (edit_position > POS_DATE)
    {
        // We're editing the date.
        clock_draw_date(&edit_now.date);
    }
    else
    {
        // We're editing the time.
        clock_draw_time(&edit_now.time);
    }
}


/**
 * Advance the edit position to the next position
*/
static void
clock_edit_next (void)
{
    // Press of the mode button advances the edit position
    switch (edit_position)
    {
        // All tens positions are immediatly followed by a ones position
        // The weekday also immediatly follows the day ones.
        case POS_HOUR_TENS:
        case POS_MIN_TENS:
        case POS_YEAR_TENS:
        case POS_MONTH_TENS:
        case POS_DAY_TENS:
        case POS_DAY_ONES:
            edit_position++;
        break;

        // A subset of the ones places just need to skip a character position
        case POS_HOUR_ONES:     // Skip the colon position
        case POS_YEAR_ONES:     // Skip the space after the year
        case POS_MONTH_ONES:    // Skip the dash between month-day
            edit_position += 2;
        break;

        // Start editing the date after the minute
        case POS_MIN_ONES:
            edit_position = POS_YEAR_TENS;
        break;

        // Skip to seconds after the weekday
        case POS_WEEKDAY:
            edit_position = POS_SECONDS;

            // Clear weekday
            // TODO: This isnt really a good spot for this, but it works.
            display_secondary_clear(0);
        break;

        // Seconds is the last position and wraps back to the hour
        case POS_SECONDS:
            edit_position = POS_HOUR_TENS;
        break;
    }
}


// EOF //