diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b0..40b5be35 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/simple_calculator_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 35571109..55d4124b 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "simple_calculator_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c new file mode 100644 index 00000000..95804ced --- /dev/null +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -0,0 +1,415 @@ +/* + * MIT License + * + * Copyright (c) 2024 Patrick McGuire + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "simple_calculator_face.h" + +void simple_calculator_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(simple_calculator_state_t)); + memset(*context_ptr, 0, sizeof(simple_calculator_state_t)); + } +} + +void simple_calculator_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + simple_calculator_state_t *state = (simple_calculator_state_t *)context; + state->placeholder = PLACEHOLDER_ONES; + state->mode = MODE_ENTERING_FIRST_NUM; + movement_request_tick_frequency(4); +} + +static void increment_placeholder(calculator_number_t *number, calculator_placeholder_t placeholder) { + uint8_t *digits[] = { + &number->hundredths, + &number->tenths, + &number->ones, + &number->tens, + &number->hundreds, + &number->thousands + }; + *digits[placeholder] = (*digits[placeholder] + 1) % 10; +} + +static float convert_to_float(calculator_number_t number) { + float result = 0.0; + + // Add the whole number portion + result += number.thousands * 1000.0f; + result += number.hundreds * 100.0f; + result += number.tens * 10.0f; + result += number.ones * 1.0f; + + // Add the fractional portion + result += number.tenths * 0.1f; + result += number.hundredths * 0.01f; + + // Round to nearest hundredth + result = roundf(result * 100) / 100; + return result; +} + +static char* update_display_number(calculator_number_t *number, char *display_string, uint8_t which_num) { + char sign = ' '; + if (number->negative) sign = '-'; + sprintf(display_string, "CA%d%c%d%d%d%d%d%d", + which_num, + sign, + number->thousands, + number->hundreds, + number->tens, + number->ones, + number->tenths, + number->hundredths); + + return display_string; +} + +static void set_operation(simple_calculator_state_t *state) { + switch (state->operation) { + case 0: + watch_display_string(" Add", 0); + break; + case 1: + watch_display_string(" sub", 0); + break; + case 2: + watch_display_string(" n&ul", 0); + break; + case 3: + watch_display_string(" div", 0); + break; + case 4: + watch_display_string(" root", 0); + break; + case 5: + watch_display_string(" pow", 0); + break; + } +} + +static void cycle_operation(simple_calculator_state_t *state) { + state->operation = (state->operation + 1) % OPERATIONS_COUNT; // Assuming there are 6 operations + printf("Current operation: %d\n", state->operation); // For debugging +} + + +static calculator_number_t convert_to_string(float number) { + calculator_number_t result; + + // Handle negative numbers + bool is_negative = (number < 0); + if (is_negative) { + number = -number; + result.negative = true; + } + + int int_part = (int)number; + float decimal_part_float = ((number - int_part) * 100); // two decimal places + printf("decimal_part_float = %f\n", decimal_part_float); //For debugging + int decimal_part = round(decimal_part_float); + printf("decimal_part = %d\n", decimal_part); //For debugging + + result.thousands = int_part / 1000 % 10; + result.hundreds = int_part / 100 % 10; + result.tens = int_part / 10 % 10; + result.ones = int_part % 10; + + result.tenths = decimal_part / 10 % 10; + result.hundredths = decimal_part % 10; + + return result; +} + +static void reset_to_zero(calculator_number_t *number) { + number->negative = false; + number->hundredths = 0; + number->tenths = 0; + number->ones = 0; + number->tens = 0; + number->hundreds = 0; + number->thousands = 0; +} + +static void set_number(calculator_number_t *number, calculator_placeholder_t placeholder, char *display_string, char *temp_display_string, movement_event_t event, uint8_t which_num) { + uint8_t display_index; + // Update display string with current number + update_display_number(number, display_string, which_num); + + // Copy the updated display string to a temporary buffer + strcpy(temp_display_string, display_string); + + // Determine the display index based on the placeholder + display_index = 9 - placeholder; + + // Blink selected placeholder + // Check if `event.subsecond` is even + if (event.subsecond % 2 == 0) { + // Replace the character at the index corresponding to the current placeholder with a space + temp_display_string[display_index] = ' '; + } + + // Display the (possibly modified) string + watch_display_string(temp_display_string, 0); +} + +static void view_results(simple_calculator_state_t *state, char *display_string) { + float first_num_float, second_num_float, result_float = 0.0f; // For arithmetic operations + // Convert the numbers to float + first_num_float = convert_to_float(state->first_num); + if (state->first_num.negative) first_num_float = first_num_float * -1; + printf("first_num_float = %f\n", first_num_float); // For debugging // For debugging + second_num_float = convert_to_float(state->second_num); + if (state->second_num.negative) second_num_float = second_num_float * -1; + printf("second_num_float = %f\n", second_num_float); // For debugging + + // Perform the calculation based on the selected operation + switch (state->operation) { + case OP_ADD: + result_float = first_num_float + second_num_float; + break; + case OP_SUB: + result_float = first_num_float - second_num_float; + break; + case OP_MULT: + result_float = first_num_float * second_num_float; + break; + case OP_DIV: + if (second_num_float != 0) { + result_float = first_num_float / second_num_float; + } else { + state->mode = MODE_ERROR; + return; + } + break; + case OP_ROOT: + if (first_num_float >= 0) { + result_float = sqrtf(first_num_float); + } else { + state->mode = MODE_ERROR; + return; + } + break; + case OP_POWER: + result_float = powf(first_num_float, second_num_float); // Power operation + break; + default: + result_float = 0.0f; + break; + } + + if (result_float > 9999.99 || result_float < -9999.99) { + state->mode = MODE_ERROR; + return; + } + + result_float = roundf(result_float * 100.0f) / 100.0f; // Might not be needed + printf("result as float = %f\n", result_float); // For debugging + + // Convert the float result to a string + state->result = convert_to_string(result_float); + + // Update the display with the result + update_display_number(&state->result, display_string, 3); + watch_display_string(display_string, 0); +} + +static void reset_from_error(simple_calculator_state_t *state) { + reset_to_zero(&state->first_num); + reset_to_zero(&state->second_num); + state->mode = MODE_ENTERING_FIRST_NUM; +} +bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + simple_calculator_state_t *state = (simple_calculator_state_t *)context; + char display_string[10]; + char temp_display_string[10]; // Temporary buffer for blinking effect + + switch (event.event_type) { + case EVENT_ACTIVATE: + break; + + case EVENT_TICK: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + set_number(&state->first_num, + state->placeholder, + display_string, + temp_display_string, + event, + 1); + break; + + case MODE_CHOOSING: + set_operation(state); + break; + + case MODE_ENTERING_SECOND_NUM: + // If doing a square root calculation, skip to results + if (state->operation == 4) { + state->mode = MODE_VIEW_RESULTS; + // otherwise, set the second number + } else { + set_number(&state->second_num, + state->placeholder, + display_string, + temp_display_string, + event, + 2); + } + break; + + case MODE_VIEW_RESULTS: + view_results(state, display_string); + break; + case MODE_ERROR: + watch_display_string("CA Error ", 0); + break; + } + break; + + case EVENT_LIGHT_BUTTON_DOWN: + break; + + case EVENT_LIGHT_BUTTON_UP: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + case MODE_ENTERING_SECOND_NUM: + // Move to the next placeholder when the light button is pressed + state->placeholder = (state->placeholder + 1) % MAX_PLACEHOLDERS; // Loop back to the start after PLACEHOLDER_THOUSANDS + break; + case MODE_CHOOSING: + cycle_operation(state); + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_LIGHT_LONG_PRESS: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + // toggle negative on state->first_num + state->first_num.negative = !state->first_num.negative; + break; + case MODE_ENTERING_SECOND_NUM: + // toggle negative on state->second_num + state->first_num.negative = !state->first_num.negative; + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_CHOOSING: + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_ALARM_BUTTON_UP: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + // Increment the digit in the current placeholder + increment_placeholder(&state->first_num, state->placeholder); + update_display_number(&state->first_num, display_string, 1); + break; + case MODE_CHOOSING: + // Confirm and select the current operation + printf("Selected operation: %d\n", state->operation); // For debugging + state->mode = MODE_ENTERING_SECOND_NUM; + break; + case MODE_ENTERING_SECOND_NUM: + // Increment the digit in the current placeholder + increment_placeholder(&state->second_num, state->placeholder); + update_display_number(&state->second_num, display_string, 2); + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_ALARM_LONG_PRESS: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + reset_to_zero(&state->first_num); + break; + case MODE_ENTERING_SECOND_NUM: + reset_to_zero(&state->second_num); + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_CHOOSING: + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_MODE_BUTTON_DOWN: + break; + + case EVENT_MODE_BUTTON_UP: + if (state->mode == MODE_ERROR) { + reset_from_error(state); + } else { + state->placeholder = PLACEHOLDER_ONES; + state->mode = (state->mode + 1) % 4; + if (state->mode == MODE_ENTERING_FIRST_NUM) { + state->first_num = state->result; + reset_to_zero(&state->second_num); + } + printf("Current mode: %d\n", state->mode); // For debugging + } + break; + + case EVENT_MODE_LONG_PRESS: + movement_move_to_next_face(); + break; + + case EVENT_TIMEOUT: + movement_request_tick_frequency(1); + movement_move_to_face(0); + break; + + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void simple_calculator_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + movement_request_tick_frequency(1); +} + diff --git a/movement/watch_faces/complication/simple_calculator_face.h b/movement/watch_faces/complication/simple_calculator_face.h new file mode 100644 index 00000000..1f77dc08 --- /dev/null +++ b/movement/watch_faces/complication/simple_calculator_face.h @@ -0,0 +1,145 @@ +/* + * MIT License + * + * Copyright (c) 2024 Patrick McGuire + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SIMPLE_CALCULATOR_FACE_H_ +#define SIMPLE_CALCULATOR_FACE_H_ + +#include "movement.h" + +/* + * Simple Calculator + * + * How to use: + * + * Flow: + * Enter first number -> Select operator -> Enter second number -> View Results + * + * How to read the display: + * - "CA" is displayed at the top to tell you that you're in the CAlculator + * - The top-right digit (1, 2, or 3) lets you know whether you're entering the + * first number (1), entering the second number (2), or viewing the results (3). + * - To the right of the top-right digit will show the number's sign. If the + * number is negative, a "-" will be displayed, otherwise it is empty. + * - The 4 large digits to the left are whole numbers and the 2 smaller digits + * on the right are the tenths and hundredths decimal places. + * + * Entering the first number: + * - Press ALARM to increment the selected (blinking) digit + * - Press LIGHT to move to the next placeholder + * - LONG PRESS the LIGHT button to toggle the number's sign to make it + * negative + * - LONG PRESS the ALARM button to reset the number to 0 + * - Press MODE to proceed to selecting the operator + * + * Selecting the operator: + * - Press the LIGHT button to cycle through available operators. They are: + * + Add + * - Subtract + * * Multiply + * / Divide + * sqrtf() Square root + * powf() Power (exponent calculation) + * - Press MODE or ALARM to proceed to entering the second number + * + * Entering the second number: + * - Everything is the same as setting the first number except that pressing + * MODE here will proceed to viewing the results + * + * Viewing the results: + * - Pressing MODE will start a new calculation with the result as the first + * number. (LONG PRESS ALARM to reset the value to 0) + * + * Errors: + * - An error will be triggered if the result is not able to be displayed, that + * is, if the value is greater than 9,999.99 or less than -9,999.99. + * - An error will also be triggered if an impossible operation is selected, + * for instance trying to divide by 0 or get the square root of a negative + * number. + * - Exit error mode and start over with any button press. + * + */ + +#define OPERATIONS_COUNT 6 +#define MAX_PLACEHOLDERS 6 + +typedef struct { + bool negative; + uint8_t hundredths; + uint8_t tenths; + uint8_t ones; + uint8_t tens; + uint8_t hundreds; + uint8_t thousands; +} calculator_number_t; + +typedef enum { + PLACEHOLDER_HUNDREDTHS, + PLACEHOLDER_TENTHS, + PLACEHOLDER_ONES, + PLACEHOLDER_TENS, + PLACEHOLDER_HUNDREDS, + PLACEHOLDER_THOUSANDS +} calculator_placeholder_t; + +typedef enum { + OP_ADD, + OP_SUB, + OP_MULT, + OP_DIV, + OP_ROOT, + OP_POWER, +} calculator_operation_t; + +typedef enum { + MODE_ENTERING_FIRST_NUM, + MODE_CHOOSING, + MODE_ENTERING_SECOND_NUM, + MODE_VIEW_RESULTS, + MODE_ERROR +} calculator_mode_t; + +typedef struct { + calculator_number_t first_num; + calculator_number_t second_num; + calculator_number_t result; + calculator_operation_t operation; + calculator_mode_t mode; + calculator_placeholder_t placeholder; +} simple_calculator_state_t; + +void simple_calculator_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void simple_calculator_face_activate(movement_settings_t *settings, void *context); +bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void simple_calculator_face_resign(movement_settings_t *settings, void *context); + +#define simple_calculator_face ((const watch_face_t){ \ + simple_calculator_face_setup, \ + simple_calculator_face_activate, \ + simple_calculator_face_loop, \ + simple_calculator_face_resign, \ + NULL, \ +}) + +#endif // SIMPLE_CALCULATOR_FACE_H_ +