Add Tachymeter face (#165)
* Create template for tachymeter * Basic working tachymeter * Improve UI for running and results * Allow editing distance * Alarm long-press to enter and exit editing mode * Improve light button behaviour * Set distance digit wise. * Use 250ms resolution for total time * Improve comments * Bugfix: Show distance when running and face becomes active * Update `%d` to `%lu` in `sprintf` call for `uint32_t` * Ignore Alarm button (Up and Long-press) when showing results * Improve GUI when running and face gets activated * Change speed indicator from `/H` to `/h` * silence warnings in tachymeter face Co-authored-by: joeycastillo <joeycastillo@utexas.edu>
This commit is contained in:
		
							parent
							
								
									18de75be5a
								
							
						
					
					
						commit
						fee6145e4d
					
				| @ -77,6 +77,7 @@ SRCS += \ | |||||||
|   ../watch_faces/complication/ratemeter_face.c \
 |   ../watch_faces/complication/ratemeter_face.c \
 | ||||||
|   ../watch_faces/complication/rpn_calculator_alt_face.c \
 |   ../watch_faces/complication/rpn_calculator_alt_face.c \
 | ||||||
|   ../watch_faces/complication/stock_stopwatch_face.c \
 |   ../watch_faces/complication/stock_stopwatch_face.c \
 | ||||||
|  |   ../watch_faces/complication/tachymeter_face.c \
 | ||||||
| # New watch faces go above this line.
 | # 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.
 | # Leave this line at the bottom of the file; it has all the targets for making your project.
 | ||||||
|  | |||||||
| @ -62,6 +62,7 @@ | |||||||
| #include "rpn_calculator_alt_face.h" | #include "rpn_calculator_alt_face.h" | ||||||
| #include "weeknumber_clock_face.h" | #include "weeknumber_clock_face.h" | ||||||
| #include "stock_stopwatch_face.h" | #include "stock_stopwatch_face.h" | ||||||
|  | #include "tachymeter_face.h" | ||||||
| // New includes go above this line.
 | // New includes go above this line.
 | ||||||
| 
 | 
 | ||||||
| #endif // MOVEMENT_FACES_H_
 | #endif // MOVEMENT_FACES_H_
 | ||||||
|  | |||||||
							
								
								
									
										268
									
								
								movement/watch_faces/complication/tachymeter_face.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								movement/watch_faces/complication/tachymeter_face.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,268 @@ | |||||||
|  | /*
 | ||||||
|  |  * MIT License | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2022 Raymundo Cassani | ||||||
|  |  * | ||||||
|  |  * 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 <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include "tachymeter_face.h" | ||||||
|  | #include "watch_utility.h" | ||||||
|  | 
 | ||||||
|  | static uint32_t _distance_from_struct(distance_digits_t dist_digits) { | ||||||
|  |     // distance from digitwise distance
 | ||||||
|  |     uint32_t retval = (dist_digits.thousands * 100000 + | ||||||
|  |                        dist_digits.hundreds  *  10000 + | ||||||
|  |                        dist_digits.tens      *   1000 + | ||||||
|  |                        dist_digits.ones      *    100 + | ||||||
|  |                        dist_digits.tenths    *     10 + | ||||||
|  |                        dist_digits.hundredths);// * 1
 | ||||||
|  |     return retval; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void tachymeter_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(tachymeter_state_t)); | ||||||
|  |         memset(*context_ptr, 0, sizeof(tachymeter_state_t)); | ||||||
|  |         tachymeter_state_t *state = (tachymeter_state_t *)*context_ptr; | ||||||
|  |         // Default distance
 | ||||||
|  |         state->dist_digits.ones = 1; | ||||||
|  |         state->distance = _distance_from_struct(state->dist_digits); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void tachymeter_face_activate(movement_settings_t *settings, void *context) { | ||||||
|  |     (void)settings; | ||||||
|  |     (void)context; | ||||||
|  |     movement_request_tick_frequency(4); // 4Hz
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void _tachymeter_face_distance_lcd(movement_event_t event, tachymeter_state_t *state){ | ||||||
|  |     char buf[11]; | ||||||
|  |     // Distance from digits
 | ||||||
|  |     state->distance = _distance_from_struct(state->dist_digits); | ||||||
|  |     sprintf(buf, "TC %c%06lu", state->running ? ' ' : 'd',  state->distance); | ||||||
|  |     // Blinking display when editing
 | ||||||
|  |     if (state->editing) { | ||||||
|  |         // Blink 'd'
 | ||||||
|  |         if (event.subsecond < 2) { | ||||||
|  |             buf[3] = ' '; | ||||||
|  |         } | ||||||
|  |         // Blink active digit
 | ||||||
|  |         if (event.subsecond % 2) { | ||||||
|  |             buf[state->active_digit + 4] = ' '; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     watch_display_string(buf, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void _tachymeter_face_totals_lcd(tachymeter_state_t *state, bool show_time){ | ||||||
|  |     char buf[15]; | ||||||
|  |     if (!show_time){ | ||||||
|  |         sprintf(buf, "TC %c%6lu", 'h',  state->total_speed); | ||||||
|  |     } else { | ||||||
|  |         sprintf(buf, "TC %c%6lu", 't',  state->total_time); | ||||||
|  |     } | ||||||
|  |     watch_display_string(buf, 0); | ||||||
|  |     if (!show_time){ | ||||||
|  |         // Show '/' besides 'H'
 | ||||||
|  |         watch_set_pixel(0, 9); | ||||||
|  |         watch_set_pixel(0, 10); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool tachymeter_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { | ||||||
|  |     (void)settings; | ||||||
|  |     tachymeter_state_t *state = (tachymeter_state_t *)context; | ||||||
|  |     switch (event.event_type) { | ||||||
|  |         case EVENT_ACTIVATE: | ||||||
|  |             // Show distance in UI
 | ||||||
|  |             if (state->total_time == 0) { | ||||||
|  |                 _tachymeter_face_distance_lcd(event, state); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case EVENT_TICK: | ||||||
|  |             // Show editing distance (blinking)
 | ||||||
|  |             if (state->editing) { | ||||||
|  |                 _tachymeter_face_distance_lcd(event, state); | ||||||
|  |             } | ||||||
|  |             if (!state->running && state->total_time != 0) { | ||||||
|  |             // Display results if finished and not cleared
 | ||||||
|  |                 if (event.subsecond < 2) { | ||||||
|  |                      _tachymeter_face_totals_lcd(state, true); | ||||||
|  |                 } else { | ||||||
|  |                      _tachymeter_face_totals_lcd(state, false); | ||||||
|  |                 } | ||||||
|  |             } else if (state->running){ | ||||||
|  |                 watch_display_string("  ", 2); | ||||||
|  |                 switch (state->animation_state) { | ||||||
|  |                     case 0: | ||||||
|  |                         watch_set_pixel(0, 7); | ||||||
|  |                         break; | ||||||
|  |                     case 1: | ||||||
|  |                         watch_set_pixel(1, 7); | ||||||
|  |                         break; | ||||||
|  |                     case 2: | ||||||
|  |                         watch_set_pixel(2, 7); | ||||||
|  |                         break; | ||||||
|  |                     case 3: | ||||||
|  |                         watch_set_pixel(2, 6); | ||||||
|  |                         break; | ||||||
|  |                     case 4: | ||||||
|  |                         watch_set_pixel(2, 8); | ||||||
|  |                         break; | ||||||
|  |                     case 5: | ||||||
|  |                         watch_set_pixel(0, 8); | ||||||
|  |                         break; | ||||||
|  |                 } | ||||||
|  |                 state->animation_state = (state->animation_state + 1) % 6; | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case EVENT_MODE_BUTTON_UP: | ||||||
|  |             movement_move_to_next_face(); | ||||||
|  |             break; | ||||||
|  |         case EVENT_LIGHT_BUTTON_UP: | ||||||
|  |             if (state->editing){ | ||||||
|  |                 // Go to next digit
 | ||||||
|  |                 state->active_digit = (state->active_digit + 1) % 6; | ||||||
|  |             } else { | ||||||
|  |                 movement_illuminate_led(); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case EVENT_LIGHT_LONG_PRESS: | ||||||
|  |             if (!state->running && !state->editing){ | ||||||
|  |                 if (state->total_time != 0){ | ||||||
|  |                     // Clear results
 | ||||||
|  |                     state->total_time = 0; | ||||||
|  |                     state->total_speed = 0; | ||||||
|  |                 } else { | ||||||
|  |                     // Default distance
 | ||||||
|  |                     state->dist_digits.thousands  = 0; | ||||||
|  |                     state->dist_digits.hundreds   = 0; | ||||||
|  |                     state->dist_digits.tens       = 0; | ||||||
|  |                     state->dist_digits.ones       = 1; | ||||||
|  |                     state->dist_digits.tenths     = 0; | ||||||
|  |                     state->dist_digits.hundredths = 0; | ||||||
|  |                     state->distance = _distance_from_struct(state->dist_digits); | ||||||
|  |                 } | ||||||
|  |             _tachymeter_face_distance_lcd(event, state); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case EVENT_ALARM_BUTTON_UP: | ||||||
|  |             if (!state->running && state->total_time == 0){ | ||||||
|  |                 if (settings->bit.button_should_sound && !state->editing) { | ||||||
|  |                     watch_buzzer_play_note(BUZZER_NOTE_C8, 50); | ||||||
|  |                 } | ||||||
|  |                 if (!state->editing) { | ||||||
|  |                     // Start running
 | ||||||
|  |                     state->running = true; | ||||||
|  |                     state->start_seconds = watch_rtc_get_date_time(); | ||||||
|  |                     state->start_subsecond = event.subsecond; | ||||||
|  |                     state->total_time = 0; | ||||||
|  |                 } else { | ||||||
|  |                     // Alarm button to increase active digit
 | ||||||
|  |                     switch (state->active_digit) { | ||||||
|  |                         case 0: | ||||||
|  |                             state->dist_digits.thousands = (state->dist_digits.thousands + 1) % 10; | ||||||
|  |                             break; | ||||||
|  |                         case 1: | ||||||
|  |                             state->dist_digits.hundreds = (state->dist_digits.hundreds + 1) % 10; | ||||||
|  |                             break; | ||||||
|  |                         case 2: | ||||||
|  |                             state->dist_digits.tens = (state->dist_digits.tens + 1) % 10; | ||||||
|  |                             break; | ||||||
|  |                         case 3: | ||||||
|  |                             state->dist_digits.ones = (state->dist_digits.ones + 1) % 10; | ||||||
|  |                             break; | ||||||
|  |                         case 4: | ||||||
|  |                             state->dist_digits.tenths = (state->dist_digits.tenths + 1) % 10; | ||||||
|  |                             break; | ||||||
|  |                         case 5: | ||||||
|  |                             state->dist_digits.hundredths = (state->dist_digits.hundredths + 1) % 10; | ||||||
|  |                             break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else if (state->running) { | ||||||
|  |                 if (settings->bit.button_should_sound && !state->editing) { | ||||||
|  |                     watch_buzzer_play_note(BUZZER_NOTE_C8, 50); | ||||||
|  |                 } | ||||||
|  |                 // Stop running
 | ||||||
|  |                 state->running = false; | ||||||
|  |                 watch_date_time now = watch_rtc_get_date_time(); | ||||||
|  |                 uint32_t now_timestamp = watch_utility_date_time_to_unix_time(now, 0); | ||||||
|  |                 uint32_t start_timestamp = watch_utility_date_time_to_unix_time(state->start_seconds, 0); | ||||||
|  |                 // Total time in centiseconds
 | ||||||
|  |                 state->total_time = ((now_timestamp*100) + (event.subsecond*25)) - ((start_timestamp*100) + (state->start_subsecond*25)); | ||||||
|  |                 // Total speed in distance units per hour
 | ||||||
|  |                 state->total_speed = (uint32_t)(3600 * 100 * state->distance / state->total_time); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case EVENT_ALARM_LONG_PRESS: | ||||||
|  |             if (!state->running && state->total_time == 0){ | ||||||
|  |                 if (!state->editing) { | ||||||
|  |                     // Enter editing
 | ||||||
|  |                     state->editing = true; | ||||||
|  |                     state->active_digit = 0; | ||||||
|  |                     if (settings->bit.button_should_sound) { | ||||||
|  |                         watch_buzzer_play_note(BUZZER_NOTE_C7, 80); | ||||||
|  |                         watch_buzzer_play_note(BUZZER_NOTE_C8, 80); | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     // Exit editing
 | ||||||
|  |                     state->editing = false; | ||||||
|  |                     // Validate distance
 | ||||||
|  |                     if(_distance_from_struct(state->dist_digits) == 0){ | ||||||
|  |                         state->dist_digits.ones = 1; | ||||||
|  |                     } | ||||||
|  |                     _tachymeter_face_distance_lcd(event, state); | ||||||
|  |                     if (settings->bit.button_should_sound) { | ||||||
|  |                         watch_buzzer_play_note(BUZZER_NOTE_C8, 80); | ||||||
|  |                         watch_buzzer_play_note(BUZZER_NOTE_C7, 80); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case EVENT_TIMEOUT: | ||||||
|  |             // Your watch face will receive this event after a period of inactivity. If it makes sense to resign,
 | ||||||
|  |             // you may uncomment this line to move back to the first watch face in the list:
 | ||||||
|  |             movement_move_to_face(0); | ||||||
|  |             break; | ||||||
|  |         case EVENT_LOW_ENERGY_UPDATE: | ||||||
|  |             // If you did not resign in EVENT_TIMEOUT, you can use this event to update the display once a minute.
 | ||||||
|  |             // Avoid displaying fast-updating values like seconds, since the display won't update again for 60 seconds.
 | ||||||
|  |             // You should also consider starting the tick animation, to show the wearer that this is sleep mode:
 | ||||||
|  |             // watch_start_tick_animation(500);
 | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  |     // return true if the watch can enter standby mode. If you are PWM'ing an LED or buzzing the buzzer here,
 | ||||||
|  |     // you should return false since the PWM driver does not operate in standby mode.
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void tachymeter_face_resign(movement_settings_t *settings, void *context) { | ||||||
|  |     (void)settings; | ||||||
|  |     (void)context; | ||||||
|  |     // handle any cleanup before your watch face goes off-screen.
 | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								movement/watch_faces/complication/tachymeter_face.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								movement/watch_faces/complication/tachymeter_face.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | /*
 | ||||||
|  |  * MIT License | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2022 Raymundo Cassani | ||||||
|  |  * | ||||||
|  |  * 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 TACHYMETER_FACE_H_ | ||||||
|  | #define TACHYMETER_FACE_H_ | ||||||
|  | 
 | ||||||
|  | #include "movement.h" | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t thousands: 4;   // 0-9 (must wrap at 10)
 | ||||||
|  |     uint8_t hundreds: 4;    // 0-9 (must wrap at 10)
 | ||||||
|  |     uint8_t tens: 4;        // 0-9 (must wrap at 10)
 | ||||||
|  |     uint8_t ones: 4;        // 0-9 (must wrap at 10)
 | ||||||
|  |     uint8_t tenths: 4;      // 0-9 (must wrap at 10)
 | ||||||
|  |     uint8_t hundredths: 4;  // 0-9 (must wrap at 10)
 | ||||||
|  | } distance_digits_t; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     bool running;                  // tachymeter status
 | ||||||
|  |     bool editing;                  // editing distance
 | ||||||
|  |     uint8_t active_digit;          // active digit at editing distance
 | ||||||
|  |     uint8_t animation_state;       // running animation state
 | ||||||
|  |     watch_date_time start_seconds; // start_seconds
 | ||||||
|  |     int8_t start_subsecond;        // start_subsecond count (each count = 250 ms)
 | ||||||
|  |     distance_digits_t dist_digits; // distance digitwise
 | ||||||
|  |     uint32_t distance;             // distance
 | ||||||
|  |     uint32_t total_time;           // total_time = now - start_time (in cs)
 | ||||||
|  |     uint32_t total_speed;          // 3600 * 100 * distance / total_time
 | ||||||
|  | } tachymeter_state_t; | ||||||
|  | 
 | ||||||
|  | void tachymeter_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); | ||||||
|  | void tachymeter_face_activate(movement_settings_t *settings, void *context); | ||||||
|  | bool tachymeter_face_loop(movement_event_t event, movement_settings_t *settings, void *context); | ||||||
|  | void tachymeter_face_resign(movement_settings_t *settings, void *context); | ||||||
|  | 
 | ||||||
|  | #define tachymeter_face ((const watch_face_t){ \ | ||||||
|  |     tachymeter_face_setup, \ | ||||||
|  |     tachymeter_face_activate, \ | ||||||
|  |     tachymeter_face_loop, \ | ||||||
|  |     tachymeter_face_resign, \ | ||||||
|  |     NULL, \ | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | #endif // TACHYMETER_FACE_H_
 | ||||||
|  | 
 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user