]> git.earman.xyz Git - sensor-watch.git/commitdiff
Initial commit of French Revolutionary (Decimal) Time face. (french_revolutionary_face)
authorCarpeNoctem <cryptomax@pm.me>
Tue, 5 Sep 2023 14:20:55 +0000 (00:20 +1000)
committerCarpeNoctem <cryptomax@pm.me>
Tue, 5 Sep 2023 14:20:55 +0000 (00:20 +1000)
movement/make/Makefile
movement/movement_faces.h
movement/watch_faces/clock/french_revolutionary_face.c [new file with mode: 0644]
movement/watch_faces/clock/french_revolutionary_face.h [new file with mode: 0644]

index 625c77296b309ac7d11e035013eab8414bfdd08a..1d8ccfe8467cc56c393f23a8d4ec5c6f316f359f 100644 (file)
@@ -118,6 +118,7 @@ SRCS += \
   ../watch_faces/complication/flashlight_face.c \
   ../watch_faces/clock/decimal_time_face.c \
   ../watch_faces/clock/wyoscan_face.c \
+  ../watch_faces/clock/french_revolutionary_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.
index ff34c0638b09ea30e8712390e60e5fd9d41debf8..9ee8737b6cea95cdf2f9085f1ebd4dbcbcf825ce 100644 (file)
@@ -95,6 +95,7 @@
 #include "flashlight_face.h"
 #include "decimal_time_face.h"
 #include "wyoscan_face.h"
+#include "french_revolutionary_face.h"
 // New includes go above this line.
 
 #endif // MOVEMENT_FACES_H_
diff --git a/movement/watch_faces/clock/french_revolutionary_face.c b/movement/watch_faces/clock/french_revolutionary_face.c
new file mode 100644 (file)
index 0000000..9801b9c
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 CarpeNoctem
+ *
+ * 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 "french_revolutionary_face.h"
+
+void french_revolutionary_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+    (void) settings;
+    if (*context_ptr == NULL) {
+        *context_ptr = malloc(sizeof(french_revolutionary_state_t));
+        memset(*context_ptr, 0, sizeof(french_revolutionary_state_t));
+        // Do any one-time tasks in here; the inside of this conditional happens only at boot.
+        french_revolutionary_state_t *state = (french_revolutionary_state_t *)*context_ptr;
+        state->use_am_pm = false;
+        state->show_seconds = true;
+        state->display_type = 0;
+        state->colon_set_after_splash = false;
+    }
+    // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
+}
+
+void french_revolutionary_face_activate(movement_settings_t *settings, void *context) {
+    (void) settings;
+    french_revolutionary_state_t *state = (french_revolutionary_state_t *)context;
+
+    // Handle any tasks related to your watch face coming on screen.
+    state->colon_set_after_splash = false;
+}
+
+bool french_revolutionary_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+    french_revolutionary_state_t *state = (french_revolutionary_state_t *)context;
+
+    char buf[11];
+    watch_date_time date_time;
+    fr_decimal_time decimal_time;
+
+    switch (event.event_type) {
+        case EVENT_ACTIVATE:
+            // Initial UI - Show a quick "splash screen"
+            watch_clear_display();
+            watch_display_string("FR  dECimL", 0);
+            break;
+        case EVENT_TICK:
+        case EVENT_LOW_ENERGY_UPDATE:
+            
+            date_time = watch_rtc_get_date_time();
+
+            decimal_time = get_decimal_time(&date_time);
+
+            set_display_buffer(buf, state, &decimal_time, &date_time);
+
+            // If we're in low-energy mode, don't write out the seconds. Also start the LE tick animation if it's not already going.
+            if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
+                buf[8] = ' ';
+                buf[9] = ' ';
+                if (!watch_tick_animation_is_running()) { watch_start_tick_animation(500); }
+            }
+
+            // Update the display with our decimal time
+            watch_display_string(buf, 0);
+
+            // Oh, and a one-off to set the colon after the "splash screen"
+            if (!state->colon_set_after_splash) {
+                watch_set_colon();
+                state->colon_set_after_splash = true;
+            }
+            break;
+        case EVENT_ALARM_BUTTON_UP:
+            state->display_type += 1 ; // cycle through the display types
+            if (state->display_type > 2) { state->display_type = 0; } // but return to 0 after 2
+            break;
+        case EVENT_ALARM_LONG_PRESS:
+            // I originally had chiming on the decimal-hour enabled, and this would enable/disable that chime, just like on
+            // the simple clock and decimal time faces. But because decimal seconds don't always line up with normal seconds,
+            // I assume the (decimal-)hourly chime could sometimes be missed. Additionally, I need this button for other purposes,
+            // now that I added seconds on/off toggle and upper normal-time with the ability to toggle that between 12/24hr format.
+            state->show_seconds = !state->show_seconds;
+            if (!state->show_seconds) { watch_display_string("  ", 8); }
+            else { watch_display_string("--", 8); }
+            break;
+        case EVENT_LIGHT_LONG_PRESS:
+            // In case anyone really wants that upper time in 12-hour format. I thought about using the global setting (settings->bit.clock_mode_24h)
+            // for this preference, but thought someone who prefers 12-hour format normally, might prefer 24hr when compared to a 10hr decimal day,
+            // so this is separate for now.
+            state->use_am_pm = !state->use_am_pm;
+            if (state->use_am_pm) {
+                watch_clear_indicator(WATCH_INDICATOR_24H);
+                if (date_time.unit.hour < 12) { watch_clear_indicator(WATCH_INDICATOR_PM); }
+                else { watch_set_indicator(WATCH_INDICATOR_PM); }
+            } else {
+                watch_clear_indicator(WATCH_INDICATOR_PM);
+                watch_set_indicator(WATCH_INDICATOR_24H);
+            }
+            break;
+        default:
+            // Movement's default loop handler will step in for any cases you don't handle above:
+            // * EVENT_LIGHT_BUTTON_DOWN lights the LED
+            // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list
+            // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured)
+            // You can override any of these behaviors by adding a case for these events to this switch statement.
+            return movement_default_loop_handler(event, settings);
+    }
+
+    // return true if the watch can enter standby mode. Generally speaking, you should always return true.
+    // Exceptions:
+    //  * If you are displaying a color using the low-level watch_set_led_color function, you should return false.
+    //  * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false.
+    // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or
+    // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions.
+    return true;
+}
+
+void french_revolutionary_face_resign(movement_settings_t *settings, void *context) {
+    (void) settings;
+    (void) context;
+
+    // handle any cleanup before your watch face goes off-screen.
+}
+
+// Calculate decimal time from normal (24hr) time
+fr_decimal_time get_decimal_time(watch_date_time *date_time) {
+    uint32_t current_24hr_secs, current_decimal_seconds;
+    fr_decimal_time decimal_time;
+    // Current 24-hr time in seconds (There are 86400 of these in a day.)
+    current_24hr_secs = date_time->unit.hour * 3600 + date_time->unit.minute * 60 + date_time->unit.second;
+
+    // Current Decimal Time in seconds. There are 100000 seconds in a 10-hr decimal-time day.
+    // current_decimal_seconds = current_24hr_seconds * 100000 / 86400, or = current_24_seconds * 1000 / 864;
+    // By chopping the extra zeros off the end, we can use uint32 instead of uint64.
+    current_decimal_seconds = current_24hr_secs * 1000 / 864;
+
+    decimal_time.hour = current_decimal_seconds / 10000;
+    // Remove the hours from total seconds and keep the remainder for below.
+    current_decimal_seconds = current_decimal_seconds - decimal_time.hour * 10000;
+
+    decimal_time.minute = current_decimal_seconds / 100;
+    // Remove the minutes from total seconds and keep the remaining seconds
+    // Note: I think I used an extra seconds variable here because sprintf or movement weren't liking a uint32...
+    decimal_time.second = current_decimal_seconds - decimal_time.hour * 100;
+    return decimal_time;
+}
+
+// Fills in the display buffer, depending on the currently-selected display option (and sub-options):
+// - Decimal-time only
+// - Decimal-time with date in top-right
+// - Decimal-time with normal time in the top (minutes first, then hours, due to display limitations)
+// Note: There is some power-saving stuff that simple clock does here around not redrawing characters that haven't changed, but we're not doing that here.
+//       I'll try to add that optimization could be added in a future commit.
+void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time *date_time) {
+    switch (state->display_type) {
+                // Decimal time only
+                case 0:
+                    // Originally I had the day slot set to "FR" (French Revolutionary time), but my brain kept thinking "Friday" whenever I saw it,
+                    // so I changed it to dT (Decimal Time) to avoid that confusion. Apologies to anyone who has the other decimal_time face and this one
+                    // installed concurrently. Maybe the splash screen will help a little.
+                    sprintf( buf, "dT  %2d%02d%02d", decimal_time->hour, decimal_time->minute, decimal_time->second );
+                    watch_clear_indicator(WATCH_INDICATOR_PM);
+                    watch_clear_indicator(WATCH_INDICATOR_24H);
+                    break;
+                // Decimal time and date
+                case 1:
+                    sprintf( buf, "dT%2d%2d%02d%02d",  date_time->unit.day, decimal_time->hour, decimal_time->minute, decimal_time->second );
+                    watch_clear_indicator(WATCH_INDICATOR_PM);
+                    watch_clear_indicator(WATCH_INDICATOR_24H);
+                    break;
+                // Decimal time on bottom, normal time above
+                case 2:
+                    if (state->use_am_pm) {
+                        // if we are in 12 hour mode, do some cleanup.
+                        watch_clear_indicator(WATCH_INDICATOR_24H);
+                        if (date_time->unit.hour < 12) {
+                            watch_clear_indicator(WATCH_INDICATOR_PM);
+                        } else {
+                            watch_set_indicator(WATCH_INDICATOR_PM);
+                        }
+                        date_time->unit.hour %= 12;
+                        if (date_time->unit.hour == 0) date_time->unit.hour = 12;
+                    } else {
+                        watch_clear_indicator(WATCH_INDICATOR_PM);
+                        watch_set_indicator(WATCH_INDICATOR_24H);
+                    }
+                    // Note, the date digits don't display a leading zero well, so we don't use it.
+                    sprintf( buf, "%02d%2d%2d%02d%02d", date_time->unit.minute, date_time->unit.hour, decimal_time->hour, decimal_time->minute, decimal_time->second );
+                    
+                    // Make the second character of the Day area more readable
+                    buf[1] = fix_character_one(buf[1]);
+                    break;
+            }
+            // Finally, if show_seconds is disabled, trim those off.
+            if (!state->show_seconds) {
+                buf[8] = ' ';
+                buf[9] = ' ';
+            }
+}
+
+// Sadly, the second character of the Day field cannot show all numbers, so we make some replacements.
+// See https://www.sensorwatch.net/docs/wig/display/#limitations-of-the-weekday-digits
+char fix_character_one(char digit) {
+    char return_char = digit; // We don't need to update this for 0, 1, 3, 7 and 8.
+    switch(digit) {
+        case '2':
+            // Roman numeral / tally representation of 2
+            return_char = '|'; // Thanks, Joey, for already having this in the character set.
+            break;
+        case '4':
+            // Looks almost like a 4 - just missing the top-left segment.
+            // 0b01000110
+            return_char = '&'; // Slight hack - I want 0b01000110, but 0b01000100 is already in the character set and will do, since B and C segments are linked in this position.
+            break;
+        case '5':
+            return_char = 'F'; // F for Five
+            break;
+        case '6':
+            return_char = 'E'; // Looks almost like a 6 - just missing the bottom-right segment. Not super happy with it, but liked it best of the options I tried.
+            break;
+        case '9':
+            return_char = 'N'; // N for Nine
+            break;
+    }
+    return return_char;
+}
\ No newline at end of file
diff --git a/movement/watch_faces/clock/french_revolutionary_face.h b/movement/watch_faces/clock/french_revolutionary_face.h
new file mode 100644 (file)
index 0000000..f18bb1f
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 CarpeNoctem
+ *
+ * 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 FRENCH_REVOLUTIONARY_FACE_H_
+#define FRENCH_REVOLUTIONARY_FACE_H_
+
+#include "movement.h"
+
+/*
+ * French Revolutionary Decimal Time
+ *
+ * Similar to the Decimal Time face, but with the day divided into ten hours instead of twenty four.
+ * Each hour is divided into one hundred minutes, and those minutes are divided into 100 seconds.
+ * I came across this one the Svalbard watch site here: https://svalbard.watch/pages/about_decimal_time.html
+ * More info here as well: https://en.wikipedia.org/wiki/Decimal_time
+ * 
+ * By default, the face just displays the current decimal time. Pressing the alarm button will toggle through other display options:
+ * 1) Just decimal time (with dT indicator at top)
+ * 2) Decimal time, with dT indicator and date above.
+ * 3) Decimal time, with 24-hr time above (where Day and Date would normally be displayed), BUT minutes first then hours.
+ *    Sadly, the first character of the date area only goes up to 3 (see https://www.sensorwatch.net/docs/wig/display/#the-day-digits)
+ *    I was going to begrudgindly leave this display option out when I realized that, but thought it would be better to have this backwards
+ *    representation of the "normal" time than not at all.
+ * 
+ * A long-press of the light button will toggle the upper time between 12-hr AM/PM and 24-hr mode. I thought of reading the main setting for this,
+ * but thought that a person could normally prefer 12hr time, but next to a 10hr day want to see the normal time in the 24hr format.
+ * 
+ * A long-press of the alarm button will toggle the seconds off and on.
+ *
+ */
+
+typedef struct {
+    bool use_am_pm; // Use 12-hr AM/PM for upper display instead of 24-hr? (Default is 24-hr)
+    bool show_seconds;
+    bool colon_set_after_splash;
+    uint8_t display_type : 2;
+} french_revolutionary_state_t;
+
+typedef struct {
+    uint8_t second : 6;    // 0-99
+    uint8_t minute : 6;    // 0-99
+    uint8_t hour : 5;      // 0-10
+} fr_decimal_time;
+
+void french_revolutionary_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void french_revolutionary_face_activate(movement_settings_t *settings, void *context);
+bool french_revolutionary_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void french_revolutionary_face_resign(movement_settings_t *settings, void *context);
+char fix_character_one(char digit);
+fr_decimal_time get_decimal_time(watch_date_time *date_time);
+void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time *date_time);
+
+
+#define french_revolutionary_face ((const watch_face_t){ \
+    french_revolutionary_face_setup, \
+    french_revolutionary_face_activate, \
+    french_revolutionary_face_loop, \
+    french_revolutionary_face_resign, \
+    NULL, \
+})
+
+#endif // FRENCH_REVOLUTIONARY_FACE_H_
+