]> git.earman.xyz Git - sensor-watch.git/commitdiff
Morse-code based RPN calculator (#164)
authorChristian Chapman <1360262+enthdegree@users.noreply.github.com>
Sat, 21 Jan 2023 01:15:28 +0000 (20:15 -0500)
committerGitHub <noreply@github.com>
Sat, 21 Jan 2023 01:15:28 +0000 (19:15 -0600)
* Added Morse code based RPN calculator

* added manual and memory register

* fixed morsecalc negative indicator, edited header comment

* adjusted stack display controls

* Fixed warnings. Added calculator token aliasing ability. Added binary shorthand for numeral entry. Extended morse code binary tree.

* ui tweaks

* Update movement_config.h

* silence warning

Co-authored-by: Christian Chapman <user@debian>
Co-authored-by: joeycastillo <joeycastillo@utexas.edu>
movement/lib/morsecalc/calc.c [new file with mode: 0644]
movement/lib/morsecalc/calc.h [new file with mode: 0644]
movement/lib/morsecalc/calc_fns.c [new file with mode: 0644]
movement/lib/morsecalc/calc_fns.h [new file with mode: 0644]
movement/lib/morsecalc/calc_strtof.c [new file with mode: 0644]
movement/lib/morsecalc/mc.c [new file with mode: 0644]
movement/lib/morsecalc/mc.h [new file with mode: 0644]
movement/make/Makefile
movement/movement_faces.h
movement/watch_faces/complication/morsecalc_face.c [new file with mode: 0644]
movement/watch_faces/complication/morsecalc_face.h [new file with mode: 0644]

diff --git a/movement/lib/morsecalc/calc.c b/movement/lib/morsecalc/calc.c
new file mode 100644 (file)
index 0000000..49b19a0
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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 "calc.h"
+#include "calc_fns.h"
+
+#define CALC_NAN (0.0/0.0)
+
+/* calc_init 
+ * Initialize calculator
+ */
+int calc_init(calc_state_t *cs) {    
+    memset(cs->stack, CALC_NAN, N_STACK*sizeof(cs->stack[0]));
+    cs->s = 0; 
+    cs->mem = 0.0;
+    return 0;
+}
+
+/* calc_input_function
+ * Try to execut the token as a calculator function
+ * TODO: Maybe replace this loop with binary search for token in a sorted calc_dict
+ */
+int calc_input_function(calc_state_t *cs, char *token) {
+    for(uint8_t idx=0; idx<sizeof(calc_dict)/sizeof(calc_dict[0]); idx++) {
+        for(uint8_t idxn=0; idxn<sizeof(calc_dict[idx].names)/sizeof(calc_dict[idx].names[0]); idxn++) {
+            if(0 == strcmp(calc_dict[idx].names[idxn], token)) { // Found a match
+                return (*calc_dict[idx].fn)(cs); // Run calculator function
+            }
+        }
+    }
+    return -1; // Unrecognized function name
+}
+
+/* calc_input_float
+ * Read the token as a float.
+ * For convenience, numerals can be written in binary:
+ * 0     1    2    3    4    5    6    7    8    9
+ * .     -    -.   --   -..  -.-  --.  ---  -... -..-
+ * e     t    n    m    d    k    g    o    b    x
+ * 
+ * Exponent signs must be entered as "p".
+ * Decimal place "." can be entered as "h" (code ....)
+ * Sign "-" can be entered as "Ch digraph" (code ----)
+ * 
+ * e.g. "4.2e-3" can be entered directly or as "4h2pC3"
+ * similarly, "0.0042" can be "eheedn"
+ */ 
+#define REPCHAR(X,Y) for(idx=0; idx<strlen(token); idx++) \
+    if(X==token[idx]) token[idx] = Y
+int calc_input_float(calc_state_t *cs, char *token) {
+    uint8_t idx;
+    REPCHAR('e', '0');
+    REPCHAR('t', '1');
+    REPCHAR('n', '2');
+    REPCHAR('m', '3');
+    REPCHAR('d', '4');
+    REPCHAR('k', '5');
+    REPCHAR('g', '6');
+    REPCHAR('o', '7');
+    REPCHAR('b', '8');
+    REPCHAR('x', '9');
+    REPCHAR('h', '.');
+    REPCHAR('C', '-');
+    REPCHAR('p', 'E');
+    
+    char *endptr;
+    double d = calc_strtof(token, &endptr);
+    if(!endptr || (uint8_t)(endptr-token)<strlen(token)) return -1; // Bad format
+    if(cs->s >= N_STACK) return -2; // Stack full
+    cs->stack[cs->s++] = d;
+    return 0;
+}
+
+/* calc_input
+ * Manipulate the stack using the entered token.
+ * If the token isn't a calculator function, try to convert it to a number and
+ * add it to the stack.
+ *
+ * Return values: 
+ *  0 if function completed successfully.
+ * -1 if token isn't a calculator function and couldn't convert to float.
+ * -2 if stack is too full or too empty
+ * -3 for something else
+ */
+int calc_input(calc_state_t *cs, char *token) {
+    int retval = calc_input_function(cs, token);
+    if(retval == -1) retval = calc_input_float(cs, token);
+    return retval;
+}
diff --git a/movement/lib/morsecalc/calc.h b/movement/lib/morsecalc/calc.h
new file mode 100644 (file)
index 0000000..1fc4749
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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 CALC_H_INCLUDED
+#define CALC_H_INCLUDED 
+
+#include <stdint.h>
+
+#define N_STACK 10 
+
+typedef struct {
+    double stack[N_STACK];
+    double mem;
+    uint8_t s; // # of items in stack 
+} calc_state_t;
+int calc_init(calc_state_t *cs);
+int calc_input(calc_state_t *cs, char *token);
+int calc_input_function(calc_state_t *cs, char *token);
+int calc_input_float(calc_state_t *cs, char *token);
+double calc_strtof(const char *str, char **endptr);
+
+#endif
diff --git a/movement/lib/morsecalc/calc_fns.c b/movement/lib/morsecalc/calc_fns.c
new file mode 100644 (file)
index 0000000..873de26
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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 <string.h>
+#include <math.h>
+
+#include "calc_fns.h" 
+
+#define STACK_CHECK_0_IN_1_OUT if(cs->s >= N_STACK) return -2
+#define STACK_CHECK_1_IN_0_OUT if(cs->s < 1) return -2
+#define STACK_CHECK_1_IN_1_OUT if(cs->s < 1) return -2
+#define STACK_CHECK_2_IN_1_OUT if(cs->s < 2) return -2
+#define STACK_CHECK_2_IN_2_OUT if(cs->s < 2) return -2
+
+static const double to_rad = M_PI/180;
+static const double to_deg = 180/M_PI;
+
+// Stack and memory control
+int calc_delete(calc_state_t *cs) {
+    if(cs->s < 1) return -2; // Check stack
+    cs->s--; 
+    return 0;
+}
+int calc_clear_stack(calc_state_t *cs) {
+    memset(cs->stack, (0.0/0.0), N_STACK*sizeof(cs->stack[0]));
+    cs->s = 0; 
+    return 0;
+}
+int calc_flip(calc_state_t *cs) {
+    STACK_CHECK_2_IN_2_OUT;
+    double buff = cs->stack[cs->s-2];
+    cs->stack[cs->s-2] = cs->stack[cs->s-1];
+    cs->stack[cs->s-1] = buff;
+    return 0;
+}
+int calc_mem_clear(calc_state_t *cs) {
+    cs->mem = 0.0;
+    return 0;
+}
+int calc_mem_recall(calc_state_t *cs) { 
+    STACK_CHECK_0_IN_1_OUT;
+    cs->stack[cs->s++] = cs->mem;
+    return 0;
+}
+int calc_mem_add(calc_state_t *cs) {
+    STACK_CHECK_1_IN_0_OUT;
+    cs->mem += cs->stack[cs->s-1];
+    cs->s--;
+    return 0;
+}
+int calc_mem_subtract(calc_state_t *cs) {
+    STACK_CHECK_1_IN_0_OUT;
+    cs->mem -= cs->stack[cs->s-1];
+    cs->s--;
+    return 0;
+}
+
+// Basic operations
+int calc_add(calc_state_t *cs) {
+    STACK_CHECK_2_IN_1_OUT; 
+    cs->stack[cs->s-2] += cs->stack[cs->s-1];
+    cs->s--;
+    return 0;
+}
+int calc_subtract(calc_state_t *cs) {
+    STACK_CHECK_2_IN_1_OUT; 
+    cs->stack[cs->s-2] -= cs->stack[cs->s-1];
+    cs->s--;
+    return 0;
+}
+int calc_negate(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = -cs->stack[cs->s-1];
+    return 0;
+}
+int calc_multiply(calc_state_t *cs) {
+    STACK_CHECK_2_IN_1_OUT; 
+    cs->stack[cs->s-2] *= cs->stack[cs->s-1];
+    cs->s--;
+    return 0;
+}
+int calc_divide(calc_state_t *cs) {
+    STACK_CHECK_2_IN_1_OUT;
+    cs->stack[cs->s-2] /= cs->stack[cs->s-1];
+    cs->s--;
+    return 0;
+}
+
+int calc_invert(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = 1.0/cs->stack[cs->s-1];
+    return 0;
+}
+
+// Constants
+int calc_e(calc_state_t *cs) {
+    STACK_CHECK_0_IN_1_OUT;
+    cs->stack[cs->s++] = M_E;
+    return 0;
+}
+int calc_pi(calc_state_t *cs) {
+    STACK_CHECK_0_IN_1_OUT;
+    cs->stack[cs->s++] = M_PI;
+    return 0;
+}
+
+// Exponential/logarithmic
+int calc_exp(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = exp(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_pow(calc_state_t *cs) {
+    STACK_CHECK_2_IN_1_OUT;
+    cs->stack[cs->s-2] = pow(cs->stack[cs->s-2], cs->stack[cs->s-1]);
+    cs->s--;
+    return 0;
+}
+int calc_ln(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = log(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_log(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = log10(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_sqrt(calc_state_t *cs)  {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = sqrt(cs->stack[cs->s-1]);
+    return 0;
+}
+
+// Trigonometric
+int calc_sin(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = sin(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_cos(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = cos(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_tan(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = tan(cs->stack[cs->s-1]);
+    return 0;
+} 
+int calc_asin(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = asin(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_acos(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = acos(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_atan(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = atan(cs->stack[cs->s-1]);
+    return 0;
+}
+int calc_atan2(calc_state_t *cs) {
+    STACK_CHECK_2_IN_1_OUT;
+    cs->stack[cs->s-2] = atan2(cs->stack[cs->s-2], cs->stack[cs->s-1]);
+    cs->s--;
+    return 0;
+}
+
+int calc_sind(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = sin(cs->stack[cs->s-1]*to_rad);
+    return 0;
+}
+int calc_cosd(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = cos(cs->stack[cs->s-1]*to_rad);
+    return 0;
+}
+int calc_tand(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = tan(cs->stack[cs->s-1]*to_rad);
+    return 0;
+}
+int calc_asind(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = asin(cs->stack[cs->s-1])*to_deg;
+    return 0;
+}
+int calc_acosd(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = acos(cs->stack[cs->s-1])*to_deg;
+    return 0;
+}
+int calc_atand(calc_state_t *cs) {
+    STACK_CHECK_1_IN_1_OUT; 
+    cs->stack[cs->s-1] = atan(cs->stack[cs->s-1])*to_deg;
+    return 0;
+}
+int calc_atan2d(calc_state_t *cs) {
+    STACK_CHECK_2_IN_1_OUT;
+    cs->stack[cs->s-2] = atan2(cs->stack[cs->s-2], cs->stack[cs->s-1])*to_deg;
+    cs->s--;
+    return 0;
+}
+
diff --git a/movement/lib/morsecalc/calc_fns.h b/movement/lib/morsecalc/calc_fns.h
new file mode 100644 (file)
index 0000000..fd1d7ab
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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 "calc.h"
+
+// Stack and register control
+int calc_delete(calc_state_t *cs);
+int calc_clear_stack(calc_state_t *cs);
+int calc_flip(calc_state_t *cs);
+int calc_mem_clear(calc_state_t *cs);
+int calc_mem_recall(calc_state_t *cs);
+int calc_mem_add(calc_state_t *cs);
+int calc_mem_subtract(calc_state_t *cs);
+
+// Basic operations
+int calc_add(calc_state_t *cs);
+int calc_subtract(calc_state_t *cs);
+int calc_negate(calc_state_t *cs);
+int calc_multiply(calc_state_t *cs);
+int calc_divide(calc_state_t *cs);
+int calc_invert(calc_state_t *cs);
+
+// Constants
+int calc_e(calc_state_t *cs);
+int calc_pi(calc_state_t *cs);
+
+// Exponential/logarithmic
+int calc_exp(calc_state_t *cs);
+int calc_pow(calc_state_t *cs);
+int calc_ln(calc_state_t *cs);
+int calc_log(calc_state_t *cs);
+int calc_sqrt(calc_state_t *cs);
+
+// Trigonometric
+int calc_sin(calc_state_t *cs);
+int calc_cos(calc_state_t *cs);
+int calc_tan(calc_state_t *cs);
+int calc_asin(calc_state_t *cs);
+int calc_acos(calc_state_t *cs);
+int calc_atan(calc_state_t *cs);
+int calc_atan2(calc_state_t *cs);
+int calc_sind(calc_state_t *cs);
+int calc_cosd(calc_state_t *cs);
+int calc_tand(calc_state_t *cs);
+int calc_asind(calc_state_t *cs);
+int calc_acosd(calc_state_t *cs);
+int calc_atand(calc_state_t *cs);
+int calc_atan2d(calc_state_t *cs);
+
+// Dictionary definition
+typedef int (*calc_fn_t)(calc_state_t *cs);
+typedef struct {
+    char *names[3]; // Token to use to run this function
+    calc_fn_t fn; // Pointer to function 
+} calc_dict_entry_t;
+
+static const calc_dict_entry_t calc_dict[] = {
+    // Stack and register control
+    {{"x"}, &calc_delete},
+    {{"xx"}, &calc_clear_stack},
+    {{"xxx"}, &calc_init},
+    {{"f"}, &calc_flip},
+    {{"mc"}, &calc_mem_clear},
+    {{"mr"}, &calc_mem_recall},
+    {{"ma"}, &calc_mem_add},
+    {{"ms"}, &calc_mem_subtract},
+
+    // Basic operations
+    {{"a"}, &calc_add}, 
+    {{"s"}, &calc_subtract},
+    {{"n"}, &calc_negate},
+    {{"m"}, &calc_multiply},
+    {{"d"}, &calc_divide},
+    {{"i"}, &calc_invert},
+    
+    // Constants
+    {{"e"}, &calc_e}, 
+    {{"pi"}, &calc_pi},  
+    
+    // Exponential/logarithmic
+    {{"exp"}, &calc_exp},
+    {{"pow"}, &calc_pow}, 
+    {{"ln"}, &calc_ln}, 
+    {{"log"}, &calc_log},
+    {{"sqrt"}, &calc_sqrt},
+    
+    // Trigonometric 
+    {{"sin", "sn"}, &calc_sin},
+    {{"cos"}, &calc_cos},
+    {{"tan"}, &calc_tan},
+    {{"asin"}, &calc_asin},
+    {{"acos"}, &calc_acos},
+    {{"atan"}, &calc_atan}, 
+    {{"atan2"}, &calc_atan2},
+    {{"sind"}, &calc_sind},
+    {{"cosd"}, &calc_cosd},
+    {{"tand"}, &calc_tand},
+    {{"asind"}, &calc_asind},
+    {{"acosd"}, &calc_acosd},
+    {{"atand"}, &calc_atand}, 
+    {{"atan2d"}, &calc_atan2d}, 
+}; 
diff --git a/movement/lib/morsecalc/calc_strtof.c b/movement/lib/morsecalc/calc_strtof.c
new file mode 100644 (file)
index 0000000..35d39fa
--- /dev/null
@@ -0,0 +1,144 @@
+//
+// strtod.c
+//
+// Convert string to double 
+//
+// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 
+// 1. Redistributions of source code must retain the above copyright 
+//    notice, this list of conditions and the following disclaimer.  
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.  
+// 3. Neither the name of the project nor the names of its contributors
+//    may be used to endorse or promote products derived from this software
+//    without specific prior written permission. 
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
+// SUCH DAMAGE.
+// 
+
+#include <ctype.h>
+#include <math.h>
+#include <float.h>
+#include <stdlib.h>
+
+#include "calc.h"
+
+double calc_strtof(const char *str, char **endptr) {
+  double number;
+  int exponent;
+  int negative;
+  char *p = (char *) str;
+  double p10;
+  int n;
+  int num_digits;
+  int num_decimals;
+
+  // Skip leading whitespace
+  while (isspace((int) *p)) p++;
+
+  // Handle optional sign
+  negative = 0;
+  switch (*p) {
+    case '-': negative = 1; // Fall through to increment position
+    __attribute__ ((fallthrough));
+    case '+': p++;
+  }
+
+  number = 0.;
+  exponent = 0;
+  num_digits = 0;
+  num_decimals = 0;
+
+  // Process string of digits
+  while (isdigit((int) *p)) {
+    number = number * 10. + (*p - '0');
+    p++;
+    num_digits++;
+  }
+
+  // Process decimal part
+  if (*p == '.') {
+    p++;
+
+    while (isdigit((int) *p)) {
+      number = number * 10. + (*p - '0');
+      p++;
+      num_digits++;
+      num_decimals++;
+    }
+
+    exponent -= num_decimals;
+  }
+
+  if (num_digits == 0) {
+    if (endptr) *endptr = p;
+    return 0.0;
+  }
+
+  // Correct for sign
+  if (negative) number = -number;
+
+  // Process an exponent string
+  if (*p == 'e' || *p == 'E') {
+    // Handle optional sign
+    negative = 0;
+    switch (*++p) {
+      case '-': negative = 1;   // Fall through to increment pos
+      __attribute__ ((fallthrough));
+      case '+': p++;
+    }
+
+    // Process string of digits
+    n = 0;
+    while (isdigit((int) *p)) {
+      n = n * 10 + (*p - '0');
+      p++;
+    }
+
+    if (negative) {
+      exponent -= n;
+    } else {
+      exponent += n;
+    }
+  }
+
+  if (exponent < DBL_MIN_EXP  || exponent > DBL_MAX_EXP) {
+    return HUGE_VAL;
+  }
+
+  // Scale the result
+  p10 = 10.;
+  n = exponent;
+  if (n < 0) n = -n;
+  while (n) {
+    if (n & 1) {
+      if (exponent < 0) {
+        number /= p10;
+      } else {
+        number *= p10;
+      }
+    }
+    n >>= 1;
+    p10 *= p10;
+  }
+
+  if (endptr) *endptr = p;
+
+  return number;
+}
+
diff --git a/movement/lib/morsecalc/mc.c b/movement/lib/morsecalc/mc.c
new file mode 100644 (file)
index 0000000..94f6511
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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 <string.h>
+#include "mc.h"
+
+/* mc_reset Initialize or reset an MC buffer
+ * Input: mc = location of buffer to reset
+ */
+void mc_reset(mc_state_t * mc) {
+    memset(mc->b, '\0', BUFFLEN*sizeof(mc->b[0]));
+    mc->bidx = 0;
+    return;
+   return;
+}
+
+/* mc_input Read an input into a morse code buffer 
+ * Input: mc = buffer to read into
+ *        c = character to read into buffer ('.' or '-', ignored otherwise).
+ * If the buffer is full, reset it instead of entering the new character.
+ */
+void mc_input(mc_state_t * mc, char c) {
+    if(mc->bidx >= BUFFLEN) mc_reset(mc);
+    else if( ('.' == c) || ('-' == c) ) {
+        mc->b[mc->bidx] = c;
+        mc->bidx++;
+    }
+    return;
+}
+
+/* mc_dec Decode a Morse code character (descend MC_DEC_KEY[])
+ * Input: b = BUFFLEN-length char array with '.'s and '-'s
+ * Output: c = Character b represents, or '\0' if not a Morse code. 
+ */
+char mc_dec(char b[BUFFLEN]) {
+    uint8_t pos = 1; // Binary tree position ('.'=0; '-'=1)
+    for(uint8_t idx=0; idx<BUFFLEN; idx++) {
+        if('.' == b[idx]) pos = 2*pos; // Descend in . direction
+        else if('-' == b[idx]) pos = 2*pos+1; // Descend in - direction
+        else break; // End of morse code segment; finished descending
+    }
+    return MC_DEC_KEY[pos-1];
+}
+
diff --git a/movement/lib/morsecalc/mc.h b/movement/lib/morsecalc/mc.h
new file mode 100644 (file)
index 0000000..0daa470
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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.
+ */
+
+
+/* mc Morse code reading methods
+*/
+#include "stdint.h"
+
+#define BUFFLEN 5
+typedef struct {
+    char b[BUFFLEN];
+    uint8_t bidx;
+} mc_state_t;
+
+// MC_DEC_KEY represents a binary tree of International Morse Code. 
+// where '.' = 0 and '-' = 1. Levels of the tree are concatenated.
+//
+// Capitals denote special characters:
+// C = Ch digraph
+// V = VERIFY (ITU-R "UNDERSTOOD")
+// R = REPEAT
+// W = WAIT
+// S = START TRANSMISSION
+// E = END OF WORK
+static const char MC_DEC_KEY[] = " etianmsurwdkgohvf\0l\0pjbxcyzq\0C\x35\x34V\x33\0R\0\x32W\0+\0\0\0\0\x31\x36=/\0\0S(\0\x37\0\0\0\x38\0\x39\x30\0\0\0\0\0E\0\0\0\0\0\0?_\0\0\0\0\"\0\0.\0\0\0\0@\0\0\0'\0\0-\0\0\0\0\0\0\0\0;!\0)\0\0\0\0\0,\0\0\0\0:\0\0\0\0\0\0\0";
+    
+void mc_reset(mc_state_t * mcb);
+void mc_input(mc_state_t * mc, char c);
+char mc_dec(char b[BUFFLEN]);
+
index 374c04bee62945c6161eada289026821112c59d8..fa43a5e9435c8b77077c0cab1510404e4963e2e5 100644 (file)
@@ -22,6 +22,7 @@ INCLUDES += \
   -I../lib/sunriset/ \
   -I../lib/vsop87/ \
   -I../lib/astrolib/ \
+  -I../lib/morsecalc/ \
 
 # If you add any other source files you wish to compile, add them after ../app.c
 # Note that you will need to add a backslash at the end of any line you wish to continue, i.e.
@@ -38,6 +39,10 @@ SRCS += \
   ../lib/sunriset/sunriset.c \
   ../lib/vsop87/vsop87a_milli.c \
   ../lib/astrolib/astrolib.c \
+  ../lib/morsecalc/calc.c \
+  ../lib/morsecalc/calc_fns.c \
+  ../lib/morsecalc/calc_strtof.c \
+  ../lib/morsecalc/mc.c \
   ../../littlefs/lfs.c \
   ../../littlefs/lfs_util.c \
   ../movement.c \
@@ -88,6 +93,7 @@ SRCS += \
   ../watch_faces/complication/tempchart_face.c \
   ../watch_faces/complication/tally_face.c \
   ../watch_faces/complication/tarot_face.c \
+  ../watch_faces/complication/morsecalc_face.c \
   ../watch_faces/complication/rpn_calculator_face.c \
 # New watch faces go above this line.
 
index 84d04653e90f5273a62c980c6b66fc532287f313..eefb05e6d398b126d4a55ee45367a66f0cc91d71 100644 (file)
@@ -71,6 +71,7 @@
 #include "tally_face.h"
 #include "tarot_face.h"
 #include "interval_face.h"
+#include "morsecalc_face.h"
 #include "rpn_calculator_face.h"
 // New includes go above this line.
 
diff --git a/movement/watch_faces/complication/morsecalc_face.c b/movement/watch_faces/complication/morsecalc_face.c
new file mode 100644 (file)
index 0000000..c54c0ea
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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.
+ */
+
+/*
+## Morse-code-based RPN calculator 
+
+The calculator is operated by first composing a **token** in Morse code, then submitting it to the calculator. A token specifies either a calculator operation or a float value.
+
+These two parts of the codebase are totally independent:
+
+ 1. The Morse-code reader (`mc.h`, `mc.c`) 
+ 2. The RPN calculator (`calc.h`, `calc.c`, `calc_fn.h`, `calc_fn.c`, `small_strtod.c`)
+
+The user interface (`morsecalc_face.h`, `morsecalc_face.c`) lets you talk to the RPN calculator through Morse code.
+
+## Controls
+
+ - `light` is dash
+ - `alarm` is dot
+ - `mode` is "finish character"
+ - long-press `mode` to quit
+ - long-press `alarm` to show stack
+ - long-press `light` to toggle the light
+   
+## Morse code token entry
+As you enter `.`s and `-`s, the morse code char you've entered will appear in the top center digit.
+At the top right is the # of morse code `.`/`-` you've input so far. The character resets at the 6th `.`/`-`.
+Once you have the character you want to enter, push `mode` to enter it. 
+The character will be appended to the current token, whose 6 trailing chars are shown on the main display.
+
+Once you've typed in the token you want, enter a blank Morse code character and then push `mode`.
+This submits it to the calculator.
+   
+Special characters:
+
+ - Backspace is `(` (`-.--.`). 
+ - Clear token input without submitting to calculator is `Start transmission` (`-.-.-`).
+    
+## Writing commands
+First the calculator will try to interpret the token as a command/stack operation. 
+Commands are defined in `calc_dict[]` in `movement/lib/morsecalc/calc_fns.h`.
+If the command doesn't appear in the dictionary, the calculator tries to interpret the token as a number.
+## Writing numbers
+Numbers are written like floating point strings. 
+Entering a number pushes it to the top of the stack if there's room.
+This can get long, so for convenience numerals can also be written in binary with .- = 01.
+
+    0   1    2    3    4    5    6    7    8    9
+    .   -    -.   --   -..  -.-  --.  ---  -... -..-
+    e   t    n    m    d    k    g    o    b    x
+
+ - Exponent signs must be entered as "p".
+ - Decimal place "." can be entered as "h" (code ....)
+ - Sign "-" can be entered as "Ch digraph" (code ----)
+For example: "4.2e-3" can be entered directly, or as "4h2pC3"
+  similarly, "0.0042" can also be entered as "eheedn"
+
+Once you submit a number to the watch face, it pushes it to the top of the stack if there's room.
+        
+## Number display
+After a command runs, the top of the stack is displayed in this format:
+   
+  - Main 4 digits = leading 4 digits
+  - Last 2 digits = exponent
+  - Top middle = [Stack location, Sign of number]
+  - Top right = [Stack exponent, Sign of exponent]
+  
+Blank sign digit means positive.
+So for example, the watch face might look like this:
+
+    [   0 -5]
+    [4200 03]
+
+... representing `+4.200e-3` is in stack location 0 (the top) and it's one of five items in the stack.
+
+## Looking at the stack
+
+To show the top of the stack, push and hold `light`/`alarm` or submit a blank token by pushing `mode` a bunch of times.
+To show the N-th stack item (0 through 9):
+
+ - Put in the Morse code for N without pushing the mode button.
+ - Push and hold `alarm`.
+    
+To show the memory register, use `m` instead of a number. 
+   
+To see all the calculator operations and their token aliases, see the `calc_dict[]` struct in `calc_fns.h` 
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "morsecalc_face.h"
+#include "watch.h"
+#include "watch_utility.h"
+#include "watch_private_display.h"
+
+// Display float on screen
+void morsecalc_print_float(double d) { 
+    // Special cases 
+    if(d == 0) {
+        watch_display_string("     0", 4); 
+        return;
+    }
+    else if(isnan(d)) {
+        watch_display_string("   nan", 4);
+        return;
+    }
+    else if(d == (1.0)/(0.0)) {
+        watch_display_string("   inf", 4);
+        return;
+    }
+    else if(d == (-1.0)/(0.0)) {
+        watch_display_character('X', 1);
+        watch_display_string("   inf", 4);
+        return;
+    }
+
+    // Record number properties
+    // Sign
+    int is_negative = d<0;
+    if(is_negative) d = -d; 
+
+    // Order of magnitude
+    int om = (int) floor(log(d)/log(10));
+    int om_is_negative = (om<0);
+
+    // Get the first 4 significant figures
+    int digits;
+    digits = round(d*pow(10.0, 3-om));
+    if(digits>9999) {
+        digits = 1000;
+        om++;
+    }
+
+    // Print signs
+    if(is_negative) {
+               // Xi; see https://joeycastillo.github.io/Sensor-Watch-Documentation/segmap
+               watch_set_pixel(0,11);
+               watch_set_pixel(2,12);
+               watch_set_pixel(2,11); 
+       }
+    else watch_display_character(' ', 1); 
+    if(om_is_negative) watch_set_pixel(1,9); 
+    else watch_display_character(' ', 2); 
+
+    // Print first 4 significant figures
+    watch_display_character('0'+(digits/1000)%10, 4);
+    watch_display_character('0'+(digits/100 )%10, 5);
+    watch_display_character('0'+(digits/10  )%10, 6);
+    watch_display_character('0'+(digits/1   )%10, 7);
+
+    // Prinat exponent
+    if(om_is_negative) om = -om; // Make exponent positive for display
+    if(om<=99) {
+        watch_display_character('0'+(om/10  )%10, 8);
+        watch_display_character('0'+(om/1   )%10, 9);
+    } else { // Over/underflow
+        if(om_is_negative) watch_display_string("    uf", 4);
+        else watch_display_string("    of", 4);
+        if(om<9999) { // Use main display to show order of magnitude
+            // (Should always succeed; max double is <2e308)
+            watch_display_character('0'+(om/1000)%10, 4);
+            watch_display_character('0'+(om/100 )%10, 5);
+            watch_display_character('0'+(om/10  )%10, 6);
+            watch_display_character('0'+(om/1   )%10, 7);
+        }
+    }
+    return;
+}
+
+// Print current input token
+void morsecalc_print_token(morsecalc_state_t *mcs) {
+    watch_display_string("          ", 0); // Clear display
+
+    // Print morse code buffer
+    char c = mc_dec(mcs->mc->b); // Decode the morse code buffer's current contents
+    if('\0' == c) c = ' '; // Needed for watch_display_character
+    watch_display_character(c, 0); // Display current morse code char in mode position
+    watch_display_character('0'+(mcs->mc->bidx), 3); // Display buffer position in top right
+
+    // Print last 6 chars of current input line 
+    uint8_t nlen = strlen(mcs->token); // number of characters in token
+    uint8_t nprint = min(nlen,6); // number of characters to print
+    watch_display_string(mcs->token+nlen-nprint, 10-nprint); // print right-aligned
+    return;
+}
+
+// Clear token buffer
+void morsecalc_reset_token(morsecalc_state_t *mcs) { 
+    memset(mcs->token, '\0', MORSECALC_TOKEN_LEN*sizeof(mcs->token[0]));
+    mcs->idxt = 0;
+    return;
+}
+
+// Print stack or memory register contents. 
+void morsecalc_print_stack(morsecalc_state_t * mcs) {
+    watch_display_string("          ", 0); // Clear display
+
+    char c = mc_dec(mcs->mc->b); 
+    if('m' == c) { // Display memory 
+        morsecalc_print_float(mcs->cs->mem);
+        watch_display_character(c, 0);
+    } 
+    else {
+        // If the morse code buffer has a numeral in it, print that stack item 
+        // Otherwise print top of stack
+        uint8_t idx = 0;
+        if(c >= '0' && c <= '9') idx = c - '0';
+        if(idx >= mcs->cs->s) watch_display_string(" empty", 4); // Stack empty
+        else morsecalc_print_float(mcs->cs->stack[mcs->cs->s-1-idx]); // Print stack item
+
+        watch_display_character('0'+idx, 0); // Print which stack item this is top center
+    }
+    watch_display_character('0'+(mcs->cs->s), 3); // Print the # of stack items top right 
+    return;
+}
+
+// Write something into the morse code buffer.
+// Input: c = dot (0), dash (1), or 'complete' ('x')
+void morsecalc_input(morsecalc_state_t * mcs, char c) {
+    int status = 0;
+    if( c != 'x' ) { // Dot or dash received
+        mc_input(mcs->mc, c);
+        morsecalc_print_token(mcs);
+    } 
+    else { // Morse code character finished
+        char dec = mc_dec(mcs->mc->b); 
+        mc_reset(mcs->mc);
+        switch(dec) {
+            case '\0': // Invalid character, do nothing
+                morsecalc_print_token(mcs);
+                break;
+                
+            case ' ': // Submit token to calculator
+                if(strlen(mcs->token) > 0) {
+                    status = calc_input(mcs->cs, mcs->token);
+                    morsecalc_reset_token(mcs); 
+                } 
+                morsecalc_print_stack(mcs);   
+                break;
+                
+            case '(': // -.--. Erase previous character in token
+                if(mcs->idxt>0) {
+                    mcs->idxt--;
+                    mcs->token[mcs->idxt] = '\0';
+                }
+                morsecalc_print_token(mcs);
+                break;
+                
+            case 'S': // -.-.- Erase entire token without submitting
+                morsecalc_reset_token(mcs); 
+                morsecalc_print_stack(mcs);   
+                break;
+                
+            default: // Add character to token
+                if(mcs->idxt < MORSECALC_TOKEN_LEN-1) {
+                    mcs->token[mcs->idxt] = dec;
+                    mcs->idxt++; 
+                    morsecalc_print_token(mcs);
+                }
+                else  watch_display_string("  full", 4); 
+                break;
+        }
+    }
+    
+    // Print errors if there are any
+    switch(status) {
+        case  0: break; // Success
+        case -1: watch_display_string("cmderr", 4); break; // Unrecognized command
+        case -2: watch_display_string("stkerr", 4); break; // Bad stack size
+        default: watch_display_string("   err", 4); break; // Other error 
+    }
+    
+    return;    
+}
+
+void morsecalc_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(morsecalc_state_t)); 
+        morsecalc_state_t *mcs = (morsecalc_state_t *)*context_ptr;
+        morsecalc_reset_token(mcs); 
+        
+        mcs->cs = (calc_state_t *) malloc(sizeof(calc_state_t));
+        calc_init(mcs->cs); 
+        
+        mcs->mc = (mc_state_t *) malloc(sizeof(mc_state_t));
+        mc_reset(mcs->mc);
+
+        mcs->led_is_on = 0;
+    }
+    return;
+}
+
+void morsecalc_face_activate(movement_settings_t *settings, void *context) {
+    (void) settings;
+    morsecalc_state_t *mcs = (morsecalc_state_t *) context;
+    mc_reset(mcs->mc);
+    morsecalc_print_stack(mcs);
+    return;
+}
+
+bool morsecalc_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+    morsecalc_state_t *mcs = (morsecalc_state_t *) context;
+    switch(event.event_type) {
+    // input
+    case EVENT_ALARM_BUTTON_UP:
+    // dot
+        morsecalc_input(mcs, '.');    
+        break;
+    case EVENT_LIGHT_BUTTON_UP:
+    // dash
+        morsecalc_input(mcs, '-'); 
+        break;
+    case EVENT_MODE_BUTTON_UP:
+    // submit character
+        morsecalc_input(mcs, 'x'); 
+        break;
+
+    // show stack
+    case EVENT_ALARM_LONG_PRESS:
+        morsecalc_print_stack(mcs);
+               mc_reset(mcs->mc);
+        break;
+
+    // toggle light
+    case EVENT_LIGHT_LONG_PRESS:
+        mcs->led_is_on = !mcs->led_is_on;
+        if(mcs->led_is_on) {
+                       watch_set_led_color(settings->bit.led_red_color ? (0xF | settings->bit.led_red_color << 4) : 0,
+                                                               settings->bit.led_green_color ? (0xF | settings->bit.led_green_color << 4) : 0);
+            movement_request_tick_frequency(4);
+        }
+        else {
+            watch_set_led_off();
+            movement_request_tick_frequency(1);
+        }
+        break;
+
+    // quit
+    case EVENT_TIMEOUT:
+        movement_move_to_next_face();
+        break;
+    case EVENT_MODE_LONG_PRESS:
+        movement_move_to_next_face();
+        break;
+
+    case EVENT_TICK:
+        if(mcs->led_is_on) { 
+                       watch_set_led_color(settings->bit.led_red_color ? (0xF | settings->bit.led_red_color << 4) : 0,
+                               settings->bit.led_green_color ? (0xF | settings->bit.led_green_color << 4) : 0);
+               }
+        break;
+       }
+    
+    return true;
+}
+
+void morsecalc_face_resign(movement_settings_t *settings, void *context) {
+    (void) settings;
+    morsecalc_state_t *mcs = (morsecalc_state_t *) context;
+    mcs->led_is_on = 0;
+       watch_set_led_off();
+    return;
+}
+
diff --git a/movement/watch_faces/complication/morsecalc_face.h b/movement/watch_faces/complication/morsecalc_face.h
new file mode 100644 (file)
index 0000000..bd0fd41
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Christian Chapman
+ *
+ * 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 MORSECALC_FACE_H_
+#define MORSECALC_FACE_H_
+#define MORSECALC_TOKEN_LEN 9
+
+#include "movement.h"
+#include "calc.h"
+#include "mc.h"
+
+void morsecalc_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void morsecalc_face_activate(movement_settings_t *settings, void *context);
+bool morsecalc_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void morsecalc_face_resign(movement_settings_t *settings, void *context);
+
+typedef struct {
+       calc_state_t *cs;
+       mc_state_t *mc; 
+       char token[MORSECALC_TOKEN_LEN];
+       uint8_t idxt;
+       uint8_t led_is_on;
+} morsecalc_state_t;
+
+void morsecalc_print_float(double d);
+void morsecalc_print_token(morsecalc_state_t *mcs);
+void morsecalc_print_stack(morsecalc_state_t *mcs);
+void morsecalc_reset_token(morsecalc_state_t *mcs);
+void morsecalc_input(morsecalc_state_t *mcs, char c);
+
+#define morsecalc_face ((const watch_face_t){ \
+    morsecalc_face_setup, \
+    morsecalc_face_activate, \
+    morsecalc_face_loop, \
+    morsecalc_face_resign, \
+    NULL, \
+})
+
+#endif // MORSECALC_FACE_H_