summaryrefslogtreecommitdiffstats
path: root/movement
diff options
context:
space:
mode:
Diffstat (limited to 'movement')
-rw-r--r--movement/filesystem.c1
-rw-r--r--movement/lib/chirpy_tx/chirpy_tx.c179
-rw-r--r--movement/lib/chirpy_tx/chirpy_tx.h98
-rw-r--r--movement/lib/chirpy_tx/test/test_main.c160
-rw-r--r--movement/lib/chirpy_tx/test/unity.c2466
-rw-r--r--movement/lib/chirpy_tx/test/unity.h687
-rw-r--r--movement/lib/chirpy_tx/test/unity_internals.h1145
-rw-r--r--movement/lib/morsecalc/calc.c8
-rw-r--r--movement/lib/morsecalc/calc_fns.c10
-rw-r--r--movement/lib/morsecalc/calc_fns.h77
-rw-r--r--movement/lib/morsecalc/mc.c66
-rw-r--r--movement/lib/morsecalc/morsecalc_display.c147
-rw-r--r--movement/lib/morsecalc/morsecalc_display.h (renamed from movement/lib/morsecalc/mc.h)30
-rw-r--r--movement/lib/morsecalc/test_morsecalc.c66
-rw-r--r--movement/make/Makefile18
-rw-r--r--movement/movement_faces.h15
-rw-r--r--movement/watch_faces/clock/repetition_minute_face.c231
-rw-r--r--movement/watch_faces/clock/repetition_minute_face.h83
-rw-r--r--movement/watch_faces/clock/world_clock2_face.c450
-rw-r--r--movement/watch_faces/clock/world_clock2_face.h63
-rw-r--r--movement/watch_faces/complication/activity_face.c725
-rw-r--r--movement/watch_faces/complication/activity_face.h89
-rw-r--r--movement/watch_faces/complication/breathing_face.c208
-rw-r--r--movement/watch_faces/complication/breathing_face.h43
-rw-r--r--movement/watch_faces/complication/day_one_face.c2
-rw-r--r--movement/watch_faces/complication/discgolf_face.c326
-rw-r--r--movement/watch_faces/complication/discgolf_face.h95
-rw-r--r--movement/watch_faces/complication/dual_timer_face.c334
-rw-r--r--movement/watch_faces/complication/dual_timer_face.h111
-rw-r--r--movement/watch_faces/complication/geomancy_face.c363
-rw-r--r--movement/watch_faces/complication/geomancy_face.h99
-rw-r--r--movement/watch_faces/complication/interval_face.c18
-rw-r--r--movement/watch_faces/complication/invaders_face.c434
-rw-r--r--movement/watch_faces/complication/invaders_face.h82
-rw-r--r--movement/watch_faces/complication/morsecalc_face.c235
-rw-r--r--movement/watch_faces/complication/morsecalc_face.h27
-rw-r--r--movement/watch_faces/complication/planetary_hours_face.c402
-rw-r--r--movement/watch_faces/complication/planetary_hours_face.h107
-rw-r--r--movement/watch_faces/complication/planetary_time_face.c338
-rw-r--r--movement/watch_faces/complication/planetary_time_face.h108
-rw-r--r--movement/watch_faces/complication/randonaut_face.c414
-rw-r--r--movement/watch_faces/complication/randonaut_face.h113
-rw-r--r--movement/watch_faces/complication/time_left_face.c357
-rw-r--r--movement/watch_faces/complication/time_left_face.h89
-rw-r--r--movement/watch_faces/complication/toss_up_face.c793
-rw-r--r--movement/watch_faces/complication/toss_up_face.h112
-rw-r--r--movement/watch_faces/demo/chirpy_demo_face.c335
-rw-r--r--movement/watch_faces/demo/chirpy_demo_face.h70
-rw-r--r--movement/watch_faces/sensor/lightmeter_face.c206
-rw-r--r--movement/watch_faces/sensor/lightmeter_face.h160
-rw-r--r--movement/watch_faces/settings/place_face.h231
51 files changed, 12707 insertions, 319 deletions
diff --git a/movement/filesystem.c b/movement/filesystem.c
index 2b345eda..97e35455 100644
--- a/movement/filesystem.c
+++ b/movement/filesystem.c
@@ -196,6 +196,7 @@ static void filesystem_cat(char *filename) {
if (info.size > 0) {
char *buf = malloc(info.size + 1);
filesystem_read_file(filename, buf, info.size);
+ buf[info.size] = '\0';
printf("%s\n", buf);
free(buf);
} else {
diff --git a/movement/lib/chirpy_tx/chirpy_tx.c b/movement/lib/chirpy_tx/chirpy_tx.c
new file mode 100644
index 00000000..41a42a58
--- /dev/null
+++ b/movement/lib/chirpy_tx/chirpy_tx.c
@@ -0,0 +1,179 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Gabor L Ugray
+ *
+ * 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 <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include "chirpy_tx.h"
+
+static const uint32_t chirpy_min_freq = 2500;
+static const uint32_t cirpy_freq_step = 250;
+
+// This many bytes are followed by a CRC and block separator
+// It's a multiple of 3 so no bits are wasted (a tone encodes 3 bits)
+// Last block can be shorter
+static const uint8_t chirpy_default_block_size = 15;
+
+// The dedicated control tone. This is the highest tone index.
+static const uint8_t chirpy_control_tone = 8;
+
+// Pre-computed tone periods. This is allocated and populated on-demand.
+static uint32_t *chirpy_tone_periods = NULL;
+
+uint8_t chirpy_crc8(const uint8_t *addr, uint16_t len) {
+ uint8_t crc = 0;
+ for (uint16_t i = 0; i < len; i++)
+ crc = chirpy_update_crc8(addr[i], crc);
+ return crc;
+}
+
+uint8_t chirpy_update_crc8(uint8_t next_byte, uint8_t crc) {
+ for (uint8_t j = 0; j < 8; j++) {
+ uint8_t mix = (crc ^ next_byte) & 0x01;
+ crc >>= 1;
+ if (mix)
+ crc ^= 0x8C;
+ next_byte >>= 1;
+ }
+ return crc;
+}
+
+static void _chirpy_append_tone(chirpy_encoder_state_t *ces, uint8_t tone) {
+ // This is BAD and should never happen. But if it does, we'd rather
+ // create a corrupt transmission than corrupt memory #$^@
+ if (ces->tone_count == CHIRPY_TONE_BUF_SIZE)
+ return;
+ ces->tone_buf[ces->tone_count] = tone;
+ ++ces->tone_count;
+}
+
+void chirpy_init_encoder(chirpy_encoder_state_t *ces, chirpy_get_next_byte_t get_next_byte) {
+ memset(ces, 0, sizeof(chirpy_encoder_state_t));
+ ces->block_size = chirpy_default_block_size;
+ ces->get_next_byte = get_next_byte;
+ _chirpy_append_tone(ces, 8);
+ _chirpy_append_tone(ces, 0);
+ _chirpy_append_tone(ces, 8);
+ _chirpy_append_tone(ces, 0);
+}
+
+static uint8_t _chirpy_retrieve_next_tone(chirpy_encoder_state_t *ces) {
+ if (ces->tone_pos == ces->tone_count)
+ return 255;
+
+ uint8_t res = ces->tone_buf[ces->tone_pos];
+ ++ces->tone_pos;
+ if (ces->tone_pos == ces->tone_count) {
+ // End of buffer: reset buffer
+ ces->tone_pos = 0;
+ ces->tone_count = 0;
+ }
+ return res;
+}
+
+static void _chirpy_encode_bits(chirpy_encoder_state_t *ces, uint8_t force_partial) {
+ while (ces->bit_count > 0) {
+ if (ces->bit_count < 3 && !force_partial) break;
+ uint8_t tone = (uint8_t)(ces->bits >> 13);
+ _chirpy_append_tone(ces, tone);
+ if (ces->bit_count >= 3) {
+ ces->bits <<= 3;
+ ces->bit_count -= 3;
+ } else {
+ ces->bits = 0;
+ ces->bit_count = 0;
+ }
+ }
+}
+
+static void _chirpy_finish_block(chirpy_encoder_state_t *ces) {
+ _chirpy_append_tone(ces, chirpy_control_tone);
+ ces->bits = ces->crc;
+ ces->bits <<= 8;
+ ces->bit_count = 8;
+ _chirpy_encode_bits(ces, 1);
+ ces->bit_count = 0;
+ ces->crc = 0;
+ ces->block_len = 0;
+ _chirpy_append_tone(ces, chirpy_control_tone);
+}
+
+static void _chirpy_finish_transmission(chirpy_encoder_state_t *ces) {
+ _chirpy_append_tone(ces, chirpy_control_tone);
+ _chirpy_append_tone(ces, chirpy_control_tone);
+}
+
+uint8_t chirpy_get_next_tone(chirpy_encoder_state_t *ces) {
+ // If there are tones left in the buffer, keep sending those
+ if (ces->tone_pos < ces->tone_count)
+ return _chirpy_retrieve_next_tone(ces);
+
+ // We know data is over: that means we've wrapped up transmission
+ // Just drain tone buffer, and then keep sendig EOB
+ if (ces->get_next_byte == 0)
+ return _chirpy_retrieve_next_tone(ces);
+
+ // Fetch next byte
+ uint8_t next_byte;
+ uint8_t got_more = ces->get_next_byte(&next_byte);
+
+ // Data over: write CRC if we sent a partial buffer; send end signal
+ if (got_more == 0) {
+ ces->get_next_byte = 0;
+ if (ces->bit_count > 0) _chirpy_encode_bits(ces, 1);
+ if (ces->block_len > 0) _chirpy_finish_block(ces);
+ _chirpy_finish_transmission(ces);
+ return _chirpy_retrieve_next_tone(ces);
+ }
+
+ // Got more data: add to bits; convert
+ uint16_t msk = next_byte;
+ msk <<= (8 - ces->bit_count);
+ ces->bits |= msk;
+ ces->bit_count += 8;
+ _chirpy_encode_bits(ces, 0);
+ ++ces->block_len;
+ ces->crc = chirpy_update_crc8(next_byte, ces->crc);
+ if (ces->block_len == ces->block_size)
+ _chirpy_finish_block(ces);
+
+ return _chirpy_retrieve_next_tone(ces);
+}
+
+uint16_t chirpy_get_tone_period(uint8_t tone) {
+ // Create pre-computed tone periods array on first use
+ if (chirpy_tone_periods == NULL) {
+ chirpy_tone_periods = malloc((chirpy_control_tone + 1) * sizeof(uint32_t));
+ for (uint8_t i = 0; i < chirpy_control_tone + 1; ++i) {
+ uint32_t freq = chirpy_min_freq + i * cirpy_freq_step;
+ uint16_t period = 1000000 / freq;
+ chirpy_tone_periods[i] = period;
+ }
+ }
+ // Return pre-computed value, but be paranoid about indexing into array
+ if (tone > chirpy_control_tone)
+ tone = chirpy_control_tone;
+ return chirpy_tone_periods[tone];
+}
diff --git a/movement/lib/chirpy_tx/chirpy_tx.h b/movement/lib/chirpy_tx/chirpy_tx.h
new file mode 100644
index 00000000..092d05c7
--- /dev/null
+++ b/movement/lib/chirpy_tx/chirpy_tx.h
@@ -0,0 +1,98 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Gabor L Ugray
+ *
+ * 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 CHIRPY_TX_H
+#define CHIRPY_TX_H
+
+/** @brief Calculates the CRC of a byte sequence.
+ */
+uint8_t chirpy_crc8(const uint8_t *addr, uint16_t len);
+
+/** @brief Updates a CRC value with the next byte of input.
+ */
+uint8_t chirpy_update_crc8(uint8_t next_byte, uint8_t crc);
+
+/** @brief Function to return next byte to be encoded.
+ * @param next_byte Pointer where implementor must write next byte (if available).
+ * @return 1 if there is a next byte, or 0 if no more data to encode.
+ */
+typedef uint8_t (*chirpy_get_next_byte_t)(uint8_t *next_byte);
+
+#define CHIRPY_TONE_BUF_SIZE 16
+
+// Holds state used by the encoder. Do not manipulate directly.
+typedef struct {
+ uint8_t tone_buf[CHIRPY_TONE_BUF_SIZE];
+ uint8_t tone_pos;
+ uint8_t tone_count;
+ uint8_t block_len;
+ uint8_t block_size;
+ uint8_t crc;
+ uint16_t bits;
+ uint8_t bit_count;
+ chirpy_get_next_byte_t get_next_byte;
+} chirpy_encoder_state_t;
+
+/** @brief Iniitializes the encoder state to be used during the transmission.
+ * @param ces Pointer to encoder state object to be initialized.
+ * @param get_next_byte Pointer to function that the encoder will call to fetch data byte by byte.
+ */
+void chirpy_init_encoder(chirpy_encoder_state_t *ces, chirpy_get_next_byte_t get_next_byte);
+
+/** @brief Returns the next tone to be transmitted.
+ * @details This function will call the get_next_byte function stored in the encoder state to
+ * retrieve the next byte to be transmitted as needed. As a single byte is encoded as several tones,
+ * and because the transmission also includes periodic CRC values, not every call to this function
+ * will result in a callback for the next data byte.
+ * @param ced Pointer to the encoder state object.
+ * @return A tone index from 0 to N (where N is the largest tone index), or 255 if the transmission is over.
+ */
+uint8_t chirpy_get_next_tone(chirpy_encoder_state_t *ces);
+
+/** @brief Returns the period value for buzzing out a tone.
+ * @param tone The tone index, 0 thru 8.
+ * @return The period for the tone's frequency, i.e., 1_000_000 / freq.
+ */
+uint16_t chirpy_get_tone_period(uint8_t tone);
+
+/** @brief Typedef for a tick handler function.
+ */
+typedef void (*chirpy_tick_fun_t)(void *context);
+
+/** @brief Creature-comfort struct for use in your chirping code.
+ * @details The idea is to handle a tick that happens 64 times per second at the outermost level.
+ * To get to the desired ~20 tones per second, increment a counter and call the actual
+ * transmission ticker when tick_counter reaches tick_compare, with a compare value of 3.
+ * seq_pos is for use by the transmission function to keep track of where it is in the data.
+ * The current transmission function is stored in tick_fun. You can have multiple phases
+ * by switching to a different function. E.g., intro countdown first, followed by data chirping.
+ */
+typedef struct {
+ uint8_t tick_count;
+ uint8_t tick_compare;
+ uint16_t seq_pos;
+ chirpy_tick_fun_t tick_fun;
+} chirpy_tick_state_t;
+
+#endif
diff --git a/movement/lib/chirpy_tx/test/test_main.c b/movement/lib/chirpy_tx/test/test_main.c
new file mode 100644
index 00000000..c76a3f3c
--- /dev/null
+++ b/movement/lib/chirpy_tx/test/test_main.c
@@ -0,0 +1,160 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Gabor L Ugray
+ *
+ * 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 <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include "../src/lib/chirpy_tx.h"
+#include "unity.h"
+
+
+void setUp(void) {
+}
+
+void tearDown(void) {
+}
+
+const char *crc_test_str0 = "";
+const char *crc_test_str1 = "h";
+const char *crc_test_str2 = "he";
+const char *crc_test_str3 = "hen";
+const char *crc_test_str4 = "henl";
+const char *crc_test_str5 = "henlO";
+
+#define CRC_VAL_COUNT 5
+const uint8_t crc_vals[] = {167, 118, 95, 92, 127};
+
+void test_crc8() {
+ TEST_ASSERT_EQUAL_UINT8(0, chirpy_crc8((const uint8_t *)crc_test_str0, 0));
+ TEST_ASSERT_EQUAL_UINT8(167, chirpy_crc8((const uint8_t *)crc_test_str1, strlen(crc_test_str1)));
+ TEST_ASSERT_EQUAL_UINT8(118, chirpy_crc8((const uint8_t *)crc_test_str2, strlen(crc_test_str2)));
+ TEST_ASSERT_EQUAL_UINT8(95, chirpy_crc8((const uint8_t *)crc_test_str3, strlen(crc_test_str3)));
+ TEST_ASSERT_EQUAL_UINT8(92, chirpy_crc8((const uint8_t *)crc_test_str4, strlen(crc_test_str4)));
+ TEST_ASSERT_EQUAL_UINT8(127, chirpy_crc8((const uint8_t *)crc_test_str5, strlen(crc_test_str5)));
+
+ uint8_t crc = 0;
+ for (uint16_t i = 0; i < CRC_VAL_COUNT; ++i) {
+ uint8_t next_byte = crc_test_str5[i];
+ crc = chirpy_update_crc8(next_byte, crc);
+ TEST_ASSERT_EQUAL_UINT8(crc_vals[i], crc);
+ }
+
+ crc = chirpy_update_crc8(0, 0);
+ TEST_ASSERT_EQUAL(0, crc);
+
+ crc = chirpy_update_crc8(0x4f, 0);
+ TEST_ASSERT_EQUAL(7, crc);
+}
+
+const uint16_t data_len_01 = 0;
+const uint8_t data_01[] = {};
+const uint16_t tones_len_01 = 6;
+const uint8_t tones_01[] = {8, 0, 8, 0, 8, 8};
+
+const uint16_t data_len_02 = 1;
+const uint8_t data_02[] = {0};
+const uint16_t tones_len_02 = 14;
+const uint8_t tones_02[] = {8, 0, 8, 0, 0, 0, 0, 8, 0, 0, 0, 8, 8, 8};
+
+const uint16_t data_len_03 = 1;
+const uint8_t data_03[] = {0x68};
+const uint16_t tones_len_03 = 14;
+const uint8_t tones_03[] = {8, 0, 8, 0, 3, 2, 0, 8, 5, 1, 6, 8, 8, 8};
+
+const uint16_t data_len_04 = 3;
+const uint8_t data_04[] = {0x68, 0x65, 0x6e};
+const uint16_t tones_len_04 = 19;
+const uint8_t tones_04[] = {8, 0, 8, 0, 3, 2, 0, 6, 2, 5, 5, 6, 8, 2, 7, 6, 8, 8, 8};
+
+const uint16_t data_len_05 = 4;
+const uint8_t data_05[] = {0x68, 0x65, 0x6e, 0x4f};
+const uint16_t tones_len_05 = 27;
+const uint8_t tones_05[] = {
+ 8, 0, 8, 0, 3, 2, 0, 6, 2, 5, 5, 6, 8, 2, 7, 6, 8,
+ 2, 3, 6, 8, 0, 1, 6, 8, 8, 8};
+
+uint8_t curr_data_pos;
+uint8_t curr_data_len;
+const uint8_t *curr_data;
+
+uint8_t get_next_byte(uint8_t *next_byte) {
+ if (curr_data_pos < curr_data_len) {
+ *next_byte = curr_data[curr_data_pos];
+ ++curr_data_pos;
+ return 1;
+ }
+ return 0;
+}
+
+void test_encoder_one(const uint8_t *data, uint16_t data_len, const uint8_t *tones, uint16_t tones_len) {
+ curr_data = data;
+ curr_data_len = data_len;
+ curr_data_pos = 0;
+ chirpy_encoder_state_t ces;
+ chirpy_init_encoder(&ces, get_next_byte);
+ ces.block_size = 3;
+
+ uint8_t got_tones[2048] = {0};
+ uint16_t got_tone_pos = 0;
+ while (got_tone_pos < 2048) {
+ uint8_t tone = chirpy_get_next_tone(&ces);
+ got_tones[got_tone_pos] = tone;
+ if (tone == 255) break;
+ ++got_tone_pos;
+ }
+ char buf1[65536];
+ char bufx[256];
+ memset(buf1, 0, 65536);
+ for (uint16_t i = 0; i < got_tone_pos; ++i) {
+ if (i == 0)
+ sprintf(bufx, "%d", got_tones[i]);
+ else
+ sprintf(bufx, ", %d", got_tones[i]);
+ strcat(buf1, bufx);
+ }
+ TEST_MESSAGE(buf1);
+ TEST_ASSERT_EQUAL(tones_len, got_tone_pos);
+ uint16_t smaller_len = tones_len < got_tone_pos ? tones_len : got_tone_pos;
+ TEST_ASSERT_EQUAL_UINT8_ARRAY(tones, got_tones, smaller_len);
+}
+
+void test_encoder() {
+ TEST_MESSAGE("Testing encoder with dataset 01");
+ test_encoder_one(data_01, data_len_01, tones_01, tones_len_01);
+ TEST_MESSAGE("Testing encoder with dataset 02");
+ test_encoder_one(data_02, data_len_02, tones_02, tones_len_02);
+ TEST_MESSAGE("Testing encoder with dataset 03");
+ test_encoder_one(data_03, data_len_03, tones_03, tones_len_03);
+ TEST_MESSAGE("Testing encoder with dataset 04");
+ test_encoder_one(data_04, data_len_04, tones_04, tones_len_04);
+ TEST_MESSAGE("Testing encoder with dataset 05");
+ test_encoder_one(data_05, data_len_05, tones_05, tones_len_05);
+}
+
+int main(void) {
+ UNITY_BEGIN();
+ RUN_TEST(test_crc8);
+ RUN_TEST(test_encoder);
+ return UNITY_END();
+}
diff --git a/movement/lib/chirpy_tx/test/unity.c b/movement/lib/chirpy_tx/test/unity.c
new file mode 100644
index 00000000..3e4bc04d
--- /dev/null
+++ b/movement/lib/chirpy_tx/test/unity.c
@@ -0,0 +1,2466 @@
+/* =========================================================================
+ Unity Project - A Test Framework for C
+ Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams
+ [Released under MIT License. Please refer to license.txt for details]
+============================================================================ */
+
+#include "unity.h"
+
+#ifndef UNITY_PROGMEM
+#define UNITY_PROGMEM
+#endif
+
+/* If omitted from header, declare overrideable prototypes here so they're ready for use */
+#ifdef UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION
+void UNITY_OUTPUT_CHAR(int);
+#endif
+
+/* Helpful macros for us to use here in Assert functions */
+#define UNITY_FAIL_AND_BAIL do { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0)
+#define UNITY_IGNORE_AND_BAIL do { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0)
+#define RETURN_IF_FAIL_OR_IGNORE do { if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) { TEST_ABORT(); } } while (0)
+
+struct UNITY_STORAGE_T Unity;
+
+#ifdef UNITY_OUTPUT_COLOR
+const char UNITY_PROGMEM UnityStrOk[] = "\033[42mOK\033[0m";
+const char UNITY_PROGMEM UnityStrPass[] = "\033[42mPASS\033[0m";
+const char UNITY_PROGMEM UnityStrFail[] = "\033[41mFAIL\033[0m";
+const char UNITY_PROGMEM UnityStrIgnore[] = "\033[43mIGNORE\033[0m";
+#else
+const char UNITY_PROGMEM UnityStrOk[] = "OK";
+const char UNITY_PROGMEM UnityStrPass[] = "PASS";
+const char UNITY_PROGMEM UnityStrFail[] = "FAIL";
+const char UNITY_PROGMEM UnityStrIgnore[] = "IGNORE";
+#endif
+static const char UNITY_PROGMEM UnityStrNull[] = "NULL";
+static const char UNITY_PROGMEM UnityStrSpacer[] = ". ";
+static const char UNITY_PROGMEM UnityStrExpected[] = " Expected ";
+static const char UNITY_PROGMEM UnityStrWas[] = " Was ";
+static const char UNITY_PROGMEM UnityStrGt[] = " to be greater than ";
+static const char UNITY_PROGMEM UnityStrLt[] = " to be less than ";
+static const char UNITY_PROGMEM UnityStrOrEqual[] = "or equal to ";
+static const char UNITY_PROGMEM UnityStrNotEqual[] = " to be not equal to ";
+static const char UNITY_PROGMEM UnityStrElement[] = " Element ";
+static const char UNITY_PROGMEM UnityStrByte[] = " Byte ";
+static const char UNITY_PROGMEM UnityStrMemory[] = " Memory Mismatch.";
+static const char UNITY_PROGMEM UnityStrDelta[] = " Values Not Within Delta ";
+static const char UNITY_PROGMEM UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless.";
+static const char UNITY_PROGMEM UnityStrNullPointerForExpected[] = " Expected pointer to be NULL";
+static const char UNITY_PROGMEM UnityStrNullPointerForActual[] = " Actual pointer was NULL";
+#ifndef UNITY_EXCLUDE_FLOAT
+static const char UNITY_PROGMEM UnityStrNot[] = "Not ";
+static const char UNITY_PROGMEM UnityStrInf[] = "Infinity";
+static const char UNITY_PROGMEM UnityStrNegInf[] = "Negative Infinity";
+static const char UNITY_PROGMEM UnityStrNaN[] = "NaN";
+static const char UNITY_PROGMEM UnityStrDet[] = "Determinate";
+static const char UNITY_PROGMEM UnityStrInvalidFloatTrait[] = "Invalid Float Trait";
+#endif
+const char UNITY_PROGMEM UnityStrErrShorthand[] = "Unity Shorthand Support Disabled";
+const char UNITY_PROGMEM UnityStrErrFloat[] = "Unity Floating Point Disabled";
+const char UNITY_PROGMEM UnityStrErrDouble[] = "Unity Double Precision Disabled";
+const char UNITY_PROGMEM UnityStrErr64[] = "Unity 64-bit Support Disabled";
+static const char UNITY_PROGMEM UnityStrBreaker[] = "-----------------------";
+static const char UNITY_PROGMEM UnityStrResultsTests[] = " Tests ";
+static const char UNITY_PROGMEM UnityStrResultsFailures[] = " Failures ";
+static const char UNITY_PROGMEM UnityStrResultsIgnored[] = " Ignored ";
+#ifndef UNITY_EXCLUDE_DETAILS
+static const char UNITY_PROGMEM UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " ";
+static const char UNITY_PROGMEM UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " ";
+#endif
+/*-----------------------------------------------
+ * Pretty Printers & Test Result Output Handlers
+ *-----------------------------------------------*/
+
+/*-----------------------------------------------*/
+/* Local helper function to print characters. */
+static void UnityPrintChar(const char* pch)
+{
+ /* printable characters plus CR & LF are printed */
+ if ((*pch <= 126) && (*pch >= 32))
+ {
+ UNITY_OUTPUT_CHAR(*pch);
+ }
+ /* write escaped carriage returns */
+ else if (*pch == 13)
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('r');
+ }
+ /* write escaped line feeds */
+ else if (*pch == 10)
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('n');
+ }
+ /* unprintable characters are shown as codes */
+ else
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('x');
+ UnityPrintNumberHex((UNITY_UINT)*pch, 2);
+ }
+}
+
+/*-----------------------------------------------*/
+/* Local helper function to print ANSI escape strings e.g. "\033[42m". */
+#ifdef UNITY_OUTPUT_COLOR
+static UNITY_UINT UnityPrintAnsiEscapeString(const char* string)
+{
+ const char* pch = string;
+ UNITY_UINT count = 0;
+
+ while (*pch && (*pch != 'm'))
+ {
+ UNITY_OUTPUT_CHAR(*pch);
+ pch++;
+ count++;
+ }
+ UNITY_OUTPUT_CHAR('m');
+ count++;
+
+ return count;
+}
+#endif
+
+/*-----------------------------------------------*/
+void UnityPrint(const char* string)
+{
+ const char* pch = string;
+
+ if (pch != NULL)
+ {
+ while (*pch)
+ {
+#ifdef UNITY_OUTPUT_COLOR
+ /* print ANSI escape code */
+ if ((*pch == 27) && (*(pch + 1) == '['))
+ {
+ pch += UnityPrintAnsiEscapeString(pch);
+ continue;
+ }
+#endif
+ UnityPrintChar(pch);
+ pch++;
+ }
+ }
+}
+/*-----------------------------------------------*/
+void UnityPrintLen(const char* string, const UNITY_UINT32 length)
+{
+ const char* pch = string;
+
+ if (pch != NULL)
+ {
+ while (*pch && ((UNITY_UINT32)(pch - string) < length))
+ {
+ /* printable characters plus CR & LF are printed */
+ if ((*pch <= 126) && (*pch >= 32))
+ {
+ UNITY_OUTPUT_CHAR(*pch);
+ }
+ /* write escaped carriage returns */
+ else if (*pch == 13)
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('r');
+ }
+ /* write escaped line feeds */
+ else if (*pch == 10)
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('n');
+ }
+ /* unprintable characters are shown as codes */
+ else
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('x');
+ UnityPrintNumberHex((UNITY_UINT)*pch, 2);
+ }
+ pch++;
+ }
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style)
+{
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ if (style == UNITY_DISPLAY_STYLE_CHAR)
+ {
+ /* printable characters plus CR & LF are printed */
+ UNITY_OUTPUT_CHAR('\'');
+ if ((number <= 126) && (number >= 32))
+ {
+ UNITY_OUTPUT_CHAR((int)number);
+ }
+ /* write escaped carriage returns */
+ else if (number == 13)
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('r');
+ }
+ /* write escaped line feeds */
+ else if (number == 10)
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('n');
+ }
+ /* unprintable characters are shown as codes */
+ else
+ {
+ UNITY_OUTPUT_CHAR('\\');
+ UNITY_OUTPUT_CHAR('x');
+ UnityPrintNumberHex((UNITY_UINT)number, 2);
+ }
+ UNITY_OUTPUT_CHAR('\'');
+ }
+ else
+ {
+ UnityPrintNumber(number);
+ }
+ }
+ else if ((style & UNITY_DISPLAY_RANGE_UINT) == UNITY_DISPLAY_RANGE_UINT)
+ {
+ UnityPrintNumberUnsigned((UNITY_UINT)number);
+ }
+ else
+ {
+ UNITY_OUTPUT_CHAR('0');
+ UNITY_OUTPUT_CHAR('x');
+ UnityPrintNumberHex((UNITY_UINT)number, (char)((style & 0xF) * 2));
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityPrintNumber(const UNITY_INT number_to_print)
+{
+ UNITY_UINT number = (UNITY_UINT)number_to_print;
+
+ if (number_to_print < 0)
+ {
+ /* A negative number, including MIN negative */
+ UNITY_OUTPUT_CHAR('-');
+ number = (~number) + 1;
+ }
+ UnityPrintNumberUnsigned(number);
+}
+
+/*-----------------------------------------------
+ * basically do an itoa using as little ram as possible */
+void UnityPrintNumberUnsigned(const UNITY_UINT number)
+{
+ UNITY_UINT divisor = 1;
+
+ /* figure out initial divisor */
+ while (number / divisor > 9)
+ {
+ divisor *= 10;
+ }
+
+ /* now mod and print, then divide divisor */
+ do
+ {
+ UNITY_OUTPUT_CHAR((char)('0' + (number / divisor % 10)));
+ divisor /= 10;
+ } while (divisor > 0);
+}
+
+/*-----------------------------------------------*/
+void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print)
+{
+ int nibble;
+ char nibbles = nibbles_to_print;
+
+ if ((unsigned)nibbles > UNITY_MAX_NIBBLES)
+ {
+ nibbles = UNITY_MAX_NIBBLES;
+ }
+
+ while (nibbles > 0)
+ {
+ nibbles--;
+ nibble = (int)(number >> (nibbles * 4)) & 0x0F;
+ if (nibble <= 9)
+ {
+ UNITY_OUTPUT_CHAR((char)('0' + nibble));
+ }
+ else
+ {
+ UNITY_OUTPUT_CHAR((char)('A' - 10 + nibble));
+ }
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number)
+{
+ UNITY_UINT current_bit = (UNITY_UINT)1 << (UNITY_INT_WIDTH - 1);
+ UNITY_INT32 i;
+
+ for (i = 0; i < UNITY_INT_WIDTH; i++)
+ {
+ if (current_bit & mask)
+ {
+ if (current_bit & number)
+ {
+ UNITY_OUTPUT_CHAR('1');
+ }
+ else
+ {
+ UNITY_OUTPUT_CHAR('0');
+ }
+ }
+ else
+ {
+ UNITY_OUTPUT_CHAR('X');
+ }
+ current_bit = current_bit >> 1;
+ }
+}
+
+/*-----------------------------------------------*/
+#ifndef UNITY_EXCLUDE_FLOAT_PRINT
+/*
+ * This function prints a floating-point value in a format similar to
+ * printf("%.7g") on a single-precision machine or printf("%.9g") on a
+ * double-precision machine. The 7th digit won't always be totally correct
+ * in single-precision operation (for that level of accuracy, a more
+ * complicated algorithm would be needed).
+ */
+void UnityPrintFloat(const UNITY_DOUBLE input_number)
+{
+#ifdef UNITY_INCLUDE_DOUBLE
+ static const int sig_digits = 9;
+ static const UNITY_INT32 min_scaled = 100000000;
+ static const UNITY_INT32 max_scaled = 1000000000;
+#else
+ static const int sig_digits = 7;
+ static const UNITY_INT32 min_scaled = 1000000;
+ static const UNITY_INT32 max_scaled = 10000000;
+#endif
+
+ UNITY_DOUBLE number = input_number;
+
+ /* print minus sign (does not handle negative zero) */
+ if (number < 0.0f)
+ {
+ UNITY_OUTPUT_CHAR('-');
+ number = -number;
+ }
+
+ /* handle zero, NaN, and +/- infinity */
+ if (number == 0.0f)
+ {
+ UnityPrint("0");
+ }
+ else if (isnan(number))
+ {
+ UnityPrint("nan");
+ }
+ else if (isinf(number))
+ {
+ UnityPrint("inf");
+ }
+ else
+ {
+ UNITY_INT32 n_int = 0;
+ UNITY_INT32 n;
+ int exponent = 0;
+ int decimals;
+ int digits;
+ char buf[16] = {0};
+
+ /*
+ * Scale up or down by powers of 10. To minimize rounding error,
+ * start with a factor/divisor of 10^10, which is the largest
+ * power of 10 that can be represented exactly. Finally, compute
+ * (exactly) the remaining power of 10 and perform one more
+ * multiplication or division.
+ */
+ if (number < 1.0f)
+ {
+ UNITY_DOUBLE factor = 1.0f;
+
+ while (number < (UNITY_DOUBLE)max_scaled / 1e10f) { number *= 1e10f; exponent -= 10; }
+ while (number * factor < (UNITY_DOUBLE)min_scaled) { factor *= 10.0f; exponent--; }
+
+ number *= factor;
+ }
+ else if (number > (UNITY_DOUBLE)max_scaled)
+ {
+ UNITY_DOUBLE divisor = 1.0f;
+
+ while (number > (UNITY_DOUBLE)min_scaled * 1e10f) { number /= 1e10f; exponent += 10; }
+ while (number / divisor > (UNITY_DOUBLE)max_scaled) { divisor *= 10.0f; exponent++; }
+
+ number /= divisor;
+ }
+ else
+ {
+ /*
+ * In this range, we can split off the integer part before
+ * doing any multiplications. This reduces rounding error by
+ * freeing up significant bits in the fractional part.
+ */
+ UNITY_DOUBLE factor = 1.0f;
+ n_int = (UNITY_INT32)number;
+ number -= (UNITY_DOUBLE)n_int;
+
+ while (n_int < min_scaled) { n_int *= 10; factor *= 10.0f; exponent--; }
+
+ number *= factor;
+ }
+
+ /* round to nearest integer */
+ n = ((UNITY_INT32)(number + number) + 1) / 2;
+
+#ifndef UNITY_ROUND_TIES_AWAY_FROM_ZERO
+ /* round to even if exactly between two integers */
+ if ((n & 1) && (((UNITY_DOUBLE)n - number) == 0.5f))
+ n--;
+#endif
+
+ n += n_int;
+
+ if (n >= max_scaled)
+ {
+ n = min_scaled;
+ exponent++;
+ }
+
+ /* determine where to place decimal point */
+ decimals = ((exponent <= 0) && (exponent >= -(sig_digits + 3))) ? (-exponent) : (sig_digits - 1);
+ exponent += decimals;
+
+ /* truncate trailing zeroes after decimal point */
+ while ((decimals > 0) && ((n % 10) == 0))
+ {
+ n /= 10;
+ decimals--;
+ }
+
+ /* build up buffer in reverse order */
+ digits = 0;
+ while ((n != 0) || (digits <= decimals))
+ {
+ buf[digits++] = (char)('0' + n % 10);
+ n /= 10;
+ }
+
+ /* print out buffer (backwards) */
+ while (digits > 0)
+ {
+ if (digits == decimals)
+ {
+ UNITY_OUTPUT_CHAR('.');
+ }
+ UNITY_OUTPUT_CHAR(buf[--digits]);
+ }
+
+ /* print exponent if needed */
+ if (exponent != 0)
+ {
+ UNITY_OUTPUT_CHAR('e');
+
+ if (exponent < 0)
+ {
+ UNITY_OUTPUT_CHAR('-');
+ exponent = -exponent;
+ }
+ else
+ {
+ UNITY_OUTPUT_CHAR('+');
+ }
+
+ digits = 0;
+ while ((exponent != 0) || (digits < 2))
+ {
+ buf[digits++] = (char)('0' + exponent % 10);
+ exponent /= 10;
+ }
+ while (digits > 0)
+ {
+ UNITY_OUTPUT_CHAR(buf[--digits]);
+ }
+ }
+ }
+}
+#endif /* ! UNITY_EXCLUDE_FLOAT_PRINT */
+
+/*-----------------------------------------------*/
+static void UnityTestResultsBegin(const char* file, const UNITY_LINE_TYPE line)
+{
+#ifdef UNITY_OUTPUT_FOR_ECLIPSE
+ UNITY_OUTPUT_CHAR('(');
+ UnityPrint(file);
+ UNITY_OUTPUT_CHAR(':');
+ UnityPrintNumber((UNITY_INT)line);
+ UNITY_OUTPUT_CHAR(')');
+ UNITY_OUTPUT_CHAR(' ');
+ UnityPrint(Unity.CurrentTestName);
+ UNITY_OUTPUT_CHAR(':');
+#else
+#ifdef UNITY_OUTPUT_FOR_IAR_WORKBENCH
+ UnityPrint("<SRCREF line=");
+ UnityPrintNumber((UNITY_INT)line);
+ UnityPrint(" file=\"");
+ UnityPrint(file);
+ UNITY_OUTPUT_CHAR('"');
+ UNITY_OUTPUT_CHAR('>');
+ UnityPrint(Unity.CurrentTestName);
+ UnityPrint("</SRCREF> ");
+#else
+#ifdef UNITY_OUTPUT_FOR_QT_CREATOR
+ UnityPrint("file://");
+ UnityPrint(file);
+ UNITY_OUTPUT_CHAR(':');
+ UnityPrintNumber((UNITY_INT)line);
+ UNITY_OUTPUT_CHAR(' ');
+ UnityPrint(Unity.CurrentTestName);
+ UNITY_OUTPUT_CHAR(':');
+#else
+ UnityPrint(file);
+ UNITY_OUTPUT_CHAR(':');
+ UnityPrintNumber((UNITY_INT)line);
+ UNITY_OUTPUT_CHAR(':');
+ UnityPrint(Unity.CurrentTestName);
+ UNITY_OUTPUT_CHAR(':');
+#endif
+#endif
+#endif
+}
+
+/*-----------------------------------------------*/
+static void UnityTestResultsFailBegin(const UNITY_LINE_TYPE line)
+{
+ UnityTestResultsBegin(Unity.TestFile, line);
+ UnityPrint(UnityStrFail);
+ UNITY_OUTPUT_CHAR(':');
+}
+
+/*-----------------------------------------------*/
+void UnityConcludeTest(void)
+{
+ if (Unity.CurrentTestIgnored)
+ {
+ Unity.TestIgnores++;
+ }
+ else if (!Unity.CurrentTestFailed)
+ {
+ UnityTestResultsBegin(Unity.TestFile, Unity.CurrentTestLineNumber);
+ UnityPrint(UnityStrPass);
+ }
+ else
+ {
+ Unity.TestFailures++;
+ }
+
+ Unity.CurrentTestFailed = 0;
+ Unity.CurrentTestIgnored = 0;
+ UNITY_PRINT_EXEC_TIME();
+ UNITY_PRINT_EOL();
+ UNITY_FLUSH_CALL();
+}
+
+/*-----------------------------------------------*/
+static void UnityAddMsgIfSpecified(const char* msg)
+{
+#ifdef UNITY_PRINT_TEST_CONTEXT
+ UnityPrint(UnityStrSpacer);
+ UNITY_PRINT_TEST_CONTEXT();
+#endif
+#ifndef UNITY_EXCLUDE_DETAILS
+ if (Unity.CurrentDetail1)
+ {
+ UnityPrint(UnityStrSpacer);
+ UnityPrint(UnityStrDetail1Name);
+ UnityPrint(Unity.CurrentDetail1);
+ if (Unity.CurrentDetail2)
+ {
+ UnityPrint(UnityStrDetail2Name);
+ UnityPrint(Unity.CurrentDetail2);
+ }
+ }
+#endif
+ if (msg)
+ {
+ UnityPrint(UnityStrSpacer);
+ UnityPrint(msg);
+ }
+}
+
+/*-----------------------------------------------*/
+static void UnityPrintExpectedAndActualStrings(const char* expected, const char* actual)
+{
+ UnityPrint(UnityStrExpected);
+ if (expected != NULL)
+ {
+ UNITY_OUTPUT_CHAR('\'');
+ UnityPrint(expected);
+ UNITY_OUTPUT_CHAR('\'');
+ }
+ else
+ {
+ UnityPrint(UnityStrNull);
+ }
+ UnityPrint(UnityStrWas);
+ if (actual != NULL)
+ {
+ UNITY_OUTPUT_CHAR('\'');
+ UnityPrint(actual);
+ UNITY_OUTPUT_CHAR('\'');
+ }
+ else
+ {
+ UnityPrint(UnityStrNull);
+ }
+}
+
+/*-----------------------------------------------*/
+static void UnityPrintExpectedAndActualStringsLen(const char* expected,
+ const char* actual,
+ const UNITY_UINT32 length)
+{
+ UnityPrint(UnityStrExpected);
+ if (expected != NULL)
+ {
+ UNITY_OUTPUT_CHAR('\'');
+ UnityPrintLen(expected, length);
+ UNITY_OUTPUT_CHAR('\'');
+ }
+ else
+ {
+ UnityPrint(UnityStrNull);
+ }
+ UnityPrint(UnityStrWas);
+ if (actual != NULL)
+ {
+ UNITY_OUTPUT_CHAR('\'');
+ UnityPrintLen(actual, length);
+ UNITY_OUTPUT_CHAR('\'');
+ }
+ else
+ {
+ UnityPrint(UnityStrNull);
+ }
+}
+
+/*-----------------------------------------------
+ * Assertion & Control Helpers
+ *-----------------------------------------------*/
+
+/*-----------------------------------------------*/
+static int UnityIsOneArrayNull(UNITY_INTERNAL_PTR expected,
+ UNITY_INTERNAL_PTR actual,
+ const UNITY_LINE_TYPE lineNumber,
+ const char* msg)
+{
+ /* Both are NULL or same pointer */
+ if (expected == actual) { return 0; }
+
+ /* print and return true if just expected is NULL */
+ if (expected == NULL)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrNullPointerForExpected);
+ UnityAddMsgIfSpecified(msg);
+ return 1;
+ }
+
+ /* print and return true if just actual is NULL */
+ if (actual == NULL)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrNullPointerForActual);
+ UnityAddMsgIfSpecified(msg);
+ return 1;
+ }
+
+ return 0; /* return false if neither is NULL */
+}
+
+/*-----------------------------------------------
+ * Assertion Functions
+ *-----------------------------------------------*/
+
+/*-----------------------------------------------*/
+void UnityAssertBits(const UNITY_INT mask,
+ const UNITY_INT expected,
+ const UNITY_INT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if ((mask & expected) != (mask & actual))
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)expected);
+ UnityPrint(UnityStrWas);
+ UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertEqualNumber(const UNITY_INT expected,
+ const UNITY_INT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (expected != actual)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ UnityPrintNumberByStyle(expected, style);
+ UnityPrint(UnityStrWas);
+ UnityPrintNumberByStyle(actual, style);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold,
+ const UNITY_INT actual,
+ const UNITY_COMPARISON_T compare,
+ const char *msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style)
+{
+ int failed = 0;
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if ((threshold == actual) && (compare & UNITY_EQUAL_TO)) { return; }
+ if ((threshold == actual)) { failed = 1; }
+
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ if ((actual > threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; }
+ if ((actual < threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; }
+ }
+ else /* UINT or HEX */
+ {
+ if (((UNITY_UINT)actual > (UNITY_UINT)threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; }
+ if (((UNITY_UINT)actual < (UNITY_UINT)threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; }
+ }
+
+ if (failed)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ UnityPrintNumberByStyle(actual, style);
+ if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); }
+ if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); }
+ if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); }
+ if (compare == UNITY_NOT_EQUAL) { UnityPrint(UnityStrNotEqual); }
+ UnityPrintNumberByStyle(threshold, style);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+#define UnityPrintPointlessAndBail() \
+do { \
+ UnityTestResultsFailBegin(lineNumber); \
+ UnityPrint(UnityStrPointless); \
+ UnityAddMsgIfSpecified(msg); \
+ UNITY_FAIL_AND_BAIL; \
+} while (0)
+
+/*-----------------------------------------------*/
+void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected,
+ UNITY_INTERNAL_PTR actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style,
+ const UNITY_FLAGS_T flags)
+{
+ UNITY_UINT32 elements = num_elements;
+ unsigned int length = style & 0xF;
+ unsigned int increment = 0;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (num_elements == 0)
+ {
+#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY
+ UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg);
+#else
+ UnityPrintPointlessAndBail();
+#endif
+ }
+
+ if (expected == actual)
+ {
+ return; /* Both are NULL or same pointer */
+ }
+
+ if (UnityIsOneArrayNull(expected, actual, lineNumber, msg))
+ {
+ UNITY_FAIL_AND_BAIL;
+ }
+
+ while ((elements > 0) && (elements--))
+ {
+ UNITY_INT expect_val;
+ UNITY_INT actual_val;
+
+ switch (length)
+ {
+ case 1:
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual;
+ if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX))
+ {
+ expect_val &= 0x000000FF;
+ actual_val &= 0x000000FF;
+ }
+ increment = sizeof(UNITY_INT8);
+ break;
+
+ case 2:
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual;
+ if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX))
+ {
+ expect_val &= 0x0000FFFF;
+ actual_val &= 0x0000FFFF;
+ }
+ increment = sizeof(UNITY_INT16);
+ break;
+
+#ifdef UNITY_SUPPORT_64
+ case 8:
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual;
+ increment = sizeof(UNITY_INT64);
+ break;
+#endif
+
+ default: /* default is length 4 bytes */
+ case 4:
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual;
+#ifdef UNITY_SUPPORT_64
+ if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX))
+ {
+ expect_val &= 0x00000000FFFFFFFF;
+ actual_val &= 0x00000000FFFFFFFF;
+ }
+#endif
+ increment = sizeof(UNITY_INT32);
+ length = 4;
+ break;
+ }
+
+ if (expect_val != actual_val)
+ {
+ if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8)))
+ { /* For UINT, remove sign extension (padding 1's) from signed type casts above */
+ UNITY_INT mask = 1;
+ mask = (mask << 8 * length) - 1;
+ expect_val &= mask;
+ actual_val &= mask;
+ }
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrElement);
+ UnityPrintNumberUnsigned(num_elements - elements - 1);
+ UnityPrint(UnityStrExpected);
+ UnityPrintNumberByStyle(expect_val, style);
+ UnityPrint(UnityStrWas);
+ UnityPrintNumberByStyle(actual_val, style);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+ /* Walk through array by incrementing the pointers */
+ if (flags == UNITY_ARRAY_TO_ARRAY)
+ {
+ expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment);
+ }
+ actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment);
+ }
+}
+
+/*-----------------------------------------------*/
+#ifndef UNITY_EXCLUDE_FLOAT
+/* Wrap this define in a function with variable types as float or double */
+#define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \
+ if (isinf(expected) && isinf(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \
+ if (UNITY_NAN_CHECK) return 1; \
+ (diff) = (actual) - (expected); \
+ if ((diff) < 0) (diff) = -(diff); \
+ if ((delta) < 0) (delta) = -(delta); \
+ return !(isnan(diff) || isinf(diff) || ((diff) > (delta)))
+ /* This first part of this condition will catch any NaN or Infinite values */
+#ifndef UNITY_NAN_NOT_EQUAL_NAN
+ #define UNITY_NAN_CHECK isnan(expected) && isnan(actual)
+#else
+ #define UNITY_NAN_CHECK 0
+#endif
+
+#ifndef UNITY_EXCLUDE_FLOAT_PRINT
+ #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \
+ do { \
+ UnityPrint(UnityStrExpected); \
+ UnityPrintFloat(expected); \
+ UnityPrint(UnityStrWas); \
+ UnityPrintFloat(actual); \
+ } while (0)
+#else
+ #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \
+ UnityPrint(UnityStrDelta)
+#endif /* UNITY_EXCLUDE_FLOAT_PRINT */
+
+/*-----------------------------------------------*/
+static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOAT actual)
+{
+ UNITY_FLOAT diff;
+ UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff);
+}
+
+/*-----------------------------------------------*/
+void UnityAssertWithinFloatArray(const UNITY_FLOAT delta,
+ UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected,
+ UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags)
+{
+ UNITY_UINT32 elements = num_elements;
+ UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected;
+ UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual;
+ UNITY_FLOAT in_delta = delta;
+ UNITY_FLOAT current_element_delta = delta;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (elements == 0)
+ {
+#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY
+ UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg);
+#else
+ UnityPrintPointlessAndBail();
+#endif
+ }
+
+ if (isinf(in_delta))
+ {
+ return; /* Arrays will be force equal with infinite delta */
+ }
+
+ if (isnan(in_delta))
+ {
+ /* Delta must be correct number */
+ UnityPrintPointlessAndBail();
+ }
+
+ if (expected == actual)
+ {
+ return; /* Both are NULL or same pointer */
+ }
+
+ if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg))
+ {
+ UNITY_FAIL_AND_BAIL;
+ }
+
+ /* fix delta sign if need */
+ if (in_delta < 0)
+ {
+ in_delta = -in_delta;
+ }
+
+ while (elements--)
+ {
+ current_element_delta = *ptr_expected * UNITY_FLOAT_PRECISION;
+
+ if (current_element_delta < 0)
+ {
+ /* fix delta sign for correct calculations */
+ current_element_delta = -current_element_delta;
+ }
+
+ if (!UnityFloatsWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual))
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrElement);
+ UnityPrintNumberUnsigned(num_elements - elements - 1);
+ UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+ if (flags == UNITY_ARRAY_TO_ARRAY)
+ {
+ ptr_expected++;
+ }
+ ptr_actual++;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertFloatsWithin(const UNITY_FLOAT delta,
+ const UNITY_FLOAT expected,
+ const UNITY_FLOAT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+
+ if (!UnityFloatsWithin(delta, expected, actual))
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertFloatsNotWithin(const UNITY_FLOAT delta,
+ const UNITY_FLOAT expected,
+ const UNITY_FLOAT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (UnityFloatsWithin(delta, expected, actual))
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ UnityPrintFloat((UNITY_DOUBLE)expected);
+ UnityPrint(UnityStrNotEqual);
+ UnityPrintFloat((UNITY_DOUBLE)actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertGreaterOrLessFloat(const UNITY_FLOAT threshold,
+ const UNITY_FLOAT actual,
+ const UNITY_COMPARISON_T compare,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ int failed;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ failed = 0;
+
+ /* Checking for "not success" rather than failure to get the right result for NaN */
+ if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; }
+ if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; }
+
+ if ((compare & UNITY_EQUAL_TO) && UnityFloatsWithin(threshold * UNITY_FLOAT_PRECISION, threshold, actual)) { failed = 0; }
+
+ if (failed)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ UnityPrintFloat(actual);
+ if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); }
+ if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); }
+ if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); }
+ UnityPrintFloat(threshold);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertFloatSpecial(const UNITY_FLOAT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLOAT_TRAIT_T style)
+{
+ const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet};
+ UNITY_INT should_be_trait = ((UNITY_INT)style & 1);
+ UNITY_INT is_trait = !should_be_trait;
+ UNITY_INT trait_index = (UNITY_INT)(style >> 1);
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ switch (style)
+ {
+ case UNITY_FLOAT_IS_INF:
+ case UNITY_FLOAT_IS_NOT_INF:
+ is_trait = isinf(actual) && (actual > 0);
+ break;
+ case UNITY_FLOAT_IS_NEG_INF:
+ case UNITY_FLOAT_IS_NOT_NEG_INF:
+ is_trait = isinf(actual) && (actual < 0);
+ break;
+
+ case UNITY_FLOAT_IS_NAN:
+ case UNITY_FLOAT_IS_NOT_NAN:
+ is_trait = isnan(actual) ? 1 : 0;
+ break;
+
+ case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */
+ case UNITY_FLOAT_IS_NOT_DET:
+ is_trait = !isinf(actual) && !isnan(actual);
+ break;
+
+ default: /* including UNITY_FLOAT_INVALID_TRAIT */
+ trait_index = 0;
+ trait_names[0] = UnityStrInvalidFloatTrait;
+ break;
+ }
+
+ if (is_trait != should_be_trait)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ if (!should_be_trait)
+ {
+ UnityPrint(UnityStrNot);
+ }
+ UnityPrint(trait_names[trait_index]);
+ UnityPrint(UnityStrWas);
+#ifndef UNITY_EXCLUDE_FLOAT_PRINT
+ UnityPrintFloat((UNITY_DOUBLE)actual);
+#else
+ if (should_be_trait)
+ {
+ UnityPrint(UnityStrNot);
+ }
+ UnityPrint(trait_names[trait_index]);
+#endif
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+#endif /* not UNITY_EXCLUDE_FLOAT */
+
+/*-----------------------------------------------*/
+#ifndef UNITY_EXCLUDE_DOUBLE
+static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_DOUBLE actual)
+{
+ UNITY_DOUBLE diff;
+ UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff);
+}
+
+/*-----------------------------------------------*/
+void UnityAssertWithinDoubleArray(const UNITY_DOUBLE delta,
+ UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected,
+ UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags)
+{
+ UNITY_UINT32 elements = num_elements;
+ UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected;
+ UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual;
+ UNITY_DOUBLE in_delta = delta;
+ UNITY_DOUBLE current_element_delta = delta;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (elements == 0)
+ {
+#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY
+ UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg);
+#else
+ UnityPrintPointlessAndBail();
+#endif
+ }
+
+ if (isinf(in_delta))
+ {
+ return; /* Arrays will be force equal with infinite delta */
+ }
+
+ if (isnan(in_delta))
+ {
+ /* Delta must be correct number */
+ UnityPrintPointlessAndBail();
+ }
+
+ if (expected == actual)
+ {
+ return; /* Both are NULL or same pointer */
+ }
+
+ if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg))
+ {
+ UNITY_FAIL_AND_BAIL;
+ }
+
+ /* fix delta sign if need */
+ if (in_delta < 0)
+ {
+ in_delta = -in_delta;
+ }
+
+ while (elements--)
+ {
+ current_element_delta = *ptr_expected * UNITY_DOUBLE_PRECISION;
+
+ if (current_element_delta < 0)
+ {
+ /* fix delta sign for correct calculations */
+ current_element_delta = -current_element_delta;
+ }
+
+ if (!UnityDoublesWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual))
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrElement);
+ UnityPrintNumberUnsigned(num_elements - elements - 1);
+ UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+ if (flags == UNITY_ARRAY_TO_ARRAY)
+ {
+ ptr_expected++;
+ }
+ ptr_actual++;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertDoublesWithin(const UNITY_DOUBLE delta,
+ const UNITY_DOUBLE expected,
+ const UNITY_DOUBLE actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (!UnityDoublesWithin(delta, expected, actual))
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertDoublesNotWithin(const UNITY_DOUBLE delta,
+ const UNITY_DOUBLE expected,
+ const UNITY_DOUBLE actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (UnityDoublesWithin(delta, expected, actual))
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ UnityPrintFloat((UNITY_DOUBLE)expected);
+ UnityPrint(UnityStrNotEqual);
+ UnityPrintFloat((UNITY_DOUBLE)actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertGreaterOrLessDouble(const UNITY_DOUBLE threshold,
+ const UNITY_DOUBLE actual,
+ const UNITY_COMPARISON_T compare,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ int failed;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ failed = 0;
+
+ /* Checking for "not success" rather than failure to get the right result for NaN */
+ if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; }
+ if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; }
+
+ if ((compare & UNITY_EQUAL_TO) && UnityDoublesWithin(threshold * UNITY_DOUBLE_PRECISION, threshold, actual)) { failed = 0; }
+
+ if (failed)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ UnityPrintFloat(actual);
+ if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); }
+ if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); }
+ if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); }
+ UnityPrintFloat(threshold);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLOAT_TRAIT_T style)
+{
+ const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet};
+ UNITY_INT should_be_trait = ((UNITY_INT)style & 1);
+ UNITY_INT is_trait = !should_be_trait;
+ UNITY_INT trait_index = (UNITY_INT)(style >> 1);
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ switch (style)
+ {
+ case UNITY_FLOAT_IS_INF:
+ case UNITY_FLOAT_IS_NOT_INF:
+ is_trait = isinf(actual) && (actual > 0);
+ break;
+ case UNITY_FLOAT_IS_NEG_INF:
+ case UNITY_FLOAT_IS_NOT_NEG_INF:
+ is_trait = isinf(actual) && (actual < 0);
+ break;
+
+ case UNITY_FLOAT_IS_NAN:
+ case UNITY_FLOAT_IS_NOT_NAN:
+ is_trait = isnan(actual) ? 1 : 0;
+ break;
+
+ case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */
+ case UNITY_FLOAT_IS_NOT_DET:
+ is_trait = !isinf(actual) && !isnan(actual);
+ break;
+
+ default: /* including UNITY_FLOAT_INVALID_TRAIT */
+ trait_index = 0;
+ trait_names[0] = UnityStrInvalidFloatTrait;
+ break;
+ }
+
+ if (is_trait != should_be_trait)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrExpected);
+ if (!should_be_trait)
+ {
+ UnityPrint(UnityStrNot);
+ }
+ UnityPrint(trait_names[trait_index]);
+ UnityPrint(UnityStrWas);
+#ifndef UNITY_EXCLUDE_FLOAT_PRINT
+ UnityPrintFloat(actual);
+#else
+ if (should_be_trait)
+ {
+ UnityPrint(UnityStrNot);
+ }
+ UnityPrint(trait_names[trait_index]);
+#endif
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+#endif /* not UNITY_EXCLUDE_DOUBLE */
+
+/*-----------------------------------------------*/
+void UnityAssertNumbersWithin(const UNITY_UINT delta,
+ const UNITY_INT expected,
+ const UNITY_INT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ if (actual > expected)
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta);
+ }
+ else
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta);
+ }
+ }
+ else
+ {
+ if ((UNITY_UINT)actual > (UNITY_UINT)expected)
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta);
+ }
+ else
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta);
+ }
+ }
+
+ if (Unity.CurrentTestFailed)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrDelta);
+ UnityPrintNumberByStyle((UNITY_INT)delta, style);
+ UnityPrint(UnityStrExpected);
+ UnityPrintNumberByStyle(expected, style);
+ UnityPrint(UnityStrWas);
+ UnityPrintNumberByStyle(actual, style);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertNumbersArrayWithin(const UNITY_UINT delta,
+ UNITY_INTERNAL_PTR expected,
+ UNITY_INTERNAL_PTR actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style,
+ const UNITY_FLAGS_T flags)
+{
+ UNITY_UINT32 elements = num_elements;
+ unsigned int length = style & 0xF;
+ unsigned int increment = 0;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (num_elements == 0)
+ {
+#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY
+ UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg);
+#else
+ UnityPrintPointlessAndBail();
+#endif
+ }
+
+ if (expected == actual)
+ {
+ return; /* Both are NULL or same pointer */
+ }
+
+ if (UnityIsOneArrayNull(expected, actual, lineNumber, msg))
+ {
+ UNITY_FAIL_AND_BAIL;
+ }
+
+ while ((elements > 0) && (elements--))
+ {
+ UNITY_INT expect_val;
+ UNITY_INT actual_val;
+
+ switch (length)
+ {
+ case 1:
+ /* fixing problems with signed overflow on unsigned numbers */
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual;
+ increment = sizeof(UNITY_INT8);
+ }
+ else
+ {
+ expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)expected;
+ actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)actual;
+ increment = sizeof(UNITY_UINT8);
+ }
+ break;
+
+ case 2:
+ /* fixing problems with signed overflow on unsigned numbers */
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual;
+ increment = sizeof(UNITY_INT16);
+ }
+ else
+ {
+ expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)expected;
+ actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)actual;
+ increment = sizeof(UNITY_UINT16);
+ }
+ break;
+
+#ifdef UNITY_SUPPORT_64
+ case 8:
+ /* fixing problems with signed overflow on unsigned numbers */
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual;
+ increment = sizeof(UNITY_INT64);
+ }
+ else
+ {
+ expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)expected;
+ actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)actual;
+ increment = sizeof(UNITY_UINT64);
+ }
+ break;
+#endif
+
+ default: /* default is length 4 bytes */
+ case 4:
+ /* fixing problems with signed overflow on unsigned numbers */
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected;
+ actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual;
+ increment = sizeof(UNITY_INT32);
+ }
+ else
+ {
+ expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)expected;
+ actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)actual;
+ increment = sizeof(UNITY_UINT32);
+ }
+ length = 4;
+ break;
+ }
+
+ if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT)
+ {
+ if (actual_val > expect_val)
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta);
+ }
+ else
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta);
+ }
+ }
+ else
+ {
+ if ((UNITY_UINT)actual_val > (UNITY_UINT)expect_val)
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta);
+ }
+ else
+ {
+ Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta);
+ }
+ }
+
+ if (Unity.CurrentTestFailed)
+ {
+ if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8)))
+ { /* For UINT, remove sign extension (padding 1's) from signed type casts above */
+ UNITY_INT mask = 1;
+ mask = (mask << 8 * length) - 1;
+ expect_val &= mask;
+ actual_val &= mask;
+ }
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrDelta);
+ UnityPrintNumberByStyle((UNITY_INT)delta, style);
+ UnityPrint(UnityStrElement);
+ UnityPrintNumberUnsigned(num_elements - elements - 1);
+ UnityPrint(UnityStrExpected);
+ UnityPrintNumberByStyle(expect_val, style);
+ UnityPrint(UnityStrWas);
+ UnityPrintNumberByStyle(actual_val, style);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+ /* Walk through array by incrementing the pointers */
+ if (flags == UNITY_ARRAY_TO_ARRAY)
+ {
+ expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment);
+ }
+ actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment);
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertEqualString(const char* expected,
+ const char* actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ UNITY_UINT32 i;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ /* if both pointers not null compare the strings */
+ if (expected && actual)
+ {
+ for (i = 0; expected[i] || actual[i]; i++)
+ {
+ if (expected[i] != actual[i])
+ {
+ Unity.CurrentTestFailed = 1;
+ break;
+ }
+ }
+ }
+ else
+ { /* handle case of one pointers being null (if both null, test should pass) */
+ if (expected != actual)
+ {
+ Unity.CurrentTestFailed = 1;
+ }
+ }
+
+ if (Unity.CurrentTestFailed)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrintExpectedAndActualStrings(expected, actual);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertEqualStringLen(const char* expected,
+ const char* actual,
+ const UNITY_UINT32 length,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber)
+{
+ UNITY_UINT32 i;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ /* if both pointers not null compare the strings */
+ if (expected && actual)
+ {
+ for (i = 0; (i < length) && (expected[i] || actual[i]); i++)
+ {
+ if (expected[i] != actual[i])
+ {
+ Unity.CurrentTestFailed = 1;
+ break;
+ }
+ }
+ }
+ else
+ { /* handle case of one pointers being null (if both null, test should pass) */
+ if (expected != actual)
+ {
+ Unity.CurrentTestFailed = 1;
+ }
+ }
+
+ if (Unity.CurrentTestFailed)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrintExpectedAndActualStringsLen(expected, actual, length);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+}
+
+/*-----------------------------------------------*/
+void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected,
+ const char** actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags)
+{
+ UNITY_UINT32 i = 0;
+ UNITY_UINT32 j = 0;
+ const char* expd = NULL;
+ const char* act = NULL;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ /* if no elements, it's an error */
+ if (num_elements == 0)
+ {
+#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY
+ UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg);
+#else
+ UnityPrintPointlessAndBail();
+#endif
+ }
+
+ if ((const void*)expected == (const void*)actual)
+ {
+ return; /* Both are NULL or same pointer */
+ }
+
+ if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg))
+ {
+ UNITY_FAIL_AND_BAIL;
+ }
+
+ if (flags != UNITY_ARRAY_TO_ARRAY)
+ {
+ expd = (const char*)expected;
+ }
+
+ do
+ {
+ act = actual[j];
+ if (flags == UNITY_ARRAY_TO_ARRAY)
+ {
+ expd = ((const char* const*)expected)[j];
+ }
+
+ /* if both pointers not null compare the strings */
+ if (expd && act)
+ {
+ for (i = 0; expd[i] || act[i]; i++)
+ {
+ if (expd[i] != act[i])
+ {
+ Unity.CurrentTestFailed = 1;
+ break;
+ }
+ }
+ }
+ else
+ { /* handle case of one pointers being null (if both null, test should pass) */
+ if (expd != act)
+ {
+ Unity.CurrentTestFailed = 1;
+ }
+ }
+
+ if (Unity.CurrentTestFailed)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ if (num_elements > 1)
+ {
+ UnityPrint(UnityStrElement);
+ UnityPrintNumberUnsigned(j);
+ }
+ UnityPrintExpectedAndActualStrings(expd, act);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+ } while (++j < num_elements);
+}
+
+/*-----------------------------------------------*/
+void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected,
+ UNITY_INTERNAL_PTR actual,
+ const UNITY_UINT32 length,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags)
+{
+ UNITY_PTR_ATTRIBUTE const unsigned char* ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected;
+ UNITY_PTR_ATTRIBUTE const unsigned char* ptr_act = (UNITY_PTR_ATTRIBUTE const unsigned char*)actual;
+ UNITY_UINT32 elements = num_elements;
+ UNITY_UINT32 bytes;
+
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ if (elements == 0)
+ {
+#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY
+ UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg);
+#else
+ UnityPrintPointlessAndBail();
+#endif
+ }
+ if (length == 0)
+ {
+ UnityPrintPointlessAndBail();
+ }
+
+ if (expected == actual)
+ {
+ return; /* Both are NULL or same pointer */
+ }
+
+ if (UnityIsOneArrayNull(expected, actual, lineNumber, msg))
+ {
+ UNITY_FAIL_AND_BAIL;
+ }
+
+ while (elements--)
+ {
+ bytes = length;
+ while (bytes--)
+ {
+ if (*ptr_exp != *ptr_act)
+ {
+ UnityTestResultsFailBegin(lineNumber);
+ UnityPrint(UnityStrMemory);
+ if (num_elements > 1)
+ {
+ UnityPrint(UnityStrElement);
+ UnityPrintNumberUnsigned(num_elements - elements - 1);
+ }
+ UnityPrint(UnityStrByte);
+ UnityPrintNumberUnsigned(length - bytes - 1);
+ UnityPrint(UnityStrExpected);
+ UnityPrintNumberByStyle(*ptr_exp, UNITY_DISPLAY_STYLE_HEX8);
+ UnityPrint(UnityStrWas);
+ UnityPrintNumberByStyle(*ptr_act, UNITY_DISPLAY_STYLE_HEX8);
+ UnityAddMsgIfSpecified(msg);
+ UNITY_FAIL_AND_BAIL;
+ }
+ ptr_exp++;
+ ptr_act++;
+ }
+ if (flags == UNITY_ARRAY_TO_VAL)
+ {
+ ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected;
+ }
+ }
+}
+
+/*-----------------------------------------------*/
+
+static union
+{
+ UNITY_INT8 i8;
+ UNITY_INT16 i16;
+ UNITY_INT32 i32;
+#ifdef UNITY_SUPPORT_64
+ UNITY_INT64 i64;
+#endif
+#ifndef UNITY_EXCLUDE_FLOAT
+ float f;
+#endif
+#ifndef UNITY_EXCLUDE_DOUBLE
+ double d;
+#endif
+} UnityQuickCompare;
+
+UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size)
+{
+ switch(size)
+ {
+ case 1:
+ UnityQuickCompare.i8 = (UNITY_INT8)num;
+ return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i8);
+
+ case 2:
+ UnityQuickCompare.i16 = (UNITY_INT16)num;
+ return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i16);
+
+#ifdef UNITY_SUPPORT_64
+ case 8:
+ UnityQuickCompare.i64 = (UNITY_INT64)num;
+ return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i64);
+#endif
+
+ default: /* 4 bytes */
+ UnityQuickCompare.i32 = (UNITY_INT32)num;
+ return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i32);
+ }
+}
+
+#ifndef UNITY_EXCLUDE_FLOAT
+/*-----------------------------------------------*/
+UNITY_INTERNAL_PTR UnityFloatToPtr(const float num)
+{
+ UnityQuickCompare.f = num;
+ return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.f);
+}
+#endif
+
+#ifndef UNITY_EXCLUDE_DOUBLE
+/*-----------------------------------------------*/
+UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num)
+{
+ UnityQuickCompare.d = num;
+ return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.d);
+}
+#endif
+
+#ifdef UNITY_INCLUDE_PRINT_FORMATTED
+
+/*-----------------------------------------------
+ * printf length modifier helpers
+ *-----------------------------------------------*/
+
+enum UnityLengthModifier {
+ UNITY_LENGTH_MODIFIER_NONE,
+ UNITY_LENGTH_MODIFIER_LONG_LONG,
+ UNITY_LENGTH_MODIFIER_LONG,
+};
+
+#define UNITY_EXTRACT_ARG(NUMBER_T, NUMBER, LENGTH_MOD, VA, ARG_T) \
+do { \
+ switch (LENGTH_MOD) \
+ { \
+ case UNITY_LENGTH_MODIFIER_LONG_LONG: \
+ { \
+ NUMBER = (NUMBER_T)va_arg(VA, long long ARG_T); \
+ break; \
+ } \
+ case UNITY_LENGTH_MODIFIER_LONG: \
+ { \
+ NUMBER = (NUMBER_T)va_arg(VA, long ARG_T); \
+ break; \
+ } \
+ case UNITY_LENGTH_MODIFIER_NONE: \
+ default: \
+ { \
+ NUMBER = (NUMBER_T)va_arg(VA, ARG_T); \
+ break; \
+ } \
+ } \
+} while (0)
+
+static enum UnityLengthModifier UnityLengthModifierGet(const char *pch, int *length)
+{
+ enum UnityLengthModifier length_mod;
+ switch (pch[0])
+ {
+ case 'l':
+ {
+ if (pch[1] == 'l')
+ {
+ *length = 2;
+ length_mod = UNITY_LENGTH_MODIFIER_LONG_LONG;
+ }
+ else
+ {
+ *length = 1;
+ length_mod = UNITY_LENGTH_MODIFIER_LONG;
+ }
+ break;
+ }
+ case 'h':
+ {
+ // short and char are converted to int
+ length_mod = UNITY_LENGTH_MODIFIER_NONE;
+ if (pch[1] == 'h')
+ {
+ *length = 2;
+ }
+ else
+ {
+ *length = 1;
+ }
+ break;
+ }
+ case 'j':
+ case 'z':
+ case 't':
+ case 'L':
+ {
+ // Not supported, but should gobble up the length specifier anyway
+ length_mod = UNITY_LENGTH_MODIFIER_NONE;
+ *length = 1;
+ break;
+ }
+ default:
+ {
+ length_mod = UNITY_LENGTH_MODIFIER_NONE;
+ *length = 0;
+ }
+ }
+ return length_mod;
+}
+
+/*-----------------------------------------------
+ * printf helper function
+ *-----------------------------------------------*/
+static void UnityPrintFVA(const char* format, va_list va)
+{
+ const char* pch = format;
+ if (pch != NULL)
+ {
+ while (*pch)
+ {
+ /* format identification character */
+ if (*pch == '%')
+ {
+ pch++;
+
+ if (pch != NULL)
+ {
+ int length_mod_size;
+ enum UnityLengthModifier length_mod = UnityLengthModifierGet(pch, &length_mod_size);
+ pch += length_mod_size;
+
+ switch (*pch)
+ {
+ case 'd':
+ case 'i':
+ {
+ UNITY_INT number;
+ UNITY_EXTRACT_ARG(UNITY_INT, number, length_mod, va, int);
+ UnityPrintNumber((UNITY_INT)number);
+ break;
+ }
+#ifndef UNITY_EXCLUDE_FLOAT_PRINT
+ case 'f':
+ case 'g':
+ {
+ const double number = va_arg(va, double);
+ UnityPrintFloat((UNITY_DOUBLE)number);
+ break;
+ }
+#endif
+ case 'u':
+ {
+ UNITY_UINT number;
+ UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int);
+ UnityPrintNumberUnsigned(number);
+ break;
+ }
+ case 'b':
+ {
+ UNITY_UINT number;
+ UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int);
+ const UNITY_UINT mask = (UNITY_UINT)0 - (UNITY_UINT)1;
+ UNITY_OUTPUT_CHAR('0');
+ UNITY_OUTPUT_CHAR('b');
+ UnityPrintMask(mask, number);
+ break;
+ }
+ case 'x':
+ case 'X':
+ {
+ UNITY_UINT number;
+ UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int);
+ UNITY_OUTPUT_CHAR('0');
+ UNITY_OUTPUT_CHAR('x');
+ UnityPrintNumberHex(number, 8);
+ break;
+ }
+ case 'p':
+ {
+ const unsigned int number = va_arg(va, unsigned int);
+ UNITY_OUTPUT_CHAR('0');
+ UNITY_OUTPUT_CHAR('x');
+ UnityPrintNumberHex((UNITY_UINT)number, 8);
+ break;
+ }
+ case 'c':
+ {
+ const int ch = va_arg(va, int);
+ UnityPrintChar((const char *)&ch);
+ break;
+ }
+ case 's':
+ {
+ const char * string = va_arg(va, const char *);
+ UnityPrint(string);
+ break;
+ }
+ case '%':
+ {
+ UnityPrintChar(pch);
+ break;
+ }
+ default:
+ {
+ /* print the unknown format character */
+ UNITY_OUTPUT_CHAR('%');
+ UnityPrintChar(pch);
+ break;
+ }
+ }
+ }
+ }
+#ifdef UNITY_OUTPUT_COLOR
+ /* print ANSI escape code */
+ else if ((*pch == 27) && (*(pch + 1) == '['))
+ {
+ pch += UnityPrintAnsiEscapeString(pch);
+ continue;
+ }
+#endif
+ else if (*pch == '\n')
+ {
+ UNITY_PRINT_EOL();
+ }
+ else
+ {
+ UnityPrintChar(pch);
+ }
+
+ pch++;
+ }
+ }
+}
+
+void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...)
+{
+ UnityTestResultsBegin(Unity.TestFile, line);
+ UnityPrint("INFO");
+ if(format != NULL)
+ {
+ UnityPrint(": ");
+ va_list va;
+ va_start(va, format);
+ UnityPrintFVA(format, va);
+ va_end(va);
+ }
+ UNITY_PRINT_EOL();
+}
+#endif /* ! UNITY_INCLUDE_PRINT_FORMATTED */
+
+
+/*-----------------------------------------------
+ * Control Functions
+ *-----------------------------------------------*/
+
+/*-----------------------------------------------*/
+void UnityFail(const char* msg, const UNITY_LINE_TYPE line)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ UnityTestResultsBegin(Unity.TestFile, line);
+ UnityPrint(UnityStrFail);
+ if (msg != NULL)
+ {
+ UNITY_OUTPUT_CHAR(':');
+
+#ifdef UNITY_PRINT_TEST_CONTEXT
+ UNITY_PRINT_TEST_CONTEXT();
+#endif
+#ifndef UNITY_EXCLUDE_DETAILS
+ if (Unity.CurrentDetail1)
+ {
+ UnityPrint(UnityStrDetail1Name);
+ UnityPrint(Unity.CurrentDetail1);
+ if (Unity.CurrentDetail2)
+ {
+ UnityPrint(UnityStrDetail2Name);
+ UnityPrint(Unity.CurrentDetail2);
+ }
+ UnityPrint(UnityStrSpacer);
+ }
+#endif
+ if (msg[0] != ' ')
+ {
+ UNITY_OUTPUT_CHAR(' ');
+ }
+ UnityPrint(msg);
+ }
+
+ UNITY_FAIL_AND_BAIL;
+}
+
+/*-----------------------------------------------*/
+void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line)
+{
+ RETURN_IF_FAIL_OR_IGNORE;
+
+ UnityTestResultsBegin(Unity.TestFile, line);
+ UnityPrint(UnityStrIgnore);
+ if (msg != NULL)
+ {
+ UNITY_OUTPUT_CHAR(':');
+ UNITY_OUTPUT_CHAR(' ');
+ UnityPrint(msg);
+ }
+ UNITY_IGNORE_AND_BAIL;
+}
+
+/*-----------------------------------------------*/
+void UnityMessage(const char* msg, const UNITY_LINE_TYPE line)
+{
+ UnityTestResultsBegin(Unity.TestFile, line);
+ UnityPrint("INFO");
+ if (msg != NULL)
+ {
+ UNITY_OUTPUT_CHAR(':');
+ UNITY_OUTPUT_CHAR(' ');
+ UnityPrint(msg);
+ }
+ UNITY_PRINT_EOL();
+}
+
+/*-----------------------------------------------*/
+/* If we have not defined our own test runner, then include our default test runner to make life easier */
+#ifndef UNITY_SKIP_DEFAULT_RUNNER
+void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum)
+{
+ Unity.CurrentTestName = FuncName;
+ Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum;
+ Unity.NumberOfTests++;
+ UNITY_CLR_DETAILS();
+ UNITY_EXEC_TIME_START();
+ if (TEST_PROTECT())
+ {
+ setUp();
+ Func();
+ }
+ if (TEST_PROTECT())
+ {
+ tearDown();
+ }
+ UNITY_EXEC_TIME_STOP();
+ UnityConcludeTest();
+}
+#endif
+
+/*-----------------------------------------------*/
+void UnitySetTestFile(const char* filename)
+{
+ Unity.TestFile = filename;
+}
+
+/*-----------------------------------------------*/
+void UnityBegin(const char* filename)
+{
+ Unity.TestFile = filename;
+ Unity.CurrentTestName = NULL;
+ Unity.CurrentTestLineNumber = 0;
+ Unity.NumberOfTests = 0;
+ Unity.TestFailures = 0;
+ Unity.TestIgnores = 0;
+ Unity.CurrentTestFailed = 0;
+ Unity.CurrentTestIgnored = 0;
+
+ UNITY_CLR_DETAILS();
+ UNITY_OUTPUT_START();
+}
+
+/*-----------------------------------------------*/
+int UnityEnd(void)
+{
+ UNITY_PRINT_EOL();
+ UnityPrint(UnityStrBreaker);
+ UNITY_PRINT_EOL();
+ UnityPrintNumber((UNITY_INT)(Unity.NumberOfTests));
+ UnityPrint(UnityStrResultsTests);
+ UnityPrintNumber((UNITY_INT)(Unity.TestFailures));
+ UnityPrint(UnityStrResultsFailures);
+ UnityPrintNumber((UNITY_INT)(Unity.TestIgnores));
+ UnityPrint(UnityStrResultsIgnored);
+ UNITY_PRINT_EOL();
+ if (Unity.TestFailures == 0U)
+ {
+ UnityPrint(UnityStrOk);
+ }
+ else
+ {
+ UnityPrint(UnityStrFail);
+#ifdef UNITY_DIFFERENTIATE_FINAL_FAIL
+ UNITY_OUTPUT_CHAR('E'); UNITY_OUTPUT_CHAR('D');
+#endif
+ }
+ UNITY_PRINT_EOL();
+ UNITY_FLUSH_CALL();
+ UNITY_OUTPUT_COMPLETE();
+ return (int)(Unity.TestFailures);
+}
+
+/*-----------------------------------------------
+ * Command Line Argument Support
+ *-----------------------------------------------*/
+#ifdef UNITY_USE_COMMAND_LINE_ARGS
+
+char* UnityOptionIncludeNamed = NULL;
+char* UnityOptionExcludeNamed = NULL;
+int UnityVerbosity = 1;
+
+/*-----------------------------------------------*/
+int UnityParseOptions(int argc, char** argv)
+{
+ int i;
+ UnityOptionIncludeNamed = NULL;
+ UnityOptionExcludeNamed = NULL;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ switch (argv[i][1])
+ {
+ case 'l': /* list tests */
+ return -1;
+ case 'n': /* include tests with name including this string */
+ case 'f': /* an alias for -n */
+ if (argv[i][2] == '=')
+ {
+ UnityOptionIncludeNamed = &argv[i][3];
+ }
+ else if (++i < argc)
+ {
+ UnityOptionIncludeNamed = argv[i];
+ }
+ else
+ {
+ UnityPrint("ERROR: No Test String to Include Matches For");
+ UNITY_PRINT_EOL();
+ return 1;
+ }
+ break;
+ case 'q': /* quiet */
+ UnityVerbosity = 0;
+ break;
+ case 'v': /* verbose */
+ UnityVerbosity = 2;
+ break;
+ case 'x': /* exclude tests with name including this string */
+ if (argv[i][2] == '=')
+ {
+ UnityOptionExcludeNamed = &argv[i][3];
+ }
+ else if (++i < argc)
+ {
+ UnityOptionExcludeNamed = argv[i];
+ }
+ else
+ {
+ UnityPrint("ERROR: No Test String to Exclude Matches For");
+ UNITY_PRINT_EOL();
+ return 1;
+ }
+ break;
+ default:
+ UnityPrint("ERROR: Unknown Option ");
+ UNITY_OUTPUT_CHAR(argv[i][1]);
+ UNITY_PRINT_EOL();
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*-----------------------------------------------*/
+int IsStringInBiggerString(const char* longstring, const char* shortstring)
+{
+ const char* lptr = longstring;
+ const char* sptr = shortstring;
+ const char* lnext = lptr;
+
+ if (*sptr == '*')
+ {
+ return 1;
+ }
+
+ while (*lptr)
+ {
+ lnext = lptr + 1;
+
+ /* If they current bytes match, go on to the next bytes */
+ while (*lptr && *sptr && (*lptr == *sptr))
+ {
+ lptr++;
+ sptr++;
+
+ /* We're done if we match the entire string or up to a wildcard */
+ if (*sptr == '*')
+ return 1;
+ if (*sptr == ',')
+ return 1;
+ if (*sptr == '"')
+ return 1;
+ if (*sptr == '\'')
+ return 1;
+ if (*sptr == ':')
+ return 2;
+ if (*sptr == 0)
+ return 1;
+ }
+
+ /* Otherwise we start in the long pointer 1 character further and try again */
+ lptr = lnext;
+ sptr = shortstring;
+ }
+
+ return 0;
+}
+
+/*-----------------------------------------------*/
+int UnityStringArgumentMatches(const char* str)
+{
+ int retval;
+ const char* ptr1;
+ const char* ptr2;
+ const char* ptrf;
+
+ /* Go through the options and get the substrings for matching one at a time */
+ ptr1 = str;
+ while (ptr1[0] != 0)
+ {
+ if ((ptr1[0] == '"') || (ptr1[0] == '\''))
+ {
+ ptr1++;
+ }
+
+ /* look for the start of the next partial */
+ ptr2 = ptr1;
+ ptrf = 0;
+ do
+ {
+ ptr2++;
+ if ((ptr2[0] == ':') && (ptr2[1] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ','))
+ {
+ ptrf = &ptr2[1];
+ }
+ } while ((ptr2[0] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ','));
+
+ while ((ptr2[0] != 0) && ((ptr2[0] == ':') || (ptr2[0] == '\'') || (ptr2[0] == '"') || (ptr2[0] == ',')))
+ {
+ ptr2++;
+ }
+
+ /* done if complete filename match */
+ retval = IsStringInBiggerString(Unity.TestFile, ptr1);
+ if (retval == 1)
+ {
+ return retval;
+ }
+
+ /* done if testname match after filename partial match */
+ if ((retval == 2) && (ptrf != 0))
+ {
+ if (IsStringInBiggerString(Unity.CurrentTestName, ptrf))
+ {
+ return 1;
+ }
+ }
+
+ /* done if complete testname match */
+ if (IsStringInBiggerString(Unity.CurrentTestName, ptr1) == 1)
+ {
+ return 1;
+ }
+
+ ptr1 = ptr2;
+ }
+
+ /* we couldn't find a match for any substrings */
+ return 0;
+}
+
+/*-----------------------------------------------*/
+int UnityTestMatches(void)
+{
+ /* Check if this test name matches the included test pattern */
+ int retval;
+ if (UnityOptionIncludeNamed)
+ {
+ retval = UnityStringArgumentMatches(UnityOptionIncludeNamed);
+ }
+ else
+ {
+ retval = 1;
+ }
+
+ /* Check if this test name matches the excluded test pattern */
+ if (UnityOptionExcludeNamed)
+ {
+ if (UnityStringArgumentMatches(UnityOptionExcludeNamed))
+ {
+ retval = 0;
+ }
+ }
+
+ return retval;
+}
+
+#endif /* UNITY_USE_COMMAND_LINE_ARGS */
+/*-----------------------------------------------*/
diff --git a/movement/lib/chirpy_tx/test/unity.h b/movement/lib/chirpy_tx/test/unity.h
new file mode 100644
index 00000000..e321a1da
--- /dev/null
+++ b/movement/lib/chirpy_tx/test/unity.h
@@ -0,0 +1,687 @@
+/* ==========================================
+ Unity Project - A Test Framework for C
+ Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams
+ [Released under MIT License. Please refer to license.txt for details]
+========================================== */
+
+#ifndef UNITY_FRAMEWORK_H
+#define UNITY_FRAMEWORK_H
+#define UNITY
+
+#define UNITY_VERSION_MAJOR 2
+#define UNITY_VERSION_MINOR 5
+#define UNITY_VERSION_BUILD 4
+#define UNITY_VERSION ((UNITY_VERSION_MAJOR << 16) | (UNITY_VERSION_MINOR << 8) | UNITY_VERSION_BUILD)
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "unity_internals.h"
+
+/*-------------------------------------------------------
+ * Test Setup / Teardown
+ *-------------------------------------------------------*/
+
+/* These functions are intended to be called before and after each test.
+ * If using unity directly, these will need to be provided for each test
+ * executable built. If you are using the test runner generator and/or
+ * Ceedling, these are optional. */
+void setUp(void);
+void tearDown(void);
+
+/* These functions are intended to be called at the beginning and end of an
+ * entire test suite. suiteTearDown() is passed the number of tests that
+ * failed, and its return value becomes the exit code of main(). If using
+ * Unity directly, you're in charge of calling these if they are desired.
+ * If using Ceedling or the test runner generator, these will be called
+ * automatically if they exist. */
+void suiteSetUp(void);
+int suiteTearDown(int num_failures);
+
+/*-------------------------------------------------------
+ * Test Reset and Verify
+ *-------------------------------------------------------*/
+
+/* These functions are intended to be called before during tests in order
+ * to support complex test loops, etc. Both are NOT built into Unity. Instead
+ * the test runner generator will create them. resetTest will run teardown and
+ * setup again, verifying any end-of-test needs between. verifyTest will only
+ * run the verification. */
+void resetTest(void);
+void verifyTest(void);
+
+/*-------------------------------------------------------
+ * Configuration Options
+ *-------------------------------------------------------
+ * All options described below should be passed as a compiler flag to all files using Unity. If you must add #defines, place them BEFORE the #include above.
+
+ * Integers/longs/pointers
+ * - Unity attempts to automatically discover your integer sizes
+ * - define UNITY_EXCLUDE_STDINT_H to stop attempting to look in <stdint.h>
+ * - define UNITY_EXCLUDE_LIMITS_H to stop attempting to look in <limits.h>
+ * - If you cannot use the automatic methods above, you can force Unity by using these options:
+ * - define UNITY_SUPPORT_64
+ * - set UNITY_INT_WIDTH
+ * - set UNITY_LONG_WIDTH
+ * - set UNITY_POINTER_WIDTH
+
+ * Floats
+ * - define UNITY_EXCLUDE_FLOAT to disallow floating point comparisons
+ * - define UNITY_FLOAT_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_FLOAT
+ * - define UNITY_FLOAT_TYPE to specify doubles instead of single precision floats
+ * - define UNITY_INCLUDE_DOUBLE to allow double floating point comparisons
+ * - define UNITY_EXCLUDE_DOUBLE to disallow double floating point comparisons (default)
+ * - define UNITY_DOUBLE_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_DOUBLE
+ * - define UNITY_DOUBLE_TYPE to specify something other than double
+ * - define UNITY_EXCLUDE_FLOAT_PRINT to trim binary size, won't print floating point values in errors
+
+ * Output
+ * - by default, Unity prints to standard out with putchar. define UNITY_OUTPUT_CHAR(a) with a different function if desired
+ * - define UNITY_DIFFERENTIATE_FINAL_FAIL to print FAILED (vs. FAIL) at test end summary - for automated search for failure
+
+ * Optimization
+ * - by default, line numbers are stored in unsigned shorts. Define UNITY_LINE_TYPE with a different type if your files are huge
+ * - by default, test and failure counters are unsigned shorts. Define UNITY_COUNTER_TYPE with a different type if you want to save space or have more than 65535 Tests.
+
+ * Test Cases
+ * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script
+
+ * Parameterized Tests
+ * - you'll want to create a define of TEST_CASE(...) and/or TEST_RANGE(...) which basically evaluates to nothing
+
+ * Tests with Arguments
+ * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity
+
+ *-------------------------------------------------------
+ * Basic Fail and Ignore
+ *-------------------------------------------------------*/
+
+#define TEST_FAIL_MESSAGE(message) UNITY_TEST_FAIL(__LINE__, (message))
+#define TEST_FAIL() UNITY_TEST_FAIL(__LINE__, NULL)
+#define TEST_IGNORE_MESSAGE(message) UNITY_TEST_IGNORE(__LINE__, (message))
+#define TEST_IGNORE() UNITY_TEST_IGNORE(__LINE__, NULL)
+#define TEST_MESSAGE(message) UnityMessage((message), __LINE__)
+#define TEST_ONLY()
+#ifdef UNITY_INCLUDE_PRINT_FORMATTED
+#define TEST_PRINTF(message, ...) UnityPrintF(__LINE__, (message), __VA_ARGS__)
+#endif
+
+/* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails.
+ * This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */
+#define TEST_PASS() TEST_ABORT()
+#define TEST_PASS_MESSAGE(message) do { UnityMessage((message), __LINE__); TEST_ABORT(); } while (0)
+
+/* This macro does nothing, but it is useful for build tools (like Ceedling) to make use of this to figure out
+ * which files should be linked to in order to perform a test. Use it like TEST_FILE("sandwiches.c") */
+#define TEST_FILE(a)
+
+/*-------------------------------------------------------
+ * Test Asserts (simple)
+ *-------------------------------------------------------*/
+
+/* Boolean */
+#define TEST_ASSERT(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expression Evaluated To FALSE")
+#define TEST_ASSERT_TRUE(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expected TRUE Was FALSE")
+#define TEST_ASSERT_UNLESS(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expression Evaluated To TRUE")
+#define TEST_ASSERT_FALSE(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expected FALSE Was TRUE")
+#define TEST_ASSERT_NULL(pointer) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, " Expected NULL")
+#define TEST_ASSERT_NOT_NULL(pointer) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, " Expected Non-NULL")
+#define TEST_ASSERT_EMPTY(pointer) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, " Expected Empty")
+#define TEST_ASSERT_NOT_EMPTY(pointer) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, " Expected Non-Empty")
+
+/* Integers (of all sizes) */
+#define TEST_ASSERT_EQUAL_INT(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_size_t(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX8(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX16(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX32(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX64(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_CHAR(expected, actual) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_BITS(mask, expected, actual) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_BITS_HIGH(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(-1), (actual), __LINE__, NULL)
+#define TEST_ASSERT_BITS_LOW(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(0), (actual), __LINE__, NULL)
+#define TEST_ASSERT_BIT_HIGH(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(-1), (actual), __LINE__, NULL)
+#define TEST_ASSERT_BIT_LOW(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(0), (actual), __LINE__, NULL)
+
+/* Integer Not Equal To (of all sizes) */
+#define TEST_ASSERT_NOT_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, NULL)
+
+/* Integer Greater Than/ Less Than (of all sizes) */
+#define TEST_ASSERT_GREATER_THAN(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, NULL)
+
+#define TEST_ASSERT_LESS_THAN(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, NULL)
+
+#define TEST_ASSERT_GREATER_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL)
+
+#define TEST_ASSERT_LESS_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL)
+
+/* Integer Ranges (of all sizes) */
+#define TEST_ASSERT_INT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_INT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_INT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_INT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_INT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_UINT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_UINT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_UINT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_UINT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_UINT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_size_t_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_HEX_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_HEX8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_HEX16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_HEX32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_HEX64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_CHAR_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+
+/* Integer Array Ranges (of all sizes) */
+#define TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_size_t_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_HEX_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+#define TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
+
+
+/* Structs and Strings */
+#define TEST_ASSERT_EQUAL_PTR(expected, actual) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_STRING(expected, actual) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, NULL)
+
+/* Arrays */
+#define TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_size_t_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+
+/* Arrays Compared To Single Value */
+#define TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_size_t(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_HEX(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, NULL)
+
+/* Floating Point (If Enabled) */
+#define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_FLOAT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual) UNITY_TEST_ASSERT_LESS_THAN_FLOAT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual) UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_NOT_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, NULL)
+#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, NULL)
+
+/* Double (If Enabled) */
+#define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_LESS_THAN_DOUBLE((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_NOT_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, NULL)
+#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, NULL)
+
+/* Shorthand */
+#ifdef UNITY_SHORTHAND_AS_OLD
+#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal")
+#endif
+#ifdef UNITY_SHORTHAND_AS_INT
+#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#endif
+#ifdef UNITY_SHORTHAND_AS_MEM
+#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, NULL)
+#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#endif
+#ifdef UNITY_SHORTHAND_AS_RAW
+#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, " Expected Equal")
+#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal")
+#endif
+#ifdef UNITY_SHORTHAND_AS_NONE
+#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#endif
+
+/*-------------------------------------------------------
+ * Test Asserts (with additional messages)
+ *-------------------------------------------------------*/
+
+/* Boolean */
+#define TEST_ASSERT_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message))
+#define TEST_ASSERT_TRUE_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message))
+#define TEST_ASSERT_UNLESS_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message))
+#define TEST_ASSERT_FALSE_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message))
+#define TEST_ASSERT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, (message))
+#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, (message))
+#define TEST_ASSERT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, (message))
+#define TEST_ASSERT_NOT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, (message))
+
+/* Integers (of all sizes) */
+#define TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_size_t_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_BITS_MESSAGE(mask, expected, actual, message) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_BITS_HIGH_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, (message))
+#define TEST_ASSERT_BITS_LOW_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, (message))
+#define TEST_ASSERT_BIT_HIGH_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, (message))
+#define TEST_ASSERT_BIT_LOW_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_CHAR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, (message))
+
+/* Integer Not Equal To (of all sizes) */
+#define TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, (message))
+
+
+/* Integer Greater Than/ Less Than (of all sizes) */
+#define TEST_ASSERT_GREATER_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, (message))
+
+#define TEST_ASSERT_LESS_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, (message))
+
+#define TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message))
+
+#define TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message))
+
+/* Integer Ranges (of all sizes) */
+#define TEST_ASSERT_INT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_INT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_INT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_INT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_INT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_UINT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_UINT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_UINT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_UINT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_UINT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_size_t_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_HEX_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_HEX8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_HEX16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_HEX32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_HEX64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_CHAR_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, (message))
+
+/* Integer Array Ranges (of all sizes) */
+#define TEST_ASSERT_INT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_INT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_INT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_INT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_INT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_UINT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_UINT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_UINT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_UINT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_UINT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_size_t_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_HEX_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_HEX8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_HEX16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_HEX32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_HEX64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+#define TEST_ASSERT_CHAR_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
+
+
+/* Structs and Strings */
+#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, (message))
+
+/* Arrays */
+#define TEST_ASSERT_EQUAL_INT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_INT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_UINT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_size_t_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_HEX64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_PTR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_STRING_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_MEMORY_ARRAY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_CHAR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+
+/* Arrays Compared To Single Value*/
+#define TEST_ASSERT_EACH_EQUAL_INT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_INT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_INT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_INT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_INT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_UINT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_UINT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_size_t_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_HEX_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_HEX8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_HEX16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_HEX32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_HEX64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_PTR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_STRING_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_MEMORY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_CHAR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, (message))
+
+/* Floating Point (If Enabled) */
+#define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_FLOAT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_THAN_FLOAT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, (message))
+#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, (message))
+
+/* Double (If Enabled) */
+#define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message))
+#define TEST_ASSERT_GREATER_THAN_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_THAN_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_THAN_DOUBLE((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_LESS_OR_EQUAL_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, (message))
+#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, (message))
+
+/* Shorthand */
+#ifdef UNITY_SHORTHAND_AS_OLD
+#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message))
+#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, (message))
+#endif
+#ifdef UNITY_SHORTHAND_AS_INT
+#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, message)
+#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#endif
+#ifdef UNITY_SHORTHAND_AS_MEM
+#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, message)
+#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#endif
+#ifdef UNITY_SHORTHAND_AS_RAW
+#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, message)
+#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, message)
+#endif
+#ifdef UNITY_SHORTHAND_AS_NONE
+#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
+#endif
+
+/* end of UNITY_FRAMEWORK_H */
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/movement/lib/chirpy_tx/test/unity_internals.h b/movement/lib/chirpy_tx/test/unity_internals.h
new file mode 100644
index 00000000..47bd370d
--- /dev/null
+++ b/movement/lib/chirpy_tx/test/unity_internals.h
@@ -0,0 +1,1145 @@
+/* ==========================================
+ Unity Project - A Test Framework for C
+ Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams
+ [Released under MIT License. Please refer to license.txt for details]
+========================================== */
+
+#ifndef UNITY_INTERNALS_H
+#define UNITY_INTERNALS_H
+
+#ifdef UNITY_INCLUDE_CONFIG_H
+#include "unity_config.h"
+#endif
+
+#ifndef UNITY_EXCLUDE_SETJMP_H
+#include <setjmp.h>
+#endif
+
+#ifndef UNITY_EXCLUDE_MATH_H
+#include <math.h>
+#endif
+
+#ifndef UNITY_EXCLUDE_STDDEF_H
+#include <stddef.h>
+#endif
+
+#ifdef UNITY_INCLUDE_PRINT_FORMATTED
+#include <stdarg.h>
+#endif
+
+/* Unity Attempts to Auto-Detect Integer Types
+ * Attempt 1: UINT_MAX, ULONG_MAX in <limits.h>, or default to 32 bits
+ * Attempt 2: UINTPTR_MAX in <stdint.h>, or default to same size as long
+ * The user may override any of these derived constants:
+ * UNITY_INT_WIDTH, UNITY_LONG_WIDTH, UNITY_POINTER_WIDTH */
+#ifndef UNITY_EXCLUDE_STDINT_H
+#include <stdint.h>
+#endif
+
+#ifndef UNITY_EXCLUDE_LIMITS_H
+#include <limits.h>
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+ #define UNITY_FUNCTION_ATTR(a) __attribute__((a))
+#else
+ #define UNITY_FUNCTION_ATTR(a) /* ignore */
+#endif
+
+#ifndef UNITY_NORETURN
+ #if defined(__cplusplus)
+ #if __cplusplus >= 201103L
+ #define UNITY_NORETURN [[ noreturn ]]
+ #endif
+ #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+ #if defined(_WIN32) && defined(_MSC_VER)
+ /* We are using MSVC compiler on Windows platform. */
+ /* Not all Windows SDKs supports <stdnoreturn.h>, but compiler can support C11: */
+ /* https://devblogs.microsoft.com/cppblog/c11-and-c17-standard-support-arriving-in-msvc/ */
+ /* Not sure, that Mingw compilers has Windows SDK headers at all. */
+ #include <sdkddkver.h>
+ #endif
+
+ /* Using Windows SDK predefined macro for detecting supported SDK with MSVC compiler. */
+ /* Mingw GCC should work without that fixes. */
+ /* Based on: */
+ /* https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=msvc-170 */
+ /* NTDDI_WIN10_FE is equal to Windows 10 SDK 2104 */
+ #if defined(_MSC_VER) && ((!defined(NTDDI_WIN10_FE)) || WDK_NTDDI_VERSION < NTDDI_WIN10_FE)
+ /* Based on tests and: */
+ /* https://docs.microsoft.com/en-us/cpp/c-language/noreturn?view=msvc-170 */
+ /* https://en.cppreference.com/w/c/language/_Noreturn */
+ #define UNITY_NORETURN _Noreturn
+ #else /* Using newer Windows SDK or not MSVC compiler */
+ #include <stdnoreturn.h>
+ #define UNITY_NORETURN noreturn
+ #endif
+ #endif
+#endif
+#ifndef UNITY_NORETURN
+ #define UNITY_NORETURN UNITY_FUNCTION_ATTR(noreturn)
+#endif
+
+/*-------------------------------------------------------
+ * Guess Widths If Not Specified
+ *-------------------------------------------------------*/
+
+/* Determine the size of an int, if not already specified.
+ * We cannot use sizeof(int), because it is not yet defined
+ * at this stage in the translation of the C program.
+ * Also sizeof(int) does return the size in addressable units on all platforms,
+ * which may not necessarily be the size in bytes.
+ * Therefore, infer it from UINT_MAX if possible. */
+#ifndef UNITY_INT_WIDTH
+ #ifdef UINT_MAX
+ #if (UINT_MAX == 0xFFFF)
+ #define UNITY_INT_WIDTH (16)
+ #elif (UINT_MAX == 0xFFFFFFFF)
+ #define UNITY_INT_WIDTH (32)
+ #elif (UINT_MAX == 0xFFFFFFFFFFFFFFFF)
+ #define UNITY_INT_WIDTH (64)
+ #endif
+ #else /* Set to default */
+ #define UNITY_INT_WIDTH (32)
+ #endif /* UINT_MAX */
+#endif
+
+/* Determine the size of a long, if not already specified. */
+#ifndef UNITY_LONG_WIDTH
+ #ifdef ULONG_MAX
+ #if (ULONG_MAX == 0xFFFF)
+ #define UNITY_LONG_WIDTH (16)
+ #elif (ULONG_MAX == 0xFFFFFFFF)
+ #define UNITY_LONG_WIDTH (32)
+ #elif (ULONG_MAX == 0xFFFFFFFFFFFFFFFF)
+ #define UNITY_LONG_WIDTH (64)
+ #endif
+ #else /* Set to default */
+ #define UNITY_LONG_WIDTH (32)
+ #endif /* ULONG_MAX */
+#endif
+
+/* Determine the size of a pointer, if not already specified. */
+#ifndef UNITY_POINTER_WIDTH
+ #ifdef UINTPTR_MAX
+ #if (UINTPTR_MAX <= 0xFFFF)
+ #define UNITY_POINTER_WIDTH (16)
+ #elif (UINTPTR_MAX <= 0xFFFFFFFF)
+ #define UNITY_POINTER_WIDTH (32)
+ #elif (UINTPTR_MAX <= 0xFFFFFFFFFFFFFFFF)
+ #define UNITY_POINTER_WIDTH (64)
+ #endif
+ #else /* Set to default */
+ #define UNITY_POINTER_WIDTH UNITY_LONG_WIDTH
+ #endif /* UINTPTR_MAX */
+#endif
+
+/*-------------------------------------------------------
+ * Int Support (Define types based on detected sizes)
+ *-------------------------------------------------------*/
+
+#if (UNITY_INT_WIDTH == 32)
+ typedef unsigned char UNITY_UINT8;
+ typedef unsigned short UNITY_UINT16;
+ typedef unsigned int UNITY_UINT32;
+ typedef signed char UNITY_INT8;
+ typedef signed short UNITY_INT16;
+ typedef signed int UNITY_INT32;
+#elif (UNITY_INT_WIDTH == 16)
+ typedef unsigned char UNITY_UINT8;
+ typedef unsigned int UNITY_UINT16;
+ typedef unsigned long UNITY_UINT32;
+ typedef signed char UNITY_INT8;
+ typedef signed int UNITY_INT16;
+ typedef signed long UNITY_INT32;
+#else
+ #error Invalid UNITY_INT_WIDTH specified! (16 or 32 are supported)
+#endif
+
+/*-------------------------------------------------------
+ * 64-bit Support
+ *-------------------------------------------------------*/
+
+/* Auto-detect 64 Bit Support */
+#ifndef UNITY_SUPPORT_64
+ #if UNITY_LONG_WIDTH == 64 || UNITY_POINTER_WIDTH == 64
+ #define UNITY_SUPPORT_64
+ #endif
+#endif
+
+/* 64-Bit Support Dependent Configuration */
+#ifndef UNITY_SUPPORT_64
+ /* No 64-bit Support */
+ typedef UNITY_UINT32 UNITY_UINT;
+ typedef UNITY_INT32 UNITY_INT;
+ #define UNITY_MAX_NIBBLES (8) /* Maximum number of nibbles in a UNITY_(U)INT */
+#else
+ /* 64-bit Support */
+ #if (UNITY_LONG_WIDTH == 32)
+ typedef unsigned long long UNITY_UINT64;
+ typedef signed long long UNITY_INT64;
+ #elif (UNITY_LONG_WIDTH == 64)
+ typedef unsigned long UNITY_UINT64;
+ typedef signed long UNITY_INT64;
+ #else
+ #error Invalid UNITY_LONG_WIDTH specified! (32 or 64 are supported)
+ #endif
+ typedef UNITY_UINT64 UNITY_UINT;
+ typedef UNITY_INT64 UNITY_INT;
+ #define UNITY_MAX_NIBBLES (16) /* Maximum number of nibbles in a UNITY_(U)INT */
+#endif
+
+/*-------------------------------------------------------
+ * Pointer Support
+ *-------------------------------------------------------*/
+
+#if (UNITY_POINTER_WIDTH == 32)
+ #define UNITY_PTR_TO_INT UNITY_INT32
+ #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX32
+#elif (UNITY_POINTER_WIDTH == 64)
+ #define UNITY_PTR_TO_INT UNITY_INT64
+ #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX64
+#elif (UNITY_POINTER_WIDTH == 16)
+ #define UNITY_PTR_TO_INT UNITY_INT16
+ #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX16
+#else
+ #error Invalid UNITY_POINTER_WIDTH specified! (16, 32 or 64 are supported)
+#endif
+
+#ifndef UNITY_PTR_ATTRIBUTE
+ #define UNITY_PTR_ATTRIBUTE
+#endif
+
+#ifndef UNITY_INTERNAL_PTR
+ #define UNITY_INTERNAL_PTR UNITY_PTR_ATTRIBUTE const void*
+#endif
+
+/* optionally define UNITY_COMPARE_PTRS_ON_ZERO_ARRAY */
+
+/*-------------------------------------------------------
+ * Float Support
+ *-------------------------------------------------------*/
+
+#ifdef UNITY_EXCLUDE_FLOAT
+
+/* No Floating Point Support */
+#ifndef UNITY_EXCLUDE_DOUBLE
+#define UNITY_EXCLUDE_DOUBLE /* Remove double when excluding float support */
+#endif
+#ifndef UNITY_EXCLUDE_FLOAT_PRINT
+#define UNITY_EXCLUDE_FLOAT_PRINT
+#endif
+
+#else
+
+/* Floating Point Support */
+#ifndef UNITY_FLOAT_PRECISION
+#define UNITY_FLOAT_PRECISION (0.00001f)
+#endif
+#ifndef UNITY_FLOAT_TYPE
+#define UNITY_FLOAT_TYPE float
+#endif
+typedef UNITY_FLOAT_TYPE UNITY_FLOAT;
+
+/* isinf & isnan macros should be provided by math.h */
+#ifndef isinf
+/* The value of Inf - Inf is NaN */
+#define isinf(n) (isnan((n) - (n)) && !isnan(n))
+#endif
+
+#ifndef isnan
+/* NaN is the only floating point value that does NOT equal itself.
+ * Therefore if n != n, then it is NaN. */
+#define isnan(n) ((n != n) ? 1 : 0)
+#endif
+
+#endif
+
+/*-------------------------------------------------------
+ * Double Float Support
+ *-------------------------------------------------------*/
+
+/* unlike float, we DON'T include by default */
+#if defined(UNITY_EXCLUDE_DOUBLE) || !defined(UNITY_INCLUDE_DOUBLE)
+
+ /* No Floating Point Support */
+ #ifndef UNITY_EXCLUDE_DOUBLE
+ #define UNITY_EXCLUDE_DOUBLE
+ #else
+ #undef UNITY_INCLUDE_DOUBLE
+ #endif
+
+ #ifndef UNITY_EXCLUDE_FLOAT
+ #ifndef UNITY_DOUBLE_TYPE
+ #define UNITY_DOUBLE_TYPE double
+ #endif
+ typedef UNITY_FLOAT UNITY_DOUBLE;
+ /* For parameter in UnityPrintFloat(UNITY_DOUBLE), which aliases to double or float */
+ #endif
+
+#else
+
+ /* Double Floating Point Support */
+ #ifndef UNITY_DOUBLE_PRECISION
+ #define UNITY_DOUBLE_PRECISION (1e-12)
+ #endif
+
+ #ifndef UNITY_DOUBLE_TYPE
+ #define UNITY_DOUBLE_TYPE double
+ #endif
+ typedef UNITY_DOUBLE_TYPE UNITY_DOUBLE;
+
+#endif
+
+/*-------------------------------------------------------
+ * Output Method: stdout (DEFAULT)
+ *-------------------------------------------------------*/
+#ifndef UNITY_OUTPUT_CHAR
+ /* Default to using putchar, which is defined in stdio.h */
+ #include <stdio.h>
+ #define UNITY_OUTPUT_CHAR(a) (void)putchar(a)
+#else
+ /* If defined as something else, make sure we declare it here so it's ready for use */
+ #ifdef UNITY_OUTPUT_CHAR_HEADER_DECLARATION
+ extern void UNITY_OUTPUT_CHAR_HEADER_DECLARATION;
+ #endif
+#endif
+
+#ifndef UNITY_OUTPUT_FLUSH
+ #ifdef UNITY_USE_FLUSH_STDOUT
+ /* We want to use the stdout flush utility */
+ #include <stdio.h>
+ #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout)
+ #else
+ /* We've specified nothing, therefore flush should just be ignored */
+ #define UNITY_OUTPUT_FLUSH() (void)0
+ #endif
+#else
+ /* If defined as something else, make sure we declare it here so it's ready for use */
+ #ifdef UNITY_OUTPUT_FLUSH_HEADER_DECLARATION
+ extern void UNITY_OUTPUT_FLUSH_HEADER_DECLARATION;
+ #endif
+#endif
+
+#ifndef UNITY_OUTPUT_FLUSH
+#define UNITY_FLUSH_CALL()
+#else
+#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH()
+#endif
+
+#ifndef UNITY_PRINT_EOL
+#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n')
+#endif
+
+#ifndef UNITY_OUTPUT_START
+#define UNITY_OUTPUT_START()
+#endif
+
+#ifndef UNITY_OUTPUT_COMPLETE
+#define UNITY_OUTPUT_COMPLETE()
+#endif
+
+#ifdef UNITY_INCLUDE_EXEC_TIME
+ #if !defined(UNITY_EXEC_TIME_START) && \
+ !defined(UNITY_EXEC_TIME_STOP) && \
+ !defined(UNITY_PRINT_EXEC_TIME) && \
+ !defined(UNITY_TIME_TYPE)
+ /* If none any of these macros are defined then try to provide a default implementation */
+
+ #if defined(UNITY_CLOCK_MS)
+ /* This is a simple way to get a default implementation on platforms that support getting a millisecond counter */
+ #define UNITY_TIME_TYPE UNITY_UINT
+ #define UNITY_EXEC_TIME_START() Unity.CurrentTestStartTime = UNITY_CLOCK_MS()
+ #define UNITY_EXEC_TIME_STOP() Unity.CurrentTestStopTime = UNITY_CLOCK_MS()
+ #define UNITY_PRINT_EXEC_TIME() { \
+ UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \
+ UnityPrint(" ("); \
+ UnityPrintNumberUnsigned(execTimeMs); \
+ UnityPrint(" ms)"); \
+ }
+ #elif defined(_WIN32)
+ #include <time.h>
+ #define UNITY_TIME_TYPE clock_t
+ #define UNITY_GET_TIME(t) t = (clock_t)((clock() * 1000) / CLOCKS_PER_SEC)
+ #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime)
+ #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime)
+ #define UNITY_PRINT_EXEC_TIME() { \
+ UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \
+ UnityPrint(" ("); \
+ UnityPrintNumberUnsigned(execTimeMs); \
+ UnityPrint(" ms)"); \
+ }
+ #elif defined(__unix__) || defined(__APPLE__)
+ #include <time.h>
+ #define UNITY_TIME_TYPE struct timespec
+ #define UNITY_GET_TIME(t) clock_gettime(CLOCK_MONOTONIC, &t)
+ #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime)
+ #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime)
+ #define UNITY_PRINT_EXEC_TIME() { \
+ UNITY_UINT execTimeMs = ((Unity.CurrentTestStopTime.tv_sec - Unity.CurrentTestStartTime.tv_sec) * 1000L); \
+ execTimeMs += ((Unity.CurrentTestStopTime.tv_nsec - Unity.CurrentTestStartTime.tv_nsec) / 1000000L); \
+ UnityPrint(" ("); \
+ UnityPrintNumberUnsigned(execTimeMs); \
+ UnityPrint(" ms)"); \
+ }
+ #endif
+ #endif
+#endif
+
+#ifndef UNITY_EXEC_TIME_START
+#define UNITY_EXEC_TIME_START() do { /* nothing*/ } while (0)
+#endif
+
+#ifndef UNITY_EXEC_TIME_STOP
+#define UNITY_EXEC_TIME_STOP() do { /* nothing*/ } while (0)
+#endif
+
+#ifndef UNITY_TIME_TYPE
+#define UNITY_TIME_TYPE UNITY_UINT
+#endif
+
+#ifndef UNITY_PRINT_EXEC_TIME
+#define UNITY_PRINT_EXEC_TIME() do { /* nothing*/ } while (0)
+#endif
+
+/*-------------------------------------------------------
+ * Footprint
+ *-------------------------------------------------------*/
+
+#ifndef UNITY_LINE_TYPE
+#define UNITY_LINE_TYPE UNITY_UINT
+#endif
+
+#ifndef UNITY_COUNTER_TYPE
+#define UNITY_COUNTER_TYPE UNITY_UINT
+#endif
+
+/*-------------------------------------------------------
+ * Internal Structs Needed
+ *-------------------------------------------------------*/
+
+typedef void (*UnityTestFunction)(void);
+
+#define UNITY_DISPLAY_RANGE_INT (0x10)
+#define UNITY_DISPLAY_RANGE_UINT (0x20)
+#define UNITY_DISPLAY_RANGE_HEX (0x40)
+#define UNITY_DISPLAY_RANGE_CHAR (0x80)
+
+typedef enum
+{
+ UNITY_DISPLAY_STYLE_INT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_INT,
+ UNITY_DISPLAY_STYLE_INT8 = 1 + UNITY_DISPLAY_RANGE_INT,
+ UNITY_DISPLAY_STYLE_INT16 = 2 + UNITY_DISPLAY_RANGE_INT,
+ UNITY_DISPLAY_STYLE_INT32 = 4 + UNITY_DISPLAY_RANGE_INT,
+#ifdef UNITY_SUPPORT_64
+ UNITY_DISPLAY_STYLE_INT64 = 8 + UNITY_DISPLAY_RANGE_INT,
+#endif
+
+ UNITY_DISPLAY_STYLE_UINT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_UINT,
+ UNITY_DISPLAY_STYLE_UINT8 = 1 + UNITY_DISPLAY_RANGE_UINT,
+ UNITY_DISPLAY_STYLE_UINT16 = 2 + UNITY_DISPLAY_RANGE_UINT,
+ UNITY_DISPLAY_STYLE_UINT32 = 4 + UNITY_DISPLAY_RANGE_UINT,
+#ifdef UNITY_SUPPORT_64
+ UNITY_DISPLAY_STYLE_UINT64 = 8 + UNITY_DISPLAY_RANGE_UINT,
+#endif
+
+ UNITY_DISPLAY_STYLE_HEX8 = 1 + UNITY_DISPLAY_RANGE_HEX,
+ UNITY_DISPLAY_STYLE_HEX16 = 2 + UNITY_DISPLAY_RANGE_HEX,
+ UNITY_DISPLAY_STYLE_HEX32 = 4 + UNITY_DISPLAY_RANGE_HEX,
+#ifdef UNITY_SUPPORT_64
+ UNITY_DISPLAY_STYLE_HEX64 = 8 + UNITY_DISPLAY_RANGE_HEX,
+#endif
+
+ UNITY_DISPLAY_STYLE_CHAR = 1 + UNITY_DISPLAY_RANGE_CHAR + UNITY_DISPLAY_RANGE_INT,
+
+ UNITY_DISPLAY_STYLE_UNKNOWN
+} UNITY_DISPLAY_STYLE_T;
+
+typedef enum
+{
+ UNITY_WITHIN = 0x0,
+ UNITY_EQUAL_TO = 0x1,
+ UNITY_GREATER_THAN = 0x2,
+ UNITY_GREATER_OR_EQUAL = 0x2 + UNITY_EQUAL_TO,
+ UNITY_SMALLER_THAN = 0x4,
+ UNITY_SMALLER_OR_EQUAL = 0x4 + UNITY_EQUAL_TO,
+ UNITY_NOT_EQUAL = 0x0,
+ UNITY_UNKNOWN
+} UNITY_COMPARISON_T;
+
+#ifndef UNITY_EXCLUDE_FLOAT
+typedef enum UNITY_FLOAT_TRAIT
+{
+ UNITY_FLOAT_IS_NOT_INF = 0,
+ UNITY_FLOAT_IS_INF,
+ UNITY_FLOAT_IS_NOT_NEG_INF,
+ UNITY_FLOAT_IS_NEG_INF,
+ UNITY_FLOAT_IS_NOT_NAN,
+ UNITY_FLOAT_IS_NAN,
+ UNITY_FLOAT_IS_NOT_DET,
+ UNITY_FLOAT_IS_DET,
+ UNITY_FLOAT_INVALID_TRAIT
+} UNITY_FLOAT_TRAIT_T;
+#endif
+
+typedef enum
+{
+ UNITY_ARRAY_TO_VAL = 0,
+ UNITY_ARRAY_TO_ARRAY,
+ UNITY_ARRAY_UNKNOWN
+} UNITY_FLAGS_T;
+
+struct UNITY_STORAGE_T
+{
+ const char* TestFile;
+ const char* CurrentTestName;
+#ifndef UNITY_EXCLUDE_DETAILS
+ const char* CurrentDetail1;
+ const char* CurrentDetail2;
+#endif
+ UNITY_LINE_TYPE CurrentTestLineNumber;
+ UNITY_COUNTER_TYPE NumberOfTests;
+ UNITY_COUNTER_TYPE TestFailures;
+ UNITY_COUNTER_TYPE TestIgnores;
+ UNITY_COUNTER_TYPE CurrentTestFailed;
+ UNITY_COUNTER_TYPE CurrentTestIgnored;
+#ifdef UNITY_INCLUDE_EXEC_TIME
+ UNITY_TIME_TYPE CurrentTestStartTime;
+ UNITY_TIME_TYPE CurrentTestStopTime;
+#endif
+#ifndef UNITY_EXCLUDE_SETJMP_H
+ jmp_buf AbortFrame;
+#endif
+};
+
+extern struct UNITY_STORAGE_T Unity;
+
+/*-------------------------------------------------------
+ * Test Suite Management
+ *-------------------------------------------------------*/
+
+void UnityBegin(const char* filename);
+int UnityEnd(void);
+void UnitySetTestFile(const char* filename);
+void UnityConcludeTest(void);
+
+#ifndef RUN_TEST
+void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum);
+#else
+#define UNITY_SKIP_DEFAULT_RUNNER
+#endif
+
+/*-------------------------------------------------------
+ * Details Support
+ *-------------------------------------------------------*/
+
+#ifdef UNITY_EXCLUDE_DETAILS
+#define UNITY_CLR_DETAILS()
+#define UNITY_SET_DETAIL(d1)
+#define UNITY_SET_DETAILS(d1,d2)
+#else
+#define UNITY_CLR_DETAILS() do { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } while (0)
+#define UNITY_SET_DETAIL(d1) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } while (0)
+#define UNITY_SET_DETAILS(d1,d2) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } while (0)
+
+#ifndef UNITY_DETAIL1_NAME
+#define UNITY_DETAIL1_NAME "Function"
+#endif
+
+#ifndef UNITY_DETAIL2_NAME
+#define UNITY_DETAIL2_NAME "Argument"
+#endif
+#endif
+
+#ifdef UNITY_PRINT_TEST_CONTEXT
+void UNITY_PRINT_TEST_CONTEXT(void);
+#endif
+
+/*-------------------------------------------------------
+ * Test Output
+ *-------------------------------------------------------*/
+
+void UnityPrint(const char* string);
+
+#ifdef UNITY_INCLUDE_PRINT_FORMATTED
+void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...);
+#endif
+
+void UnityPrintLen(const char* string, const UNITY_UINT32 length);
+void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number);
+void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style);
+void UnityPrintNumber(const UNITY_INT number_to_print);
+void UnityPrintNumberUnsigned(const UNITY_UINT number);
+void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print);
+
+#ifndef UNITY_EXCLUDE_FLOAT_PRINT
+void UnityPrintFloat(const UNITY_DOUBLE input_number);
+#endif
+
+/*-------------------------------------------------------
+ * Test Assertion Functions
+ *-------------------------------------------------------
+ * Use the macros below this section instead of calling
+ * these directly. The macros have a consistent naming
+ * convention and will pull in file and line information
+ * for you. */
+
+void UnityAssertEqualNumber(const UNITY_INT expected,
+ const UNITY_INT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style);
+
+void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold,
+ const UNITY_INT actual,
+ const UNITY_COMPARISON_T compare,
+ const char *msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style);
+
+void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected,
+ UNITY_INTERNAL_PTR actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style,
+ const UNITY_FLAGS_T flags);
+
+void UnityAssertBits(const UNITY_INT mask,
+ const UNITY_INT expected,
+ const UNITY_INT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber);
+
+void UnityAssertEqualString(const char* expected,
+ const char* actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber);
+
+void UnityAssertEqualStringLen(const char* expected,
+ const char* actual,
+ const UNITY_UINT32 length,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber);
+
+void UnityAssertEqualStringArray( UNITY_INTERNAL_PTR expected,
+ const char** actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags);
+
+void UnityAssertEqualMemory( UNITY_INTERNAL_PTR expected,
+ UNITY_INTERNAL_PTR actual,
+ const UNITY_UINT32 length,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags);
+
+void UnityAssertNumbersWithin(const UNITY_UINT delta,
+ const UNITY_INT expected,
+ const UNITY_INT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style);
+
+void UnityAssertNumbersArrayWithin(const UNITY_UINT delta,
+ UNITY_INTERNAL_PTR expected,
+ UNITY_INTERNAL_PTR actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_DISPLAY_STYLE_T style,
+ const UNITY_FLAGS_T flags);
+
+#ifndef UNITY_EXCLUDE_SETJMP_H
+UNITY_NORETURN void UnityFail(const char* message, const UNITY_LINE_TYPE line);
+UNITY_NORETURN void UnityIgnore(const char* message, const UNITY_LINE_TYPE line);
+#else
+void UnityFail(const char* message, const UNITY_LINE_TYPE line);
+void UnityIgnore(const char* message, const UNITY_LINE_TYPE line);
+#endif
+
+void UnityMessage(const char* message, const UNITY_LINE_TYPE line);
+
+#ifndef UNITY_EXCLUDE_FLOAT
+void UnityAssertFloatsWithin(const UNITY_FLOAT delta,
+ const UNITY_FLOAT expected,
+ const UNITY_FLOAT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber);
+
+void UnityAssertFloatsNotWithin(const UNITY_FLOAT delta,
+ const UNITY_FLOAT expected,
+ const UNITY_FLOAT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber);
+
+void UnityAssertGreaterOrLessFloat(const UNITY_FLOAT threshold,
+ const UNITY_FLOAT actual,
+ const UNITY_COMPARISON_T compare,
+ const char* msg,
+ const UNITY_LINE_TYPE linenumber);
+
+void UnityAssertWithinFloatArray(const UNITY_FLOAT delta,
+ UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected,
+ UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags);
+
+void UnityAssertFloatSpecial(const UNITY_FLOAT actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLOAT_TRAIT_T style);
+#endif
+
+#ifndef UNITY_EXCLUDE_DOUBLE
+void UnityAssertDoublesWithin(const UNITY_DOUBLE delta,
+ const UNITY_DOUBLE expected,
+ const UNITY_DOUBLE actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber);
+
+void UnityAssertDoublesNotWithin(const UNITY_DOUBLE delta,
+ const UNITY_DOUBLE expected,
+ const UNITY_DOUBLE actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber);
+
+void UnityAssertGreaterOrLessDouble(const UNITY_DOUBLE threshold,
+ const UNITY_DOUBLE actual,
+ const UNITY_COMPARISON_T compare,
+ const char* msg,
+ const UNITY_LINE_TYPE linenumber);
+
+void UnityAssertWithinDoubleArray(const UNITY_DOUBLE delta,
+ UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected,
+ UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual,
+ const UNITY_UINT32 num_elements,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLAGS_T flags);
+
+void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual,
+ const char* msg,
+ const UNITY_LINE_TYPE lineNumber,
+ const UNITY_FLOAT_TRAIT_T style);
+#endif
+
+/*-------------------------------------------------------
+ * Helpers
+ *-------------------------------------------------------*/
+
+UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size);
+#ifndef UNITY_EXCLUDE_FLOAT
+UNITY_INTERNAL_PTR UnityFloatToPtr(const float num);
+#endif
+#ifndef UNITY_EXCLUDE_DOUBLE
+UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num);
+#endif
+
+/*-------------------------------------------------------
+ * Error Strings We Might Need
+ *-------------------------------------------------------*/
+
+extern const char UnityStrOk[];
+extern const char UnityStrPass[];
+extern const char UnityStrFail[];
+extern const char UnityStrIgnore[];
+
+extern const char UnityStrErrFloat[];
+extern const char UnityStrErrDouble[];
+extern const char UnityStrErr64[];
+extern const char UnityStrErrShorthand[];
+
+/*-------------------------------------------------------
+ * Test Running Macros
+ *-------------------------------------------------------*/
+
+#ifndef UNITY_EXCLUDE_SETJMP_H
+#define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0)
+#define TEST_ABORT() longjmp(Unity.AbortFrame, 1)
+#else
+#define TEST_PROTECT() 1
+#define TEST_ABORT() return
+#endif
+
+/* Automatically enable variadic macros support, if it not enabled before */
+#ifndef UNITY_SUPPORT_VARIADIC_MACROS
+ #ifdef __STDC_VERSION__
+ #if __STDC_VERSION__ >= 199901L
+ #define UNITY_SUPPORT_VARIADIC_MACROS
+ #endif
+ #endif
+#endif
+
+/* This tricky series of macros gives us an optional line argument to treat it as RUN_TEST(func, num=__LINE__) */
+#ifndef RUN_TEST
+#ifdef UNITY_SUPPORT_VARIADIC_MACROS
+#define RUN_TEST(...) RUN_TEST_AT_LINE(__VA_ARGS__, __LINE__, throwaway)
+#define RUN_TEST_AT_LINE(func, line, ...) UnityDefaultTestRun(func, #func, line)
+#endif
+#endif
+
+/* Enable default macros for masking param tests test cases */
+#ifdef UNITY_SUPPORT_TEST_CASES
+ #ifdef UNITY_SUPPORT_VARIADIC_MACROS
+ #if !defined(TEST_CASE) && !defined(UNITY_EXCLUDE_TEST_CASE)
+ #define TEST_CASE(...)
+ #endif
+ #if !defined(TEST_RANGE) && !defined(UNITY_EXCLUDE_TEST_RANGE)
+ #define TEST_RANGE(...)
+ #endif
+ #endif
+#endif
+
+/* If we can't do the tricky version, we'll just have to require them to always include the line number */
+#ifndef RUN_TEST
+#ifdef CMOCK
+#define RUN_TEST(func, num) UnityDefaultTestRun(func, #func, num)
+#else
+#define RUN_TEST(func) UnityDefaultTestRun(func, #func, __LINE__)
+#endif
+#endif
+
+#define TEST_LINE_NUM (Unity.CurrentTestLineNumber)
+#define TEST_IS_IGNORED (Unity.CurrentTestIgnored)
+#define UNITY_NEW_TEST(a) \
+ Unity.CurrentTestName = (a); \
+ Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)(__LINE__); \
+ Unity.NumberOfTests++;
+
+#ifndef UNITY_BEGIN
+#define UNITY_BEGIN() UnityBegin(__FILE__)
+#endif
+
+#ifndef UNITY_END
+#define UNITY_END() UnityEnd()
+#endif
+
+#ifndef UNITY_SHORTHAND_AS_INT
+#ifndef UNITY_SHORTHAND_AS_MEM
+#ifndef UNITY_SHORTHAND_AS_NONE
+#ifndef UNITY_SHORTHAND_AS_RAW
+#define UNITY_SHORTHAND_AS_OLD
+#endif
+#endif
+#endif
+#endif
+
+/*-----------------------------------------------
+ * Command Line Argument Support
+ *-----------------------------------------------*/
+
+#ifdef UNITY_USE_COMMAND_LINE_ARGS
+int UnityParseOptions(int argc, char** argv);
+int UnityTestMatches(void);
+#endif
+
+/*-------------------------------------------------------
+ * Basic Fail and Ignore
+ *-------------------------------------------------------*/
+
+#define UNITY_TEST_FAIL(line, message) UnityFail( (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_IGNORE(line, message) UnityIgnore( (message), (UNITY_LINE_TYPE)(line))
+
+/*-------------------------------------------------------
+ * Test Asserts
+ *-------------------------------------------------------*/
+
+#define UNITY_TEST_ASSERT(condition, line, message) do { if (condition) { /* nothing*/ } else { UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), (message)); } } while (0)
+#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (UNITY_LINE_TYPE)(line), (message))
+#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (UNITY_LINE_TYPE)(line), (message))
+#define UNITY_TEST_ASSERT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) == 0), (UNITY_LINE_TYPE)(line), (message))
+#define UNITY_TEST_ASSERT_NOT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) != 0), (UNITY_LINE_TYPE)(line), (message))
+
+#define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT)
+#define UNITY_TEST_ASSERT_EQUAL_INT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8)
+#define UNITY_TEST_ASSERT_EQUAL_INT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16)
+#define UNITY_TEST_ASSERT_EQUAL_INT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32)
+#define UNITY_TEST_ASSERT_EQUAL_UINT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT)
+#define UNITY_TEST_ASSERT_EQUAL_UINT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8)
+#define UNITY_TEST_ASSERT_EQUAL_UINT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16)
+#define UNITY_TEST_ASSERT_EQUAL_UINT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32)
+#define UNITY_TEST_ASSERT_EQUAL_HEX8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8)
+#define UNITY_TEST_ASSERT_EQUAL_HEX16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16)
+#define UNITY_TEST_ASSERT_EQUAL_HEX32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32)
+#define UNITY_TEST_ASSERT_EQUAL_CHAR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR)
+#define UNITY_TEST_ASSERT_BITS(mask, expected, actual, line, message) UnityAssertBits((UNITY_INT)(mask), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line))
+
+#define UNITY_TEST_ASSERT_NOT_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR)
+
+#define UNITY_TEST_ASSERT_GREATER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT)
+#define UNITY_TEST_ASSERT_GREATER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8)
+#define UNITY_TEST_ASSERT_GREATER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16)
+#define UNITY_TEST_ASSERT_GREATER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32)
+#define UNITY_TEST_ASSERT_GREATER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT)
+#define UNITY_TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8)
+#define UNITY_TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16)
+#define UNITY_TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32)
+#define UNITY_TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8)
+#define UNITY_TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16)
+#define UNITY_TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32)
+#define UNITY_TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR)
+
+#define UNITY_TEST_ASSERT_SMALLER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR)
+
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16) (threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32) (threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR)
+
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR)
+
+#define UNITY_TEST_ASSERT_INT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT)
+#define UNITY_TEST_ASSERT_INT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8)
+#define UNITY_TEST_ASSERT_INT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_INT16) (expected), (UNITY_INT)(UNITY_INT16) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16)
+#define UNITY_TEST_ASSERT_INT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_INT32) (expected), (UNITY_INT)(UNITY_INT32) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32)
+#define UNITY_TEST_ASSERT_UINT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT)
+#define UNITY_TEST_ASSERT_UINT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8)
+#define UNITY_TEST_ASSERT_UINT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16)
+#define UNITY_TEST_ASSERT_UINT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32)
+#define UNITY_TEST_ASSERT_HEX8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8)
+#define UNITY_TEST_ASSERT_HEX16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16)
+#define UNITY_TEST_ASSERT_HEX32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32)
+#define UNITY_TEST_ASSERT_CHAR_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR)
+
+#define UNITY_TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY)
+
+
+#define UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_PTR_TO_INT)(expected), (UNITY_PTR_TO_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER)
+#define UNITY_TEST_ASSERT_EQUAL_STRING(expected, actual, line, message) UnityAssertEqualString((const char*)(expected), (const char*)(actual), (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len, line, message) UnityAssertEqualStringLen((const char*)(expected), (const char*)(actual), (UNITY_UINT32)(len), (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_EQUAL_MEMORY(expected, actual, len, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), 1, (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY)
+
+#define UNITY_TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY)
+
+#define UNITY_TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT16)(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT32)(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_PTR_TO_INT) (expected), (UNITY_POINTER_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_VAL)
+
+#ifdef UNITY_SUPPORT_64
+#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64)
+#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64)
+#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64)
+#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64)
+#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64)
+#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64)
+#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64)
+#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64)
+#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64)
+#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY)
+#else
+#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64)
+#endif
+
+#ifdef UNITY_EXCLUDE_FLOAT
+#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat)
+#else
+#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsNotWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message))
+#define UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message))
+#define UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)(delta), (UNITY_FLOAT*)(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)0, (UNITY_FLOAT*)(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)0, UnityFloatToPtr(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN)
+#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN)
+#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET)
+#endif
+
+#ifdef UNITY_EXCLUDE_DOUBLE
+#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble)
+#else
+#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesNotWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message))
+#define UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message))
+#define UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)(delta), (UNITY_DOUBLE*)(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)0, (UNITY_DOUBLE*)(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY)
+#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)0, UnityDoubleToPtr(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL)
+#define UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line))
+#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN)
+#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET)
+#endif
+
+/* End of UNITY_INTERNALS_H */
+#endif
diff --git a/movement/lib/morsecalc/calc.c b/movement/lib/morsecalc/calc.c
index 49b19a00..ed7eb25b 100644
--- a/movement/lib/morsecalc/calc.c
+++ b/movement/lib/morsecalc/calc.c
@@ -24,6 +24,7 @@
#include <stdlib.h>
#include <string.h>
+#include <stdio.h>
#include "calc.h"
#include "calc_fns.h"
@@ -41,12 +42,11 @@ int calc_init(calc_state_t *cs) {
}
/* 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
+ * Try to execute the token as a calculator function
*/
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++) {
+ for(uint8_t idxn=0; idxn<calc_dict[idx].n_names; idxn++) {
if(0 == strcmp(calc_dict[idx].names[idxn], token)) { // Found a match
return (*calc_dict[idx].fn)(cs); // Run calculator function
}
@@ -108,6 +108,6 @@ int calc_input_float(calc_state_t *cs, char *token) {
*/
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);
+ if(-1 == retval) retval = calc_input_float(cs, token);
return retval;
}
diff --git a/movement/lib/morsecalc/calc_fns.c b/movement/lib/morsecalc/calc_fns.c
index 873de26b..9b40a6fa 100644
--- a/movement/lib/morsecalc/calc_fns.c
+++ b/movement/lib/morsecalc/calc_fns.c
@@ -227,4 +227,14 @@ int calc_atan2d(calc_state_t *cs) {
cs->s--;
return 0;
}
+int calc_torad(calc_state_t *cs) {
+ STACK_CHECK_1_IN_1_OUT;
+ cs->stack[cs->s-1] = cs->stack[cs->s-1]*to_rad;
+ return 0;
+}
+int calc_todeg(calc_state_t *cs) {
+ STACK_CHECK_1_IN_1_OUT;
+ cs->stack[cs->s-1] = cs->stack[cs->s-1]*to_deg;
+ return 0;
+}
diff --git a/movement/lib/morsecalc/calc_fns.h b/movement/lib/morsecalc/calc_fns.h
index fd1d7aba..15f56b0e 100644
--- a/movement/lib/morsecalc/calc_fns.h
+++ b/movement/lib/morsecalc/calc_fns.h
@@ -67,57 +67,62 @@ 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);
+int calc_torad(calc_state_t *cs);
+int calc_todeg(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
+ uint8_t n_names; // Number of aliases
+ const char ** names; // 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},
+ {1, (const char*[]){"x"}, &calc_delete},
+ {1, (const char*[]){"xx"}, &calc_clear_stack},
+ {1, (const char*[]){"xxx"}, &calc_init},
+ {1, (const char*[]){"f"}, &calc_flip},
+ {1, (const char*[]){"mc"}, &calc_mem_clear},
+ {1, (const char*[]){"mr"}, &calc_mem_recall},
+ {1, (const char*[]){"ma"}, &calc_mem_add},
+ {1, (const char*[]){"ms"}, &calc_mem_subtract},
// Basic operations
- {{"a"}, &calc_add},
- {{"s"}, &calc_subtract},
- {{"n"}, &calc_negate},
- {{"m"}, &calc_multiply},
- {{"d"}, &calc_divide},
- {{"i"}, &calc_invert},
+ {1, (const char*[]){"a"}, &calc_add},
+ {1, (const char*[]){"s"}, &calc_subtract},
+ {1, (const char*[]){"n"}, &calc_negate},
+ {1, (const char*[]){"m"}, &calc_multiply},
+ {1, (const char*[]){"d"}, &calc_divide},
+ {1, (const char*[]){"i"}, &calc_invert},
// Constants
- {{"e"}, &calc_e},
- {{"pi"}, &calc_pi},
+ {1, (const char*[]){"e"}, &calc_e},
+ {1, (const char*[]){"pi"}, &calc_pi},
// Exponential/logarithmic
- {{"exp"}, &calc_exp},
- {{"pow"}, &calc_pow},
- {{"ln"}, &calc_ln},
- {{"log"}, &calc_log},
- {{"sqrt"}, &calc_sqrt},
+ {1, (const char*[]){"exp"}, &calc_exp},
+ {1, (const char*[]){"pow"}, &calc_pow},
+ {1, (const char*[]){"ln"}, &calc_ln},
+ {1, (const char*[]){"log"}, &calc_log},
+ {1, (const char*[]){"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},
+ {2, (const char*[]){"sin", "sn"}, &calc_sin},
+ {1, (const char*[]){"cos"}, &calc_cos},
+ {1, (const char*[]){"tan"}, &calc_tan},
+ {1, (const char*[]){"asin"}, &calc_asin},
+ {1, (const char*[]){"acos"}, &calc_acos},
+ {1, (const char*[]){"atan"}, &calc_atan},
+ {1, (const char*[]){"atan2"}, &calc_atan2},
+ {1, (const char*[]){"sind"}, &calc_sind},
+ {1, (const char*[]){"cosd"}, &calc_cosd},
+ {1, (const char*[]){"tand"}, &calc_tand},
+ {1, (const char*[]){"asind"}, &calc_asind},
+ {1, (const char*[]){"acosd"}, &calc_acosd},
+ {1, (const char*[]){"atand"}, &calc_atand},
+ {1, (const char*[]){"atan2d"}, &calc_atan2d},
+ {1, (const char*[]){"tor"}, &calc_torad},
+ {1, (const char*[]){"tod"}, &calc_todeg},
};
diff --git a/movement/lib/morsecalc/mc.c b/movement/lib/morsecalc/mc.c
deleted file mode 100644
index 94f6511b..00000000
--- a/movement/lib/morsecalc/mc.c
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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/morsecalc_display.c b/movement/lib/morsecalc/morsecalc_display.c
new file mode 100644
index 00000000..68f06fbe
--- /dev/null
+++ b/movement/lib/morsecalc/morsecalc_display.c
@@ -0,0 +1,147 @@
+/*
+ * 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 "watch_private_display.h"
+#include "morsecalc_display.h"
+
+// Display float on screen
+void morsecalc_display_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_display_token(morsecalc_state_t *mcs) {
+ watch_display_string(" ", 0); // Clear display
+
+ // Print morse code buffer
+ char c = MORSECODE_TREE[mcs->mc]; // 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
+
+ unsigned int v = mcs->mc+1;
+ char bidx = 0; while (v >>= 1) bidx++;
+ watch_display_character('0'+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;
+}
+
+// Print stack or memory register contents.
+void morsecalc_display_stack(morsecalc_state_t * mcs) {
+ watch_display_string(" ", 0); // Clear display
+
+ char c = MORSECODE_TREE[mcs->mc];
+ if('m' == c) { // Display memory
+ morsecalc_display_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_display_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;
+}
+
diff --git a/movement/lib/morsecalc/mc.h b/movement/lib/morsecalc/morsecalc_display.h
index 0daa470e..74f8b0cc 100644
--- a/movement/lib/morsecalc/mc.h
+++ b/movement/lib/morsecalc/morsecalc_display.h
@@ -22,30 +22,14 @@
* SOFTWARE.
*/
+#include "morsecalc_face.h"
-/* mc Morse code reading methods
-*/
-#include "stdint.h"
+// Display float on screen
+void morsecalc_display_float(double d);
-#define BUFFLEN 5
-typedef struct {
- char b[BUFFLEN];
- uint8_t bidx;
-} mc_state_t;
+// Print current input token
+void morsecalc_display_token(morsecalc_state_t *mcs);
-// 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]);
+// Print stack or memory register contents.
+void morsecalc_display_stack(morsecalc_state_t *mcs);
diff --git a/movement/lib/morsecalc/test_morsecalc.c b/movement/lib/morsecalc/test_morsecalc.c
new file mode 100644
index 00000000..fc640bad
--- /dev/null
+++ b/movement/lib/morsecalc/test_morsecalc.c
@@ -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.
+ */
+
+// Computer console interface to calc and morsecode for testing without involving watch stuff.
+// cc calc_strtof.c calc.c calc_fns.c test_morsecalc.c -lm
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "calc.h"
+#include "calc_fns.h"
+
+int main(void) {
+ calc_state_t cs;
+ calc_init(&cs);
+
+ char * word = malloc(0);
+ unsigned int nword = 0;
+ char c;
+ int retval = 0;
+ for(unsigned int ii = 0; ii < 100; ii++) {
+ c = getchar();
+ word = realloc(word, (++nword)*sizeof(char));
+ word[nword-1] = c;
+ if((nword > 0) && isspace(c)) { // Word is finished
+ word[nword-1] = '\0';
+ retval = calc_input(&cs, word); // Submit word
+ word = realloc(word, 0); nword = 0; // Clear word
+
+ switch(retval) {
+ case 0: printf("Success.\n"); break;
+ case -1: printf("Bad command.\n"); break;
+ case -2: printf("Stack over/underflow.\n"); break;
+ case -3: printf("Error.\n"); break;
+ }
+ if(cs.s > 0) printf("[%i]: %.4f\n", cs.s, cs.stack[cs.s-1]);
+ else printf("[%i]\n", cs.s);
+ }
+ }
+
+ free(word);
+ return 0;
+}
diff --git a/movement/make/Makefile b/movement/make/Makefile
index 417be4c5..952f3c70 100644
--- a/movement/make/Makefile
+++ b/movement/make/Makefile
@@ -17,6 +17,7 @@ INCLUDES += \
-I../watch_faces/sensor/ \
-I../watch_faces/demo/ \
-I../../littlefs/ \
+ -I../lib/chirpy_tx/ \
-I../lib/TOTP/ \
-I../lib/base32/ \
-I../lib/sunriset/ \
@@ -31,6 +32,7 @@ INCLUDES += \
# ../drivers/lis2dh.c \
# ../watch_faces/fitness/step_count_face.c
SRCS += \
+ ../lib/chirpy_tx/chirpy_tx.c \
../lib/TOTP/sha1.c \
../lib/TOTP/sha256.c \
../lib/TOTP/sha512.c \
@@ -42,7 +44,7 @@ SRCS += \
../lib/morsecalc/calc.c \
../lib/morsecalc/calc_fns.c \
../lib/morsecalc/calc_strtof.c \
- ../lib/morsecalc/mc.c \
+ ../lib/morsecalc/morsecalc_display.c \
../../littlefs/lfs.c \
../../littlefs/lfs_util.c \
../movement.c \
@@ -95,9 +97,23 @@ SRCS += \
../watch_faces/complication/tarot_face.c \
../watch_faces/complication/morsecalc_face.c \
../watch_faces/complication/rpn_calculator_face.c \
+ ../watch_faces/complication/activity_face.c \
+ ../watch_faces/demo/chirpy_demo_face.c \
../watch_faces/complication/ships_bell_face.c \
+ ../watch_faces/sensor/lightmeter_face.c \
+ ../watch_faces/complication/discgolf_face.c \
../watch_faces/complication/habit_face.c \
+ ../watch_faces/complication/planetary_time_face.c \
+ ../watch_faces/complication/planetary_hours_face.c \
+ ../watch_faces/complication/breathing_face.c \
+ ../watch_faces/clock/repetition_minute_face.c \
../watch_faces/complication/timer_face.c \
+ ../watch_faces/complication/invaders_face.c \
+ ../watch_faces/clock/world_clock2_face.c \
+ ../watch_faces/complication/time_left_face.c \
+ ../watch_faces/complication/randonaut_face.c \
+ ../watch_faces/complication/toss_up_face.c \
+ ../watch_faces/complication/geomancy_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 56e722dd..0e2a45b1 100644
--- a/movement/movement_faces.h
+++ b/movement/movement_faces.h
@@ -73,9 +73,24 @@
#include "interval_face.h"
#include "morsecalc_face.h"
#include "rpn_calculator_face.h"
+#include "activity_face.h"
+#include "chirpy_demo_face.h"
#include "ships_bell_face.h"
+#include "lightmeter_face.h"
+#include "discgolf_face.h"
#include "habit_face.h"
+#include "planetary_time_face.h"
+#include "planetary_hours_face.h"
+#include "breathing_face.h"
+#include "repetition_minute_face.h"
#include "timer_face.h"
+#include "invaders_face.h"
+#include "world_clock2_face.h"
+#include "time_left_face.h"
+#include "randonaut_face.h"
+#include "toss_up_face.h"
+#include "geomancy_face.h"
+#include "dual_timer_face.h"
// New includes go above this line.
#endif // MOVEMENT_FACES_H_
diff --git a/movement/watch_faces/clock/repetition_minute_face.c b/movement/watch_faces/clock/repetition_minute_face.c
new file mode 100644
index 00000000..a0fbe077
--- /dev/null
+++ b/movement/watch_faces/clock/repetition_minute_face.c
@@ -0,0 +1,231 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Jonas Termeau
+ *
+ * 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 "repetition_minute_face.h"
+#include "watch.h"
+#include "watch_utility.h"
+#include "watch_private_display.h"
+
+void play_hour_chime(void) {
+ watch_buzzer_play_note(BUZZER_NOTE_C6, 75);
+ watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
+}
+
+void play_quarter_chime(void) {
+ watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
+ watch_buzzer_play_note(BUZZER_NOTE_REST, 150);
+ watch_buzzer_play_note(BUZZER_NOTE_C6, 75);
+ watch_buzzer_play_note(BUZZER_NOTE_REST, 750);
+}
+
+void play_minute_chime(void) {
+ watch_buzzer_play_note(BUZZER_NOTE_E6, 75);
+ watch_buzzer_play_note(BUZZER_NOTE_REST, 500);
+}
+
+static void _update_alarm_indicator(bool settings_alarm_enabled, repetition_minute_state_t *state) {
+ state->alarm_enabled = settings_alarm_enabled;
+ if (state->alarm_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL);
+ else watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
+}
+
+void repetition_minute_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(repetition_minute_state_t));
+ repetition_minute_state_t *state = (repetition_minute_state_t *)*context_ptr;
+ state->signal_enabled = false;
+ state->watch_face_index = watch_face_index;
+ }
+}
+
+void repetition_minute_face_activate(movement_settings_t *settings, void *context) {
+ repetition_minute_state_t *state = (repetition_minute_state_t *)context;
+
+ if (watch_tick_animation_is_running()) watch_stop_tick_animation();
+
+ if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H);
+
+ // handle chime indicator
+ if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
+ else watch_clear_indicator(WATCH_INDICATOR_BELL);
+
+ // show alarm indicator if there is an active alarm
+ _update_alarm_indicator(settings->bit.alarm_enabled, state);
+
+ watch_set_colon();
+
+ // this ensures that none of the timestamp fields will match, so we can re-render them all.
+ state->previous_date_time = 0xFFFFFFFF;
+}
+
+bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ repetition_minute_state_t *state = (repetition_minute_state_t *)context;
+ char buf[11];
+ uint8_t pos;
+
+ watch_date_time date_time;
+ uint32_t previous_date_time;
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ case EVENT_TICK:
+ case EVENT_LOW_ENERGY_UPDATE:
+ date_time = watch_rtc_get_date_time();
+ previous_date_time = state->previous_date_time;
+ state->previous_date_time = date_time.reg;
+
+ // check the battery voltage once a day...
+ if (date_time.unit.day != state->last_battery_check) {
+ state->last_battery_check = date_time.unit.day;
+ watch_enable_adc();
+ uint16_t voltage = watch_get_vcc_voltage();
+ watch_disable_adc();
+ // 2.2 volts will happen when the battery has maybe 5-10% remaining?
+ // we can refine this later.
+ state->battery_low = (voltage < 2200);
+ }
+
+ // ...and set the LAP indicator if low.
+ if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP);
+
+ if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
+ // everything before seconds is the same, don't waste cycles setting those segments.
+ watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8);
+ watch_display_character_lp_seconds('0' + date_time.unit.second % 10, 9);
+ break;
+ } else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
+ // everything before minutes is the same.
+ pos = 6;
+ sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
+ } else {
+ // other stuff changed; let's do it all.
+ if (!settings->bit.clock_mode_24h) {
+ // if we are in 12 hour mode, do some cleanup.
+ 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;
+ }
+ pos = 0;
+ if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
+ if (!watch_tick_animation_is_running()) watch_start_tick_animation(500);
+ sprintf(buf, "%s%2d%2d%02d ", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute);
+ } else {
+ sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second);
+ }
+ }
+ watch_display_string(buf, pos);
+ // handle alarm indicator
+ if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state);
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ state->signal_enabled = !state->signal_enabled;
+ if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL);
+ else watch_clear_indicator(WATCH_INDICATOR_BELL);
+ break;
+ case EVENT_BACKGROUND_TASK:
+ // uncomment this line to snap back to the clock face when the hour signal sounds:
+ // movement_move_to_face(state->watch_face_index);
+ if (watch_is_buzzer_or_led_enabled()) {
+ // if we are in the foreground, we can just beep.
+ movement_play_signal();
+ } else {
+ // if we were in the background, we need to enable the buzzer peripheral first,
+ watch_enable_buzzer();
+ // beep quickly (this call blocks for 275 ms),
+ movement_play_signal();
+ // and then turn the buzzer peripheral off again.
+ watch_disable_buzzer();
+ }
+ break;
+ case EVENT_LIGHT_LONG_UP:
+ /*
+ * Howdy neighbors, this is the actual complication. Like an actual
+ * (very expensive) watch with a repetition minute complication it's
+ * boring at 00:00 or 1:00 and very quite musical at 23:59 or 12:59.
+ */
+
+ date_time = watch_rtc_get_date_time();
+
+
+ int hours = date_time.unit.hour;
+ int quarters = date_time.unit.minute / 15;
+ int minutes = date_time.unit.minute % 15;
+
+ // chiming hours
+ if (!settings->bit.clock_mode_24h) {
+ hours = date_time.unit.hour % 12;
+ if (hours == 0) hours = 12;
+ }
+ if (hours > 0) {
+ int count = 0;
+ for(count = hours; count > 0; --count) {
+ play_hour_chime();
+ }
+ }
+
+ // chiming quarters (if needed)
+ if (quarters > 0) {
+ int count = 0;
+ for(count = quarters; count > 0; --count) {
+ play_quarter_chime();
+ }
+ }
+
+ // chiming minutes (if needed)
+ if (minutes > 0) {
+ int count = 0;
+ for(count = minutes; count > 0; --count) {
+ play_minute_chime();
+ }
+ }
+
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void repetition_minute_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+bool repetition_minute_face_wants_background_task(movement_settings_t *settings, void *context) {
+ (void) settings;
+ repetition_minute_state_t *state = (repetition_minute_state_t *)context;
+ if (!state->signal_enabled) return false;
+
+ watch_date_time date_time = watch_rtc_get_date_time();
+
+ return date_time.unit.minute == 0;
+}
diff --git a/movement/watch_faces/clock/repetition_minute_face.h b/movement/watch_faces/clock/repetition_minute_face.h
new file mode 100644
index 00000000..5a897bc1
--- /dev/null
+++ b/movement/watch_faces/clock/repetition_minute_face.h
@@ -0,0 +1,83 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Jonas Termeau
+ *
+ * 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 REPETITION_MINUTE_FACE_H_
+#define REPETITION_MINUTE_FACE_H_
+
+#include "movement.h"
+
+/*
+ * A hopefully useful complication for friendly neighbors in the dark
+ *
+ * Originating from 1676 from reverend and mechanician Edward Barlow, and
+ * perfected in 1820 by neighbor Abraham Breguet, a minute repeater or
+ * "repetition minute" is a complication in a mechanical watch or clock that
+ * chimes the hours and often minutes at the press of a button. There are many
+ * types of repeater, from the simple repeater which merely strikes the number
+ * of hours, to the minute repeater which chimes the time down to the minute,
+ * using separate tones for hours, quarter hours, and minutes. They originated
+ * before widespread artificial illumination, to allow the time to be determined
+ * in the dark, and were also used by the visually impaired.
+ *
+ *
+ * How to use it :
+ *
+ * Long press the light button to get an auditive reading of the time like so :
+ * 0..23 (1..12 if 24-hours format isn't enabled) low beep(s) for the hours
+ * 0..3 low-high couple pitched beeps for the quarters
+ * 0..14 high pitched beep(s) for the remaining minutes
+ *
+ * Prerequisite : a watch with a working buzzer
+ *
+ * ~ Only in the darkness can you see the stars. - Martin Luther King ~
+ *
+ */
+
+typedef struct {
+ uint32_t previous_date_time;
+ uint8_t last_battery_check;
+ uint8_t watch_face_index;
+ bool signal_enabled;
+ bool battery_low;
+ bool alarm_enabled;
+} repetition_minute_state_t;
+
+void play_hour_chime(void);
+void play_quarter_chime(void);
+void play_minute_chime(void);
+void repetition_minute_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void repetition_minute_face_activate(movement_settings_t *settings, void *context);
+bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void repetition_minute_face_resign(movement_settings_t *settings, void *context);
+bool repetition_minute_face_wants_background_task(movement_settings_t *settings, void *context);
+
+#define repetition_minute_face ((const watch_face_t){ \
+ repetition_minute_face_setup, \
+ repetition_minute_face_activate, \
+ repetition_minute_face_loop, \
+ repetition_minute_face_resign, \
+ repetition_minute_face_wants_background_task, \
+})
+
+#endif // REPETITION_MINUTE_FACE_H_
diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c
new file mode 100644
index 00000000..0077f639
--- /dev/null
+++ b/movement/watch_faces/clock/world_clock2_face.c
@@ -0,0 +1,450 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Konrad Rieck
+ * Copyright (c) 2022 Joey Castillo
+ *
+ * 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.
+ */
+
+/*
+ * World Clock 2
+ * =============
+ *
+ * This is an alternative world clock face that allows the user to cycle
+ * through a list of selected time zones. It extends the original
+ * implementation by Joey Castillo. The face has two modes *display mode*
+ * and *settings mode*.
+ *
+ * ### Settings mode
+ *
+ * When the clock face is activated for the first time, it enters
+ * *settings mode*. Here, the user can select the time zones they want to
+ * display. The face shows a summary of the current time zone:
+ *
+ * - The top of the face displays the first two letters of the time zone
+ * abbreviation, such as "PS" for Pacific Standard Time or CE for
+ * "Central European Time".
+ *
+ * - The upper-right corner shows the index number of the time zone. This
+ * helps avoid confusion when multiple time zones have the same
+ * two-letter abbreviation.
+ *
+ * - The main display shows the offset from UTC, with a "+" indicating a
+ * positive offset and a "-" indicating a negative offset. For example,
+ * the offset for Japanese Standard Time is displayed as "+9:00".
+ *
+ * The user can navigate through the time zones and select them using the
+ * following buttons:
+ *
+ * - The *alarm button* moves forward to the next time zone, while the
+ * *light button* moves backward to the previous zone. This way, the
+ * user can cycle through all 41 supported time zones.
+ *
+ * - A *long press* on the *light button* selects the current time zone,
+ * and the signal indicator appears at the top left. Another *long
+ * press* of the *light button* deselects the time zone.
+ *
+ * - A *long press* on the *alarm button* exits settings mode and returns
+ * to display mode.
+ *
+ * ### Display mode
+ *
+ * In the display mode, the face shows the time of the currently selected
+ * time zone. The face includes the following components:
+ *
+ * - The top of the face displays the first two letters of the time zone
+ * abbreviation, such as "PS" for Pacific Standard Time or "CE" for
+ * Central European Time.
+ *
+ * - The upper-right corner shows the current day of the month, which
+ * helps indicate time zones that cross the international date line
+ * with respect to the local time.
+ *
+ * - The main display shows the time in the selected time zone in either
+ * 12-hour or 24-hour form. There is no timeout, allowing users to keep
+ * the chosen time zone displayed for as long as they wish.
+ *
+ * The user can navigate through the selected time zones using the
+ * following buttons:
+ *
+ * - The *alarm button* moves to the next selected time zone, while the
+ * light button moves to the *previous zone*. If no time zone is
+ * selected, the face simply shows UTC.
+ *
+ * - A *long press* on the *alarm button* enters settings mode and
+ * enables the user to re-configure the selected time zones.
+ *
+ * - A *long press* on the *light button* activates the LED illumination
+ * of the watch.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "world_clock2_face.h"
+#include "watch.h"
+#include "watch_utility.h"
+#include "watch_utility.h"
+
+static bool refresh_face;
+
+/* Simple macros for navigation */
+#define FORWARD +1
+#define BACKWARD -1
+
+/* Activate refresh of time */
+#define REFRESH_TIME 0xffffffff
+
+/* List of all time zone names */
+const char *zone_names[] = {
+ "UTC", // 0 : 0:00:00 (UTC)
+ "CET", // 1 : 1:00:00 (Central European Time)
+ "SAST", // 2 : 2:00:00 (South African Standard Time)
+ "ARST", // 3 : 3:00:00 (Arabia Standard Time)
+ "IRST", // 4 : 3:30:00 (Iran Standard Time)
+ "GET", // 5 : 4:00:00 (Georgia Standard Time)
+ "AFT", // 6 : 4:30:00 (Afghanistan Time)
+ "PKT", // 7 : 5:00:00 (Pakistan Standard Time)
+ "IST", // 8 : 5:30:00 (Indian Standard Time)
+ "NPT", // 9 : 5:45:00 (Nepal Time)
+ "KGT", // 10 : 6:00:00 (Kyrgyzstan time)
+ "MYST", // 11 : 6:30:00 (Myanmar Time)
+ "THA", // 12 : 7:00:00 (Thailand Standard Time)
+ "CST", // 13 : 8:00:00 (China Standard Time, Australian Western Standard Time)
+ "ACWS", // 14 : 8:45:00 (Australian Central Western Standard Time)
+ "JST", // 15 : 9:00:00 (Japan Standard Time, Korea Standard Time)
+ "ACST", // 16 : 9:30:00 (Australian Central Standard Time)
+ "AEST", // 17 : 10:00:00 (Australian Eastern Standard Time)
+ "LHST", // 18 : 10:30:00 (Lord Howe Standard Time)
+ "SBT", // 19 : 11:00:00 (Solomon Islands Time)
+ "NZST", // 20 : 12:00:00 (New Zealand Standard Time)
+ "CHAS", // 21 : 12:45:00 (Chatham Standard Time)
+ "TOT", // 22 : 13:00:00 (Tonga Time)
+ "CHAD", // 23 : 13:45:00 (Chatham Daylight Time)
+ "LINT", // 24 : 14:00:00 (Line Islands Time)
+ "BIT", // 25 : -12:00:00 (Baker Island Time)
+ "NUT", // 26 : -11:00:00 (Niue Time)
+ "HST", // 27 : -10:00:00 (Hawaii-Aleutian Standard Time)
+ "MART", // 28 : -9:30:00 (Marquesas Islands Time)
+ "AKST", // 29 : -9:00:00 (Alaska Standard Time)
+ "PST", // 30 : -8:00:00 (Pacific Standard Time)
+ "MST", // 31 : -7:00:00 (Mountain Standard Time)
+ "CST", // 32 : -6:00:00 (Central Standard Time)
+ "EST", // 33 : -5:00:00 (Eastern Standard Time)
+ "VET", // 34 : -4:30:00 (Venezuelan Standard Time)
+ "AST", // 35 : -4:00:00 (Atlantic Standard Time)
+ "NST", // 36 : -3:30:00 (Newfoundland Standard Time)
+ "BRT", // 37 : -3:00:00 (Brasilia Time)
+ "NDT", // 38 : -2:30:00 (Newfoundland Daylight Time)
+ "FNT", // 39 : -2:00:00 (Fernando de Noronha Time)
+ "AZOT", // 40 : -1:00:00 (Azores Standard Time)
+};
+
+/* Modulo function */
+static inline unsigned int mod(int a, int b)
+{
+ int r = a % b;
+ return r < 0 ? r + b : r;
+}
+
+/* Find the next selected time zone */
+static inline uint8_t find_selected_zone(world_clock2_state_t *state, int direction)
+{
+ uint8_t i = state->current_zone;
+
+ do {
+ i = mod(i + direction, NUM_TIME_ZONES);
+ /* Could not find a selected zone. Return UTC */
+ if (i == state->current_zone) {
+ return 0;
+ }
+ } while (!state->zones[i].selected);
+
+ return i;
+}
+
+/* Beep when zone is enabled. An octave up */
+static void beep_enable() {
+ watch_buzzer_play_note(BUZZER_NOTE_G7, 50);
+ watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
+ watch_buzzer_play_note(BUZZER_NOTE_C8, 75);
+}
+
+/* Beep when zone id disable. An octave down */
+static void beep_disable() {
+ watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
+ watch_buzzer_play_note(BUZZER_NOTE_REST, 75);
+ watch_buzzer_play_note(BUZZER_NOTE_G7, 75);
+}
+
+void world_clock2_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(world_clock2_state_t));
+ memset(*context_ptr, 0, sizeof(world_clock2_state_t));
+
+ /* Start in settings mode */
+ world_clock2_state_t *state = (world_clock2_state_t *) * context_ptr;
+ state->current_mode = WORLD_CLOCK2_MODE_SETTINGS;
+ }
+}
+
+void world_clock2_face_activate(movement_settings_t *settings, void *context)
+{
+ (void) settings;
+ world_clock2_state_t *state = (world_clock2_state_t *) context;
+
+ if (watch_tick_animation_is_running())
+ watch_stop_tick_animation();
+
+ switch (state->current_mode) {
+ case WORLD_CLOCK2_MODE_DISPLAY:
+ /* Normal tick frequency */
+ movement_request_tick_frequency(1);
+ break;
+ case WORLD_CLOCK2_MODE_SETTINGS:
+ /* Faster frequency for blinking effect */
+ movement_request_tick_frequency(4);
+ break;
+ }
+ refresh_face = true;
+}
+
+static bool mode_display(movement_event_t event, movement_settings_t *settings, world_clock2_state_t *state)
+{
+ char buf[11];
+ uint8_t pos;
+
+ uint32_t timestamp;
+ uint32_t previous_date_time;
+ watch_date_time date_time;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ case EVENT_TICK:
+ case EVENT_LOW_ENERGY_UPDATE:
+ /* Update indicators and colon on refresh */
+ if (refresh_face) {
+ watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
+ watch_set_colon();
+ if (settings->bit.clock_mode_24h)
+ watch_set_indicator(WATCH_INDICATOR_24H);
+
+ state->previous_date_time = REFRESH_TIME;
+ refresh_face = false;
+ }
+
+ /* Determine current time at time zone and store date/time */
+ date_time = watch_rtc_get_date_time();
+ timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60);
+ date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->current_zone] * 60);
+ previous_date_time = state->previous_date_time;
+ state->previous_date_time = date_time.reg;
+
+ if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
+ /* Everything before seconds is the same, don't waste cycles setting those segments. */
+ pos = 8;
+ sprintf(buf, "%02d", date_time.unit.second);
+ } else if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) {
+ /* Everything before minutes is the same. */
+ pos = 6;
+ sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second);
+ } else {
+ /* Other stuff changed; Let's do it all. */
+ if (!settings->bit.clock_mode_24h) {
+ /* If we are in 12 hour mode, do some cleanup. */
+ 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;
+ }
+
+ pos = 0;
+ if (event.event_type == EVENT_LOW_ENERGY_UPDATE) {
+ if (!watch_tick_animation_is_running())
+ watch_start_tick_animation(500);
+
+ sprintf(buf, "%.2s%2d%2d%02d ",
+ zone_names[state->current_zone],
+ date_time.unit.day,
+ date_time.unit.hour,
+ date_time.unit.minute);
+ } else {
+ sprintf(buf, "%.2s%2d%2d%02d%02d",
+ zone_names[state->current_zone],
+ date_time.unit.day,
+ date_time.unit.hour,
+ date_time.unit.minute,
+ date_time.unit.second);
+ }
+ }
+ watch_display_string(buf, pos);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ state->current_zone = find_selected_zone(state, FORWARD);
+ state->previous_date_time = REFRESH_TIME;
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ /* Do nothing. */
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ state->current_zone = find_selected_zone(state, BACKWARD);
+ state->previous_date_time = REFRESH_TIME;
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ movement_illuminate_led();
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ /* Switch to settings mode */
+ state->current_mode = WORLD_CLOCK2_MODE_SETTINGS;
+ refresh_face = true;
+ movement_request_tick_frequency(1);
+
+ if (settings->bit.button_should_sound)
+ watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
+ break;
+ case EVENT_MODE_BUTTON_UP:
+ /* Reset frequency and move to next face */
+ movement_request_tick_frequency(1);
+ movement_move_to_next_face();
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+static bool mode_settings(movement_event_t event, movement_settings_t *settings, world_clock2_state_t *state)
+{
+ char buf[11];
+ int8_t hours, minutes;
+ uint8_t zone;
+ div_t result;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ case EVENT_TICK:
+ case EVENT_LOW_ENERGY_UPDATE:
+ /* Update indicator and colon on refresh */
+ if (refresh_face) {
+ watch_clear_colon();
+ watch_clear_indicator(WATCH_INDICATOR_24H);
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ refresh_face = false;
+ }
+ result = div(movement_timezone_offsets[state->current_zone], 60);
+ hours = result.quot;
+ minutes = result.rem;
+
+ /*
+ * Display time zone. The range of the parameters is reduced
+ * to avoid accidentally overflowing the buffer and to suppress
+ * corresponding compiler warnings.
+ */
+ sprintf(buf, "%.2s%2d %c%02d%02d",
+ zone_names[state->current_zone],
+ state->current_zone % 100,
+ hours < 0 ? '-' : '+',
+ abs(hours) % 24,
+ abs(minutes) % 60);
+
+ /* Let the zone number blink */
+ if (event.subsecond % 2)
+ buf[2] = buf[3] = ' ';
+
+ if (state->zones[state->current_zone].selected)
+ watch_set_indicator(WATCH_INDICATOR_SIGNAL);
+ else
+ watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
+
+ watch_display_string(buf, 0);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ state->current_zone = mod(state->current_zone + FORWARD, NUM_TIME_ZONES);
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ state->current_zone = mod(state->current_zone + BACKWARD, NUM_TIME_ZONES);
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ /* Do nothing */
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ /* Find next selected zone */
+ if (!state->zones[state->current_zone].selected)
+ state->current_zone = find_selected_zone(state, FORWARD);
+
+ /* Switch to display mode */
+ state->current_mode = WORLD_CLOCK2_MODE_DISPLAY;
+ refresh_face = true;
+ movement_request_tick_frequency(1);
+
+ if (settings->bit.button_should_sound)
+ watch_buzzer_play_note(BUZZER_NOTE_C8, 50);
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ /* Toggle selection of current zone */
+ zone = state->current_zone;
+ state->zones[zone].selected = !state->zones[zone].selected;
+
+ if (settings->bit.button_should_sound) {
+ if (state->zones[zone].selected) {
+ beep_enable();
+ } else {
+ beep_disable();
+ }
+ }
+ break;
+ case EVENT_MODE_BUTTON_UP:
+ /* Reset frequency and move to next face */
+ movement_request_tick_frequency(1);
+ movement_move_to_next_face();
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+bool world_clock2_face_loop(movement_event_t event, movement_settings_t *settings, void *context)
+{
+ world_clock2_state_t *state = (world_clock2_state_t *) context;
+ switch (state->current_mode) {
+ case WORLD_CLOCK2_MODE_DISPLAY:
+ return mode_display(event, settings, state);
+ case WORLD_CLOCK2_MODE_SETTINGS:
+ return mode_settings(event, settings, state);
+ }
+ return false;
+}
+
+void world_clock2_face_resign(movement_settings_t *settings, void *context)
+{
+ (void) settings;
+ (void) context;
+}
diff --git a/movement/watch_faces/clock/world_clock2_face.h b/movement/watch_faces/clock/world_clock2_face.h
new file mode 100644
index 00000000..f70dca1f
--- /dev/null
+++ b/movement/watch_faces/clock/world_clock2_face.h
@@ -0,0 +1,63 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Konrad Rieck
+ * Copyright (c) 2022 Joey Castillo
+ *
+ * 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 WORLD_CLOCK2_FACE_H_
+#define WORLD_CLOCK2_FACE_H_
+
+/* Number of zones. See movement_timezone_offsets. */
+#define NUM_TIME_ZONES 41
+
+#include "movement.h"
+
+typedef enum {
+ WORLD_CLOCK2_MODE_DISPLAY,
+ WORLD_CLOCK2_MODE_SETTINGS
+} world_clock2_mode_t;
+
+typedef struct {
+ bool selected;
+} world_clock2_zone_t;
+
+typedef struct {
+ world_clock2_zone_t zones[NUM_TIME_ZONES];
+ world_clock2_mode_t current_mode;
+ uint8_t current_zone;
+ uint32_t previous_date_time;
+} world_clock2_state_t;
+
+void world_clock2_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr);
+void world_clock2_face_activate(movement_settings_t *settings, void *context);
+bool world_clock2_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void world_clock2_face_resign(movement_settings_t *settings, void *context);
+
+#define world_clock2_face ((const watch_face_t){ \
+ world_clock2_face_setup, \
+ world_clock2_face_activate, \
+ world_clock2_face_loop, \
+ world_clock2_face_resign, \
+ NULL, \
+})
+
+#endif /* WORLD_CLOCK2_FACE_H_ */
diff --git a/movement/watch_faces/complication/activity_face.c b/movement/watch_faces/complication/activity_face.c
new file mode 100644
index 00000000..f92984cd
--- /dev/null
+++ b/movement/watch_faces/complication/activity_face.c
@@ -0,0 +1,725 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Gabor L Ugray
+ *
+ * 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.
+ */
+
+/* ** TODO
+ * ===================
+ * -- Additional power-saving optimizations
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "activity_face.h"
+#include "chirpy_tx.h"
+#include "watch.h"
+#include "watch_utility.h"
+
+// ===========================================================================
+// This part is configurable: you can edit values here to customize you activity face
+// In particular, with num_enabled_activities and enabled_activities you can choose a subset of the
+// activities that you want to see in your watch.
+// You can also add new items to activity_names, but don't redefine or remove existing ones.
+
+// If a logged activity is shorter than this, then it won't be added to log when it ends.
+// This way scarce log slots are not taken up by aborted events that weren't real activities.
+static const uint16_t activity_min_length_sec = 60;
+
+// Supported activities. ID of activity is index in this buffer
+// W e should never change order or redefine items, only add new items when needed.
+static const char activity_names[][7] = {
+ " bIKE ",
+ "uuaLK ",
+ " rUn ",
+ "DAnCE ",
+ " yOgA ",
+ "CrOSS ",
+ "Suuinn",
+ "ELLIP ",
+ " gYnn",
+ " rOuu",
+ "SOCCEr",
+ " FOOTb",
+ " bALL ",
+ " SKI ",
+};
+
+// Currently enabled activities. This makes picking on first subface easier: why show activities you personally never do.
+static const uint8_t enabled_activities[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+
+// Number of currently enabled activities (size of enabled_activities).
+static const uint8_t num_enabled_activities = sizeof(enabled_activities) / sizeof(uint8_t);
+
+// End configurable section
+// ===========================================================================
+
+// One logged activity
+typedef struct __attribute__((__packed__)) {
+ // Activity's start time
+ watch_date_time start_time;
+
+ // Total duration of activity, including time spend in paus
+ uint16_t total_sec;
+
+ // Number of seconds the activity was paused
+ uint16_t pause_sec;
+
+ // Type of activity (index in activity_names)
+ uint8_t activity_type;
+
+} activity_item_t;
+
+#define MAX_ACTIVITY_SECONDS 28800 // 8 hours = 28800 sec
+
+// Size of (fixed) buffer to log activites. Takes up x9 bytes in SRAM if face is installed.
+#define ACTIVITY_LOG_SZ 99
+
+// Number of activities in buffer.
+static uint8_t activity_log_count = 0;
+
+// Buffer with all logged activities.
+static activity_item_t activity_log_buffer[ACTIVITY_LOG_SZ];
+
+#define CHIRPY_PREFIX_LEN 2
+// First two bytes chirped out, to identify transmission as from the activity face
+static const uint8_t activity_chirpy_prefix[CHIRPY_PREFIX_LEN] = {0x27, 0x00};
+
+// The face's different UI modes (views).
+typedef enum {
+ ACTM_CHOOSE = 0,
+ ACTM_LOGGING,
+ ACTM_PAUSED,
+ ACTM_DONE,
+ ACTM_LOGSIZE,
+ ACTM_CHIRP,
+ ACTM_CHIRPING,
+ ACTM_CLEAR,
+ ACTM_CLEAR_CONFIRM,
+ ACTM_CLEAR_DONE,
+} activity_mode_t;
+
+// The full state of the activity face
+typedef struct {
+ // Current mode (which secondary face, or ongoing operation like logging)
+ activity_mode_t mode;
+
+ // Index of currently selected activity in enabled_activities
+ uint8_t type_ix;
+
+ // Used for different things depending on mode
+ // In ACTM_DONE: countdown for animation, before returning to start face
+ // In ACTM_LOGGING and ACTM_PAUSED: drives blinking colon and alternating time display
+ // In ACTM_LOGSIZE, ACTM_CLEAR: enables timeout return to choose screen
+ uint16_t counter;
+
+ // Start of currently logged activity, if any
+ watch_date_time start_time;
+
+ // Total seconds elapsed since logging started
+ uint16_t curr_total_sec;
+
+ // Total paused seconds in current log
+ uint16_t curr_pause_sec;
+
+ // Helps us handle 1/64 ticks during transmission; including countdown timer
+ chirpy_tick_state_t chirpy_tick_state;
+
+ // Used by chirpy encoder during transmission
+ chirpy_encoder_state_t chirpy_encoder_state;
+
+ // 0: Running normally
+ // 1: In LE mode
+ // 2: Just woke up from LE mode. Will go to 0 after ignoring ALARM_BUTTON_UP.
+ uint8_t le_state;
+
+} activity_state_t;
+
+#define ACTIVITY_BUF_SZ 14
+
+// Temp buffer used for sprintf'ing content for the display.
+char activity_buf[ACTIVITY_BUF_SZ];
+
+// Needed by _activity_get_next_byte to keep track of where we are in transmission
+uint16_t *activity_seq_pos;
+
+static void _activity_clear_buffers() {
+ // Clear activity buffer; 0xcd is good for diagnostics
+ memset(activity_log_buffer, 0xcd, ACTIVITY_LOG_SZ * sizeof(activity_item_t));
+ // Clear display buffer
+ memset(activity_buf, 0, ACTIVITY_BUF_SZ);
+}
+
+static void _activity_display_choice(activity_state_t *state);
+static void _activity_update_logging_screen(movement_settings_t *settings, activity_state_t *state);
+static uint8_t _activity_get_next_byte(uint8_t *next_byte);
+
+void activity_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(activity_state_t));
+ memset(*context_ptr, 0, sizeof(activity_state_t));
+ // This happens only at boot
+ _activity_clear_buffers();
+ }
+ // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
+}
+
+void activity_face_activate(movement_settings_t *settings, void *context) {
+ (void)settings;
+ (void)context;
+
+ // Not using this function. Calling _activity_activate from the event handler.
+ // That is what we get both when the face is shown upon navigation by MODE,
+ // and when waking from low energy state.
+}
+
+// Called from the ACTIVATE event handler in the loop
+static void _activity_activate(movement_settings_t *settings, activity_state_t *state) {
+ // If waking from low-energy state and currently logging: update seconds values
+ // Those are not up-to-date because ticks have not been coming
+ if (state->le_state != 0 && state->mode == ACTM_LOGGING) {
+ state->le_state = 2;
+ 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_time, 0);
+ uint32_t total_seconds = now_timestamp - start_timestamp;
+ state->curr_total_sec = total_seconds;
+ _activity_update_logging_screen(settings, state);
+ }
+ // Regular activation: start from defaults
+ else {
+ state->le_state = 0;
+ state->mode = 0;
+ state->type_ix = 0;
+ _activity_display_choice(state);
+ }
+}
+
+static void _activity_display_choice(activity_state_t *state) {
+ watch_display_string("AC", 0);
+ // If buffer is full: We say "FULL"
+ if (activity_log_count >= ACTIVITY_LOG_SZ) {
+ watch_display_string(" FULL ", 4);
+ }
+ // Otherwise, we show currently activity
+ else {
+ uint8_t activity_ix = enabled_activities[state->type_ix];
+ const char *name = activity_names[activity_ix];
+ watch_display_string((char *)name, 4);
+ }
+}
+
+const uint8_t activity_anim_pixels[][2] = {
+ {1, 4}, // TL
+ {0, 5}, // BL
+ {0, 6}, // BOT
+ {1, 6}, // BR
+ {2, 5}, // TR
+ {2, 4}, // TOP
+ // {2, 4}, // MID
+};
+
+static void _activity_update_logging_screen(movement_settings_t *settings, activity_state_t *state) {
+ watch_duration_t duration;
+
+ watch_display_string("AC ", 0);
+
+ // If we're in LE state: per-minute update is special
+ if (state->le_state == 1) {
+ 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_time, 0);
+ uint32_t total_seconds = now_timestamp - start_timestamp;
+ duration = watch_utility_seconds_to_duration(total_seconds);
+ sprintf(activity_buf, " %d%02d ", duration.hours, duration.minutes);
+ watch_display_string(activity_buf, 4);
+ watch_set_colon();
+ watch_set_indicator(WATCH_INDICATOR_LAP);
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ watch_clear_indicator(WATCH_INDICATOR_24H);
+ return;
+ }
+
+ // Show elapsed time, or PAUSE
+ if ((state->counter % 5) < 3) {
+ watch_set_indicator(WATCH_INDICATOR_LAP);
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ watch_clear_indicator(WATCH_INDICATOR_24H);
+ if (state->mode == ACTM_PAUSED) {
+ watch_display_string(" PAUSE", 4);
+ watch_clear_colon();
+ } else {
+ duration = watch_utility_seconds_to_duration(state->curr_total_sec);
+ // Under 10 minutes: M:SS
+ if (state->curr_total_sec < 600) {
+ sprintf(activity_buf, " %01d%02d", duration.minutes, duration.seconds);
+ watch_display_string(activity_buf, 4);
+ watch_clear_colon();
+ }
+ // Under an hour: MM:SS
+ else if (state->curr_total_sec < 3600) {
+ sprintf(activity_buf, " %02d%02d", duration.minutes, duration.seconds);
+ watch_display_string(activity_buf, 4);
+ watch_clear_colon();
+ }
+ // Over an hour: H:MM:SS
+ // (We never go to two-digit hours; stop at 8)
+ else {
+ sprintf(activity_buf, " %d%02d%02d", duration.hours, duration.minutes, duration.seconds);
+ watch_display_string(activity_buf, 4);
+ watch_set_colon();
+ }
+ }
+ }
+ // Briefly, show time without seconds
+ else {
+ watch_clear_indicator(WATCH_INDICATOR_LAP);
+ watch_date_time now = watch_rtc_get_date_time();
+ uint8_t hour = now.unit.hour;
+ if (!settings->bit.clock_mode_24h) {
+ watch_clear_indicator(WATCH_INDICATOR_24H);
+ if (hour < 12)
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ else
+ watch_set_indicator(WATCH_INDICATOR_PM);
+ hour %= 12;
+ if (hour == 0) hour = 12;
+ }
+ else {
+ watch_set_indicator(WATCH_INDICATOR_24H);
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ }
+ sprintf(activity_buf, "%2d%02d ", hour, now.unit.minute);
+ watch_set_colon();
+ watch_display_string(activity_buf, 4);
+ }
+}
+
+static void _activity_quit_chirping() {
+ watch_clear_indicator(WATCH_INDICATOR_BELL);
+ watch_set_buzzer_off();
+ movement_request_tick_frequency(1);
+}
+
+static void _activity_chirp_tick_transmit(void *context) {
+ activity_state_t *state = (activity_state_t *)context;
+
+ uint8_t tone = chirpy_get_next_tone(&state->chirpy_encoder_state);
+ // Transmission over?
+ if (tone == 255) {
+ _activity_quit_chirping();
+ state->mode = ACTM_CHIRP;
+ state->counter = 0;
+ watch_display_string("AC CHIRP ", 0);
+ return;
+ }
+ uint16_t period = chirpy_get_tone_period(tone);
+ watch_set_buzzer_period(period);
+ watch_set_buzzer_on();
+}
+
+static void _activity_chirp_tick_countdown(void *context) {
+ activity_state_t *state = (activity_state_t *)context;
+
+ // Countdown over: start actual broadcast
+ if (state->chirpy_tick_state.seq_pos == 8 * 3) {
+ state->chirpy_tick_state.tick_compare = 3;
+ state->chirpy_tick_state.tick_count = 2; // tick_compare - 1, so it starts immediately
+ state->chirpy_tick_state.seq_pos = 0;
+ state->chirpy_tick_state.tick_fun = _activity_chirp_tick_transmit;
+ return;
+ }
+ // Sound or turn off buzzer
+ if ((state->chirpy_tick_state.seq_pos % 8) == 0) {
+ watch_set_buzzer_period(NotePeriods[BUZZER_NOTE_A5]);
+ watch_set_buzzer_on();
+ if (state->chirpy_tick_state.seq_pos == 0) {
+ watch_display_string(" --- ", 4);
+ } else if (state->chirpy_tick_state.seq_pos == 8) {
+ watch_display_string(" --", 5);
+ } else if (state->chirpy_tick_state.seq_pos == 16) {
+ watch_display_string(" -", 5);
+ }
+ } else if ((state->chirpy_tick_state.seq_pos % 8) == 1) {
+ watch_set_buzzer_off();
+ }
+ ++state->chirpy_tick_state.seq_pos;
+}
+
+static uint8_t _activity_get_next_byte(uint8_t *next_byte) {
+ uint16_t num_bytes = 2 + activity_log_count * sizeof(activity_item_t);
+ uint16_t pos = *activity_seq_pos;
+
+ // Init counter
+ if (pos == 0) {
+ sprintf(activity_buf, "%3d", activity_log_count);
+ watch_display_string(activity_buf, 5);
+ }
+
+ if (pos == num_bytes) {
+ return 0;
+ }
+ // Two-byte prefix
+ if (pos < 2) {
+ (*next_byte) = activity_chirpy_prefix[pos];
+ }
+ // Data
+ else {
+ pos -= 2;
+ uint16_t ix = pos / sizeof(activity_item_t);
+ const activity_item_t *itm = &activity_log_buffer[ix];
+ uint16_t ofs = pos % sizeof(activity_item_t);
+
+ // Update counter when starting new item
+ if (ofs == 0) {
+ sprintf(activity_buf, "%3d", activity_log_count - ix);
+ watch_display_string(activity_buf, 5);
+ }
+
+ // Do this the hard way, byte by byte, to avoid high/low endedness issues
+ // Higher order bytes first, is our serialization format
+ uint8_t val;
+ // watch_date_time start_time;
+ // uint16_t total_sec;
+ // uint16_t pause_sec;
+ // uint8_t activity_type;
+ if (ofs == 0)
+ val = (itm->start_time.reg & 0xff000000) >> 24;
+ else if (ofs == 1)
+ val = (itm->start_time.reg & 0x00ff0000) >> 16;
+ else if (ofs == 2)
+ val = (itm->start_time.reg & 0x0000ff00) >> 8;
+ else if (ofs == 3)
+ val = (itm->start_time.reg & 0x000000ff);
+ else if (ofs == 4)
+ val = (itm->total_sec & 0xff00) >> 8;
+ else if (ofs == 5)
+ val = (itm->total_sec & 0x00ff);
+ else if (ofs == 6)
+ val = (itm->pause_sec & 0xff00) >> 8;
+ else if (ofs == 7)
+ val = (itm->pause_sec & 0x00ff);
+ else
+ val = itm->activity_type;
+ (*next_byte) = val;
+ }
+ ++(*activity_seq_pos);
+ return 1;
+}
+
+static void _activity_finish_logging(activity_state_t *state) {
+ // Save this activity
+ // If shorter than minimum for log: don't save
+ // Sanity check about buffer length. This should never happen, but also we never want to overrun by error
+ if (state->curr_total_sec >= activity_min_length_sec && activity_log_count + 1 < ACTIVITY_LOG_SZ) {
+ activity_item_t *itm = &activity_log_buffer[activity_log_count];
+ itm->start_time = state->start_time;
+ itm->total_sec = state->curr_total_sec;
+ itm->pause_sec = state->curr_pause_sec;
+ itm->activity_type = state->type_ix;
+ ++activity_log_count;
+ }
+
+ // Go to DONE animation
+ // TODO: Not in LE mode
+ state->mode = ACTM_DONE;
+ watch_clear_indicator(WATCH_INDICATOR_LAP);
+ movement_request_tick_frequency(2);
+ state->counter = 6 * 1;
+ watch_clear_display();
+ watch_display_string("AC dONE ", 0);
+}
+
+static void _activity_handle_tick(movement_settings_t *settings, activity_state_t *state) {
+ // Display stopwatch-like duration while logging, alternating with time
+ if (state->mode == ACTM_LOGGING || state->mode == ACTM_PAUSED) {
+ ++state->counter;
+ ++state->curr_total_sec;
+ if (state->mode == ACTM_PAUSED)
+ ++state->curr_pause_sec;
+ // If we've reached max activity length: finish logging
+ if (state->curr_total_sec == MAX_ACTIVITY_SECONDS) {
+ _activity_finish_logging(state);
+ }
+ // Still logging: refresh display
+ else {
+ _activity_update_logging_screen(settings, state);
+ }
+ }
+ // Display countown animation, and exit face when down
+ else if (state->mode == ACTM_DONE) {
+ if (state->counter == 0) {
+ movement_move_to_face(0);
+ movement_request_tick_frequency(1);
+ }
+ else {
+ uint8_t cd = state->counter % 6;
+ watch_clear_pixel(activity_anim_pixels[cd][0], activity_anim_pixels[cd][1]);
+ --state->counter;
+ cd = state->counter % 6;
+ watch_set_pixel(activity_anim_pixels[cd][0], activity_anim_pixels[cd][1]);
+ }
+ }
+ // Log size, chirp, clear: return to choose after some time
+ else if (state->mode == ACTM_LOGSIZE || state->mode == ACTM_CHIRP || state->mode == ACTM_CLEAR) {
+ ++state->counter;
+ // Leave Log Size after 20 seconds
+ // Leave Clear after only 10: this is danger zone, we don't like hanging around here
+ // Leave Chirp after 2 minutes: most likely need to the time to fiddle with mic & Chirpy RX on the computer
+ uint16_t timeout = 20;
+ if (state->mode == ACTM_CLEAR) timeout = 10;
+ else if (state->mode == ACTM_CHIRP) timeout = 120;
+ if (state->counter > timeout) {
+ state->mode = ACTM_CHOOSE;
+ _activity_display_choice(state);
+ }
+ }
+ // Chirping
+ else if (state->mode == ACTM_CHIRPING) {
+ ++state->chirpy_tick_state.tick_count;
+ if (state->chirpy_tick_state.tick_count == state->chirpy_tick_state.tick_compare) {
+ state->chirpy_tick_state.tick_count = 0;
+ state->chirpy_tick_state.tick_fun(state);
+ }
+ }
+ // Clear confirm: blink CLEAR
+ else if (state->mode == ACTM_CLEAR_CONFIRM) {
+ ++state->counter;
+ if ((state->counter % 2) == 0)
+ watch_display_string("CLEAR ", 4);
+ else
+ watch_display_string(" ", 4);
+ if (state->counter > 12) {
+ state->mode = ACTM_CHOOSE;
+ _activity_display_choice(state);
+ movement_request_tick_frequency(1);
+ }
+ }
+ // Clear done: fill up zeroes, then return to choose screen
+ else if (state->mode == ACTM_CLEAR_DONE) {
+ ++state->counter;
+ // Animation done
+ if (state->counter == 7) {
+ state->mode = ACTM_CHOOSE;
+ _activity_display_choice(state);
+ movement_request_tick_frequency(1);
+ return;
+ }
+ // Display current state of animation
+ sprintf(activity_buf, " ");
+ uint8_t nZeros = state->counter + 1;
+ if (nZeros > 6) nZeros = 6;
+ for (uint8_t i = 0; i < nZeros; ++i) {
+ activity_buf[i] = '0';
+ }
+ watch_display_string(activity_buf, 4);
+ }
+}
+
+static void _activity_alarm_long(movement_settings_t *settings, activity_state_t *state) {
+ // On choose face: start logging activity
+ if (state->mode == ACTM_CHOOSE) {
+ // If buffer is full: Ignore this long press
+ if (activity_log_count >= ACTIVITY_LOG_SZ)
+ return;
+ // OK, we go ahead and start logging
+ state->start_time = watch_rtc_get_date_time();
+ state->curr_total_sec = 0;
+ state->curr_pause_sec = 0;
+ state->counter = -1;
+ state->mode = ACTM_LOGGING;
+ watch_set_indicator(WATCH_INDICATOR_LAP);
+ _activity_update_logging_screen(settings, state);
+ }
+ // If logging or paused: end logging
+ else if (state->mode == ACTM_LOGGING || state->mode == ACTM_PAUSED) {
+ _activity_finish_logging(state);
+ }
+ // If chirp: kick off chirping
+ else if (state->mode == ACTM_CHIRP) {
+ // Set up our tick handling for countdown beeps
+ activity_seq_pos = &state->chirpy_tick_state.seq_pos;
+ state->chirpy_tick_state.tick_compare = 8;
+ state->chirpy_tick_state.tick_count = 7; // tick_compare - 1, so it starts immediately
+ state->chirpy_tick_state.seq_pos = 0;
+ state->chirpy_tick_state.tick_fun = _activity_chirp_tick_countdown;
+ // Set up chirpy encoder
+ chirpy_init_encoder(&state->chirpy_encoder_state, _activity_get_next_byte);
+ // Show bell; switch to 64/sec ticks
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ movement_request_tick_frequency(64);
+ state->mode = ACTM_CHIRPING;
+ }
+ // If clear: confirm (unless empty)
+ else if (state->mode == ACTM_CLEAR) {
+ if (activity_log_count == 0)
+ return;
+ state->mode = ACTM_CLEAR_CONFIRM;
+ state->counter = -1;
+ movement_request_tick_frequency(4);
+ }
+ // If clear confirm: do clear.
+ else if (state->mode == ACTM_CLEAR_CONFIRM) {
+ _activity_clear_buffers();
+ activity_log_count = 0;
+ state->mode = ACTM_CLEAR_DONE;
+ state->counter = -1;
+ watch_display_string("0 ", 4);
+ movement_request_tick_frequency(2);
+ }
+}
+
+static void _activity_alarm_short(movement_settings_t *settings, activity_state_t *state) {
+ // In the choose face, short ALARM cycles through activities
+ if (state->mode == ACTM_CHOOSE) {
+ state->type_ix = (state->type_ix + 1) % num_enabled_activities;
+ _activity_display_choice(state);
+ }
+ // If logging: pause
+ else if (state->mode == ACTM_LOGGING) {
+ state->mode = ACTM_PAUSED;
+ state->counter = 0;
+ _activity_update_logging_screen(settings, state);
+ }
+ // If paused: Update paused seconds count and return to logging
+ else if (state->mode == ACTM_PAUSED) {
+ state->mode = ACTM_LOGGING;
+ state->counter = 0;
+ _activity_update_logging_screen(settings, state);
+ }
+ // If chirping: stoppit
+ else if (state->mode == ACTM_CHIRPING) {
+ _activity_quit_chirping();
+ state->mode = ACTM_CHIRP;
+ state->counter = 0;
+ watch_display_string("AC CHIRP ", 0);
+ }
+}
+
+static void _activity_light_short(activity_state_t *state) {
+ // If choose face: move to log size
+ if (state->mode == ACTM_CHOOSE) {
+ state->mode = ACTM_LOGSIZE;
+ state->counter = 0;
+ sprintf(activity_buf, "AC L#g%3d", activity_log_count);
+ watch_display_string(activity_buf, 0);
+ }
+ // If log size face: move to chirp
+ else if (state->mode == ACTM_LOGSIZE) {
+ state->mode = ACTM_CHIRP;
+ state->counter = 0;
+ watch_display_string("AC CHIRP ", 0);
+ }
+ // If chirp face: move to clear
+ else if (state->mode == ACTM_CHIRP) {
+ state->mode = ACTM_CLEAR;
+ state->counter = 0;
+ watch_display_string("AC CLEAR ", 0);
+ }
+ // If clear face: return to choose face
+ else if (state->mode == ACTM_CLEAR || state->mode == ACTM_CLEAR_CONFIRM) {
+ state->mode = ACTM_CHOOSE;
+ _activity_display_choice(state);
+ movement_request_tick_frequency(1);
+ }
+ // While logging or paused, light is light
+ else if (state->mode == ACTM_LOGGING || state->mode == ACTM_PAUSED) {
+ movement_illuminate_led();
+ }
+ // Otherwise, we don't do light.
+}
+
+bool activity_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ activity_state_t *state = (activity_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ _activity_activate(settings, state);
+ break;
+ case EVENT_TICK:
+ _activity_handle_tick(settings, state);
+ break;
+ case EVENT_MODE_BUTTON_UP:
+ if (state->mode != ACTM_LOGGING && state->mode != ACTM_PAUSED && state->mode != ACTM_CHIRPING) {
+ movement_request_tick_frequency(1);
+ movement_move_to_next_face();
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ _activity_light_short(state);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ // We also receive ALARM press that woke us up from LE state
+ // Don't want to act on that as if it were a real button press for us
+ if (state->le_state != 2)
+ _activity_alarm_short(settings, state);
+ else
+ state->le_state = 0;
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ _activity_alarm_long(settings, state);
+ break;
+ case EVENT_TIMEOUT:
+ if (state->mode != ACTM_LOGGING && state->mode != ACTM_PAUSED &&
+ state->mode != ACTM_CHIRP && state->mode != ACTM_CHIRPING) {
+ movement_request_tick_frequency(1);
+ movement_move_to_face(0);
+ }
+ break;
+ case EVENT_LOW_ENERGY_UPDATE:
+ state->le_state = 1;
+ // If we're in paused logging mode: let's lose this activity. Pause is not meant for over an hour.
+ if (state->mode == ACTM_PAUSED) {
+ // When waking, face will revert to default screen
+ state->mode = ACTM_CHOOSE;
+ watch_display_string("AC SLEEP ", 0);
+ watch_clear_colon();
+ watch_clear_indicator(WATCH_INDICATOR_LAP);
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ }
+ else {
+ _activity_update_logging_screen(settings, state);
+ watch_start_tick_animation(500);
+ }
+ break;
+ default:
+ movement_default_loop_handler(event, settings);
+ break;
+ }
+
+ // Return true if the watch can enter standby mode. False needed when chirping.
+ if (state->mode == ACTM_CHIRPING)
+ return false;
+ else
+ return true;
+}
+
+void activity_face_resign(movement_settings_t *settings, void *context) {
+ (void)settings;
+ (void)context;
+
+ // Face should only ever temporarily request a higher frequency, so by the time we're resigning,
+ // this should not be needed. But we don't want an error to create a situation that drains the battery.
+ // Rather do this defensively here.
+ movement_request_tick_frequency(1);
+}
diff --git a/movement/watch_faces/complication/activity_face.h b/movement/watch_faces/complication/activity_face.h
new file mode 100644
index 00000000..c72f7099
--- /dev/null
+++ b/movement/watch_faces/complication/activity_face.h
@@ -0,0 +1,89 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Gabor L Ugray
+ *
+ * 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 ACTIVITY_FACE_H_
+#define ACTIVITY_FACE_H_
+
+#include "movement.h"
+
+/*
+ * ACTIVITY WATCH FACE
+ *
+ * The Activity face lets you record activities like you would do with a fitness watch.
+ * It supports different activities like running, biking, rowing etc., and for each recorded activity
+ * it stores when it started and how long it was.
+ *
+ * You can save up to 99 activities this way. Every once in a while you can chirp them out
+ * using the watch's piezo buzzer as a modem, then clear the log in the watch.
+ * To record and decode a chirpy transmission on your computer, you can use the web app here:
+ * https://jealousmarkup.xyz/off/chirpy/rx/
+ *
+ * Using the face
+ *
+ * When you activate the face, it starts with the first screen to select the activity you want to log.
+ * ALARM cycles through the list of activities.
+ * LONG ALARM starts logging.
+ * While logging is in progress, the face alternates between the elapsed time and the current time.
+ * You can press ALARM to pause (e.g., while you hop in to the baker's for a croissant during your jog).
+ * Pressing ALARM again resumes the activity.
+ * LONG ALARM stops logging and saves the activity.
+ *
+ * When you're not loggin, you can press LIGHT to access the secondary faces.
+ * LIGHT #1 => Shows the size of the log (how many activities have been recorded).
+ * LIGHT #2 => The screen to chirp out the data. Press LONG ALARM to start chirping.
+ * LIGHT #3 => The screen to clear the log in the watch. Press LONG ALARM twice to clear data.
+ *
+ * Quirky details
+ *
+ * The face will discard short activities (less than a minute) when you press LONG ALARM to finish logging.
+ * These were probably logged by mistake, and it's better to save slots and chirping battery power for
+ * stuff that really matters.
+ *
+ * The face will continue to record an activity past the normal one-hour mark, when the watch
+ * enters low energy mode. However, it will always stop at 8 hours. If an activity takes that long,
+ * you probably just forgot to stop logging.
+ *
+ * The log is stored in regular memory. It will be lost when you remove the battery, so make
+ * sure you chirp it out before taking the watch apart.
+ *
+ * See the top of activity_face.c for some customization options. What you most likely want to do
+ * is reduce the list of activities shown on the first screen to the ones you are regularly doing.
+ *
+ */
+
+void activity_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void activity_face_activate(movement_settings_t *settings, void *context);
+bool activity_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void activity_face_resign(movement_settings_t *settings, void *context);
+
+#define activity_face ((const watch_face_t){ \
+ activity_face_setup, \
+ activity_face_activate, \
+ activity_face_loop, \
+ activity_face_resign, \
+ NULL, \
+})
+
+#endif // ACTIVITY_FACE_H_
+
diff --git a/movement/watch_faces/complication/breathing_face.c b/movement/watch_faces/complication/breathing_face.c
new file mode 100644
index 00000000..8c1fdec7
--- /dev/null
+++ b/movement/watch_faces/complication/breathing_face.c
@@ -0,0 +1,208 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Bernd Plontsch
+ *
+ * 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 "breathing_face.h"
+#include "watch.h"
+
+typedef struct {
+ uint8_t current_stage;
+ bool sound_on;
+} breathing_state_t;
+
+static void beep_in (void);
+static void beep_in_hold (void);
+static void beep_out (void);
+static void beep_out_hold (void);
+
+void breathing_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+ // These next two lines just silence the compiler warnings associated with unused parameters.
+ // We have no use for the settings or the watch_face_index, so we make that explicit here.
+ (void) settings;
+ (void) watch_face_index;
+ // At boot, context_ptr will be NULL indicating that we don't have anyplace to store our context.
+ if (*context_ptr == NULL) {
+ // in this case, we allocate an area of memory sufficient to store the stuff we need to track.
+ *context_ptr = malloc(sizeof(breathing_state_t));
+ }
+}
+
+void breathing_face_activate(movement_settings_t *settings, void *context) {
+ // same as above: silence the warning, we don't need to check the settings.
+ (void) settings;
+ // we do however need to set some things in our context. Here we cast it to the correct type...
+ breathing_state_t *state = (breathing_state_t *)context;
+ // ...and set the initial state of our watch face.
+ state->current_stage = 0;
+ state->sound_on = true;
+}
+
+const int NOTE_LENGTH = 80;
+
+void beep_in (void) {
+ const BuzzerNote notes[] = {
+ BUZZER_NOTE_C4,
+ BUZZER_NOTE_D4,
+ BUZZER_NOTE_E4,
+ };
+ const uint16_t durations[] = {
+ NOTE_LENGTH,
+ NOTE_LENGTH,
+ NOTE_LENGTH
+ };
+ for(size_t i = 0, count = sizeof(notes) / sizeof(notes[0]); i < count; i++) {
+ watch_buzzer_play_note(notes[i], durations[i]);
+ }
+}
+
+void beep_in_hold (void) {
+ const BuzzerNote notes[] = {
+ BUZZER_NOTE_E4,
+ BUZZER_NOTE_REST,
+ BUZZER_NOTE_E4,
+ };
+ const uint16_t durations[] = {
+ NOTE_LENGTH,
+ NOTE_LENGTH * 2,
+ NOTE_LENGTH,
+ };
+ for(size_t i = 0, count = sizeof(notes) / sizeof(notes[0]); i < count; i++) {
+ watch_buzzer_play_note(notes[i], durations[i]);
+ }
+}
+
+void beep_out (void) {
+ const BuzzerNote notes[] = {
+ BUZZER_NOTE_E4,
+ BUZZER_NOTE_D4,
+ BUZZER_NOTE_C4,
+ };
+ const uint16_t durations[] = {
+ NOTE_LENGTH,
+ NOTE_LENGTH,
+ NOTE_LENGTH,
+ };
+ for(size_t i = 0, count = sizeof(notes) / sizeof(notes[0]); i < count; i++) {
+ watch_buzzer_play_note(notes[i], durations[i]);
+ }
+}
+
+void beep_out_hold (void) {
+ const BuzzerNote notes[] = {
+ BUZZER_NOTE_C4,
+ BUZZER_NOTE_REST * 2,
+ BUZZER_NOTE_C4,
+ };
+ const uint16_t durations[] = {
+ NOTE_LENGTH,
+ NOTE_LENGTH,
+ NOTE_LENGTH,
+ };
+ for(size_t i = 0, count = sizeof(notes) / sizeof(notes[0]); i < count; i++) {
+ watch_buzzer_play_note(notes[i], durations[i]);
+ }
+}
+
+bool breathing_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ (void) settings;
+ breathing_state_t *state = (breathing_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ case EVENT_TICK:
+
+ if (state->sound_on == true) {
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ } else {
+ watch_clear_indicator(WATCH_INDICATOR_BELL);
+ }
+
+ switch (state->current_stage)
+ {
+ case 0: { watch_display_string("Breath", 4); if (state->sound_on) beep_in(); } break;
+ case 1: watch_display_string("In 3", 4); break;
+ case 2: watch_display_string("In 2", 4); break;
+ case 3: watch_display_string("In 1", 4); break;
+
+ case 4: { watch_display_string("Hold 4", 4); if (state->sound_on) beep_in_hold(); } break;
+ case 5: watch_display_string("Hold 3", 4); break;
+ case 6: watch_display_string("Hold 2", 4); break;
+ case 7: watch_display_string("Hold 1", 4); break;
+
+ case 8: { watch_display_string("Ou t 4", 4); if (state->sound_on) beep_out(); } break;
+ case 9: watch_display_string("Ou t 3", 4); break;
+ case 10: watch_display_string("Ou t 2", 4); break;
+ case 11: watch_display_string("Ou t 1", 4); break;
+
+ case 12: { watch_display_string("Hold 4", 4); if (state->sound_on) beep_out_hold(); } break;
+ case 13: watch_display_string("Hold 3", 4); break;
+ case 14: watch_display_string("Hold 2", 4); break;
+ case 15: watch_display_string("Hold 1", 4); break;
+ default:
+ break;
+ }
+
+ // and increment it so that it will update on the next tick.
+ state->current_stage = (state->current_stage + 1) % 16;
+
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ state->sound_on = !state->sound_on;
+ if (state->sound_on == true) {
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ } else {
+ watch_clear_indicator(WATCH_INDICATOR_BELL);
+ }
+ break;
+ case EVENT_LOW_ENERGY_UPDATE:
+ // This low energy mode update occurs once a minute, if the watch face is in the
+ // foreground when Movement enters low energy mode. We have the option of supporting
+ // this mode, but since our watch face animates once a second, the "Hello there" face
+ // isn't very useful in this mode. So we choose not to support it. (continued below)
+ break;
+ case EVENT_TIMEOUT:
+ // ... Instead, we respond to the timeout event. This event happens after a configurable
+ // interval on screen (1-30 minutes). The watch will give us this event as a chance to
+ // resign control if we want to, and in this case, we do.
+ // This function will return the watch to the first screen (usually a simple clock),
+ // and it will do it long before the watch enters low energy mode. This ensures we
+ // won't be on screen, and thus opts us out of getting the EVENT_LOW_ENERGY_UPDATE above.
+
+ // movement_move_to_face(0);
+ break;
+ default:
+ movement_default_loop_handler(event, settings);
+ break;
+ }
+
+ return true;
+}
+
+void breathing_face_resign(movement_settings_t *settings, void *context) {
+ // our watch face, like most watch faces, has nothing special to do when resigning.
+ // watch faces that enable a peripheral or interact with a sensor may want to turn it off here.
+ (void) settings;
+ (void) context;
+}
diff --git a/movement/watch_faces/complication/breathing_face.h b/movement/watch_faces/complication/breathing_face.h
new file mode 100644
index 00000000..2ff947a2
--- /dev/null
+++ b/movement/watch_faces/complication/breathing_face.h
@@ -0,0 +1,43 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Bernd Plontsch
+ *
+ * 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 BREATHING_FACE_H_
+#define BREATHING_FACE_H_
+
+#include "movement.h"
+
+void breathing_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void breathing_face_activate(movement_settings_t *settings, void *context);
+bool breathing_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void breathing_face_resign(movement_settings_t *settings, void *context);
+
+#define breathing_face ((const watch_face_t){ \
+ breathing_face_setup, \
+ breathing_face_activate, \
+ breathing_face_loop, \
+ breathing_face_resign, \
+ NULL, \
+})
+
+#endif // BREATHING_FACE_H_
diff --git a/movement/watch_faces/complication/day_one_face.c b/movement/watch_faces/complication/day_one_face.c
index 7ce43bfa..25ce1c23 100644
--- a/movement/watch_faces/complication/day_one_face.c
+++ b/movement/watch_faces/complication/day_one_face.c
@@ -33,7 +33,7 @@ static uint32_t _day_one_face_juliandaynum(uint16_t year, uint16_t month, uint16
}
static void _day_one_face_update(day_one_state_t state) {
- char buf[14];
+ char buf[15];
watch_date_time date_time = watch_rtc_get_date_time();
uint32_t julian_date = _day_one_face_juliandaynum(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day);
uint32_t julian_birthdate = _day_one_face_juliandaynum(state.birth_year, state.birth_month, state.birth_day);
diff --git a/movement/watch_faces/complication/discgolf_face.c b/movement/watch_faces/complication/discgolf_face.c
new file mode 100644
index 00000000..0852bf1f
--- /dev/null
+++ b/movement/watch_faces/complication/discgolf_face.c
@@ -0,0 +1,326 @@
+#include <stdlib.h>
+#include <string.h>
+#include "discgolf_face.h"
+#include "watch.h" // Remember to change number of courses in this file
+#include "watch_utility.h"
+
+/*
+ * Keep track of scores in discgolf or golf!
+ * The watch face operates in three different modes:
+ *
+ * - dg_setting: Select a course
+ * Enter this mode by holding down the light button. The screen will display
+ * the label for the hole and the lowest score since last boot.
+ * Press alarm to loop through the holes. Press the light button to make a
+ * selection. This will reset all scores and start a new game in dg_idle mode.
+ *
+ * -dg_idle: We're playing a hole
+ * This either shows your current score relative to par, or the score for a
+ * particular hole.
+ * At the start of a game, press alarm to loop through the holes and leave it
+ * your starting hole. For optimal experience, play the course linearly after that
+ * If you're viewing the hole you're supposed to be playing, the watch face will
+ * display your score relative to par.
+ * Use the alarm button to view other holes than the one you're playing, in which
+ * case the input score for that hole will be displayed, in case it needs changing.
+ * Long press the alarm button to snap back to currently playing hole.
+ * To input scores for a hole in this mode, press the light button.
+ *
+ * -dg_scoring: Input score for a hole
+ * In this mode, if the score is 0 (hasn't been entered during this round),
+ * it will blink, indicating we're in scoring mode. Press the alarm button
+ * to increment the score up until 15, in which case it loops back to 0.
+ * Press the light button to save the score for that hole, advance one hole
+ * if you're not editing an already input score, and returning to idle mode.
+ *
+ * When all scores have been entered, the LAP indicator turns on. At that point, if we enter
+ * dg_setting to select a course, the score for that round is evaluated against the current
+ * lowest score for that course, and saved if it is better.
+*/
+///////////////////////////////////////////////////////////////////////////////////////////////
+// Enter course data
+/* Initialize lowest scores with a high number */
+int8_t best[courses];
+
+static const uint8_t pars[][18] = {
+ { 3, 3, 4, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, // Grafarholt
+ { 3, 4, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3 }, // Gufunes
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Vífilsstaðir
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Dalvegur
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Laugardalur
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0 }, // Guðmundarlundur
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Víðistaðatún
+ { 3, 3, 3, 4, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Fossvogur
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Klambratún
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Seljahverfi
+ { 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Fella- og Hóla
+};
+static const uint8_t holes[] = { // Number of holes on each course
+ 18,
+ 18,
+ 10,
+ 10,
+ 10,
+ 10,
+ 9,
+ 9,
+ 9,
+ 9,
+ 9
+};
+/* Two-letter descriptive labels, second field can only be A, B, C, D, E, F, H, I, J, L, N, O, R, T, U and X. */
+static const char labels[][2] = {
+ { 'G', 'H' },
+ { 'G', 'N' },
+ { 'V', 'I' },
+ { 'D', 'V' },
+ { 'L', 'A' },
+ { 'G', 'L' },
+ { 'V', 'T' },
+ { 'F', 'V' },
+ { 'K', 'T' },
+ { 'S', 'E' },
+ { 'F', 'H' }
+};
+
+// End of course data
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+/* Beep function */
+static inline void beep(movement_settings_t *settings) {
+ if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50);
+}
+
+/* Prep for a new round */
+static inline void reset(discgolf_state_t *state) {
+ for (int i = 0; i < holes[state->course]; i++) {
+ state->scores[i] = 0;
+ }
+ state->hole = 1;
+ watch_clear_indicator(WATCH_INDICATOR_LAP);
+ return;
+}
+
+/* Total number of throws so far */
+static inline uint8_t score_sum(discgolf_state_t *state) {
+ uint8_t sum = 0;
+ for (int i = 0; i < holes[state->course]; i++) {
+ sum = sum + state->scores[i];
+ }
+ return sum;
+}
+
+/* Count how many holes have been played */
+static inline uint8_t count_played(discgolf_state_t *state) {
+ uint8_t holes_played = 0;
+ for (int i = 0; i < holes[state->course]; i++) {
+ if (state->scores[i] > 0) holes_played++;
+ }
+ return holes_played;
+}
+
+
+/* Calculate the current score relative to par */
+static inline int8_t calculate_score(discgolf_state_t *state) {
+ uint8_t par_sum = 0;
+ uint8_t score_sum = 0;
+
+ for (int i = 0; i < holes[state->course]; i++) {
+ if (state->scores[i] > 0) {
+ par_sum = par_sum + pars[state->course][i];
+ score_sum = score_sum + state->scores[i];
+ }
+ }
+ return score_sum - par_sum;
+}
+
+/* Store score if it's the best so far */
+static inline void store_best(discgolf_state_t *state) {
+ uint8_t played = count_played(state);
+ if ( played == holes[state->course] ) {
+ int8_t high_score = calculate_score(state);
+ if (high_score < best[state->course] ) {
+ best[state->course] = high_score;
+ }
+ }
+}
+
+/* Configuration at boot, the high score array can be initialized with your high scores if they're known */
+void discgolf_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(discgolf_state_t));
+ discgolf_state_t *state = (discgolf_state_t *)*context_ptr;
+ memset(*context_ptr, 0, sizeof(discgolf_state_t));
+ state->hole = 1;
+ state->course = 0;
+ state->playing = holes[state->course] + 1;
+ for (int i = 0; i < courses; i++) best[i] = 99;
+ state->mode = dg_setting;
+ }
+}
+
+void discgolf_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ watch_clear_colon();
+ discgolf_state_t *state = (discgolf_state_t *)context;
+
+ /* If we were playing, go to current hole */
+ if (state->playing <= holes[0]) {
+ state->hole = state->playing;
+ }
+ /* Set LAP if round finished */
+ if (count_played(state) == holes[state->course] ) {
+ watch_set_indicator(WATCH_INDICATOR_LAP);
+ }
+ movement_request_tick_frequency(4);
+}
+
+bool discgolf_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ (void) settings;
+
+ discgolf_state_t *state = (discgolf_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_TIMEOUT:
+ /* Snap to first screen if we're not playing*/
+ if ( count_played(state) == holes[state->course] || state->mode == dg_setting) {
+ movement_move_to_face(0);
+ }
+ /* Cancel scoring if timed out */
+ if (state->mode == dg_scoring) {
+ state->scores[state->hole] = 0;
+ state->mode = dg_idle;
+ }
+ break;
+ /* Advance if not scoring */
+ case EVENT_MODE_BUTTON_UP:
+ if ( state->mode != dg_scoring ) {
+ movement_move_to_next_face();
+ }
+ break;
+ /* Go to default face if not scoring */
+ case EVENT_MODE_LONG_PRESS:
+ if ( state->mode != dg_scoring ) {
+ movement_move_to_face(0);
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ switch ( state->mode ) {
+ case dg_idle:
+ /* Check if selected hole is the first one */
+ if ( score_sum(state) == 0 ) {
+ state->playing = state->hole;
+ }
+ /* Enter scoring mode */
+ state->mode = dg_scoring;
+ break;
+ case dg_scoring:
+ /* Set the LAP indicator if all scores are entered */
+ if (count_played(state) == holes[state->course] ) {
+ watch_set_indicator(WATCH_INDICATOR_LAP);
+ }
+ /* Advance to next hole if not editing previously set score */
+ if ( state->hole == state->playing ) {
+ if (state->hole < holes[state->course]) state->hole++;
+ else state->hole = 1;
+ if (state->playing < holes[state->course]) state->playing++;
+ else state->playing = 1;
+ }
+ /* Return to idle */
+ state->mode = dg_idle;
+ break;
+ case dg_setting:
+ /* Return to idle */
+ state->playing = holes[state->course] + 1;
+ state->mode = dg_idle;
+ break;
+ }
+ beep(settings);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ switch (state->mode) {
+ /* Setting, loop through courses */
+ case dg_setting:
+ state->course = (state->course + 1) % courses;
+ break;
+ /* Scoring, increment score for current hole */
+ case dg_scoring:
+ state->scores[state->hole - 1] = (state->scores[state->hole - 1] + 1) % 16; // Loop around at 15
+ break;
+ /* Idle, loop through holes */
+ case dg_idle:
+ if (state->hole < holes[state->course]) {
+ state->hole++;
+ } else { state->hole = 1; }
+ break;
+ }
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ /* Enter setting mode, reset state */
+ if ( state->mode == dg_idle ) {
+ state->mode = dg_setting;
+ store_best(state);
+ reset(state);
+ beep(settings);
+ }
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ /* Snap back to currently playing hole if we've established one*/
+ if ( (state->mode == dg_idle) && (state->hole != state->playing) && (state->playing <= holes[state->course]) ) {
+ state->hole = state->playing;
+ beep(settings);
+ }
+ break;
+ default:
+ break;
+ }
+
+ char buf[21];
+ char prefix;
+ int8_t diff;
+
+ switch (state->mode) {
+ /* Setting mode, display course label and high score */
+ case dg_setting:
+ if ( best[state->course] < 0 ) {
+ prefix = '-';
+ } else { prefix = ' '; }
+ sprintf(buf, "%c%c %c%2d ", labels[state->course][0], labels[state->course][1], prefix, abs(best[state->course]));
+ break;
+ /* Idle, show relative or input score */
+ case dg_idle:
+ if (state->hole == state->playing) {
+ diff = calculate_score(state);
+ if ( diff < 0 ) {
+ prefix = '-';
+ } else { prefix = ' '; }
+ sprintf(buf, "%c%c%2d %c%2d ", labels[state->course][0], labels[state->course][1], state->hole, prefix, abs(diff));
+ } else {
+ sprintf(buf, "%c%c%2d %2d ", labels[state->course][0], labels[state->course][1], state->hole, state->scores[state->hole - 1]);
+ }
+ break;
+ /* Scoring, show set score */
+ case dg_scoring:
+ sprintf(buf, "%c%c%2d %2d ", labels[state->course][0], labels[state->course][1], state->hole, state->scores[state->hole - 1]);
+ break;
+ }
+
+ /* Blink during scoring */
+ if (event.subsecond % 2 && state->mode == dg_scoring) {
+ buf[6] = buf[7] = ' ';
+ }
+
+ /* Draw screen */
+ watch_display_string(buf, 0);
+
+ return true;
+}
+
+void discgolf_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+ watch_clear_indicator(WATCH_INDICATOR_LAP);
+}
diff --git a/movement/watch_faces/complication/discgolf_face.h b/movement/watch_faces/complication/discgolf_face.h
new file mode 100644
index 00000000..5e8068e0
--- /dev/null
+++ b/movement/watch_faces/complication/discgolf_face.h
@@ -0,0 +1,95 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Þorsteinn Jón Gautason
+ *
+ * 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.
+ */
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Keep track of scores in discgolf or golf!
+ * The watch face operates in three different modes:
+ *
+ * - dg_setting: Select a course
+ * Enter this mode by holding down the light button. The screen will display
+ * the label for the hole and the lowest score since last boot.
+ * Press alarm to loop through the holes. Press the light button to make a
+ * selection. This will reset all scores and start a new game in dg_idle mode.
+ *
+ * -dg_idle: We're playing a hole
+ * This either shows your current score relative to par, or the score for a
+ * particular hole.
+ * At the start of a game, press alarm to loop through the holes and leave it
+ * your starting hole. For optimal experience, play the course linearly after that
+ * If you're viewing the hole you're supposed to be playing, the watch face will
+ * display your score relative to par.
+ * Use the alarm button to view other holes than the one you're playing, in which
+ * case the input score for that hole will be displayed, in case it needs changing.
+ * Long press the alarm button to snap back to currently playing hole.
+ * To input scores for a hole in this mode, press the light button.
+ *
+ * -dg_scoring: Input score for a hole
+ * In this mode, if the score is 0 (hasn't been entered during this round),
+ * it will blink, indicating we're in scoring mode. Press the alarm button
+ * to increment the score up until 15, in which case it loops back to 0.
+ * Press the light button to save the score for that hole, advance one hole
+ * if you're not editing an already input score, and returning to idle mode.
+ *
+ * When all scores have been entered, the LAP indicator turns on. At that point, if we enter
+ * dg_setting to select a course, the score for that round is evaluated against the current
+ * lowest score for that course, and saved if it is better.
+*/
+
+
+#ifndef DISCGOLF_FACE_H_
+#define DISCGOLF_FACE_H_
+
+#include "movement.h"
+#define courses 11
+
+typedef enum {
+ dg_setting, // We are selecting a course
+ dg_scoring, // We are inputting our score
+ dg_idle, // We have input our score and are playing a hole
+} discgolf_mode_t;
+
+typedef struct {
+ uint8_t course; // Index for course selection, from 0
+ uint8_t hole; // Index for current hole, from 1
+ uint8_t playing; // Current hole
+ int scores[18]; // Scores for each played hole
+ discgolf_mode_t mode; // Watch face mode
+} discgolf_state_t;
+
+void discgolf_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void discgolf_face_activate(movement_settings_t *settings, void *context);
+bool discgolf_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void discgolf_face_resign(movement_settings_t *settings, void *context);
+
+#define discgolf_face ((const watch_face_t){ \
+ discgolf_face_setup, \
+ discgolf_face_activate, \
+ discgolf_face_loop, \
+ discgolf_face_resign, \
+ NULL, \
+})
+
+#endif // DISCGOLF_FACE_H_
diff --git a/movement/watch_faces/complication/dual_timer_face.c b/movement/watch_faces/complication/dual_timer_face.c
new file mode 100644
index 00000000..f98c35b4
--- /dev/null
+++ b/movement/watch_faces/complication/dual_timer_face.c
@@ -0,0 +1,334 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ * Copyright (c) 2022 Andreas Nebinger
+ *
+ * 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 "dual_timer_face.h"
+#include "watch.h"
+#include "watch_utility.h"
+#include "watch_rtc.h"
+
+/*
+ * IMPORTANT: This watch face uses the same TC2 callback counter as the Stock Stopwatch
+ * watch-face. It works through calling a global handler function. The two watch-faces
+ * therefore can't coexist within the same firmware. If you want to compile this watch-face
+ * then you need to remove the line <../watch_faces/complication/stock_stopwatch_face.c \>
+ * from the Makefile.
+ */
+
+// FROM stock_stopwatch_face.c ////////////////////////////////////////////////
+// Copyright (c) 2022 Andreas Nebinger
+
+#if __EMSCRIPTEN__
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#else
+#include "../../../watch-library/hardware/include/saml22j18a.h"
+#include "../../../watch-library/hardware/include/component/tc.h"
+#include "../../../watch-library/hardware/hri/hri_tc_l22.h"
+#endif
+
+static const watch_date_time distant_future = {.unit = {0, 0, 0, 1, 1, 63}};
+static bool _is_running;
+static uint32_t _ticks;
+
+#if __EMSCRIPTEN__
+
+static long _em_interval_id = 0;
+
+void em_dual_timer_cb_handler(void *userData) {
+ // interrupt handler for emscripten 128 Hz callbacks
+ (void) userData;
+ _ticks++;
+}
+
+static void _dual_timer_cb_initialize() { }
+
+static inline void _dual_timer_cb_stop() {
+ emscripten_clear_interval(_em_interval_id);
+ _em_interval_id = 0;
+ _is_running = false;
+}
+
+static inline void _dual_timer_cb_start() {
+ // initiate 128 hz callback
+ _em_interval_id = emscripten_set_interval(em_dual_timer_cb_handler, (double)(1000/128), (void *)NULL);
+}
+
+#else
+
+static inline void _dual_timer_cb_start() {
+ // start the TC2 timer
+ hri_tc_set_CTRLA_ENABLE_bit(TC2);
+ _is_running = true;
+}
+
+static inline void _dual_timer_cb_stop() {
+ // stop the TC2 timer
+ hri_tc_clear_CTRLA_ENABLE_bit(TC2);
+ _is_running = false;
+}
+
+static void _dual_timer_cb_initialize() {
+ // setup and initialize TC2 for a 64 Hz interrupt
+ hri_mclk_set_APBCMASK_TC2_bit(MCLK);
+ hri_gclk_write_PCHCTRL_reg(GCLK, TC2_GCLK_ID, GCLK_PCHCTRL_GEN_GCLK3 | GCLK_PCHCTRL_CHEN);
+ _dual_timer_cb_stop();
+ hri_tc_write_CTRLA_reg(TC2, TC_CTRLA_SWRST);
+ hri_tc_wait_for_sync(TC2, TC_SYNCBUSY_SWRST);
+ hri_tc_write_CTRLA_reg(TC2, TC_CTRLA_PRESCALER_DIV64 | // 32 Khz divided by 64 divided by 4 results in a 128 Hz interrupt
+ TC_CTRLA_MODE_COUNT8 |
+ TC_CTRLA_RUNSTDBY);
+ hri_tccount8_write_PER_reg(TC2, 3);
+ hri_tc_set_INTEN_OVF_bit(TC2);
+ NVIC_ClearPendingIRQ(TC2_IRQn);
+ NVIC_EnableIRQ (TC2_IRQn);
+}
+
+// you need to take stock_stopwatch.c out of the Makefile or this will create a conflict
+// you have to choose between one of the stopwatches
+ void TC2_Handler(void) {
+ // interrupt handler for TC2 (globally!)
+ _ticks++;
+ TC2->COUNT8.INTFLAG.reg |= TC_INTFLAG_OVF;
+}
+
+#endif
+
+// STATIC FUNCTIONS ///////////////////////////////////////////////////////////
+
+/** @brief converts tick counts to duration struct for time display
+ */
+static dual_timer_duration_t ticks_to_duration(uint32_t ticks) {
+ dual_timer_duration_t duration;
+ uint8_t hours = 0;
+ uint8_t days = 0;
+
+ // count hours and days
+ while (ticks >= (128 * 60 * 60)) {
+ ticks -= (128 * 60 * 60);
+ hours++;
+ if (hours >= 24) {
+ hours -= 24;
+ days++;
+ }
+ }
+
+ // convert minutes, seconds, centiseconds
+ duration.centiseconds = (ticks & 0x7F) * 100 / 128;
+ duration.seconds = (ticks >> 7) % 60;
+ duration.minutes = (ticks >> 7) / 60;
+ duration.hours = hours;
+ duration.days = days;
+
+ return duration;
+}
+
+/** @brief starts one of the dual timers
+ * @details Starts a dual timer. If no previous timer is running it starts the global
+ * tick counter. If a previous timer is already running it registers the current tick.
+ */
+static void start_timer(dual_timer_state_t *state, bool timer) {
+ // if it is not running yet, run it
+ if ( !_is_running ) {
+ _is_running = true;
+ movement_request_tick_frequency(16);
+ state->start_ticks[timer] = 0;
+ state->stop_ticks[timer] = 0;
+ _ticks = 0;
+ _dual_timer_cb_start();
+ movement_schedule_background_task(distant_future);
+ } else {
+ // if another timer is already running save the current tick
+ state->start_ticks[timer] = _ticks;
+ state->stop_ticks[timer] = _ticks;
+ }
+ state->running[timer] = true;
+}
+
+/** @brief stops one of the dual timers
+ * @details Stops a dual timer. If no other timer is running it stops the global
+ * tick counter. If another timer is already running it registers the current stop tick.
+ */
+static void stop_timer(dual_timer_state_t *state, bool timer) {
+ // stop timer and save duration
+ state->stop_ticks[timer] = _ticks;
+ state->duration[timer] = ticks_to_duration(state->stop_ticks[timer] - state->start_ticks[timer]);
+ state->running[timer] = false;
+ // if the other timer is not running, stop callback
+ if ( state->running[!timer] == false ) {
+ _is_running = false;
+ _dual_timer_cb_stop();
+ movement_request_tick_frequency(1);
+ movement_cancel_background_task();
+ }
+}
+
+/** @brief displays the measured time for each of the dual timers
+ * @details displays the dual timer. Below 1 hour it displays the timed minutes, seconds,
+ * and centiseconds. Above that it shows the timed hours, minutes, and seconds. If it
+ * has run for more than a day it shows the days, hours, and minutes.
+ * When the timer is running, the colon blinks every half second.
+ * It also indicates at the top if another counter is running and for how long.
+ */
+static void dual_timer_display(dual_timer_state_t *state) {
+ char buf[11];
+ char oi[3];
+ // get the current time count of the selected counter
+ dual_timer_duration_t timer = state->running[state->show] ? ticks_to_duration(state->stop_ticks[state->show] - state->start_ticks[state->show]) : state->duration[state->show];
+ // get the current time count of the other counter
+ dual_timer_duration_t other = ticks_to_duration(state->stop_ticks[!state->show] - state->start_ticks[!state->show]);
+
+ if ( timer.days > 0 )
+ sprintf(buf, "%02u%02u%02u", timer.days, timer.hours, timer.minutes);
+ else if ( timer.hours > 0 )
+ sprintf(buf, "%02u%02u%02u", timer.hours, timer.minutes, timer.seconds);
+ else
+ sprintf(buf, "%02u%02u%02u", timer.minutes, timer.seconds, timer.centiseconds);
+ watch_display_string(buf, 4);
+
+ // which counter is displayed
+ watch_display_string(state->show ? "B" : "A", 0);
+
+ // indicate whether other counter is running
+ watch_display_string(state->running[!state->show] && (_ticks % 100) < 50 ? "+" : " ", 1);
+
+ // indicate for how long the other counter has been running
+ sprintf(oi, "%2u", other.days > 0 ? other.days : (other.hours > 0 ? other.hours : (other.minutes > 0 ? other.minutes : (other.seconds > 0 ? other.seconds : other.centiseconds))));
+ watch_display_string( (state->stop_ticks[!state->show] - state->start_ticks[!state->show]) > 0 ? oi : " ", 2);
+
+ // blink colon when running
+ if ( timer.centiseconds > 50 || !state->running[state->show] ) watch_set_colon();
+ else watch_clear_colon();
+}
+
+// PUBLIC WATCH FACE FUNCTIONS ////////////////////////////////////////////////
+
+void dual_timer_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(dual_timer_state_t));
+ memset(*context_ptr, 0, sizeof(dual_timer_state_t));
+ _ticks = 0;
+ }
+ if (!_is_running) {
+ _dual_timer_cb_initialize();
+ }
+}
+
+void dual_timer_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+ if (_is_running) {
+ movement_schedule_background_task(distant_future);
+ }
+}
+
+bool dual_timer_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ dual_timer_state_t *state = (dual_timer_state_t *)context;
+
+ // timers stop at 99:23:59:59:99
+ if ( (_ticks - state->start_ticks[0]) >= 1105919999 )
+ stop_timer(state, 0);
+
+ if ( (_ticks - state->start_ticks[1]) >= 1105919999 )
+ stop_timer(state, 1);
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ watch_set_colon();
+ if (_is_running) {
+ movement_request_tick_frequency(16);
+ if ( state->running[0] )
+ state->show = 0;
+ else state->show = 1;
+ } else {
+ if (state->stop_ticks[0] > 0 || state->stop_ticks[1] > 0)
+ dual_timer_display(state);
+ else watch_display_string("A 000000", 0);
+ }
+ break;
+ case EVENT_TICK:
+ if ( _is_running ) {
+ // update stop ticks
+ if ( state->running[0] )
+ state->stop_ticks[0] = _ticks;
+ if ( state->running[1] )
+ state->stop_ticks[1] = _ticks;
+ dual_timer_display(state);
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ // start/stop timer B
+ state->running[1] = !state->running[1];
+ if ( state->running[1] ) {
+ start_timer(state, 1);
+ } else {
+ stop_timer(state, 1);
+ }
+ break;
+ case EVENT_ALARM_BUTTON_DOWN:
+ // start/stop timer A
+ state->running[0] = !state->running[0];
+ if ( state->running[0] ) {
+ start_timer(state, 0);
+ } else {
+ stop_timer(state, 0);
+ }
+ break;
+ case EVENT_MODE_BUTTON_DOWN:
+ // switch between the timers
+ state->show = !state->show;
+ dual_timer_display(state);
+ break;
+ case EVENT_MODE_BUTTON_UP:
+ // don't switch to next face...
+ break;
+ case EVENT_MODE_LONG_PRESS:
+ // ...but do it on long press MODE!
+ movement_move_to_next_face();
+ break;
+ case EVENT_TIMEOUT:
+ // go back to
+ if (!_is_running) movement_move_to_face(0);
+ break;
+ case EVENT_LOW_ENERGY_UPDATE:
+ dual_timer_display(state);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void dual_timer_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+ movement_cancel_background_task();
+ // handle any cleanup before your watch face goes off-screen.
+}
+
diff --git a/movement/watch_faces/complication/dual_timer_face.h b/movement/watch_faces/complication/dual_timer_face.h
new file mode 100644
index 00000000..d7c6cfa0
--- /dev/null
+++ b/movement/watch_faces/complication/dual_timer_face.h
@@ -0,0 +1,111 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ * Copyright (c) 2022 Andreas Nebinger
+ *
+ * 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 DUAL_TIMER_FACE_H_
+#define DUAL_TIMER_FACE_H_
+
+#include "movement.h"
+
+/*
+ * IMPORTANT: This watch face uses the same TC2 callback counter as the Stock Stopwatch
+ * watch-face. It works through calling a global handler function. The two watch-faces
+ * therefore can't coexist within the same firmware. If you want to compile this watch-face
+ * then you need to remove the line <../watch_faces/complication/stock_stopwatch_face.c \>
+ * from the Makefile.
+ */
+
+/*
+ * DUAL TIMER
+ * ==========
+ *
+ * Inspired by special ops and tactical trope targeted watches like the Nixon Regulus
+ * that feature two chronographs for timing two simultaneous events, here is a watch
+ * face that expands upon Andreas Nebinger's Stock Stopwatch Face code to implement this
+ * functionality.
+ *
+ * ALARM starts/stops timer A, resets on the next start
+ * LIGHT starts/stops timer B, resets on the next start
+ *
+ * When a timer is running, tapping MODE toggles between displaying timers A or B, as
+ * indicated at the top of the display.
+ *
+ * The currently selected timer shows minutes, seconds, and 100ths of seconds until the
+ * timed event passes the hour mark. Then it shows hours, minutes, and seconds. Once
+ * it runs for more than a day it shows days, hours, and minutes. The blinking colon
+ * indicates that the timer is running.
+ *
+ * The longest time span the timers are able to track as 99 days and 23:59:59:99 hours and
+ * they will simply stop.
+ *
+ * If the other timer that is not currently selected is also running then a plus sign is
+ * blinking next to the A or B indicator. Its progress is indicated by showing the timer's
+ * currently highest time unit ( 100ths of seconds if less than a second, seconds if less
+ * than a minute, minutes if less than an hour, hours if less than a day, days if more than
+ * a day).
+ *
+ * Please Note: If at least one timer is running then the default function of the MODE
+ * button to move to the next watch face is disabled to be able to use it to toggle between
+ * the timers. In this case LONG PRESSING MODE will move to the next face instead of moving
+ * back to the default watch face.
+ *
+ */
+
+typedef struct {
+ uint8_t centiseconds : 7; // 0-59
+ uint8_t seconds : 6; // 0-59
+ uint8_t minutes : 6; // 0-59
+ uint8_t hours : 5; // 0-23
+ uint8_t days : 7; // 0-99
+} dual_timer_duration_t;
+
+typedef struct {
+ uint32_t start_ticks[2];
+ uint32_t stop_ticks[2];
+ dual_timer_duration_t duration[2];
+ bool running[2];
+ bool show;
+} dual_timer_state_t;
+
+void dual_timer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void dual_timer_face_activate(movement_settings_t *settings, void *context);
+bool dual_timer_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void dual_timer_face_resign(movement_settings_t *settings, void *context);
+
+#if __EMSCRIPTEN__
+void em_dual_timer_cb_handler(void *userData);
+#else
+void TC2_Handler(void);
+#endif
+
+#define dual_timer_face ((const watch_face_t){ \
+ dual_timer_face_setup, \
+ dual_timer_face_activate, \
+ dual_timer_face_loop, \
+ dual_timer_face_resign, \
+ NULL, \
+})
+
+#endif // DUAL_TIMER_FACE_H_
+
diff --git a/movement/watch_faces/complication/geomancy_face.c b/movement/watch_faces/complication/geomancy_face.c
new file mode 100644
index 00000000..c741b2d7
--- /dev/null
+++ b/movement/watch_faces/complication/geomancy_face.c
@@ -0,0 +1,363 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 "toss_up_face.h"
+#include "geomancy_face.h"
+
+// CONSTANTS //////////////////////////////////////////////////////////////////
+
+// The Bagua 八卦 Trigrams encoded as 3bit tribbles, represented as binary integer
+static const uint32_t bagua = 0b00000101001110010111011100000000;
+
+// The King Wen Sequence 文王卦序 of the I Ching 易經 Hexagrams 卦 encoded as an array
+// of decimal integers in the order of two combined Trigram tribbles from 0b000000 to
+// 0b111111
+static const uint8_t wen_order[] = {
+ 1, 22, 7, 19, 15, 34, 44, 11,
+ 14, 51, 38, 52, 61, 55, 30, 32,
+ 6, 3, 28, 58, 39, 63, 46, 5,
+ 45, 17, 47, 56, 31, 49, 27, 43,
+ 23, 26, 2, 41, 50, 20, 16, 24,
+ 35, 21, 62, 36, 54, 29, 48, 12,
+ 18, 40, 59, 60, 53, 37, 57, 9,
+ 10, 25, 4, 8, 33, 13, 42, 0
+};
+
+// The geomantic figures encoded as 4 bit nibbles, represented as hexadecimal integer
+static const uint64_t geomantic = 0x4ABF39D25E76C180;
+
+// Abbreviations of the Names of the Geomantic Figures in the order of the 4 bit nibbles
+// from 0b0000 to 0b1111
+static const char figures[16][2] = {
+ "VI" /* Via */, "Hd" /* Head of the Dragon */, "PA" /* Puella */, "GF" /* Greater Fortune*/,
+ "PR" /* Puer */, "AQ" /* Acquisitio */, "CA" /* Carcer */, "TR" /* Tristitia */,
+ "Td" /* Tail of the Dragon */, "CO" /* Conjunctio */, "AM" /* Amissio */, "AL" /* Albus */,
+ "LF" /* Lesser Fortune */, "RU" /* Rubeus */, "LA" /* Laetitia */, "PO" /* Populus */
+};
+
+// DECLARATIONS ///////////////////////////////////////////////////////////////
+
+static void geomancy_face_display();
+static nibble_t _geomancy_pick_figure();
+static tribble_t _iching_pick_trigram();
+static uint8_t _iching_form_hexagram();
+static void _geomancy_display(nibble_t code);
+static void _display_hexagram(uint8_t hexagram, char* str);
+static void _fix_broken_line(uint8_t hexagram);
+static void _throw_animation(geomancy_state_t *state);
+
+// WATCH FACE FUNCTIONS ///////////////////////////////////////////////////////
+
+void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+ (void) watch_face_index;
+ (void) settings;
+ if (*context_ptr == NULL) {
+ *context_ptr = malloc(sizeof(geomancy_state_t));
+ memset(*context_ptr, 0, sizeof(geomancy_state_t));
+ }
+}
+
+void geomancy_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ geomancy_state_t *state = (geomancy_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ state->animate = false;
+ state->animation = 0;
+ watch_display_string(" IChing", 0);
+ break;
+ case EVENT_TICK:
+ if ( state->animate ) {
+ state->animation = (state->animation + 1) % 39;
+ geomancy_face_display(state);
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ if ( state->animate ) break;
+ if ( state->mode <= 1 ) state->mode = 2;
+ else if ( state->mode >= 2 ) state->mode = 0;
+ geomancy_face_display(state);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ if ( state->animate ) break;
+ switch ( state->mode ) {
+ case 0:
+ state->mode++;
+ // fall through
+ case 1:
+ state->animate = true;
+ state->i_ching_hexagram = _iching_form_hexagram();
+ break;
+ case 2:
+ state->mode++;
+ // fall through
+ case 3:
+ state->animate = true;
+ state->geomantic_figure = _geomancy_pick_figure().bits;
+ break;
+ default:
+ break;
+ }
+ geomancy_face_display(state);
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ if ( state->animate ) break;
+ state->caption = !state->caption;
+ watch_display_string(" ", 0);
+ geomancy_face_display(state);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+ return true;
+}
+
+void geomancy_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+// STATIC FUNCTIONS ///////////////////////////////////////////////////////////
+
+/** @brief display handler */
+static void geomancy_face_display(geomancy_state_t *state);
+static void geomancy_face_display(geomancy_state_t *state) {
+ char token[7] = {0};
+ nibble_t figure = *((nibble_t*) &state->geomantic_figure);
+ switch ( state->mode ) {
+ case 0:
+ watch_display_string(" IChing", 0);
+ break;
+ case 1:
+ _throw_animation(state);
+ if ( !state->animate ) {
+ _display_hexagram(state->i_ching_hexagram, token);
+ watch_display_string(token, 4);
+ _fix_broken_line(state->i_ching_hexagram);
+ if (state->caption) {
+ sprintf(token, "%2d", wen_order[state->i_ching_hexagram] + 1);
+ watch_display_string(token, 2);
+ }
+ }
+ break;
+ case 2:
+ watch_display_string(" GeomCy", 0);
+ break;
+ case 3:
+ _throw_animation(state);
+ if ( !state->animate ) {
+ if ( state->caption ) {
+ sprintf(token, "%c%c", figures[state->geomantic_figure][0], figures[state->geomantic_figure][1]);
+ watch_display_string(token, 0);
+ }
+ _geomancy_display(figure);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/** @brief screen clearing animation between castings */
+static void _throw_animation(geomancy_state_t *state) {
+ movement_request_tick_frequency(16);
+ switch ( state->animation ) {
+ case 0:
+ watch_set_pixel(0, 22);
+ break;
+ case 1:
+ watch_set_pixel(2, 22);
+ watch_set_pixel(2, 23);
+ watch_clear_pixel(0, 22);
+ break;
+ case 2:
+ watch_set_pixel(1, 22);
+ watch_set_pixel(0, 23);
+ break;
+ case 3:
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ watch_set_pixel(2, 21);
+ watch_set_pixel(1, 21);
+ watch_clear_pixel(2, 22);
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ watch_clear_pixel(1, 23);
+ break;
+ case 4:
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ watch_set_pixel(2, 10);
+ watch_set_pixel(0, 1);
+ break;
+ case 5:
+ watch_clear_pixel(2, 21);
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(1, 20);
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ watch_clear_pixel(1, 1);
+ watch_clear_pixel(0, 0);
+ watch_clear_pixel(2, 1);
+ watch_set_pixel(2, 19);
+ watch_set_pixel(0, 19);
+ watch_set_pixel(1, 2);
+ watch_set_pixel(0, 2);
+ break;
+ case 6:
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(0, 1);
+ watch_set_pixel(2, 18);
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 3);
+ watch_set_pixel(0, 4);
+ break;
+ case 7:
+ watch_clear_pixel(2, 19);
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(1, 18);
+ watch_clear_pixel(1, 19);
+ watch_clear_pixel(1, 2);
+ watch_clear_pixel(0, 2);
+ watch_clear_pixel(1, 3);
+ watch_clear_pixel(0, 3);
+ watch_clear_pixel(2, 2);
+ watch_set_pixel(1, 4);
+ watch_set_pixel(0, 5);
+ break;
+ case 8:
+ watch_clear_pixel(2, 18);
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 3);
+ watch_clear_pixel(0, 4);
+ watch_set_pixel(2, 5);
+ watch_set_pixel(1, 6);
+ break;
+ case 9:
+ watch_clear_pixel(1, 4);
+ watch_clear_pixel(0, 5);
+ watch_clear_pixel(1, 5);
+ watch_clear_pixel(2, 4);
+ watch_clear_pixel(0, 6);
+ break;
+ case 10:
+ watch_clear_pixel(2, 5);
+ watch_clear_pixel(1, 6);
+ break;
+ case 11:
+ state->animate = false;
+ state->animation = 0;
+ movement_request_tick_frequency(1);
+ break;
+ }
+}
+
+// I CHING FUNCTIONS //////////////////////////////////////////////////////////
+
+/** @brief form a trigram from three random bit picks
+ */
+static tribble_t _iching_pick_trigram(void) {
+ uint8_t index = (divine_bit() << 2) | (divine_bit() << 1) | divine_bit();
+ tribble_t trigram = {(bagua >> (3 * index)) & 0b111};
+ return trigram;
+}
+
+/** @brief form a hexagram from two trigrams
+ */
+static uint8_t _iching_form_hexagram(void);
+static uint8_t _iching_form_hexagram(void) {
+ tribble_t inner = _iching_pick_trigram();
+ tribble_t outer = _iching_pick_trigram();
+ uint8_t hexagram = (inner.bits << 3) | outer.bits;
+ return hexagram;
+}
+
+/** @brief display hexagram
+ * @details | for unbroken lines and Ξ for broken lines, left of display is bottom
+ */
+static void _display_hexagram(uint8_t hexagram, char* str);
+static void _display_hexagram(uint8_t hexagram, char* str) {
+ str[6] = '\0'; // Null-terminate the string
+ for (uint8_t i = 0; i < 6; i++) {
+ if (hexagram & (1 << (5 - i))) {
+ str[i] = '1';
+ } else {
+ str[i] = '=';
+ }
+ }
+}
+
+/** @brief when Ξ digits show as = then manually add a line on top
+ */
+static void _fix_broken_line(uint8_t hexagram) {
+ for (uint8_t i = 0; i < 6; i++) {
+ if (!(hexagram & (1 << (5 - i)))) {
+ if ( i == 1 ) watch_set_pixel(2, 20);
+ if ( i == 3 ) watch_set_pixel(2, 1);
+ if ( i == 4 ) watch_set_pixel(2, 2);
+ if ( i == 5 ) watch_set_pixel(2, 4);
+ }
+ }
+}
+
+// GEOMANCY FUNCTIONS /////////////////////////////////////////////////////////
+
+/** @brief choose a geomantic figure from four random bits
+ * @details 0 represents · and 1 represents : counting from the bottom
+ */
+static nibble_t _geomancy_pick_figure(void);
+static nibble_t _geomancy_pick_figure(void) {
+ uint8_t index = (divine_bit() << 3) | (divine_bit() << 2) | (divine_bit() << 1) | divine_bit();
+ nibble_t figure = {(geomantic >> (4 * (15 - index))) & 0xF};
+ return figure;
+}
+
+/** @brief display the geomantic figure, left of display is bottom
+ */
+static void _geomancy_display(nibble_t code) {
+ // draw geomantic figures
+ bool row1 = (code.bits >> 3) & 1;
+ bool row2 = (code.bits >> 2) & 1;
+ bool row3 = (code.bits >> 1) & 1;
+ bool row4 = code.bits & 1;
+
+ if ( row1 ) watch_set_pixel(1, 18); else watch_set_pixel(1, 19);
+ if ( row2 ) { watch_set_pixel(2, 20); watch_set_pixel(0, 21);} else watch_set_pixel(1, 20);
+ if ( row3 ) watch_set_pixel(0, 22); else watch_set_pixel(1, 23);
+ if ( row4 ) { watch_set_pixel(2, 1); watch_set_pixel(0, 0);} else watch_set_pixel(1, 1);
+} \ No newline at end of file
diff --git a/movement/watch_faces/complication/geomancy_face.h b/movement/watch_faces/complication/geomancy_face.h
new file mode 100644
index 00000000..4a19ba85
--- /dev/null
+++ b/movement/watch_faces/complication/geomancy_face.h
@@ -0,0 +1,99 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 GEOMANCY_FACE_H_
+#define GEOMANCY_FACE_H_
+
+#include "movement.h"
+
+/*
+ * GEOMANCY WATCH FACE
+ *
+ * A simple and straightforward watch face for the ancient Eastern geomantic divination system
+ * of I Ching and the western system of "Geomancy". It is an optional addition to the Toss Up
+ * Face.
+ *
+ * The LIGHT button toggles between the two systems of geomancy.
+ *
+ * The ALARM button casts an I Ching hexagram or Geomantic figure based on drawing virtual
+ * stalks from the True Random Number Generator in the Sensor Watch.
+ *
+ * The figures are flipped 90 degrees clockwise, so the left side is the bottom and the
+ * right side the top.
+ *
+ * LONG PRESSING ALARM toggles the display of the King Wen sequence index for the cast I Ching
+ * Hexagram (https://en.wikipedia.org/wiki/King_Wen_sequence )or the abbreviated name for the
+ * cast Geomantic Figure:
+ *
+ * GF - Greater Fortune (Fortuna Major)
+ * LF - Lesser Fortune (Fortuna Minor)
+ * PO - Populus
+ * VI - Via
+ * AL - Albus
+ * CO - Conjunctio
+ * PA - Puella
+ * AM - Amissio
+ * PR - Puer
+ * RU - Rubeus
+ * AQ - Acquisitio
+ * LA - Laetitia
+ * TR - Tristitia
+ * CA - Carcer
+ * HD - Head of the Dragon (Caput Draconis)
+ * TD - Tail of the Dragon (Cauda Draconis)
+ *
+ */
+
+typedef struct {
+ uint8_t bits : 4;
+} nibble_t;
+
+typedef struct {
+ uint8_t bits : 3;
+} tribble_t;
+
+typedef struct {
+ uint8_t mode : 3;
+ uint8_t geomantic_figure;
+ uint8_t i_ching_hexagram : 6;
+ bool caption;
+ uint8_t animation;
+ bool animate;
+} geomancy_state_t;
+
+void geomancy_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void geomancy_face_activate(movement_settings_t *settings, void *context);
+bool geomancy_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void geomancy_face_resign(movement_settings_t *settings, void *context);
+
+#define geomancy_face ((const watch_face_t){ \
+ geomancy_face_setup, \
+ geomancy_face_activate, \
+ geomancy_face_loop, \
+ geomancy_face_resign, \
+ NULL, \
+})
+
+#endif // GEOMANCY_FACE_H_
+
diff --git a/movement/watch_faces/complication/interval_face.c b/movement/watch_faces/complication/interval_face.c
index e8ebe321..dabc6b1e 100644
--- a/movement/watch_faces/complication/interval_face.c
+++ b/movement/watch_faces/complication/interval_face.c
@@ -129,15 +129,15 @@ static const int8_t _sound_seq_break[] = {BUZZER_NOTE_B6, 15, BUZZER_NOTE_REST,
static const int8_t _sound_seq_cooldown[] = {BUZZER_NOTE_C7, 15, BUZZER_NOTE_REST, 1, -2, 1, BUZZER_NOTE_C7, 24, 0};
static const int8_t _sound_seq_finish[] = {BUZZER_NOTE_C7, 6, BUZZER_NOTE_E7, 6, BUZZER_NOTE_G7, 6, BUZZER_NOTE_C8, 18, 0};
-interval_setting_idx_t _setting_idx;
-int8_t _ticks;
-bool _erase_timer_flag;
-uint32_t _target_ts;
-uint32_t _now_ts;
-uint32_t _paused_ts;
-uint8_t _timer_work_round;
-uint8_t _timer_full_round;
-uint8_t _timer_run_state;
+static interval_setting_idx_t _setting_idx;
+static int8_t _ticks;
+static bool _erase_timer_flag;
+static uint32_t _target_ts;
+static uint32_t _now_ts;
+static uint32_t _paused_ts;
+static uint8_t _timer_work_round;
+static uint8_t _timer_full_round;
+static uint8_t _timer_run_state;
static inline void _inc_uint8(uint8_t *value, uint8_t step, uint8_t max) {
*value += step;
diff --git a/movement/watch_faces/complication/invaders_face.c b/movement/watch_faces/complication/invaders_face.c
new file mode 100644
index 00000000..c3b13c68
--- /dev/null
+++ b/movement/watch_faces/complication/invaders_face.c
@@ -0,0 +1,434 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Andreas Nebinger
+ *
+ * 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.
+ */
+
+// Emulator only: need time() to seed the random number generator
+#if __EMSCRIPTEN__
+#include <time.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "watch_private_display.h"
+#include "invaders_face.h"
+
+#define INVADERS_FACE_WAVES_PER_STAGE 9 // number of waves per stage (there are two stages)
+#define INVADERS_FACE_WAVE_INVADERS 16 // number of invaders attacking per wave
+
+static const uint8_t _defense_lines_segdata[3][2] = {{2, 12}, {2, 11}, {0, 11}};
+static const uint8_t _bonus_points_segdata[4][2] = {{2, 7}, {2, 8}, {2, 9}, {0, 10}};
+static const uint8_t _bonus_points_helper[] = {1, 5, 9, 11, 15, 19, 21, 25, 29};
+
+static const int8_t _sound_seq_game_start[] = {BUZZER_NOTE_A6, 1, BUZZER_NOTE_A7, 3, -2, 1, BUZZER_NOTE_REST, 10, BUZZER_NOTE_A6, 1, BUZZER_NOTE_A7, 3, -2, 1, 0};
+static const int8_t _sound_seq_shot_hit[] = {BUZZER_NOTE_A6, 1, BUZZER_NOTE_A7, 2, 0};
+static const int8_t _sound_seq_shot_miss[] = {BUZZER_NOTE_A7, 1, 0};
+static const int8_t _sound_seq_ufo_hit[] = {BUZZER_NOTE_A6, 1, BUZZER_NOTE_A7, 2, -2, 1, 0};
+static const int8_t _sound_seq_def_gone[] = {BUZZER_NOTE_A6, 1, BUZZER_NOTE_A7, 3, -2, 3, BUZZER_NOTE_REST, 40, BUZZER_NOTE_A6, 1, BUZZER_NOTE_A7, 3, -2, 4, 0};
+static const int8_t _sound_seq_next_wave[] = {BUZZER_NOTE_A6, 2, BUZZER_NOTE_A7, 2, BUZZER_NOTE_REST, 8, BUZZER_NOTE_A6, 2, BUZZER_NOTE_A7, 2, -2, 1,
+ BUZZER_NOTE_REST, 32,
+ BUZZER_NOTE_A6, 2, BUZZER_NOTE_A7, 2, BUZZER_NOTE_REST, 8, BUZZER_NOTE_A6, 2, BUZZER_NOTE_A7, 2, -2, 1, 0};
+static const int8_t _sound_seq_game_over[] = {BUZZER_NOTE_A6, 1, BUZZER_NOTE_A7, 3, -2, 11, 0};
+
+typedef enum {
+ invaders_state_activated,
+ invaders_state_pre_game,
+ invaders_state_playing,
+ invaders_state_in_wave_break,
+ invaders_state_pre_next_wave,
+ invaders_state_next_wave,
+ invaders_state_game_over
+} invaders_current_state_t;
+
+typedef struct {
+ bool ufo_next : 1; // indicates whether next invader is a ufo
+ bool inv_checking : 1; // flag to indicate whether we are currently moving invaders (to prevent race conditions)
+ bool suspend_buttons : 1; // used while playing the game over sequence to prevent involuntary immediate restarts
+} invaders_signals_t;
+
+static int8_t _invaders[6]; // array of current invaders values (-1 = empty, 10 = ufo)
+static uint8_t _wave_invaders[INVADERS_FACE_WAVE_INVADERS]; // all invaders for the current wave. (Predefined to save cpu cycles when playing.)
+static invaders_current_state_t _current_state;
+static uint8_t _defense_lines; // number of defense lines which have been broken in the current wave
+static uint8_t _aim; // current "aim" digit
+static uint8_t _invader_idx; // index of next invader attacking in current wave (0 to 15)
+static uint8_t _wave_position; // current position of first invader. When > 6 the defense is broken
+static uint8_t _wave_tick_freq; // number of ticks passing until the next invader is inserted
+static uint8_t _ticks; // counts the ticks
+static uint8_t _bonus_countdown; // ticks countdown until the bonus point indicator is cleared
+static uint8_t _waves; // counts the waves (_wave_tick_freq decreases slowly depending on _wave value)
+static uint8_t _shots_in_wave; // number of shots in current wave. If 30 is reached, the game is over
+static uint8_t _invaders_shot; // number of sucessfully shot invaders in current wave
+static uint8_t _invaders_shot_sum; // current sum of invader digits shot (needed to determine if a ufo is coming)
+static invaders_signals_t _signals; // holds severals flags
+static uint16_t _score; // score of the current game
+
+/// @brief return a random number. 0 <= return_value < num_values
+static inline uint8_t _get_rand_num(uint8_t num_values) {
+#if __EMSCRIPTEN__
+ return rand() % num_values;
+#else
+ return arc4random_uniform(num_values);
+#endif
+}
+
+/// @brief callback function to re-enable light and alarm buttons after playing a sound sequence
+static inline void _resume_buttons() {
+ _signals.suspend_buttons = false;
+}
+
+/// @brief play a sound sequence if the game is in beepy mode
+static inline void _play_sequence(invaders_state_t *state, int8_t *sequence) {
+ if (state->sound_on) watch_buzzer_play_sequence((int8_t *)sequence, NULL);
+}
+
+/// @brief draw the remaining defense lines
+static void _display_defense_lines() {
+ watch_display_character(' ', 1);
+ for (uint8_t i = 0; i < 3 - _defense_lines; i++) watch_set_pixel(_defense_lines_segdata[i][0], _defense_lines_segdata[i][1]);
+}
+
+/** @brief draw label followed by the given score value
+ * @param label string displayed in the upper left corner
+ * @param score score to display
+ */
+static void _display_score(char *label, uint16_t score) {
+ watch_display_character(label[0], 0);
+ watch_display_character(label[1], 1);
+ char buf[10];
+ sprintf(buf, " %06d", (score * 10));
+ watch_display_string(buf, 2);
+}
+
+/// @brief draw an invader at the given position
+static inline void _display_invader(int8_t invader, uint8_t position) {
+ switch (invader) {
+ case 10:
+ watch_display_character('n', position);
+ break;
+ case -1:
+ watch_display_character(' ', position);
+ break;
+ default:
+ watch_display_character(invader + 48, position);
+ break;
+ }
+}
+
+/// @brief game over: show score and set state
+static void _game_over(invaders_state_t *state) {
+ _display_score("GO", _score);
+ _current_state = invaders_state_game_over;
+ movement_request_tick_frequency(1);
+ _signals.suspend_buttons = true;
+ if (state->sound_on) watch_buzzer_play_sequence((int8_t *)_sound_seq_game_over, _resume_buttons);
+ // save current score to highscore, if applicable
+ if (_score > state->highscore) state->highscore = _score;
+}
+
+/// @brief initialize the current wave
+static void _init_wave() {
+ uint8_t i;
+ if (_current_state == invaders_state_in_wave_break) {
+ _invader_idx = _invaders_shot;
+ } else {
+ _invader_idx = _invaders_shot = _invaders_shot_sum = _defense_lines = _shots_in_wave = 0;
+ }
+ // pre-fill invaders
+ for (i = _invader_idx; i < INVADERS_FACE_WAVE_INVADERS; i++) _wave_invaders[i] = _get_rand_num(10);
+ // init invaders field
+ for (i = 1; i < 6; i++) _invaders[i] = -1;
+ _invaders[0] = _wave_invaders[_invader_idx];
+ _wave_position = _aim = _bonus_countdown = 0;
+ _signals.ufo_next = _signals.inv_checking = _signals.suspend_buttons = false;
+ _current_state = invaders_state_playing;
+ // determine wave speed
+ _wave_tick_freq = 6 - ((_waves % INVADERS_FACE_WAVES_PER_STAGE) + 1) / 2;
+ if (_waves >= INVADERS_FACE_WAVES_PER_STAGE) _wave_tick_freq--;
+ // clear display
+ watch_display_string(" ", 2);
+ watch_display_character('0', 0);
+ _display_defense_lines();
+ // draw first invader
+ watch_display_character(_wave_invaders[_invader_idx] + 48, 9);
+}
+
+/** @brief move invaders and add a new one, if necessary
+ * @returns true, if invaders have reached position 6, false otherwise
+ */
+static bool _move_invaders() {
+ if (_wave_position == 5) return true;
+ _signals.inv_checking = true;
+ if (_invaders[_wave_position] >= 0) _wave_position++;
+ int8_t i;
+ // move invaders
+ for (i = _wave_position; i > 0; i--) _invaders[i] = _invaders[i - 1];
+ if (_invader_idx < INVADERS_FACE_WAVE_INVADERS - 1) {
+ // add invader
+ _invader_idx++;
+ if (_signals.ufo_next) {
+ _invaders[0] = 10;
+ _signals.ufo_next = false;
+ } else {
+ _invaders[0] = _wave_invaders[_invader_idx];
+ }
+ } else {
+ // just add an empty invader slot
+ _invaders[0] = -1;
+ }
+ // update display
+ for (i = 0; i <= _wave_position; i++) {
+ _display_invader(_invaders[i], 9 - i);
+ }
+ _signals.inv_checking = false;
+ return false;
+}
+
+void invaders_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(invaders_state_t));
+ memset(*context_ptr, 0, sizeof(invaders_state_t));
+ invaders_state_t *state = (invaders_state_t *)*context_ptr;
+ // default: sound on
+ state->sound_on = true;
+ }
+#if __EMSCRIPTEN__
+ // simulator only: seed the randon number generator
+ time_t t;
+ srand((unsigned) time(&t));
+#endif
+}
+
+void invaders_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+ _current_state = invaders_state_activated;
+ _signals.suspend_buttons = false;
+}
+
+bool invaders_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ invaders_state_t *state = (invaders_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ // show highscore
+ _display_score("GA", state->highscore);
+ break;
+ case EVENT_TICK:
+ _ticks++;
+ switch (_current_state) {
+ case invaders_state_in_wave_break:
+ case invaders_state_pre_game:
+ case invaders_state_next_wave:
+ // wait 2 secs to start the first round
+ if (_ticks >= 2) {
+ _ticks = 0;
+ _init_wave();
+ _current_state = invaders_state_playing;
+ movement_request_tick_frequency(4);
+ }
+ break;
+ case invaders_state_playing:
+ // game is playing
+ if (_ticks >= _wave_tick_freq) {
+ _ticks = 0;
+ if (_move_invaders()) {
+ // invaders broke through
+ if (_defense_lines < 2) {
+ // start current wave over
+ _defense_lines++;
+ _display_defense_lines();
+ _display_score("GA", _score);
+ _current_state = invaders_state_in_wave_break;
+ movement_request_tick_frequency(1);
+ _play_sequence(state, (int8_t *)_sound_seq_def_gone);
+ } else {
+ // game over
+ _game_over(state);
+ }
+ }
+ }
+ // handle bonus points indicators
+ if (_bonus_countdown) {
+ _bonus_countdown--;
+ if (!_bonus_countdown) {
+ watch_display_character(' ', 2);
+ watch_display_character(' ', 3);
+ }
+ }
+ break;
+ case invaders_state_pre_next_wave:
+ if (_ticks >= 3) {
+ // switch to next wave
+ _ticks = 0;
+ movement_request_tick_frequency(1);
+ _display_score("GA", _score);
+ watch_set_pixel(1, 9);
+ watch_display_character((_waves % INVADERS_FACE_WAVES_PER_STAGE) + 49, 3);
+ _current_state = invaders_state_next_wave;
+ _waves++;
+ if (_waves == INVADERS_FACE_WAVES_PER_STAGE * 2) _waves = 0;
+ _play_sequence(state, (int8_t *)_sound_seq_next_wave);
+ }
+ default:
+ break;
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ if (!_signals.suspend_buttons) {
+ if (_current_state == invaders_state_playing) {
+ // cycle the aim
+ _aim = (_aim + 1) % 11;
+ _display_invader(_aim, 0);
+ } else if (_current_state == invaders_state_activated || _current_state == invaders_state_game_over) {
+ // just illuminate the LED
+ movement_illuminate_led();
+ }
+ }
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ if ((_current_state == invaders_state_activated || _current_state == invaders_state_game_over) && !_signals.suspend_buttons) {
+ // switch between beepy and silent mode
+ state->sound_on = !state->sound_on;
+ watch_buzzer_play_note(BUZZER_NOTE_A7, state->sound_on ? 65 : 25);
+ }
+ break;
+ case EVENT_ALARM_BUTTON_DOWN:
+ if (!_signals.suspend_buttons) {
+ switch (_current_state) {
+ case invaders_state_game_over:
+ case invaders_state_activated:
+ // initialize the game
+ _waves = 0;
+ _score = 0;
+ movement_request_tick_frequency(1);
+ _ticks = 0;
+ _current_state = invaders_state_pre_game;
+ _play_sequence(state, (int8_t *)_sound_seq_game_start);
+ break;
+ case invaders_state_playing: {
+ // "shoot"
+ _shots_in_wave++;
+ if (_shots_in_wave == 30) {
+ // max number of shots reached: game over
+ _game_over(state);
+ } else {
+ // wait if we are currently deleting an invader
+ while (_signals.inv_checking);
+ // proceed
+ _signals.inv_checking = true;
+ bool skip = false;
+ for (int8_t i = _wave_position; i >= 0 && !skip; i--) {
+ // if (_invaders[i] == -1) break;
+ if (_invaders[i] == _aim) {
+ // invader is shot
+ skip = true;
+ _invaders_shot++;
+ _play_sequence(state, _aim == 10 ? (int8_t *)_sound_seq_ufo_hit : (int8_t *)_sound_seq_shot_hit);
+ if (_invaders_shot == INVADERS_FACE_WAVE_INVADERS) {
+ // last invader shot: wave sucessfully completed
+ watch_display_character(' ', 9 - _wave_position);
+ _ticks = 0;
+ _current_state = invaders_state_pre_next_wave;
+ _signals.inv_checking = false;
+ } else {
+ // check for ufo appearance
+ if (_aim && _aim < 10) {
+ _invaders_shot_sum = (_invaders_shot_sum + _aim) % 10;
+ if (_invaders_shot_sum == 0) _signals.ufo_next = true;
+ }
+ // remove invader
+ if (_wave_position == 0 || i == 5) {
+ _invaders[i] = -1;
+ } else {
+ for (uint8_t j = i; j < _wave_position; j++) {
+ _invaders[j] = _invaders[j + 1];
+ _display_invader(_invaders[j], 9 - j);
+ }
+ }
+ watch_display_character(' ', 9 - _wave_position);
+ if (_wave_position) _wave_position--;
+ // update score
+ if (_aim == 10) {
+ // ufo shot. The original game uses a ridiculously complicated scoring system here...
+ uint8_t bonus_points = 0;
+ uint8_t j;
+ for (j = 0; j < sizeof(_bonus_points_helper) && !bonus_points; j++) {
+ if (_shots_in_wave == _bonus_points_helper[j]) {
+ bonus_points = 30;
+ } else if (_shots_in_wave - 1 == _bonus_points_helper[j]) {
+ bonus_points = 20;
+ }
+ }
+ if (!bonus_points) bonus_points = 10;
+ bonus_points += (6 - i);
+ if ((_waves >= INVADERS_FACE_WAVES_PER_STAGE) && i) bonus_points += (6 - i);
+ _score += bonus_points;
+ // represent bonus points by bars
+ for (j = 0; j < (bonus_points / 10); j++) watch_set_pixel(_bonus_points_segdata[j][0], _bonus_points_segdata[j][1]);
+ _bonus_countdown = 9;
+ } else {
+ // regular invader
+ _score += (6 - _wave_position) * (_waves >= INVADERS_FACE_WAVES_PER_STAGE ? 2 : 1);
+ }
+ }
+ }
+ }
+ if (!skip) _play_sequence(state, (int8_t *)_sound_seq_shot_miss);
+ _signals.inv_checking = false;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ break;
+ case EVENT_TIMEOUT:
+ movement_move_to_face(0);
+ 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 invaders_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+ _current_state = invaders_state_game_over;
+}
+
diff --git a/movement/watch_faces/complication/invaders_face.h b/movement/watch_faces/complication/invaders_face.h
new file mode 100644
index 00000000..59126dd5
--- /dev/null
+++ b/movement/watch_faces/complication/invaders_face.h
@@ -0,0 +1,82 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Andreas Nebinger
+ *
+ * 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 INVADERS_FACE_H_
+#define INVADERS_FACE_H_
+
+#include "movement.h"
+
+/*
+ * Remake of the "famous" Casio Number Invaders Game
+ *
+ * This is an authentic remake of the invaders game, found on the Casio
+ * calculator wristwatch CA-85 or CA-851. There were also some calculators
+ * sold with this game, like MG-880.
+ *
+ * How to play:
+ *
+ * Press the alarm button to start the game.
+ * "Invaders" (just digits, tbh) will start coming in from the right hand side.
+ * Press the light button to "aim". The digit on the top of the display cycles
+ * from 0 to 9. If your aiming digit is identical to one of the invaders,
+ * press the alarm button to "shoot". The corresponding invader will disappear.
+ * If the invaders reach beneath the very first position, you loose one defense
+ * line. When all three defense lines are gone, the game is over.
+ * Also: If you shoot more than 29 times per round, you loose the game.
+ * Good to know: There are 16 invaders per wave. There is a short break between
+ * waves.
+ *
+ * What are the "n" invaders? Ufos!
+ *
+ * Whenever the sum of all invaders shot is divisible by 10 the next invader
+ * will be an ufo, represented by the n-symbol. Shooting a ufo gets you extra
+ * points. Example: shoot 2, 5, 3 --> ufo next
+ *
+ * As for points: the earlier you shoot an invader, the more points you get.
+ *
+ * Anything else? Long pressing the light button toggles sound on or off. (Not
+ * while playing.)
+ *
+ */
+
+typedef struct {
+ uint16_t highscore;
+ bool sound_on;
+} invaders_state_t;
+
+void invaders_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void invaders_face_activate(movement_settings_t *settings, void *context);
+bool invaders_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void invaders_face_resign(movement_settings_t *settings, void *context);
+
+#define invaders_face ((const watch_face_t){ \
+ invaders_face_setup, \
+ invaders_face_activate, \
+ invaders_face_loop, \
+ invaders_face_resign, \
+ NULL, \
+})
+
+#endif // INVADERS_FACE_H_
+
diff --git a/movement/watch_faces/complication/morsecalc_face.c b/movement/watch_faces/complication/morsecalc_face.c
index cdf0e198..ca03a1a9 100644
--- a/movement/watch_faces/complication/morsecalc_face.c
+++ b/movement/watch_faces/complication/morsecalc_face.c
@@ -26,7 +26,6 @@
## 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`)
@@ -39,7 +38,7 @@ The user interface (`morsecalc_face.h`, `morsecalc_face.c`) lets you talk to the
- `light` is dash
- `alarm` is dot
- `mode` is "finish character"
- - long-press `mode` to quit
+ - long-press `mode` or submit a blank token to switch faces
- long-press `alarm` to show stack
- long-press `light` to toggle the light
@@ -48,7 +47,6 @@ As you enter `.`s and `-`s, the morse code char you've entered will appear in th
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.
@@ -77,7 +75,6 @@ This can get long, so for convenience numerals can also be written in binary wit
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
@@ -97,7 +94,6 @@ So for example, the watch face might look like this:
... 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):
@@ -113,101 +109,25 @@ To see all the calculator operations and their token aliases, see the `calc_dict
#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);
+#include "morsecalc_face.h"
+#include "morsecalc_display.h"
- // 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);
- }
- }
+/* mc_input Read an input into a morse code buffer
+ * Input: mc = index of MORSECODE_TREE[]
+ * len = max morse code char length
+ * in = character to read into buffer (0='.', 1='-', ignored otherwise).
+ * If the buffer is full, reset it instead of entering the new character.
+ */
+static void morsecode_input(unsigned int *mc, unsigned int len, char in) {
+ if(*mc >= (unsigned int) ((1<<len)-1)) *mc = 0;
+ else if((in == 0) | (in == 1)) *mc = (*mc)*2+in+1;
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) {
@@ -216,75 +136,46 @@ void morsecalc_reset_token(morsecalc_state_t *mcs) {
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) {
+// Write a completed morse code character to the calculator
+void morsecalc_input(morsecalc_state_t * mcs) {
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
+ char dec = MORSECODE_TREE[mcs->mc];
+ mcs->mc = 0;
+ switch(dec) {
+ case '\0': // Invalid character, do nothing
+ morsecalc_display_token(mcs);
+ break;
+
+ case ' ': // Submit token to calculator
+ if(mcs->idxt > 0) {
+ mcs->token[mcs->idxt] = '\0';
+ status = calc_input(mcs->cs, mcs->token);
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;
- }
+ }
+ morsecalc_display_stack(mcs);
+ break;
+
+ case '(': // -.--. Erase previous character in token
+ if(mcs->idxt>0) {
+ mcs->idxt--;
+ mcs->token[mcs->idxt] = '\0';
+ }
+ morsecalc_display_token(mcs);
+ break;
+
+ case 'S': // -.-.- Erase entire token without submitting
+ morsecalc_reset_token(mcs);
+ morsecalc_display_stack(mcs);
+ break;
+
+ default: // Add character to token
+ if(mcs->idxt < MORSECALC_TOKEN_LEN-1) {
+ mcs->token[mcs->idxt] = dec;
+ mcs->idxt = min(mcs->idxt+1, MORSECALC_TOKEN_LEN);
+ morsecalc_display_token(mcs);
+ }
+ else watch_display_string(" full", 4);
+ break;
}
// Print errors if there are any
@@ -308,10 +199,7 @@ void morsecalc_face_setup(movement_settings_t *settings, uint8_t watch_face_inde
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->mc = 0;
mcs->led_is_on = 0;
}
return;
@@ -320,8 +208,8 @@ void morsecalc_face_setup(movement_settings_t *settings, uint8_t watch_face_inde
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);
+ mcs->mc = 0;
+ morsecalc_display_stack(mcs);
return;
}
@@ -331,21 +219,24 @@ bool morsecalc_face_loop(movement_event_t event, movement_settings_t *settings,
// input
case EVENT_ALARM_BUTTON_UP:
// dot
- morsecalc_input(mcs, '.');
+ morsecode_input(&mcs->mc, MORSECODE_LEN, 0);
+ morsecalc_display_token(mcs);
break;
case EVENT_LIGHT_BUTTON_UP:
// dash
- morsecalc_input(mcs, '-');
+ morsecode_input(&mcs->mc, MORSECODE_LEN, 1);
+ morsecalc_display_token(mcs);
break;
case EVENT_MODE_BUTTON_UP:
- // submit character
- morsecalc_input(mcs, 'x');
+ // submit character (or quit)
+ if(mcs->mc || mcs->idxt) morsecalc_input(mcs);
+ else movement_move_to_next_face();
break;
// show stack
case EVENT_ALARM_LONG_PRESS:
- morsecalc_print_stack(mcs);
- mc_reset(mcs->mc);
+ morsecalc_display_stack(mcs);
+ mcs->mc = 0;
break;
// toggle light
@@ -364,7 +255,7 @@ bool morsecalc_face_loop(movement_event_t event, movement_settings_t *settings,
// quit
case EVENT_TIMEOUT:
- movement_move_to_next_face();
+ movement_move_to_face(0);
break;
case EVENT_MODE_LONG_PRESS:
movement_move_to_next_face();
diff --git a/movement/watch_faces/complication/morsecalc_face.h b/movement/watch_faces/complication/morsecalc_face.h
index bd0fd416..2ee18622 100644
--- a/movement/watch_faces/complication/morsecalc_face.h
+++ b/movement/watch_faces/complication/morsecalc_face.h
@@ -24,11 +24,27 @@
#ifndef MORSECALC_FACE_H_
#define MORSECALC_FACE_H_
-#define MORSECALC_TOKEN_LEN 9
+
+#define MORSECALC_TOKEN_LEN 32
+#define MORSECODE_LEN 5
#include "movement.h"
#include "calc.h"
-#include "mc.h"
+
+/*
+ * MC International Morse Code binary tree
+ * Levels of the tree are concatenated.
+ * '.' = 0 and '-' = 1.
+ *
+ * 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 MORSECODE_TREE[] = " 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";
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);
@@ -37,17 +53,14 @@ void morsecalc_face_resign(movement_settings_t *settings, void *context);
typedef struct {
calc_state_t *cs;
- mc_state_t *mc;
+ unsigned int mc; // Morse code character
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);
+void morsecalc_input(morsecalc_state_t *mcs);
#define morsecalc_face ((const watch_face_t){ \
morsecalc_face_setup, \
diff --git a/movement/watch_faces/complication/planetary_hours_face.c b/movement/watch_faces/complication/planetary_hours_face.c
new file mode 100644
index 00000000..acded917
--- /dev/null
+++ b/movement/watch_faces/complication/planetary_hours_face.c
@@ -0,0 +1,402 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 <math.h>
+#include "sunriset.h"
+#include "watch.h"
+#include "watch_utility.h"
+#include "planetary_hours_face.h"
+
+#if __EMSCRIPTEN__
+#include <emscripten.h>
+#endif
+
+// STATIC FUNCTIONS AND CONSTANTS /////////////////////////////////////////////
+
+/** @brief Planetary rulers in the Chaldean order from slowest to fastest
+ * @details Planetary rulers in the Chaldean order from slowest to fastest:
+ * Jupiter, Mars, Sun, Venus, Mercury, Moon
+ */
+static const char planets[7][3] = {"Sa", "Ju", "Ma", "So", "Ve", "Me", "Lu"}; // Latin
+static const char planetes[7][3] = {"Ch", "Ze", "Ar", "He", "Af", "Hr", "Se"}; // Greek
+
+/** @brief Ruler of each weekday for easy lookup
+ */
+static const uint8_t plindex[7] = {3, 6, 2, 5, 1, 4, 0}; // day ruler index
+
+/** @brief Astrological symbol for each planet
+ */
+static void _planetary_icon(uint8_t planet) {
+
+ watch_clear_pixel(0, 13);
+ watch_clear_pixel(0, 14);
+ watch_clear_pixel(1, 13);
+ watch_clear_pixel(1, 14);
+ watch_clear_pixel(1, 15);
+ watch_clear_pixel(2, 13);
+ watch_clear_pixel(2, 14);
+ watch_clear_pixel(2, 15);
+
+ switch (planet) {
+ case 0: // Saturn
+ watch_set_pixel(0, 14);
+ watch_set_pixel(2, 14);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(2, 13);
+ break;
+ case 1: // Jupiter
+ watch_set_pixel(0, 14);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(1, 14);
+ break;
+ case 2: // Mars
+ watch_set_pixel(2, 14);
+ watch_set_pixel(2, 15);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(2, 13);
+ watch_set_pixel(1, 13);\
+ break;
+ case 3: // Sol
+ watch_set_pixel(0, 14);
+ watch_set_pixel(2, 14);
+ watch_set_pixel(1, 13);
+ watch_set_pixel(2, 13);
+ watch_set_pixel(0, 13);
+ watch_set_pixel(2, 15);
+ break;
+ case 4: // Venus
+ watch_set_pixel(0, 14);
+ watch_set_pixel(0, 13);
+ watch_set_pixel(1, 13);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(1, 14);
+ break;
+ case 5: // Mercury
+ watch_set_pixel(0, 14);
+ watch_set_pixel(1, 13);
+ watch_set_pixel(1, 14);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(2, 15);
+ break;
+ case 6: // Luna
+ watch_set_pixel(2, 14);
+ watch_set_pixel(2, 15);
+ watch_set_pixel(2, 13);
+ break;
+ }
+}
+
+/** @details A solar phase can be a day phase between sunrise and sunset or an alternating night phase.
+ * This function calculates the start and end of the current phase based on a given geographic location.
+ * It also calculates the start of the next following phase.
+ */
+static void _planetary_solar_phases(movement_settings_t *settings, planetary_hours_state_t *state) {
+ uint8_t phase, h;
+ double sunrise, sunset;
+ double hour_duration, next_hour_duration;
+ uint32_t now_epoch;
+ uint32_t sunrise_epoch_today, sunset_epoch_today, midnight_epoch_today;
+ uint32_t sunset_epoch_yesterday, midnight_epoch_yesterday;
+ uint32_t sunrise_epoch_tomorrow, sunset_epoch_tomorrow, midnight_epoch_tomorrow;
+ movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
+
+ // check if we have a location. If not, display error
+ if (movement_location.reg == 0) {
+ watch_display_string(" no Loc", 0);
+ state->no_location = true;
+ return;
+ }
+
+ // location detected
+ state->no_location = false;
+
+ watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time
+ watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC
+ watch_date_time scratch_time; // scratchpad, contains different values at different times
+ watch_date_time midnight;
+ scratch_time.reg = midnight.reg = utc_now.reg;
+ midnight.unit.hour = midnight.unit.minute = midnight.unit.second = 0; // start of the day at midnight
+
+ // get location coordinate
+ int16_t lat_centi = (int16_t)movement_location.bit.latitude;
+ int16_t lon_centi = (int16_t)movement_location.bit.longitude;
+ double lat = (double)lat_centi / 100.0;
+ double lon = (double)lon_centi / 100.0;
+
+ // save UTC offset
+ state->utc_offset = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0;
+
+ // calculate sunrise and sunset of current day in decimal hours after midnight
+ sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset);
+
+ // calculate sunrise and sunset UNIX timestamps
+ midnight_epoch_today = watch_utility_date_time_to_unix_time(midnight, 0);
+ sunrise_epoch_today = midnight_epoch_today + sunrise * 3600;
+ sunset_epoch_today = midnight_epoch_today + sunset * 3600;
+
+ // go back to yesterday and calculate sunset
+ midnight_epoch_yesterday = midnight_epoch_today - 86400;
+ scratch_time = watch_utility_date_time_from_unix_time(midnight_epoch_yesterday, 0);
+ sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset);
+ sunset_epoch_yesterday = midnight_epoch_yesterday + sunset * 3600;
+
+ // go to tomorrow and calculate sunrise and sunset
+ midnight_epoch_tomorrow = midnight_epoch_today + 86400;
+ scratch_time = watch_utility_date_time_from_unix_time(midnight_epoch_tomorrow, 0);
+ sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset);
+ sunrise_epoch_tomorrow = midnight_epoch_tomorrow + sunrise * 3600;
+ sunset_epoch_tomorrow = midnight_epoch_tomorrow + sunset * 3600;
+
+ // get UNIX epoch time
+ now_epoch = watch_utility_date_time_to_unix_time(utc_now, 0);
+
+ // by default we assume it is daytime (phase 1) between sunrise and sunset
+ phase = 1;
+ state->phase_start = sunrise_epoch_today;
+ state->phase_end = sunset_epoch_today;
+ state->phase_next = sunrise_epoch_tomorrow;
+ state->start_at_night = false;
+
+ // night time calculations
+ if ( now_epoch < sunrise_epoch_today && now_epoch < sunset_epoch_today ) phase = 0; // morning before dawn
+ if ( now_epoch > sunrise_epoch_today && now_epoch >= sunset_epoch_today ) phase = 2; // evening after dusk
+
+ // phase 0: we are before sunrise
+ if ( phase == 0) {
+ state->phase_start = sunset_epoch_yesterday;
+ state->phase_end = sunrise_epoch_today;
+ state->phase_next = sunset_epoch_today;
+ state->start_at_night = true;
+ }
+
+ // phase 2: we are after sunset
+ if ( phase == 2) {
+ state->phase_start = sunset_epoch_today;
+ state->phase_end = sunrise_epoch_tomorrow;
+ state->phase_next = sunset_epoch_tomorrow;
+ state->start_at_night = true;
+ }
+
+ // calculate the duration of a planetary hour during this and the next solar phase
+ hour_duration = ( state->phase_end - state->phase_start ) / 12.0;
+ next_hour_duration = ( state->phase_next - state->phase_end ) / 12.0;
+
+ // populate list of 24 planetary hour start points in UNIX timestamp format
+ // starting from the beginning of the current phase
+ for ( h = 0; h < 24; h++ ) {
+ if ( h < 12 ) state->planetary_hours[h] = state->phase_start + h * hour_duration; // current phase
+ else state->planetary_hours[h] = state->phase_end + ( h - 12 ) * next_hour_duration; // next phase
+ }
+
+ // initialize
+ state->hour = 0;
+ state->ruler = 0;
+ state->skip_to_current = true;
+
+}
+
+/** @details A planetary hour is one of exactly twelve hours of a solar phase. Its length varies.
+ * This function calculates the current planetary hour and divides it up into relative minutes and seconds.
+ * It also calculates the current planetary ruler of the hour and of the day.
+ */
+static void _planetary_hours(movement_settings_t *settings, planetary_hours_state_t *state) {
+ char buf[14];
+ char ruler[3];
+ uint8_t weekday, planet, planetary_hour;
+ uint32_t current_hour_epoch;
+ watch_date_time scratch_time;
+
+ // check if we have a location. If not, display error
+ if ( state->no_location ) {
+ watch_display_string(" no Loc", 0);
+ return;
+ }
+
+ // get current time
+ watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time
+ watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC
+ current_hour_epoch = watch_utility_date_time_to_unix_time(utc_now, 0);
+
+ // set the current planetary hour as default screen
+ if ( state->skip_to_current ) {
+ state->hour = ( current_hour_epoch - state->phase_start ) / (( state->phase_end - state->phase_start ) / 12.0);
+ state->skip_to_current = false;
+ }
+
+
+ // when current phase ends calculate the next phase
+ if ( watch_utility_date_time_to_unix_time(utc_now, 0) >= state->phase_end ) {
+ _planetary_solar_phases(settings, state);
+ return;
+ }
+
+ if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H);
+
+ // roll over hour iterator
+ if ( state->hour < 0 ) state->hour = 23;
+ if ( state->hour > 23 ) state->hour = 0;
+ if ( state->ruler < 0 ) state->hour = 2;
+ if ( state->ruler > 2 ) state->hour = 0;
+
+ // clear indicators
+ watch_clear_indicator(WATCH_INDICATOR_BELL);
+ watch_clear_indicator(WATCH_INDICATOR_LAP);
+
+ // display bell indicator when displaying the current planetary hour
+ if ( state->hour < 24 )
+ if ( current_hour_epoch >= state->planetary_hours[state->hour] && current_hour_epoch < state->planetary_hours[state->hour + 1]) {
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ }
+
+ // display LAP indicator when the hours of the next phase belong to the next day
+ if ( state->start_at_night == true && state->hour > 11 )
+ watch_set_indicator(WATCH_INDICATOR_LAP);
+
+ // determine weekday from start of current phase
+ scratch_time = watch_utility_date_time_from_unix_time(state->phase_start, 0);
+ scratch_time = watch_utility_date_time_convert_zone(scratch_time, 0, state->utc_offset * 3600);
+ weekday = watch_utility_get_iso8601_weekday_number(scratch_time.unit.year, scratch_time.unit.month, scratch_time.unit.day) - 1;
+
+ // which planetary hour are we in?
+ planetary_hour = state->hour % 12;
+
+ // accomodate night hour count
+ if ( state->hour < 12 ) {
+ if ( state->start_at_night ) {
+ planetary_hour += 12;
+ }
+ } else {
+ if ( state->start_at_night ) {
+ weekday = ( weekday + 1 ) % 7;
+ } else {
+ planetary_hour += 12;
+ }
+ }
+
+ // make datetime object for selected planetary hour
+ scratch_time = watch_utility_date_time_from_unix_time(state->planetary_hours[state->hour], 0);
+ scratch_time = watch_utility_date_time_convert_zone(scratch_time, 0, state->utc_offset * 3600);
+
+ // round minutes
+ if (scratch_time.unit.second < 30 && scratch_time.unit.minute > 0 ) scratch_time.unit.minute--;
+ else if ( scratch_time.unit.minute < 59 ) scratch_time.unit.minute++;
+
+ // if we are in 12 hour mode, do some cleanup
+ if (!settings->bit.clock_mode_24h) {
+ if (scratch_time.unit.hour < 12) {
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ } else {
+ watch_set_indicator(WATCH_INDICATOR_PM);
+ }
+ scratch_time.unit.hour %= 12;
+ if (scratch_time.unit.hour == 0) scratch_time.unit.hour = 12;
+ }
+
+ // planetary ruler of the hour
+ planet = ( plindex[weekday] + planetary_hour ) % 7;
+
+ // latin or greek ruler names or astrological symbol
+ if ( state->ruler == 0 ) strncpy(ruler, planets[planet], 3);
+ if ( state->ruler == 1 ) strncpy(ruler, planetes[planet], 3);
+ if ( state->ruler == 2 ) strncpy(ruler, " ", 3);
+
+ // display planetary time with ruler of the hour or ruler of the day
+ sprintf(buf, "%s%2d%2d%02d ", ruler, (planetary_hour % 24) + 1, scratch_time.unit.hour, scratch_time.unit.minute);
+
+ watch_set_colon();
+ watch_display_string(buf, 0);
+
+ if ( state->ruler == 2 ) _planetary_icon(planet);
+}
+
+// PUBLIC WATCH FACE FUNCTIONS ////////////////////////////////////////////////
+
+void planetary_hours_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+ (void) watch_face_index;
+ (void) settings;
+ if (*context_ptr == NULL) {
+ *context_ptr = malloc(sizeof(planetary_hours_state_t));
+ memset(*context_ptr, 0, sizeof(planetary_hours_state_t));
+ }
+}
+
+void planetary_hours_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ if (watch_tick_animation_is_running()) watch_stop_tick_animation();
+
+#if __EMSCRIPTEN__
+ int16_t browser_lat = EM_ASM_INT({ return lat; });
+ int16_t browser_lon = EM_ASM_INT({ return lon; });
+ if ((watch_get_backup_data(1) == 0) && (browser_lat || browser_lon)) {
+ movement_location_t browser_loc;
+ browser_loc.bit.latitude = browser_lat;
+ browser_loc.bit.longitude = browser_lon;
+ watch_store_backup_data(browser_loc.reg, 1);
+ }
+#endif
+
+ planetary_hours_state_t *state = (planetary_hours_state_t *)context;
+ _planetary_solar_phases(settings, state);
+
+}
+
+bool planetary_hours_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ planetary_hours_state_t *state = (planetary_hours_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ // Show your initial UI here.
+ watch_clear_indicator(WATCH_INDICATOR_PM);
+ watch_clear_indicator(WATCH_INDICATOR_24H);
+ _planetary_hours(settings, state);
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ state->ruler = (state->ruler + 1) % 3;
+ _planetary_hours(settings, state);
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ state->skip_to_current = true;
+ _planetary_hours(settings, state);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ state->hour++;
+ _planetary_hours(settings, state);
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ state->hour--;
+ _planetary_hours(settings, state);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+ return true;
+}
+
+void planetary_hours_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
diff --git a/movement/watch_faces/complication/planetary_hours_face.h b/movement/watch_faces/complication/planetary_hours_face.h
new file mode 100644
index 00000000..53237df2
--- /dev/null
+++ b/movement/watch_faces/complication/planetary_hours_face.h
@@ -0,0 +1,107 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ * Copyright (c) 2022 Joey Castillo (sunrise_sunset_face)
+ *
+ * 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 planetary_hours_face_H_
+#define planetary_hours_face_H_
+
+#include "movement.h"
+#include "sunrise_sunset_face.h"
+
+/*
+ * BACKGROUND
+
+ * Both the 24 hour day and the order of our weekdays have quite esoteric roots.
+ * The ancient Egyptians divided the day up into 12 hours of sunlight and 12 hours
+ * of night time. Obviously the length of these hours varied throughout the year.
+ *
+ * The Greeks assigned each hour a ruler of the planetary gods in the ancient
+ * "Chaldean" order from slowest (Chronos for Saturn) to fastest (Selene for Moon).
+ * Because 24 hours cannot be equally divided by seven, the planetary rulers carried
+ * over to the first hour of the next day, effectively ruling over the entire day
+ * and lending the whole day their name. The seven day week was born.
+ *
+ * PLANETARY HOUR CHART COMPLICATION
+ *
+ * This complication watch face displays the start time of the current planetary hour
+ * according to the given location and day of the year. The number of the current
+ * planetary hour (1 - 24) is indicated at the top right.
+ *
+ * Short pressing the ALARM button flips through the start times of the following
+ * planetary hours, long pressing it flips backwards in time. A long press of the
+ * LIGHT button immediately switches back to the start time of the current hour.
+ * The Bell indicator always marks the current planetary hour in the list.
+ * The LAP indicator shows up when the hours of the next phase are part of the
+ * upcoming day instead of the current one. This happens when the watch face is
+ * launched after sunset.
+ *
+ * The planetary ruler of the current hour and day is displayed at the top in
+ * Latin or Greek shorthand notation:
+ *
+ * Saturn (SA) / Chronos (CH) / ♄
+ * Jupiter (JU) / Zeus (ZE) / ♃
+ * Mars (MA) / Ares (AR) / ♂
+ * Sol (SO) / Helios (HE) / ☉
+ * Venus (VE) / Aphrodite (AF) / ♀
+ * Mercury (ME) / Hermes (HR) / ☿
+ * Luna (LU) / Selene (SE) / ☾
+ *
+ * A short press of the LIGHT button toggles between Latin and Greek ruler shorthand
+ * notation.
+ *
+ * (IMPORTANT: Make sure the watch's time, timezone and location are set correctly for this
+ * watch face to work properly!)
+ */
+
+typedef struct {
+ // Anything you need to keep track of, put it here!
+ uint32_t planetary_hours[24];
+ uint32_t phase_start;
+ uint32_t phase_end;
+ uint32_t phase_next;
+ bool next;
+ double utc_offset;
+ bool no_location;
+ int8_t hour;
+ int8_t ruler;
+ bool start_at_night;
+ bool skip_to_current;
+ sunrise_sunset_state_t sunstate;
+} planetary_hours_state_t;
+
+void planetary_hours_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void planetary_hours_face_activate(movement_settings_t *settings, void *context);
+bool planetary_hours_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void planetary_hours_face_resign(movement_settings_t *settings, void *context);
+
+#define planetary_hours_face ((const watch_face_t){ \
+ planetary_hours_face_setup, \
+ planetary_hours_face_activate, \
+ planetary_hours_face_loop, \
+ planetary_hours_face_resign, \
+ NULL, \
+})
+
+#endif // planetary_hours_face_H_
+
diff --git a/movement/watch_faces/complication/planetary_time_face.c b/movement/watch_faces/complication/planetary_time_face.c
new file mode 100644
index 00000000..56a18cf2
--- /dev/null
+++ b/movement/watch_faces/complication/planetary_time_face.c
@@ -0,0 +1,338 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 <math.h>
+#include "sunriset.h"
+#include "watch.h"
+#include "watch_utility.h"
+#include "planetary_time_face.h"
+
+#if __EMSCRIPTEN__
+#include <emscripten.h>
+#endif
+
+// STATIC FUNCTIONS AND CONSTANTS /////////////////////////////////////////////
+
+/** @brief Planetary rulers in the Chaldean order from slowest to fastest
+ * @details Planetary rulers in the Chaldean order from slowest to fastest:
+ * Jupiter, Mars, Sun, Venus, Mercury, Moon
+ */
+static const char planets[7][3] = {"Sa", "Ju", "Ma", "So", "Ve", "Me", "Lu"}; // Latin
+static const char planetes[7][3] = {"Ch", "Ze", "Ar", "He", "Af", "Hr", "Se"}; // Greek
+
+/** @brief Ruler of each weekday for easy lookup
+ */
+static const uint8_t plindex[7] = {3, 6, 2, 5, 1, 4, 0}; // day ruler index
+
+/** @brief Astrological symbol for each planet
+ */
+static void _planetary_icon(uint8_t planet) {
+
+ watch_clear_pixel(0, 13);
+ watch_clear_pixel(0, 14);
+ watch_clear_pixel(1, 13);
+ watch_clear_pixel(1, 14);
+ watch_clear_pixel(1, 15);
+ watch_clear_pixel(2, 13);
+ watch_clear_pixel(2, 14);
+ watch_clear_pixel(2, 15);
+
+ switch (planet) {
+ case 0: // Saturn
+ watch_set_pixel(0, 14);
+ watch_set_pixel(2, 14);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(2, 13);
+ break;
+ case 1: // Jupiter
+ watch_set_pixel(0, 14);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(1, 14);
+ break;
+ case 2: // Mars
+ watch_set_pixel(2, 14);
+ watch_set_pixel(2, 15);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(2, 13);
+ watch_set_pixel(1, 13);\
+ break;
+ case 3: // Sol
+ watch_set_pixel(0, 14);
+ watch_set_pixel(2, 14);
+ watch_set_pixel(1, 13);
+ watch_set_pixel(2, 13);
+ watch_set_pixel(0, 13);
+ watch_set_pixel(2, 15);
+ break;
+ case 4: // Venus
+ watch_set_pixel(0, 14);
+ watch_set_pixel(0, 13);
+ watch_set_pixel(1, 13);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(1, 14);
+ break;
+ case 5: // Mercury
+ watch_set_pixel(0, 14);
+ watch_set_pixel(1, 13);
+ watch_set_pixel(1, 14);
+ watch_set_pixel(1, 15);
+ watch_set_pixel(2, 15);
+ break;
+ case 6: // Luna
+ watch_set_pixel(2, 14);
+ watch_set_pixel(2, 15);
+ watch_set_pixel(2, 13);
+ break;
+ }
+}
+
+/** @details solar phase can be a day phase between sunrise and sunset or an alternating night phase.
+ * This function calculates the start and end of the current phase based on a given geographic location.
+ */
+static void _planetary_solar_phase(movement_settings_t *settings, planetary_time_state_t *state) {
+ uint8_t phase;
+ double sunrise, sunset;
+ uint32_t now_epoch, sunrise_epoch, sunset_epoch, midnight_epoch;
+ movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
+
+ // check if we have a location. If not, display error
+ if (movement_location.reg == 0) {
+ watch_display_string(" no Loc", 0);
+ state->no_location = true;
+ return;
+ }
+
+ // location detected
+ state->no_location = false;
+
+ watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time
+ watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC
+ watch_date_time scratch_time; // scratchpad, contains different values at different times
+ watch_date_time midnight;
+ scratch_time.reg = midnight.reg = utc_now.reg;
+ midnight.unit.hour = midnight.unit.minute = midnight.unit.second = 0; // start of the day at midnight
+
+ // get location coordinate
+ int16_t lat_centi = (int16_t)movement_location.bit.latitude;
+ int16_t lon_centi = (int16_t)movement_location.bit.longitude;
+ double lat = (double)lat_centi / 100.0;
+ double lon = (double)lon_centi / 100.0;
+
+ // save UTC offset
+ state->utc_offset = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0;
+
+ // get UNIX epoch time
+ now_epoch = watch_utility_date_time_to_unix_time(utc_now, 0);
+ midnight_epoch = watch_utility_date_time_to_unix_time(midnight, 0);
+
+ // calculate sunrise and sunset of current day in decimal hours after midnight
+ sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset);
+
+ // calculate sunrise and sunset UNIX timestamps
+ sunrise_epoch = midnight_epoch + sunrise * 3600;
+ sunset_epoch = midnight_epoch + sunset * 3600;
+
+ // by default we assume it is daytime (phase 1) between sunrise and sunset
+ phase = 1;
+ state->night = false;
+ state->phase_start = sunrise_epoch;
+ state->phase_end = sunset_epoch;
+
+ // night time calculations
+ if ( now_epoch < sunrise_epoch && now_epoch < sunset_epoch ) phase = 0; // morning before dawn
+ if ( now_epoch > sunrise_epoch && now_epoch >= sunset_epoch ) phase = 2; // evening after dusk
+
+ // phase 0: we are before sunrise
+ if ( phase == 0) {
+ // go back to yesterday and calculate sunset
+ midnight_epoch -= 86400;
+ scratch_time = watch_utility_date_time_from_unix_time(midnight_epoch, 0);
+ sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset);
+ sunset_epoch = midnight_epoch + sunset * 3600;
+ // we are still in yesterday's night hours
+ state->night = true;
+ state->phase_start = sunset_epoch;
+ state->phase_end = sunrise_epoch;
+ }
+
+ // phase 2: we are after sunset
+ if ( phase == 2) {
+ // skip to tomorrow and calculate sunrise
+ midnight_epoch += 86400;
+ scratch_time = watch_utility_date_time_from_unix_time(midnight_epoch, 0);
+ sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset);
+ sunrise_epoch = midnight_epoch + sunrise * 3600;
+ // we are still in yesterday's night hours
+ state->night = true;
+ state->phase_start = sunset_epoch;
+ state->phase_end = sunrise_epoch;
+ }
+
+ // calculate the duration of a planetary second during this solar phase
+ // and convert to Hertz so we can call a faster tick rate
+ state->freq = (1 / ((double)( state->phase_end - state->phase_start ) / 43200));
+}
+
+/** @details A planetary hour is one of exactly twelve hours of a solar phase. Its length varies.
+ * This function calculates the current planetary hour and divides it up into relative minutes and seconds.
+ * It also calculates the current planetary ruler of the hour and of the day.
+ */
+static void _planetary_time(movement_event_t event, movement_settings_t *settings, planetary_time_state_t *state) {
+ char buf[14];
+ char ruler[3];
+ double night_hour_count = 0.0;
+ uint8_t weekday, planet, planetary_hour;
+ double hour_duration, current_hour, current_minute, current_second;
+
+ watch_set_colon();
+
+ // get current time and convert to UTC
+ state->scratch = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0);
+
+ // when current phase ends calculate the next phase
+ if ( watch_utility_date_time_to_unix_time(state->scratch, 0) >= state->phase_end ) {
+ _planetary_solar_phase(settings, state);
+ return;
+ }
+
+ if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H);
+
+ // PM for night hours, otherwise the night hours are counted from 13
+ if ( state->night ) {
+ if (settings->bit.clock_mode_24h) night_hour_count = 12;
+ else watch_set_indicator(WATCH_INDICATOR_PM);
+ }
+
+ // calculate the duration of a planetary hour during this solar phase
+ hour_duration = (( state->phase_end - state->phase_start)) / 12.0;
+
+ // which planetary hour are we in?
+
+ // RTC only provides full second precision, so we have to manually add subseconds with each tick
+ current_hour = ((( watch_utility_date_time_to_unix_time(state->scratch, 0) ) + event.subsecond * 0.11111111) - state->phase_start ) / hour_duration;
+ planetary_hour = floor(current_hour) + ( state->night ? 12 : 0 );
+ current_hour += night_hour_count; //adjust for 24hr display
+ current_minute = modf(current_hour, &current_hour) * 60.0;
+ current_second = modf(current_minute, &current_minute) * 60.0;
+
+ // the day changes after sunrise, so if we are at night it is yesterday's planetary day
+ // hence we take the datetime object of when the last solar phase started as the current day
+ // and then fill in the hours and minutes
+ state->scratch = watch_utility_date_time_from_unix_time(state->phase_start, 0);
+ state->scratch.unit.hour = floor(current_hour);
+ state->scratch.unit.minute = floor(current_minute);
+ state->scratch.unit.second = (uint8_t)floor(current_second) % 60;
+
+ // what weekday is it (0 - 6)
+ weekday = watch_utility_get_iso8601_weekday_number(state->scratch.unit.year, state->scratch.unit.month, state->scratch.unit.day) - 1;
+
+ // planetary ruler of the hour or the day
+ if ( state->day_ruler ) planet = plindex[weekday];
+ else planet = ( plindex[weekday] + planetary_hour ) % 7;
+
+ // latin or greek ruler names or astrological symbol
+ if ( state->ruler == 0 ) strncpy(ruler, planets[planet], 3);
+ if ( state->ruler == 1 ) strncpy(ruler, planetes[planet], 3);
+ if ( state->ruler == 2 ) strncpy(ruler, " ", 3);
+
+ // display planetary time with ruler of the hour or ruler of the day
+ if ( state->day_ruler ) sprintf(buf, "%s d%2d%02d%02d", ruler, state->scratch.unit.hour, state->scratch.unit.minute, state->scratch.unit.second);
+ else sprintf(buf, "%s h%2d%02d%02d", ruler, state->scratch.unit.hour, state->scratch.unit.minute, state->scratch.unit.second);
+
+ watch_display_string(buf, 0);
+
+ if ( state->ruler == 2 ) _planetary_icon(planet);
+
+}
+
+// PUBLIC WATCH FACE FUNCTIONS ////////////////////////////////////////////////
+
+void planetary_time_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+ (void) watch_face_index;
+ (void) settings;
+ if (*context_ptr == NULL) {
+ *context_ptr = malloc(sizeof(planetary_time_state_t));
+ memset(*context_ptr, 0, sizeof(planetary_time_state_t));
+ }
+}
+
+void planetary_time_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ if (watch_tick_animation_is_running()) watch_stop_tick_animation();
+
+#if __EMSCRIPTEN__
+ int16_t browser_lat = EM_ASM_INT({ return lat; });
+ int16_t browser_lon = EM_ASM_INT({ return lon; });
+ if ((watch_get_backup_data(1) == 0) && (browser_lat || browser_lon)) {
+ movement_location_t browser_loc;
+ browser_loc.bit.latitude = browser_lat;
+ browser_loc.bit.longitude = browser_lon;
+ watch_store_backup_data(browser_loc.reg, 1);
+ }
+#endif
+
+ planetary_time_state_t *state = (planetary_time_state_t *)context;
+
+ // calculate phase
+ _planetary_solar_phase(settings, state);
+}
+
+bool planetary_time_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ planetary_time_state_t *state = (planetary_time_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ _planetary_time(event, settings, state);
+ if ( state->freq > 1 )
+ // for hours with shorter seconds let's increase the tick to not skip seconds in the display
+ movement_request_tick_frequency( 8 );
+ break;
+ case EVENT_TICK:
+ _planetary_time(event, settings, state);
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ state->ruler = (state->ruler + 1) % 3;
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ // Just in case you have need for another button.
+ state->day_ruler = !state->day_ruler;
+ break;
+ case EVENT_LOW_ENERGY_UPDATE:
+ watch_start_tick_animation(500);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void planetary_time_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+ movement_request_tick_frequency( 1 );
+}
+
diff --git a/movement/watch_faces/complication/planetary_time_face.h b/movement/watch_faces/complication/planetary_time_face.h
new file mode 100644
index 00000000..0ecc11af
--- /dev/null
+++ b/movement/watch_faces/complication/planetary_time_face.h
@@ -0,0 +1,108 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ * Copyright (c) 2022 Joey Castillo (sunrise_sunset_face)
+ *
+ * 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 planetary_time_face_H_
+#define planetary_time_face_H_
+
+#include "movement.h"
+#include "sunrise_sunset_face.h"
+
+/*
+ * BACKGROUND
+
+ * Both the 24 hour day and the order of our weekdays have quite esoteric roots.
+ * The ancient Egyptians divided the day up into 12 hours of sunlight and 12 hours
+ * of night time. Obviously the length of these hours varied throughout the year.
+ *
+ * The Greeks assigned each hour a ruler of the planetary gods in the ancient
+ * "Chaldean" order from slowest (Chronos for Saturn) to fastest (Selene for Moon).
+ * Because 24 hours cannot be equally divided by seven, the planetary rulers carried
+ * over to the first hour of the next day, effectively ruling over the entire day
+ * and lending the whole day their name. The seven day week was born.
+ *
+ * PLANETARY TIME COMPLICATION
+ *
+ * The hour digits of this complication watch-face display the current planetary hour
+ * according to the given location and day of the year (First hour from 12am to 1am,
+ * the second hour from 1am to 2am, and so forth).
+ *
+ * Like with normal clocks the minutes and seconds help dividing the hour into smaller
+ * units. On this watch-face, all units naturally vary in length because the planetary
+ * hours are not fixed by duration but by the moments of sunrise and sunset which
+ * obviously vary throughout the year, especially in higher latitudes.
+ *
+ * On this watch-face the hours indicated as 12am to 12pm (00:00 - 12:00) are used for
+ * the planetary daytime hours between sunrise and sunset and hours indicated as 12pm
+ * to 12am (12:00 - 00:00) are used for the planetary night hours after sunset and before
+ * sunrise.
+ *
+ * The planetary ruler of the current hour and day is displayed at the top in Latin or
+ * Greek shorthand notation:
+ *
+ * Saturn (SA) / Chronos (CH) / ♄
+ * Jupiter (JU) / Zeus (ZE) / ♃
+ * Mars (MA) / Ares (AR) / ♂
+ * Sol (SO) / Helios (HE) / ☉
+ * Venus (VE) / Aphrodite (AF) / ♀
+ * Mercury (ME) / Hermes (HR) / ☿
+ * Luna (LU) / Selene (SE) / ☾
+ *
+ * The ALARM button toggles between displaying the ruler of the hour and the ruler of the day
+ *
+ * The LIGHT button toggles between Latin and Greek ruler shorthand notation
+ *
+ * (IMPORTANT: Make sure the watch's time, timezone and location are set correctly for this
+ * watch face to work properly!)
+ */
+
+typedef struct {
+ // Anything you need to keep track of, put it here!
+ uint32_t phase_start;
+ uint32_t phase_end;
+ bool night;
+ double utc_offset;
+ double freq;
+ uint8_t ruler;
+ bool day_ruler;
+ bool no_location;
+ sunrise_sunset_state_t sunstate;
+ watch_date_time scratch;
+} planetary_time_state_t;
+
+void planetary_time_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void planetary_time_face_activate(movement_settings_t *settings, void *context);
+bool planetary_time_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void planetary_time_face_resign(movement_settings_t *settings, void *context);
+
+#define planetary_time_face ((const watch_face_t){ \
+ planetary_time_face_setup, \
+ planetary_time_face_activate, \
+ planetary_time_face_loop, \
+ planetary_time_face_resign, \
+ NULL, \
+})
+
+#endif // planetary_time_face_H_
+
diff --git a/movement/watch_faces/complication/randonaut_face.c b/movement/watch_faces/complication/randonaut_face.c
new file mode 100644
index 00000000..1a3eb21d
--- /dev/null
+++ b/movement/watch_faces/complication/randonaut_face.c
@@ -0,0 +1,414 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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.
+ */
+
+// Emulator only: need time() to seed the random number generator.
+#if __EMSCRIPTEN__
+#include <time.h>
+#else
+#include "saml22j18a.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "filesystem.h"
+#include "randonaut_face.h"
+
+#define R 6371 // Earth's radius in km
+#define PI 3.14159265358979323846
+
+static void _get_location_from_file(randonaut_state_t *state);
+static void _save_point_to_file(randonaut_state_t *state);
+static void _get_entropy(randonaut_state_t *state);
+static void _generate_blindspot(randonaut_state_t *state);
+static void _randonaut_face_display(randonaut_state_t *state);
+static void _generate_blindspot(randonaut_state_t *state);
+static uint32_t _get_pseudo_entropy(uint32_t max);
+static uint32_t _get_true_entropy(void);
+static void _get_entropy(randonaut_state_t *state);
+
+// MOVEMENT WATCH FACE FUNCTIONS //////////////////////////////////////////////
+
+void randonaut_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(randonaut_state_t));
+ memset(*context_ptr, 0, sizeof(randonaut_state_t));
+ // Do any one-time tasks in here; the inside of this conditional happens only at boot.
+ }
+ // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
+}
+
+void randonaut_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ randonaut_state_t *state = (randonaut_state_t *)context;
+ _get_location_from_file(state);
+ state->face.mode = 0;
+ state->radius = 1000;
+ _get_entropy(state);
+ state->chance = true;
+ // Handle any tasks related to your watch face coming on screen.
+}
+
+bool randonaut_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ randonaut_state_t *state = (randonaut_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ // Show your initial UI here.
+ break;
+ case EVENT_TICK:
+ // If needed, update your display here.
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ switch ( state->face.mode ) {
+ case 0: // home
+ state->face.mode = 2; //point
+ state->face.location_format = 0; // title
+ break;
+ case 1: // generate
+ state->face.mode = 0; //home
+ break;
+ case 2: // point
+ state->face.mode = 0; //home
+ break;
+ case 3: // setup radius
+ state->face.mode = 4; // toggle to RNG
+ break;
+ case 4: // setup RNG
+ state->face.mode = 3; // toggle to Radius
+ break;
+ case 5: // data processing
+ break;
+ }
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ switch ( state->face.mode ) {
+ case 3: // setup
+ case 4:
+ state->face.mode = 0; //home
+ break;
+ default:
+ state->face.mode = 3; //setup
+ watch_clear_display();
+ }
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ switch ( state->face.mode ) {
+ case 0: //home
+ state->face.mode = 1; // generate
+ break;
+ case 2: // point
+ state->face.location_format = (( state->face.location_format + 1) % (7));
+ if ( state->face.location_format == 0 )
+ state->face.location_format++;
+ break;
+ case 3: //setup radius
+ state->radius += 500;
+ if ( state->radius > 10000 )
+ state->radius = 1000;
+ break;
+ case 4: //setup RNG
+ state->face.rng = (state->face.rng + 1) % 3;
+ switch ( state->face.rng ) {
+ case 0:
+ state->chance = true;
+ break;
+ case 1:
+ state->chance = false;
+ state->quantum = true;
+ break;
+ case 2:
+ state->chance = false;
+ state->quantum = false;
+ break;
+ }
+ break;
+ case 5: // data processing
+ _save_point_to_file(state);
+ break;
+ default:
+ break;
+ }
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ if ( state->face.mode == 5 )
+ state->face.mode = 0; // home
+ else
+ state->face.mode = 5; // data processing
+ 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:
+ // 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);
+ }
+
+ _randonaut_face_display(state);
+
+ // 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 randonaut_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+
+ // handle any cleanup before your watch face goes off-screen.
+}
+
+// PRIVATE STATIC FUNCTIONS ///////////////////////////////////////////////////
+
+/** @brief display handler
+ */
+static void _randonaut_face_display(randonaut_state_t *state) {
+ char buf[12];
+ watch_clear_colon();
+ switch ( state->face.mode ) {
+ case 0: //home
+ sprintf(buf, "RA Rando");
+ break;
+ case 1: //generate
+ if ( state->quantum )
+ // All Hail Steve /;[;[/.;]/[.;[/;/;/;/;.;.];.]]--=/
+ for ( uint8_t c = 100; c > 0; c--) {
+ watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(0x33-0x1C));
+ watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(3432-3409));
+ watch_set_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(0xE +9));
+ watch_set_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(23));
+ watch_set_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(12+7+11));
+ if( c < 70 ) {
+ watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(12+7+11));
+ }
+ if ( c < 60 ) {
+ watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(0xD68-0xD4A));
+ }
+ if ( c < 50 ) {
+ watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(14+9));
+ }
+ delay_ms(_get_pseudo_entropy(c)+20);
+ if ( c < 30 ) {
+ watch_display_string(" ",_get_pseudo_entropy(10));
+ }
+ watch_clear_pixel(_get_pseudo_entropy(02),_get_pseudo_entropy(3432-3409));
+ watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(51-28));
+ watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(23));
+ if ( c < 20 ) {
+ watch_clear_pixel(_get_pseudo_entropy(02),_get_pseudo_entropy(51-28));
+ watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(14+9));
+ watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(0xD68-0xD4A));
+ watch_clear_pixel(_get_pseudo_entropy(0x2),_get_pseudo_entropy(3432-3409));
+ watch_clear_pixel(_get_pseudo_entropy(002),_get_pseudo_entropy(12+7+11));
+ watch_clear_pixel(_get_pseudo_entropy(2),_get_pseudo_entropy(51-28));
+ }
+ }
+ else
+ for ( uint8_t c = 30; c > 0; c--) {
+ watch_display_string("1", _get_pseudo_entropy(10));
+ watch_display_string("0", _get_pseudo_entropy(10));
+ watch_display_string("11", _get_pseudo_entropy(10));
+ watch_display_string("00", _get_pseudo_entropy(10));
+ delay_ms(50);
+ watch_display_string(" ", _get_pseudo_entropy(10));
+ watch_display_string(" ", _get_pseudo_entropy(10));
+ watch_display_string(" ", _get_pseudo_entropy(10));
+ watch_display_string(" ", _get_pseudo_entropy(10));
+ }
+ _generate_blindspot(state);
+ watch_clear_display();
+ state->face.mode = 2; // point
+ state->face.location_format = 1; // distance
+ watch_display_string("RA Found", 0);
+ delay_ms(500);
+ sprintf(buf, "RA Found");
+ break;
+ case 2: //point
+ switch ( state->face.location_format ) {
+ case 0:
+ sprintf(buf, "RA Point");
+ break;
+ case 1: // distance to point
+ watch_clear_display();
+ sprintf(buf, "DI m %d", state->point.distance );
+ break;
+ case 2: // bearing relative to point
+ watch_clear_display();
+ sprintf(buf, "BE # %d", state->point.bearing );
+ break;
+ case 3: // latitude DD._____
+ sprintf(state->scratchpad, "%07d", abs(state->point.latitude));
+ sprintf(buf, "LA #%c %c%c ", state->point.latitude < 0 ? '-' : '+', state->scratchpad[0], state->scratchpad[1]);
+ break;
+ case 4: // latitude __.DDDDD
+ sprintf(buf, "LA , %c%c%c%c%c", state->scratchpad[2], state->scratchpad[3],state->scratchpad[4], state->scratchpad[5],state->scratchpad[6]);
+ break;
+ case 5: // longitude DD._____
+ sprintf(state->scratchpad, "%08d", abs(state->point.longitude));
+ sprintf(buf, "LO #%c%c%c%c ", state->point.longitude < 0 ? '-' : '+',state->scratchpad[0], state->scratchpad[1], state->scratchpad[2]);
+ break;
+ case 6: // longitude __.DDDDD
+ sprintf(buf, "LO , %c%c%c%c%c", state->scratchpad[3], state->scratchpad[4],state->scratchpad[5], state->scratchpad[6],state->scratchpad[7]);
+ break;
+ }
+ break;
+ case 3: // setup radius
+ watch_set_colon();
+ if ( state->radius < 10000 )
+ sprintf(buf, "RA m %d ", state->radius);
+ else
+ sprintf(buf, "RA m%d ", state->radius);
+ break;
+ case 4: // setup RNG
+ sprintf(buf, "RN G %s ", state->chance ? "Chnce" : (state->quantum ? "True" : "Psudo"));
+ break;
+ case 5: // data processing
+ sprintf(buf, "WR File ");
+ }
+ watch_display_string(buf, 0);
+}
+
+/** @brief Official Randonautica Blindspot Algorithm
+ */
+static void _generate_blindspot(randonaut_state_t *state) {
+
+ _get_entropy(state);
+
+ double lat = (double)state->location.latitude / 100000;
+ double lon = (double)state->location.longitude / 100000;
+ uint16_t radius = state->radius;
+
+ const double random_distance = radius * sqrt( (double)state->entropy / INT32_MAX ) / 1000.0;
+ const double random_bearing = 2.0 * PI * (double)state->entropy / INT32_MAX;
+
+ const double phi = lat * PI / 180;
+ const double lambda = lon * PI / 180;
+ const double alpha = random_distance / R;
+
+ lat = asin( sin(phi) * cos(alpha) + cos(phi) * sin(alpha) * cos(random_bearing) );
+ lon = lambda + atan2( sin(random_bearing) * sin(alpha) * cos(phi), cos(alpha) - sin(phi) * sin( lat ));
+
+ state->point.latitude = (int)round(lat * (180 / PI) * 100000);
+ state->point.longitude = (int)round(lon * (180 / PI) * 100000);
+ state->point.distance = random_distance * 1000;
+ state->point.bearing = (uint16_t)round(random_bearing * (180 / PI) < 0 ? random_bearing * (180 / PI) + 360 : random_bearing * (180 / PI));
+}
+
+
+/** @brief pseudo random number generator
+ */
+static uint32_t _get_pseudo_entropy(uint32_t max) {
+ #if __EMSCRIPTEN__
+ return rand() % max;
+ #else
+ return arc4random_uniform(max);
+ #endif
+}
+
+/** @brief true random number generator
+ */
+static uint32_t _get_true_entropy(void) {
+ #if __EMSCRIPTEN__
+ return rand() % INT32_MAX;
+ #else
+ hri_mclk_set_APBCMASK_TRNG_bit(MCLK);
+ hri_trng_set_CTRLA_ENABLE_bit(TRNG);
+
+ while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready
+
+ hri_trng_clear_CTRLA_ENABLE_bit(TRNG);
+ hri_mclk_clear_APBCMASK_TRNG_bit(MCLK);
+ return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it
+ #endif
+}
+
+/** @brief get location from place.loc
+ */
+static void _get_location_from_file(randonaut_state_t *state) {
+ movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1);
+ coordinate_t place;
+ if (filesystem_file_exists("place.loc")) {
+ if (filesystem_read_file("place.loc", (char*)&place, sizeof(place)))
+ state->location = place;
+ } else {
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ state->location.latitude = movement_location.bit.latitude * 1000;
+ state->location.longitude = movement_location.bit.longitude * 1000;
+ }
+}
+
+/** @brief save generated point to place.loc
+ */
+static void _save_point_to_file(randonaut_state_t *state) {
+ watch_set_indicator(WATCH_INDICATOR_SIGNAL);
+ coordinate_t place;
+ place.latitude = state->point.latitude;
+ place.longitude = state->point.longitude;
+ if (filesystem_write_file("place.loc", (char*)&place, sizeof(place))) {
+ delay_ms(100);
+ watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
+ } else {
+ watch_clear_indicator(WATCH_INDICATOR_SIGNAL);
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ delay_ms(500);
+ watch_clear_indicator(WATCH_INDICATOR_BELL);
+
+ }
+}
+
+/** @brief get pseudo/quantum entropy and filter modulo bias
+ */
+static void _get_entropy(randonaut_state_t *state) {
+ if ( state->chance ) {
+ state->quantum = (bool)(state->entropy % 2);
+ }
+ do {
+ if ( !state->quantum ) {
+ state->entropy = _get_pseudo_entropy(INT32_MAX);
+ } else {
+ state->entropy = _get_true_entropy();
+ }
+ } while (state->entropy >= INT32_MAX || state->entropy <= 0);
+ state->entropy %= INT32_MAX;
+} \ No newline at end of file
diff --git a/movement/watch_faces/complication/randonaut_face.h b/movement/watch_faces/complication/randonaut_face.h
new file mode 100644
index 00000000..fabde798
--- /dev/null
+++ b/movement/watch_faces/complication/randonaut_face.h
@@ -0,0 +1,113 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 RANDONAUT_FACE_H_
+#define RANDONAUT_FACE_H_
+
+#include "movement.h"
+#include "place_face.h"
+
+/*
+ * RANDONAUT FACE
+ * ==============
+ *
+ * Randonauting is a way to turn the world around you into an adventure and get the user outside
+ * of their day-to-day routine by using a random number generator to derive a coordinate to journey
+ * to. In Randonauts lore so-called "Blind Spots" are places you cannot reach methodologically. They
+ * may exist in your own backyard for your whole life and you will never even notice them, because
+ * you simply have no reason to go to that exact place or look in its direction. Since the very
+ * limitations of our behavioral algorithms are the reason for the existence of blindspots, they
+ * can only be found using a randomizer.
+ *
+ * This watch face generates a random location based on the watch's location and a set radius using
+ * the official Randonautica Blind Spot algorithm.
+ *
+ * The ALARM button starts the random location generation and then automatically displays the found
+ * Blind Spot.
+ *
+ * By pressing ALARM again the user can flip through different pieces of information about the Blind
+ * Spot: Distance (DI), Bearing Degree (BE), Latitude degrees and decimal digits (LA), Longitude
+ * degrees and decimal digits (LO).
+ *
+ * Pressing LIGHT switches between generating a new blind spot ("Rando") and displaying the info of
+ * the last generated one ("Point").
+ *
+ * LONG PRESSING LIGHT toggles setup mode. Here pressing LIGHT switches between setting the desired
+ * radius (RA) and setting the random number generator (RNG) for generating the blind spot.
+ *
+ * ALARM changes the values respectively:
+ *
+ * - The radius can be set in 500 meter steps between 1000 and 10,000 meters
+ *
+ * - The RNG can be set to "true" which utilizes the SAML22J's internal True Random Number Generator
+ * - Setting it to "psudo" will use the pseudorandom number generation algorithm arc4random
+ * - Setting it to "chance" will randomly chose either of the RNGs for each generation (default)
+ *
+ * LONG PRESSING ALARM toggles DATA mode in which the currently generated Blind Spot coordinate can
+ * be written to the <place.loc> file on the watch (press ALARM) and set as active high precision
+ * location used by other watch faces. It does not overwrite the low precision location information
+ * in the watch register commonly used for astronomical watch faces.
+ *
+ */
+
+typedef struct {
+ uint8_t mode :3;
+ uint8_t location_format :3;
+ uint8_t rng: 2;
+} randonaut_face_mode_t;
+
+typedef struct {
+ int32_t latitude : 26;
+ int32_t longitude : 26;
+ uint16_t distance : 14;
+ uint16_t bearing : 9;
+} randonaut_coordinate_t;
+
+typedef struct {
+ // Anything you need to keep track of, put it here!
+ coordinate_t location;
+ randonaut_coordinate_t point;
+ uint16_t radius : 14;
+ uint32_t entropy;
+ bool quantum;
+ bool chance;
+ randonaut_face_mode_t face;
+ char scratchpad[10];
+} randonaut_state_t;
+
+void randonaut_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void randonaut_face_activate(movement_settings_t *settings, void *context);
+bool randonaut_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void randonaut_face_resign(movement_settings_t *settings, void *context);
+
+#define randonaut_face ((const watch_face_t){ \
+ randonaut_face_setup, \
+ randonaut_face_activate, \
+ randonaut_face_loop, \
+ randonaut_face_resign, \
+ NULL, \
+})
+
+#endif // RANDONAUT_FACE_H_
+
diff --git a/movement/watch_faces/complication/time_left_face.c b/movement/watch_faces/complication/time_left_face.c
new file mode 100644
index 00000000..cc1077aa
--- /dev/null
+++ b/movement/watch_faces/complication/time_left_face.c
@@ -0,0 +1,357 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Andreas Nebinger, based on the work of Joey Castillo
+ *
+ * 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 "time_left_face.h"
+#include "watch.h"
+#include "watch_private_display.h"
+
+const char _state_titles[][3] = {{'D', 'L', ' '}, {'D', 'L', ' '}, {'D', 'A', ' '}, {'D', 'A', ' '}, {'Y', 'R', 'b'}, {'M', 'O', 'b'}, {'D', 'A', 'b'},
+ {'Y', 'R', 'd'}, {'M', 'O', 'd'}, {'D', 'A', 'd'}};
+const uint8_t TIME_LEFT_FACE_STATES = sizeof(_state_titles) / 3; // total number of state pages
+#define TIME_LEFT_FACE_SETTINGS_STATE 4 // number of first settings state
+const uint8_t _percentage_segdata[][2] = {{1, 2}, {2, 2}, {2, 3}, {1, 3}}; // segment data for drawing the percentage sign
+const uint8_t _animation_segdata[][2] = {{2, 8}, {1, 8}, {2, 7}, {2, 6}}; // segment data for the ticking animation
+
+static bool _quick_ticks_running;
+
+static uint32_t _juliandaynum(uint16_t year, uint16_t month, uint16_t day) {
+ // from here: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation
+ return (1461 * (year + 4800 + (month - 14) / 12)) / 4 + (367 * (month - 2 - 12 * ((month - 14) / 12))) / 12 - (3 * ((year + 4900 + (month - 14) / 12) / 100))/4 + day - 32075;
+}
+
+/// @brief displays an integer value with or without using positions 8 and 9
+static void _display_integer(char *buf) {
+ if (buf[1] == ' ') {
+ // display at position 4 if the value is short enough, don't use positions 8 and 9
+ watch_display_character(' ', 8);
+ watch_display_character(' ', 9);
+ watch_display_string((char *)buf + 2, 4);
+ } else {
+ // otherwise just display using position 8 and 9
+ watch_display_string(buf, 4);
+ }
+ watch_clear_colon();
+}
+
+///@brief display a percentage value
+static void _display_percentage(float percentage, char *buf) {
+ // always stay positive
+ if (percentage < 0) {
+ percentage *= -1;
+ // Switch display to days 'O'ver
+ watch_display_character('O', 1);
+ }
+ int32_t integral = percentage;
+ if (integral >= 100) {
+ // percentage equals 100 or more: don't do fractional part
+ sprintf(buf, " %3li o", integral);
+ watch_clear_colon();
+ } else {
+ // display percentage with two decimal places
+ uint8_t fraction = (int)(percentage * (float)100) % 100;
+ sprintf(buf, "%2li%02u o", integral, fraction);
+ watch_set_colon();
+ }
+ watch_display_string(buf, 4);
+ // draw (parts of) percentage symbol
+ for (uint8_t i = 0; i < sizeof(_percentage_segdata) / 2; i++)
+ watch_set_pixel(_percentage_segdata[i][0], _percentage_segdata[i][1]);
+}
+
+/// @brief draw the current state to the display
+static void _draw(time_left_state_t *state, uint8_t subsecond) {
+ char buf[17];
+ watch_display_character(_state_titles[state->current_page][0], 0);
+ watch_display_character(_state_titles[state->current_page][1], 1);
+ watch_display_character(' ', 2);
+ watch_display_character(_state_titles[state->current_page][2], 3);
+ if (state->current_page < TIME_LEFT_FACE_SETTINGS_STATE) {
+ // we are displaying days left or days from birth
+ watch_date_time date_time = watch_rtc_get_date_time();
+ uint32_t julian_current_day = _juliandaynum(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day);
+ uint32_t julian_target_day = _juliandaynum(state->target_date.bit.year, state->target_date.bit.month, state->target_date.bit.day);
+ int32_t days_left = julian_target_day - julian_current_day;
+ if (state->current_page == 0) {
+ // display number of days left
+ sprintf(buf, "%6li", days_left);
+ _display_integer(buf);
+ } else {
+ // calculate starting date
+ uint32_t julian_start_day = _juliandaynum(state->birth_date.bit.year, state->birth_date.bit.month, state->birth_date.bit.day);
+ if ((state->current_page & 1) == 1) {
+ float percentage_left;
+ if (julian_start_day == julian_target_day) {
+ // failsafe
+ percentage_left = 0;
+ } else {
+ // display correct percentages
+ percentage_left = (float)days_left * (float)100 / (float)(julian_target_day - julian_start_day);
+ }
+ _display_percentage(state->current_page == 1 ? percentage_left : 100 - percentage_left, buf);
+ } else {
+ // display days from birth
+ sprintf(buf, "%6li", (int32_t)julian_current_day - julian_start_day);
+ _display_integer(buf);
+ }
+ }
+ } else {
+ // we are in settings mode
+ switch (state->current_page) {
+ case TIME_LEFT_FACE_SETTINGS_STATE:
+ // birth year
+ sprintf(buf, "%04u ", state->birth_date.bit.year);
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 1:
+ // birth month
+ sprintf(buf, " %02u", state->birth_date.bit.month);
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 2:
+ // birth day of month
+ sprintf(buf, " %02u", state->birth_date.bit.day);
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 3:
+ // target year
+ sprintf(buf, "%04u", state->target_date.bit.year);
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 4:
+ // target month
+ sprintf(buf, " %02u", state->target_date.bit.month);
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 5:
+ // target day of month
+ sprintf(buf, " %02u", state->target_date.bit.day);
+ break;
+ default:
+ break;
+ }
+ // blink current settings position or display value
+ if ((subsecond & 1) == 1)
+ watch_display_string(" ", 4);
+ else
+ watch_display_string(buf, 4);
+ }
+}
+
+/// @brief handle short or long pressing the alarm button
+static void _handle_alarm_button(time_left_state_t *state) {
+
+ const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31};
+ uint32_t tmp_day;
+ switch (state->current_page) {
+ case TIME_LEFT_FACE_SETTINGS_STATE: // birth year
+ state->birth_date.bit.year++;
+ if (state->birth_date.bit.year > state->current_year + 10) state->birth_date.bit.year = 1959;
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 1: // birth month
+ state->birth_date.bit.month = (state->birth_date.bit.month % 12) + 1;
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 2: // birth day
+ tmp_day = state->birth_date.bit.day; // use a temporary variable to avoid messing up the months
+ tmp_day++;
+ // handle February 29th on a leap year
+ if (((tmp_day > days_in_month[state->birth_date.bit.month - 1]) && (state->birth_date.bit.month != 2 || (state->birth_date.bit.year % 4) != 0))
+ || (state->birth_date.bit.month == 2 && (state->birth_date.bit.year % 4) == 0 && tmp_day > 29)) {
+ tmp_day = 1;
+ }
+ state->birth_date.bit.day = tmp_day;
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 3: // target year
+ state->target_date.bit.year++;
+ if (state->target_date.bit.year >2083) state->target_date.bit.year = state->current_year - 10;
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 4: // target month
+ state->target_date.bit.month = (state->target_date.bit.month % 12) + 1;
+ break;
+ case TIME_LEFT_FACE_SETTINGS_STATE + 5: // target day
+ tmp_day = state->target_date.bit.day;
+ tmp_day++;
+ // handle February 29th on a leap year
+ if (((tmp_day > days_in_month[state->target_date.bit.month - 1]) && (state->target_date.bit.month != 2 || (state->target_date.bit.year % 4) != 0))
+ || (state->target_date.bit.month == 2 && (state->target_date.bit.year % 4) == 0 && tmp_day > 29)) {
+ tmp_day = 1;
+ }
+ state->target_date.bit.day = tmp_day;
+ break;
+ }
+}
+
+static void _initiate_setting(time_left_state_t *state) {
+ state->current_page = TIME_LEFT_FACE_SETTINGS_STATE;
+ watch_clear_colon();
+ movement_request_tick_frequency(4);
+}
+
+static void _resume_setting(time_left_state_t *state) {
+ state->current_page = 0;
+ movement_request_tick_frequency(1);
+}
+
+static void _abort_quick_ticks() {
+ if (_quick_ticks_running) {
+ _quick_ticks_running = false;
+ movement_request_tick_frequency(4);
+ }
+}
+
+void time_left_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(time_left_state_t));
+ memset(*context_ptr, 0, sizeof(time_left_state_t));
+ time_left_state_t *state = (time_left_state_t *)*context_ptr;
+ state->birth_date.reg = watch_get_backup_data(2);
+ if (state->birth_date.reg == 0) {
+ // if birth date is totally blank, set a reasonable starting date. this works well for anyone under 63, but
+ // you can keep pressing to go back to 1900; just pass the current year. also picked this date because if you
+ // set it to 1959-01-02, it counts up from the launch of Luna-1, the first spacecraft to leave the well.
+ state->birth_date.bit.year = 1959;
+ state->birth_date.bit.month = 1;
+ state->birth_date.bit.day = 1;
+ watch_store_backup_data(state->birth_date.reg, 2);
+ // set target date to today + 10 years (just to have any value)
+ watch_date_time date_time = watch_rtc_get_date_time();
+ state->target_date.bit.year = date_time.unit.year + WATCH_RTC_REFERENCE_YEAR + 10;
+ state->target_date.bit.month = date_time.unit.month;
+ state->target_date.bit.day = date_time.unit.day;
+ }
+ }
+}
+
+void time_left_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ time_left_state_t *state = (time_left_state_t *)context;
+
+ // stash the current year, useful in birthday setting mode
+ watch_date_time date_time = watch_rtc_get_date_time();
+ state->current_year = date_time.unit.year + WATCH_RTC_REFERENCE_YEAR;
+ _quick_ticks_running = false;
+ // fetch the user's birth date from the birthday register
+ state->birth_date.reg = watch_get_backup_data(2);
+ // store original value of the birthdate to be able to detect changes on resigning
+ state->birth_date_when_activated.reg = state->birth_date.reg;
+}
+
+bool time_left_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ (void) settings;
+ time_left_state_t *state = (time_left_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ _draw(state, event.subsecond);
+ break;
+ case EVENT_LOW_ENERGY_UPDATE:
+ case EVENT_TICK: {
+ uint8_t subsecond = event.subsecond;
+ if (_quick_ticks_running) {
+ if (watch_get_pin_level(BTN_ALARM)) {
+ _handle_alarm_button(state);
+ subsecond = 0;
+ } else _abort_quick_ticks();
+ }
+ if (state->current_page >= TIME_LEFT_FACE_SETTINGS_STATE) {
+ // we are in settings mode. Draw to blink
+ _draw(state, subsecond);
+ } else {
+ // otherwise, check if we have to update. the display only needs to change at midnight
+ watch_date_time date_time = watch_rtc_get_date_time();
+ if (date_time.unit.hour == 0 && date_time.unit.minute == 0 && date_time.unit.second == 0) {
+ _draw(state, subsecond);
+ }
+ // handle the ticking animation
+ uint8_t animation_step = date_time.unit.second % (sizeof(_animation_segdata) / 2);
+ watch_set_pixel(_animation_segdata[animation_step][0], _animation_segdata[animation_step][1]);
+ if (animation_step == 0) animation_step = (sizeof(_animation_segdata) / 2) - 1;
+ else animation_step--;
+ watch_clear_pixel(_animation_segdata[animation_step][0], _animation_segdata[animation_step][1]);
+ }
+ break;
+ }
+ case EVENT_LIGHT_BUTTON_DOWN:
+ // do not illuminate here (this is done when releasing the button)
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ if (state->current_page < TIME_LEFT_FACE_SETTINGS_STATE) movement_illuminate_led();
+ else {
+ // cycle through the settings pages
+ state->current_page++;
+ if (state->current_page >= TIME_LEFT_FACE_STATES) {
+ // we have done a full settings cycle, so resume to normal
+ _resume_setting(state);
+ _draw(state, event.subsecond);
+ }
+ }
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ if (state->current_page >= TIME_LEFT_FACE_SETTINGS_STATE) {
+ _resume_setting(state);
+ } else {
+ _initiate_setting(state);
+ }
+ _draw(state, event.subsecond);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ _abort_quick_ticks();
+ if (state->current_page < TIME_LEFT_FACE_SETTINGS_STATE) {
+ // alternate between days left and percentage left
+ state->current_page = (state->current_page + 1) % TIME_LEFT_FACE_SETTINGS_STATE;
+ } else {
+ // we are in settings mode, so increment the current settings value
+ _handle_alarm_button(state);
+ }
+ _draw(state, 0);
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ if (state->current_page >= TIME_LEFT_FACE_SETTINGS_STATE) {
+ // initiate fast cycling for settings values
+ movement_request_tick_frequency(8);
+ _quick_ticks_running = true;
+ _handle_alarm_button(state);
+ _draw(state, 0);
+ }
+ break;
+ case EVENT_TIMEOUT:
+ movement_move_to_face(0);
+ break;
+ default:
+ movement_default_loop_handler(event, settings);
+ break;
+ }
+
+ return true;
+}
+
+void time_left_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ time_left_state_t *state = (time_left_state_t *)context;
+
+ if (state->current_page >= TIME_LEFT_FACE_SETTINGS_STATE) _resume_setting(state);
+
+ // if the user changed their birth date, store it to the birth date register
+ if (state->birth_date_when_activated.reg != state->birth_date.reg) {
+ watch_store_backup_data(state->birth_date.reg, 2);
+ }
+}
diff --git a/movement/watch_faces/complication/time_left_face.h b/movement/watch_faces/complication/time_left_face.h
new file mode 100644
index 00000000..0ed7fd28
--- /dev/null
+++ b/movement/watch_faces/complication/time_left_face.h
@@ -0,0 +1,89 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 Andreas Nebinger, based on the work of Joey Castillo
+ *
+ * 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 TIME_LEFT_FACE_H_
+#define TIME_LEFT_FACE_H_
+
+#include "movement.h"
+
+/*
+ * The Time Left Face helps you to visualize how far you have proceeded in a certain
+ * time span. Much like the Day One Face, you can set your beginning date. In addition
+ * to that, you also set your target or destination date. You can then use the face
+ * to display your progress in different ways.
+ *
+ * Usage:
+ *
+ * - Long pressing of the light button starts the settings mode:
+ * - First, you set the beginning date (indicated by a 'b' in the upper right corner).
+ * - Start by setting the year (indicated by the letter 'YR'). Use the alarm button
+ * to cycle the value. Short pressing the light button brings you to the next
+ * settings page.
+ * - Set the values in this order:
+ * a. beginning date (indicated by a 'b'): year - month - day
+ * b. destination date (indicated by a 'd'): year - month - day
+ * - After cycling through all settings pages, the face resumes to display mode.
+ *
+ * - In display mode, use the alarm button (short press) to cycle through these four
+ * types of display:
+ * a. number of days left ('DL') until the destination date is reached.
+ * b. remaining days expressed as percentage of total time span. The value is shown
+ * with two decimals, using the colon as decimal point.
+ * c. number of days passed ('DA') since the beginning date.
+ * d. number of days passed expressed as percentage of total time span. (Two decimal
+ * points.)
+ *
+ * What is this for?
+ *
+ * You can use this watch face to be reminded of any kind of progess between a set
+ * start and end date. The brave among us can use it as a kind of memento mori
+ * visualization. Set your date of birth and look up the average life expectancy of
+ * your age cohort based on publicly available mortality tables. Then, set the
+ * statistically expected day of death as the target date and you will be able to
+ * see how much of your time has passed and how much is still to come.
+ *
+ */
+
+typedef struct {
+ uint8_t current_page;
+ uint16_t current_year;
+ movement_birthdate_t birth_date;
+ movement_birthdate_t birth_date_when_activated;
+ movement_birthdate_t target_date;
+} time_left_state_t;
+
+void time_left_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void time_left_face_activate(movement_settings_t *settings, void *context);
+bool time_left_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void time_left_face_resign(movement_settings_t *settings, void *context);
+
+#define time_left_face ((const watch_face_t){ \
+ time_left_face_setup, \
+ time_left_face_activate, \
+ time_left_face_loop, \
+ time_left_face_resign, \
+ NULL, \
+})
+
+#endif // TIME_LEFT_FACE_H_
diff --git a/movement/watch_faces/complication/toss_up_face.c b/movement/watch_faces/complication/toss_up_face.c
new file mode 100644
index 00000000..08dd0052
--- /dev/null
+++ b/movement/watch_faces/complication/toss_up_face.c
@@ -0,0 +1,793 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 "toss_up_face.h"
+#if __EMSCRIPTEN__
+#include <time.h>
+#else
+#include "saml22j18a.h"
+#endif
+
+static const char heads[] = { '8', 'h', '4', 'E', '(' };
+static const char tails[] = { '0', '+', 'N', '3', ')' };
+static const uint8_t dd[] = {2, 4, 6, 8, 10,12,20,24,30,32,36,48,99};
+
+static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice);
+static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails);
+void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state);
+static void _toss_up_face_display(toss_up_state_t *state);
+static void _dice_animation(toss_up_state_t *state);
+static void _coin_animation(toss_up_state_t *state);
+
+// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
+
+void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) {
+ (void) watch_face_index;
+ (void) settings;
+ if (*context_ptr == NULL) {
+ *context_ptr = malloc(sizeof(toss_up_state_t));
+ memset(*context_ptr, 0, sizeof(toss_up_state_t));
+ toss_up_state_t *state = (toss_up_state_t *)*context_ptr;
+
+ // defaults
+ state->coin_num = 1;
+ state->dice_num = 1;
+ state->dice_sides[0] = 6;
+ state->dice_sides[1] = 6;
+ state->dice_sides[2] = 6;
+ state->coin_style[0] = '8';
+ state->coin_style[1] = '0';
+ }
+}
+
+void toss_up_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ toss_up_state_t *state = (toss_up_state_t *)context;
+ uint8_t i = 0;
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ watch_display_string(" Coins ", 0);
+ break;
+ case EVENT_TICK:
+ if ( state->animate ) {
+ state->animation = (state->animation + 1);
+ _toss_up_face_display(state);
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_DOWN:
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ if ( state->animate ) break;
+ // change between coins and dice
+ if ( state->mode <= 1 ) state->mode = 2;
+ else if ( state->mode >= 2 ) state->mode = 0;
+ _toss_up_face_display(state);
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ // toss
+ if ( state->animate ) break;
+ switch (state->mode) {
+ case 0:
+ state->mode++;
+ // fall through
+ case 1:
+ state->animate = true;
+ for (i = 0; i < state->coin_num; i++) {
+ state->coins[i] = divine_bit();
+ }
+ break;
+ case 2:
+ state->mode++;
+ // fall through
+ case 3:
+ state->animate = true;
+ for (i = 0; i < state->dice_num; i++) {
+ state->dice[i] = roll_dice(state->dice_sides[i]);
+ }
+ break;
+ default:
+ break;
+ }
+ _toss_up_face_display(state);
+ break;
+ case EVENT_LIGHT_LONG_PRESS:
+ if ( state->animate ) break;
+ state->animate = false;
+ switch (state->mode) {
+ case 0: // change to default coin style
+ state->coin_style[0] = heads[0];
+ state->coin_style[1] = tails[0];
+ state->coinface = 0;
+ break;
+ case 1: // change the coin style
+ state->coinface = (state->coinface + 1) % 5;
+ state->coin_style[0] = heads[state->coinface];
+ state->coin_style[1] = tails[state->coinface];
+ break;
+ case 2: // change to default dice sides
+ state->dice_sides[0] = 6;
+ state->dice_sides[1] = 6;
+ state->dice_sides[2] = 6;
+ state->dd = 0;
+ break;
+ case 3: // change the sides of the dice
+ state->dd = (state->dd + 1) % 13;
+ state->dice_sides[state->dice_num-1] = dd[state->dd];
+ state->dice[state->dice_num-1] = dd[state->dd];
+ break;
+ default:
+ break;
+ }
+ _toss_up_face_display(state);
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ if ( state->animate ) break;
+ state->animate = false;
+ switch (state->mode) {
+ case 0: // back to one coin
+ state->coin_num = 1;
+ break;
+ case 1: // up to 6 coins total
+ state->coin_num = (state->coin_num % 6) + 1;
+ break;
+ case 2: // back to one dice
+ state->dice_num = 1;
+ break;
+ case 3: // add up to 3 dice total
+ state->dice_num = (state->dice_num % 3) + 1;
+ state->dd = 0;
+ break;
+ default:
+ break;
+ }
+ _toss_up_face_display(state);
+ break;
+ default:
+ return movement_default_loop_handler(event, settings);
+ }
+
+ return true;
+}
+
+void toss_up_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+}
+
+// STATIC FUNCTIONS ///////////////////////////////////////////////////////////
+
+/** @brief handles the display
+ */
+static void _toss_up_face_display(toss_up_state_t *state) {
+ char buf[11] = {0};
+ char token[7] = {0};
+ switch ( state->mode ) {
+ case 0: // coins title
+ sprintf(buf, " Coins ");
+ break;
+ case 1: // coins divination
+ _coin_animation(state);
+ if ( !state->animate ) {
+ watch_clear_display();
+ _display_coins(token, state->coins, state->coin_num, state);
+ sprintf(buf, " %s", token);
+ }
+ break;
+ case 2: // dice title
+ sprintf(buf, " Dice ");
+ break;
+ case 3: // dice divination
+ _dice_animation(state);
+ if ( !state->animate ) {
+ _roll_dice_multiple(token, state->dice, state->dice_num + 1);
+ sprintf(buf, " %s", token);
+ }
+ break;
+ default:
+ break;
+ }
+ watch_display_string(buf, 0);
+}
+
+/** @brief divination method to derive a bit from 32 TRNG bits
+ */
+uint8_t divine_bit(void) {
+ uint32_t stalks;
+ do { // modulo bias filter
+ stalks = get_true_entropy(); // get 32 TRNG bits as stalks
+ } while (stalks >= INT32_MAX || stalks <= 0);
+
+ uint8_t pile1_xor = 0;
+ uint8_t pile2_xor = 0;
+ // Divide the stalks into two piles, alternating ends
+ for (uint8_t i = 0; i < 16; i++) {
+ uint8_t left_bit = (stalks >> (31 - 2*i)) & 1;
+ uint8_t right_bit = (stalks >> (30 - 2*i)) & 1;
+ if (i % 2 == 0) {
+ pile1_xor ^= left_bit;
+ pile2_xor ^= right_bit;
+ } else {
+ pile1_xor ^= right_bit;
+ pile2_xor ^= left_bit;
+ }
+ }
+ // Take the XOR of the pile results
+ uint8_t result_xor = pile1_xor ^ pile2_xor;
+ // Output 1 if result_xor is 1, 0 otherwise
+ return result_xor;
+}
+
+/** @brief get 32 True Random Number bits
+ */
+uint32_t get_true_entropy(void) {
+ #if __EMSCRIPTEN__
+ return rand() % INT32_MAX;
+ #else
+ hri_mclk_set_APBCMASK_TRNG_bit(MCLK);
+ hri_trng_set_CTRLA_ENABLE_bit(TRNG);
+
+ while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY)); // Wait for TRNG data to be ready
+
+ hri_trng_clear_CTRLA_ENABLE_bit(TRNG);
+ hri_mclk_clear_APBCMASK_TRNG_bit(MCLK);
+ return hri_trng_read_DATA_reg(TRNG); // Read a single 32-bit word from TRNG and return it
+ #endif
+}
+
+// COIN FUNCTIONS /////////////////////////////////////////////////////////////
+
+/** @brief sort tossed coins into a pile of heads and a pile of tails
+ */
+static void _sort_coins(char* token, uint8_t num_bits, uint8_t bits, char* heads, char* tails) {
+ uint8_t num_ones = 0;
+ for (uint8_t i = 0; i < num_bits; i++) {
+ if ((bits >> i) & 1) {
+ *token++ = *heads;
+ num_ones++;
+ }
+ }
+ if ( num_bits < 6 ) {
+ for (uint8_t i = 0; i < (6 - num_bits); i++) {
+ *token++ = ' ';
+ }
+ }
+ for (uint8_t i = 0; i < (num_bits - num_ones); i++) {
+
+ *token++ = *tails;
+ }
+}
+
+/** @brief convert bool array of coinflips to integer for sorting
+ */
+void _display_coins(char* token, bool* bit_array, uint8_t length, toss_up_state_t *state) {
+ uint8_t bits = 0;
+ for (uint8_t i = 0; i < length; i++) {
+ if (bit_array[i]) {
+ bits |= (1 << (length - 1 - i));
+ }
+ }
+ _sort_coins(token, length, bits, &state->coin_style[0], &state->coin_style[1]);
+}
+
+/** @brief coin animation
+ */
+static void _coin_animation(toss_up_state_t *state) {
+ bool heads = false;
+ bool tails = false;
+ for (uint8_t i = 0; i < state->coin_num; i++) {
+ if (state->coins[i] == true) {
+ heads = true;
+ } else {
+ tails = true;
+ }
+ }
+ movement_request_tick_frequency(32);
+ switch ( state->animation ) {
+ case 0:
+ watch_display_string(" ", 4);
+ if ( heads ) {
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 18);
+ } else {
+ state->animation = 12;
+ }
+ break;
+ case 1:
+ if ( heads ) {
+ watch_set_pixel(1, 18);
+ }
+ break;
+ case 2:
+ if ( heads ) {
+ watch_set_pixel(0, 19);
+ watch_set_pixel(2, 19);
+ }
+ break;
+ case 3:
+ if ( heads ) {
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 18);
+ }
+ break;
+ case 4:
+ if ( heads ) {
+ watch_clear_pixel(1, 18);
+ }
+ break;
+ case 5:
+ if ( heads ) {
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(2, 19);
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ }
+ break;
+ case 6:
+ if ( heads ) {
+ watch_set_pixel(2, 20);
+ watch_set_pixel(0, 21);
+ }
+ break;
+ case 7:
+ if ( heads ) {
+ watch_set_pixel(1, 21);
+ watch_set_pixel(2, 21);
+ }
+ break;
+ case 8:
+ if ( heads ) {
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ }
+ break;
+ case 9:
+ if ( heads ) {
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ }
+ break;
+ case 10:
+ if ( heads ) {
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 21);
+ watch_set_pixel(1, 22);
+ watch_set_pixel(2, 22);
+ }
+ break;
+ case 11:
+ if ( heads ) {
+ watch_set_pixel(0, 22);
+ }
+ break;
+ case 12:
+ if ( heads ) {
+ watch_set_pixel(2, 23);
+ watch_set_pixel(0, 23);
+ }
+ if ( tails ) {
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 18);
+ }
+ break;
+ case 13:
+ if ( heads ) {
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(1, 18);
+ }
+ break;
+ case 14:
+ if ( heads ) {
+ watch_clear_pixel(0, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(0, 19);
+ watch_set_pixel(2, 19);
+ }
+ break;
+ case 15:
+ if ( heads ) {
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 18);
+ }
+ break;
+ case 16:
+ if ( heads ) {
+ watch_set_pixel(2, 1);
+ watch_set_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 18);
+ }
+ break;
+ case 17:
+ if ( heads ) {
+ watch_set_pixel(2, 10);
+ watch_set_pixel(0, 1);
+ }
+ if ( tails ) {
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(2, 19);
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ }
+ break;
+ case 18:
+ if ( heads ) {
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 20);
+ watch_set_pixel(0, 21);
+ }
+ break;
+ case 19:
+ if ( heads ) {
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(1, 21);
+ watch_set_pixel(2, 21);
+ }
+ break;
+ case 20:
+ if ( heads ) {
+ watch_set_pixel(2, 1);
+ watch_set_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ }
+ break;
+ case 21:
+ if ( heads ) {
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ }
+ break;
+ case 22:
+ if ( heads ) {
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(0, 1);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 21);
+ watch_set_pixel(1, 22);
+ watch_set_pixel(2, 22);
+ }
+ break;
+ case 23:
+ if ( heads ) {
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(0, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(0, 22);
+ }
+ break;
+ case 24:
+ if ( heads ) {
+ watch_set_pixel(2, 23);
+ watch_set_pixel(0, 23);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(1, 0);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 23);
+ watch_set_pixel(0, 23);
+ }
+ break;
+ case 25:
+ if ( heads ) {
+ watch_set_pixel(0, 22);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 22);
+ }
+ break;
+ case 26:
+ if ( heads ) {
+ watch_set_pixel(1, 22);
+ watch_set_pixel(2, 22);
+ }
+ if ( tails ) {
+ watch_clear_pixel(0, 22);
+ }
+ break;
+ case 27:
+ if ( heads ) {
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 23);
+ watch_clear_pixel(0, 23);
+ watch_set_pixel(2, 0);
+ watch_set_pixel(1, 0);
+ }
+ break;
+ case 28:
+ if ( heads ) {
+ watch_clear_pixel(0, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 1);
+ watch_set_pixel(0, 0);
+ }
+ break;
+ case 29:
+ if ( heads ) {
+ watch_set_pixel(1, 21);
+ watch_set_pixel(2, 21);
+ watch_clear_pixel(1, 22);
+ watch_clear_pixel(2, 22);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 10);
+ watch_set_pixel(0, 1);
+ }
+ break;
+ case 30:
+ if ( heads ) {
+ watch_set_pixel(2, 20);
+ watch_set_pixel(0, 21);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(2, 0);
+ }
+ break;
+ case 31:
+ if ( heads ) {
+ watch_set_pixel(1, 17);
+ watch_set_pixel(0, 20);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(0, 0);
+ }
+ break;
+ case 32:
+ if ( heads ) {
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 21);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(0, 1);
+ watch_set_pixel(0, 2);
+ watch_set_pixel(1, 2);
+ }
+ break;
+ case 33:
+ if ( heads ) {
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(0, 21);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 2);
+ watch_set_pixel(0, 3);
+ }
+ break;
+ case 34:
+ if ( heads ) {
+ watch_set_pixel(0, 19);
+ watch_set_pixel(2, 19);
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 20);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 3);
+ watch_set_pixel(0, 4);
+ }
+ break;
+ case 35:
+ if ( heads ) {
+ watch_set_pixel(1, 18);
+ }
+ if ( tails ) {
+ watch_clear_pixel(1, 2);
+ watch_clear_pixel(0, 2);
+ }
+ break;
+ case 36:
+ if ( heads ) {
+ watch_set_pixel(0, 18);
+ watch_set_pixel(2, 18);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 2);
+ watch_clear_pixel(0, 3);
+ }
+ break;
+ case 37:
+ if ( heads ) {
+ watch_clear_pixel(0, 19);
+ watch_clear_pixel(2, 19);
+ }
+ if ( tails ) {
+ watch_clear_pixel(2, 3);
+ watch_clear_pixel(0, 4);
+ watch_set_pixel(1, 4);
+ watch_set_pixel(0, 5);
+ }
+ break;
+ case 38:
+ if ( heads ) {
+ watch_clear_pixel(1, 18);
+ }
+ if ( tails ) {
+ watch_set_pixel(2, 4);
+ watch_set_pixel(0, 6);
+ }
+ break;
+ case 39:
+ if ( heads ) {
+ watch_clear_pixel(0, 18);
+ watch_clear_pixel(2, 18);
+ }
+ if ( tails ) {
+ watch_set_pixel(1, 6);
+ watch_set_pixel(2, 5);
+ }
+ state->animate = false;
+ state->animation = 0;
+ movement_request_tick_frequency(1);
+ }
+}
+
+// DICE FUNCTIONS /////////////////////////////////////////////////////////////
+
+/** @brief rolls a dice
+ */
+uint8_t roll_dice(uint8_t sides) {
+ uint8_t bits_needed = 0;
+ uint8_t temp_sides = sides - 1;
+ uint8_t result = 0;
+ while (temp_sides > 0) {
+ bits_needed++; // how many bits do we need to represent this number?
+ temp_sides >>= 1; // Shift right to check the next bit
+ }
+ do {
+ result = 0;
+ for (int i = 0; i < bits_needed; i++) {
+ result <<= 1; // Shift left to make room for the next bit
+ result |= divine_bit(); // Add the next bit to the result
+ }
+ } while ( result > sides -1 );
+ return result + 1; // Add 1 to convert the range from 0 to sides-1 to 1 to sides
+}
+
+/** @brief roll multiple dice and print a char array for displaying them
+ */
+static void _roll_dice_multiple(char* result, uint8_t* dice, uint8_t num_dice) {
+ // initialize the result array to all spaces
+ memset(result, ' ', 6);
+
+ // roll the dice and write the result to the result array
+ for (uint8_t i = 0; i < num_dice-1; i++) {
+ uint8_t dice_result = dice[i];
+ uint8_t tens_digit = dice_result / 10;
+ uint8_t ones_digit = dice_result % 10;
+ result[(i * 2)] = tens_digit == 0 ? ' ' : (char)('0' + tens_digit);
+ result[(i * 2) + 1] = (char)('0' + ones_digit);
+ }
+}
+
+/** @brief dice animation
+ */
+static void _dice_animation(toss_up_state_t *state) {
+ watch_display_string(" ", 4);
+ for (uint8_t i = 0; i < state->dice_num; i++) {
+ watch_display_string("0",i*2 + 5);
+ }
+ movement_request_tick_frequency(16);
+ switch ( state->animation ) {
+ case 0:
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 0);
+ watch_clear_pixel(1, 6);
+ break;
+ case 1:
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(0, 6);
+ break;
+ case 2:
+ watch_clear_pixel(2, 21);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(0, 5);
+ break;
+ case 3:
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(1, 4);
+ break;
+ case 4:
+ watch_clear_pixel(0, 21);
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(2, 4);
+ break;
+ case 5:
+ watch_clear_pixel(0, 20);
+ watch_clear_pixel(0, 1);
+ watch_clear_pixel(2, 5);
+ break;
+ case 6:
+ watch_clear_pixel(1, 17);
+ watch_clear_pixel(0, 0);
+ watch_clear_pixel(1, 6);
+ break;
+ case 7:
+ watch_clear_pixel(2, 20);
+ watch_clear_pixel(1, 0);
+ watch_clear_pixel(0, 6);
+ break;
+ case 8:
+ watch_clear_pixel(2, 21);
+ watch_clear_pixel(2, 0);
+ watch_clear_pixel(0, 5);
+ break;
+ case 9:
+ watch_clear_pixel(1, 21);
+ watch_clear_pixel(2, 1);
+ watch_clear_pixel(1, 4);
+ break;
+ case 10:
+ watch_clear_pixel(0, 21);
+ watch_clear_pixel(2, 10);
+ watch_clear_pixel(2, 4);
+ break;
+ case 11:
+ watch_clear_pixel(0, 20);
+ watch_clear_pixel(0, 1);
+ watch_clear_pixel(2, 5);
+ state->animate = false;
+ state->animation = 0;
+ movement_request_tick_frequency(1);
+ }
+} \ No newline at end of file
diff --git a/movement/watch_faces/complication/toss_up_face.h b/movement/watch_faces/complication/toss_up_face.h
new file mode 100644
index 00000000..ca6136a7
--- /dev/null
+++ b/movement/watch_faces/complication/toss_up_face.h
@@ -0,0 +1,112 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 TOSS_UP_FACE_H_
+#define TOSS_UP_FACE_H_
+
+#include "movement.h"
+
+/*
+ * TOSS UP FACE
+ * ============
+ *
+ * Playful watch face for games of chance or divination using coins or dice.
+ *
+ * LIGHT switches between Coins and Dice mode
+ *
+ * COINS
+ * =====
+ *
+ * ALARM tosses a coin. If it lands on heads it gets sorted to the left side of the
+ * display, if it lands on tails then sorted to the right side.
+ *
+ * LONG PRESSING ALARM adds up to 5 more coins to the toss for more nuance in the decision
+ * making (e.g. three heads vs two tails could be read as "yes, but with serious doubts").
+ *
+ * LONG PRESSING LIGHT flips through additional style for the coins from the default Ө/O
+ * to H/T (heads/tails), Y/N (yes/no), E/Ǝ, C/Ↄ
+ *
+ * LONG PRESSING ALARM on the "Coins" title page resets to one coin.
+ * LONG PRESSING LIGHT on the "Coins" title page resets the style to Ө/O
+ *
+ * DICE
+ * ====
+ *
+ * ALARM rolls a six sided dice.
+ *
+ * LONG PRESSING ALARM adds up to 2 more dice to the roll.
+ *
+ * LONG PRESSING LIGHT flips through other available polyhedral dice types with less or more
+ * than the default 6 sides. The options are D2, D4, D6, D8, D10, D12, D20, D24, D30, D32, D36,
+ * D48, and a hypothetical D99.
+ *
+ * When more than one dice is used for a roll this changes only the last added dice. (see Note
+ * below)
+ *
+ * LONG PRESSING ALARM on the "Dice" title page resets to one dice.
+ * LONG PRESSING LIGHT on the "Dice" title page resets the dice to D6.
+ *
+ * Please Note: If you need let's say a D8, D12, and D20 for your rolls then the procedure to
+ * set this up would be as follows: from the default screen where you can roll the one D6 dice
+ * you would LONG PRESS LIGHT a few times to change the D6 to a D8, then LONG PRESS ALARM to add
+ * a second dice, LONG PRESS LIGHT again until the second dice changes to D12, then LONG PRESS
+ * ALARM to add the third dice and LONG PRESS LIGHT again a few times until it becomes a D20.
+ *
+ */
+
+typedef struct {
+ // Anything you need to keep track of, put it here!
+ uint32_t entropy;
+ uint8_t mode : 4; // 1 coin, 2 coins, 3 coins, 4 coins, dice, iching, geomnc
+ bool setup;
+ bool coins[6];
+ uint8_t coin_num : 3;
+ char coin_style[2];
+ uint8_t coinface : 3;
+ uint8_t dice[3];
+ uint8_t dice_num : 2;
+ uint8_t dd : 6;
+ uint8_t dice_sides[3];
+ uint8_t animation;
+ bool animate;
+} toss_up_state_t;
+
+uint32_t get_true_entropy(void);
+uint8_t divine_bit(void);
+uint8_t roll_dice(uint8_t sides);
+void toss_up_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void toss_up_face_activate(movement_settings_t *settings, void *context);
+bool toss_up_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void toss_up_face_resign(movement_settings_t *settings, void *context);
+
+#define toss_up_face ((const watch_face_t){ \
+ toss_up_face_setup, \
+ toss_up_face_activate, \
+ toss_up_face_loop, \
+ toss_up_face_resign, \
+ NULL, \
+})
+
+#endif // TOSS_UP_FACE_H_
+
diff --git a/movement/watch_faces/demo/chirpy_demo_face.c b/movement/watch_faces/demo/chirpy_demo_face.c
new file mode 100644
index 00000000..ee3e5b41
--- /dev/null
+++ b/movement/watch_faces/demo/chirpy_demo_face.c
@@ -0,0 +1,335 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Gabor L Ugray
+ *
+ * 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 "chirpy_demo_face.h"
+#include "chirpy_tx.h"
+#include "filesystem.h"
+
+typedef enum {
+ CDM_CHOOSE = 0,
+ CDM_CHIRPING,
+} chirpy_demo_mode_t;
+
+typedef enum {
+ CDP_SCALE = 0,
+ CDP_INFO_SHORT,
+ CDP_INFO_LONG,
+ CDP_INFO_NANOSEC,
+} chirpy_demo_program_t;
+
+typedef struct {
+ // Current mode
+ chirpy_demo_mode_t mode;
+
+ // Selected program
+ chirpy_demo_program_t program;
+
+ // Helps us handle 1/64 ticks during transmission; including countdown timer
+ chirpy_tick_state_t tick_state;
+
+ // Used by chirpy encoder during transmission
+ chirpy_encoder_state_t encoder_state;
+
+} chirpy_demo_state_t;
+
+static uint8_t long_data_str[] =
+ "There once was a ship that put to sea\n"
+ "The name of the ship was the Billy of Tea\n"
+ "The winds blew up, her bow dipped down\n"
+ "O blow, my bully boys, blow (huh)\n"
+ "\n"
+ "Soon may the Wellerman come\n"
+ "To bring us sugar and tea and rum\n"
+ "One day, when the tonguin' is done\n"
+ "We'll take our leave and go\n";
+
+static uint16_t short_data_len = 20;
+
+static uint8_t short_data[] = {
+ 0x27,
+ 0x00,
+ 0x0c,
+ 0x42,
+ 0xa3,
+ 0xd4,
+ 0x06,
+ 0x54,
+ 0x00,
+ 0x00,
+ 0x02,
+ 0x0c,
+ 0x6b,
+ 0x05,
+ 0x5a,
+ 0x09,
+ 0xd8,
+ 0x00,
+ 0xf5,
+ 0x00,
+};
+
+#define NANOSEC_INI_FILE_NAME "nanosec.ini"
+
+static uint8_t *nanosec_buffer = 0;
+static uint16_t nanosec_buffer_size = 0;
+
+void chirpy_demo_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(chirpy_demo_state_t));
+ memset(*context_ptr, 0, sizeof(chirpy_demo_state_t));
+ // Do any one-time tasks in here; the inside of this conditional happens only at boot.
+ }
+ // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep.
+}
+
+void chirpy_demo_face_activate(movement_settings_t *settings, void *context) {
+ (void)settings;
+ chirpy_demo_state_t *state = (chirpy_demo_state_t *)context;
+
+ memset(context, 0, sizeof(chirpy_demo_state_t));
+ state->mode = CDM_CHOOSE;
+ state->program = CDP_SCALE;
+
+ // Do we have nanosec data? Load it.
+ int32_t sz = filesystem_get_file_size(NANOSEC_INI_FILE_NAME);
+ if (sz > 0) {
+ // We will free this in resign.
+ // I don't like any kind of dynamic allocation in long-running embedded software...
+ // But there's no way around it here; I don't want to hard-wire (and squat) any fixed size structure
+ // Nanosec data may change in the future too
+ nanosec_buffer_size = sz + 2;
+ nanosec_buffer = malloc(nanosec_buffer_size);
+ // First two bytes of prefix, so Chirpy RX can recognize this data type
+ nanosec_buffer[0] = 0xc0;
+ nanosec_buffer[1] = 0x00;
+ // Read file
+ filesystem_read_file(NANOSEC_INI_FILE_NAME, (char*)&nanosec_buffer[2], sz);
+ }
+}
+
+// To create / check test file in emulator:
+// echo TestData > nanosec.ini
+// cat nanosec.ini
+
+static void _cdf_update_lcd(chirpy_demo_state_t *state) {
+ watch_display_string("CH", 0);
+ if (state->program == CDP_SCALE)
+ watch_display_string(" SCALE", 4);
+ else if (state->program == CDP_INFO_SHORT)
+ watch_display_string("SHORT ", 4);
+ else if (state->program == CDP_INFO_LONG)
+ watch_display_string(" LOng ", 4);
+ else if (state->program == CDP_INFO_NANOSEC)
+ watch_display_string("nAnO ", 4);
+ else
+ watch_display_string("---- ", 4);
+}
+
+static void _cdf_quit_chirping(chirpy_demo_state_t *state) {
+ state->mode = CDM_CHOOSE;
+ watch_set_buzzer_off();
+ watch_clear_indicator(WATCH_INDICATOR_BELL);
+ movement_request_tick_frequency(1);
+}
+
+static void _cdf_scale_tick(void *context) {
+ chirpy_demo_state_t *state = (chirpy_demo_state_t *)context;
+ chirpy_tick_state_t *tick_state = &state->tick_state;
+
+ // Scale goes in 200Hz increments from 700 Hz to 12.3 kHz -> 58 steps
+ if (tick_state->seq_pos == 58) {
+ _cdf_quit_chirping(state);
+ return;
+ }
+ uint32_t freq = 700 + tick_state->seq_pos * 200;
+ uint32_t period = 1000000 / freq;
+ watch_set_buzzer_period(period);
+ watch_set_buzzer_on();
+ ++tick_state->seq_pos;
+}
+
+static void _cdf_data_tick(void *context) {
+ chirpy_demo_state_t *state = (chirpy_demo_state_t *)context;
+
+ uint8_t tone = chirpy_get_next_tone(&state->encoder_state);
+ // Transmission over?
+ if (tone == 255) {
+ _cdf_quit_chirping(state);
+ return;
+ }
+ uint16_t period = chirpy_get_tone_period(tone);
+ watch_set_buzzer_period(period);
+ watch_set_buzzer_on();
+}
+
+static uint8_t *curr_data_ptr;
+static uint16_t curr_data_ix;
+static uint16_t curr_data_len;
+
+static uint8_t _cdf_get_next_byte(uint8_t *next_byte) {
+ if (curr_data_ix == curr_data_len)
+ return 0;
+ *next_byte = curr_data_ptr[curr_data_ix];
+ ++curr_data_ix;
+ return 1;
+}
+
+static void _cdf_countdown_tick(void *context) {
+ chirpy_demo_state_t *state = (chirpy_demo_state_t *)context;
+ chirpy_tick_state_t *tick_state = &state->tick_state;
+
+ // Countdown over: start actual broadcast
+ if (tick_state->seq_pos == 8 * 3) {
+ tick_state->tick_compare = 3;
+ tick_state->tick_count = -1;
+ tick_state->seq_pos = 0;
+ // We'll be chirping out a scale
+ if (state->program == CDP_SCALE) {
+ tick_state->tick_fun = _cdf_scale_tick;
+ }
+ // We'll be chirping out data
+ else {
+ // Set up the encoder
+ chirpy_init_encoder(&state->encoder_state, _cdf_get_next_byte);
+ tick_state->tick_fun = _cdf_data_tick;
+ // Set up the data
+ curr_data_ix = 0;
+ if (state->program == CDP_INFO_SHORT) {
+ curr_data_ptr = short_data;
+ curr_data_len = short_data_len;
+ } else if (state->program == CDP_INFO_LONG) {
+ curr_data_ptr = long_data_str;
+ curr_data_len = strlen((const char *)long_data_str);
+ } else if (state->program == CDP_INFO_NANOSEC) {
+ curr_data_ptr = nanosec_buffer;
+ curr_data_len = nanosec_buffer_size;
+ }
+ }
+ return;
+ }
+ // Sound or turn off buzzer
+ if ((tick_state->seq_pos % 8) == 0) {
+ watch_set_buzzer_period(NotePeriods[BUZZER_NOTE_A5]);
+ watch_set_buzzer_on();
+ } else if ((tick_state->seq_pos % 8) == 1) {
+ watch_set_buzzer_off();
+ }
+ ++tick_state->seq_pos;
+}
+
+static void _cdm_setup_chirp(chirpy_demo_state_t *state) {
+ // We want frequent callbacks from now on
+ movement_request_tick_frequency(64);
+ watch_set_indicator(WATCH_INDICATOR_BELL);
+ state->mode = CDM_CHIRPING;
+ // Set up tick state; start with countdown
+ state->tick_state.tick_count = -1;
+ state->tick_state.tick_compare = 8;
+ state->tick_state.seq_pos = 0;
+ state->tick_state.tick_fun = _cdf_countdown_tick;
+}
+
+bool chirpy_demo_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ (void)settings;
+ chirpy_demo_state_t *state = (chirpy_demo_state_t *)context;
+
+ switch (event.event_type) {
+ case EVENT_ACTIVATE:
+ _cdf_update_lcd(state);
+ break;
+ case EVENT_MODE_BUTTON_UP:
+ // Do not exit face while we're chirping
+ if (state->mode != CDM_CHIRPING) {
+ movement_move_to_next_face();
+ }
+ break;
+ case EVENT_LIGHT_BUTTON_UP:
+ // We don't do light.
+ break;
+ case EVENT_ALARM_BUTTON_UP:
+ // If in choose mode: select next program
+ if (state->mode == CDM_CHOOSE) {
+ if (state->program == CDP_SCALE)
+ state->program = CDP_INFO_SHORT;
+ else if (state->program == CDP_INFO_SHORT)
+ state->program = CDP_INFO_LONG;
+ else if (state->program == CDP_INFO_LONG) {
+ if (nanosec_buffer_size > 0)
+ state->program = CDP_INFO_NANOSEC;
+ else
+ state->program = CDP_SCALE;
+ } else if (state->program == CDP_INFO_NANOSEC)
+ state->program = CDP_SCALE;
+ _cdf_update_lcd(state);
+ }
+ // If chirping: stoppit
+ else if (state->mode == CDM_CHIRPING) {
+ _cdf_quit_chirping(state);
+ }
+ break;
+ case EVENT_ALARM_LONG_PRESS:
+ // If in choose mode: start chirping
+ if (state->mode == CDM_CHOOSE) {
+ _cdm_setup_chirp(state);
+ }
+ break;
+ case EVENT_TICK:
+ if (state->mode == CDM_CHIRPING) {
+ ++state->tick_state.tick_count;
+ if (state->tick_state.tick_count == state->tick_state.tick_compare) {
+ state->tick_state.tick_count = 0;
+ state->tick_state.tick_fun(context);
+ }
+ }
+ break;
+ case EVENT_TIMEOUT:
+ // Do not time out while we're chirping
+ if (state->mode != CDM_CHIRPING) {
+ movement_move_to_face(0);
+ }
+ default:
+ break;
+ }
+
+ // Return true if the watch can enter standby mode. False needed when chirping.
+ if (state->mode == CDM_CHIRPING)
+ return false;
+ else
+ return true;
+}
+
+void chirpy_demo_face_resign(movement_settings_t *settings, void *context) {
+ (void)settings;
+ (void)context;
+
+ if (nanosec_buffer != 0) {
+ free(nanosec_buffer);
+ nanosec_buffer = 0;
+ nanosec_buffer_size = 0;
+ }
+}
diff --git a/movement/watch_faces/demo/chirpy_demo_face.h b/movement/watch_faces/demo/chirpy_demo_face.h
new file mode 100644
index 00000000..2d34107a
--- /dev/null
+++ b/movement/watch_faces/demo/chirpy_demo_face.h
@@ -0,0 +1,70 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 <#author_name#>
+ *
+ * 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 CHIRPY_DEMO_FACE_H_
+#define CHIRPY_DEMO_FACE_H_
+
+#include "movement.h"
+
+/*
+ * CHIRPY DEMO FACE
+ *
+ * This face demonstrates the chirpy-tx library. It is intended to help you
+ * include chirpy-tx in your own watch faces that need to transmit data out
+ * of the watch.
+ *
+ * The face's first screen lets you select from a few built-in transmissions:
+ *
+ * SCALE cycles through frequencies in fixed increments. This is intended to
+ * collect and analyze audio samples from different watches. With this info
+ * it may be possible to improve chirpy-tx's parameters like the frequencies
+ * it uses to make the method more robust.
+ *
+ * SHORT is a small transmission that contains data taked from the activity_face.
+ *
+ * LONG is a longer transmission that contains the first two strophes of a
+ * famous sea shanty.
+ *
+ * Select the transmission you want with ALARM, the press LONG ALARM to chirp.
+ *
+ * To record and decode a chirpy transmission on your computer, you can use the web app here:
+ * https://jealousmarkup.xyz/off/chirpy/rx/
+ *
+ */
+
+void chirpy_demo_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void chirpy_demo_face_activate(movement_settings_t *settings, void *context);
+bool chirpy_demo_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void chirpy_demo_face_resign(movement_settings_t *settings, void *context);
+
+#define chirpy_demo_face ((const watch_face_t){ \
+ chirpy_demo_face_setup, \
+ chirpy_demo_face_activate, \
+ chirpy_demo_face_loop, \
+ chirpy_demo_face_resign, \
+ NULL, \
+})
+
+#endif // CHIRPY_DEMO_FACE_H_
+
diff --git a/movement/watch_faces/sensor/lightmeter_face.c b/movement/watch_faces/sensor/lightmeter_face.c
new file mode 100644
index 00000000..861e28d8
--- /dev/null
+++ b/movement/watch_faces/sensor/lightmeter_face.c
@@ -0,0 +1,206 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022 CC
+ *
+ * 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.
+ */
+
+/* Aperture-priority Light Meter Face
+ *
+ * Tested with the "Q3Q-SWAB-A1-00 Temperature + Test Points + OPT3001" flexboard.
+ * This flexboard could use a revision:
+ *
+ * - The thermistor components should be moved west a mm or flipped to the backside
+ * to avoid stressing the flexboard against the processor so much.
+ * - The 'no connect' pad falls off easily.
+ *
+ * Controls:
+ *
+ * - Trigger a measurement by long-pressing Alarm.
+ * Sensor integration is happening when the Signal indicator is on.
+ *
+ * - ISO setting can be cycled by long-pressing Light.
+ * During integration the current ISO setting will be displayed.
+ *
+ * - EV measurement in the top right: "LAP" indicates "half stop".
+ * So "LAP -1" means EV = -1.5. Likewise "LAP 13" means EV = +13.5
+ *
+ * - Aperture in the bottom right: the last 3 main digits are the f-stop.
+ * Adjust this number in half-stop increments using Alarm = +1/2 and Light = -1/2.
+ *
+ * - Best shutter speed in the bottom left: the first 3 digits are the shutter speed.
+ * Some special chars are needed here: "-" = seconds, "h" = extra half second, "K" = thousands.
+ * "HI" or "LO" if there's no shutter in the dictionary within 0.5 stops of correct exposure.
+ *
+ * - Mode long-press changes the main digits to show raw sensor lux measurements.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "lightmeter_face.h"
+#include "watch_utility.h"
+#include "watch_slcd.h"
+
+uint16_t lightmeter_mod(uint16_t m, uint16_t n) { return (m%n + n)%n; }
+
+void lightmeter_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(lightmeter_state_t));
+ lightmeter_state_t *state = (lightmeter_state_t*) *context_ptr;
+ state->waiting_for_conversion = 0;
+ state->lux = 0.0;
+ state->mode = 0;
+ state->iso = LIGHTMETER_ISO_100;
+ state->ap = LIGHTMETER_AP_4P0;
+ }
+}
+
+void lightmeter_face_activate(movement_settings_t *settings, void *context) {
+ (void) settings;
+ lightmeter_state_t *state = (lightmeter_state_t*) context;
+ state->waiting_for_conversion = 0;
+ lightmeter_show_ev(state); // Print most current reading
+ watch_enable_i2c();
+ return;
+}
+
+void lightmeter_show_ev(lightmeter_state_t *state) {
+
+ float ev = max(min(
+ log2(state->lux) +
+ lightmeter_isos[state->iso].ev +
+ LIGHTMETER_CALIBRATION,
+ 99), -9);
+ int evt = round(2*ev); // Truncated EV
+
+ // Print EV
+ char strbuff[7];
+ watch_clear_all_indicators();
+ watch_display_string("EV ", 0);
+
+ sprintf(strbuff, "%2i", (uint16_t) abs(evt/2)); // Print whole part of EV
+ watch_display_string(strbuff, 2);
+ if(evt%2) watch_set_indicator(WATCH_INDICATOR_LAP); // Indicate half stop
+ if(ev<0) watch_set_pixel(1,9); // Indicate negative EV
+
+ // Handle lux mode
+ if(state->mode == 1) {
+ sprintf(strbuff, "%6.0f", min(state->lux, 999999.0));
+ watch_display_string(strbuff, 4);
+ return;
+ }
+
+ // Find and print best shutter speed
+ uint16_t bestsh = 0;
+ float besterr = 1.0/0.0;
+ float errbuf = 1.0/0.0;
+ float comp_ev = ev + lightmeter_aps[state->ap].ev;
+ for(uint16_t ind = 2; ind < LIGHTMETER_N_SHS; ind++) {
+ errbuf = comp_ev + lightmeter_shs[ind].ev;
+ if( fabs(errbuf) < fabs(besterr)) {
+ besterr = errbuf;
+ bestsh = ind;
+ }
+ }
+ if(besterr >= 0.5) watch_display_string(lightmeter_shs[LIGHTMETER_SH_HIGH].str, 4);
+ else if(besterr <= -0.5) watch_display_string(lightmeter_shs[LIGHTMETER_SH_LOW].str, 4);
+ else watch_display_string(lightmeter_shs[bestsh].str, 4);
+
+ // Print aperture
+ watch_display_string(lightmeter_aps[state->ap].str, 7);
+ return;
+}
+
+bool lightmeter_face_loop(movement_event_t event, movement_settings_t *settings, void *context) {
+ (void) settings;
+ lightmeter_state_t *state = (lightmeter_state_t*) context;
+
+ opt3001_Config_t c;
+ switch (event.event_type) {
+ case EVENT_TICK:
+ if(state->waiting_for_conversion) { // Check if measurement is ready...
+ c = opt3001_readConfig(lightmeter_addr);
+ if(c.ConversionReady) {
+ state->waiting_for_conversion = 0;
+ opt3001_t result = opt3001_readResult(lightmeter_addr);
+ state->lux = result.lux;
+ lightmeter_show_ev(state);
+ }
+ }
+ break;
+
+ case EVENT_ALARM_BUTTON_UP: // Increment aperture
+ state->ap = lightmeter_mod(state->ap+1, LIGHTMETER_N_APS);
+
+ lightmeter_show_ev(state);
+ break;
+
+ case EVENT_LIGHT_BUTTON_UP: // Decrement aperture
+ if(state->ap == 0) state->ap = LIGHTMETER_N_APS-1;
+ else state->ap = lightmeter_mod(state->ap-1, LIGHTMETER_N_APS);
+
+ lightmeter_show_ev(state);
+ break;
+
+ case EVENT_LIGHT_LONG_PRESS: // Cycle ISO
+ state->iso = lightmeter_mod(state->iso+1, LIGHTMETER_N_ISOS);
+
+ watch_clear_all_indicators();
+ watch_display_string("EV ", 0);
+ watch_display_string(lightmeter_isos[state->iso].str, 4);
+ break;
+
+ case EVENT_ALARM_LONG_PRESS: // Take measurement
+ opt3001_writeConfig(lightmeter_addr, lightmeter_takeNewReading);
+ state->waiting_for_conversion = 1;
+
+ watch_clear_all_indicators();
+ watch_display_string("EV ", 0);
+ watch_display_string(lightmeter_isos[state->iso].str, 4);
+ watch_set_indicator(WATCH_INDICATOR_SIGNAL);
+ break;
+
+ case EVENT_MODE_LONG_PRESS: // Toggle mode
+ state->mode = !state->mode;
+ lightmeter_show_ev(state);
+ break;
+
+ case EVENT_TIMEOUT:
+ movement_move_to_face(0);
+ break;
+
+ default:
+ movement_default_loop_handler(event, settings);
+ break;
+ }
+ return true;
+}
+
+void lightmeter_face_resign(movement_settings_t *settings, void *context) {
+ (void) settings;
+ (void) context;
+ opt3001_writeConfig(lightmeter_addr, lightmeter_off);
+ watch_disable_i2c();
+ return;
+}
diff --git a/movement/watch_faces/sensor/lightmeter_face.h b/movement/watch_faces/sensor/lightmeter_face.h
new file mode 100644
index 00000000..2f8813f4
--- /dev/null
+++ b/movement/watch_faces/sensor/lightmeter_face.h
@@ -0,0 +1,160 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 CC
+ *
+ * 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 LIGHTMETER_FACE_H_
+#define LIGHTMETER_FACE_H_
+
+#include "movement.h"
+#include "opt3001.h"
+
+#define LIGHTMETER_CALIBRATION 2.58
+typedef struct {
+ char * str;
+ float ev;
+} lightmeter_ev_t;
+
+static const lightmeter_ev_t lightmeter_isos[] = {
+ {" i 25", -2},
+ {" i 50", -1},
+ {" i 100", 0},
+ {" i 160", 0.68},
+ {" i 200", 1},
+ {" i 400", 2},
+ {" i 800", 3},
+ {" i1600", 4}};
+typedef enum {
+ LIGHTMETER_ISO_25, LIGHTMETER_ISO_50, LIGHTMETER_ISO_100, LIGHTMETER_ISO_160, LIGHTMETER_ISO_200, LIGHTMETER_ISO_400, LIGHTMETER_ISO_800, LIGHTMETER_ISO_1600,
+ LIGHTMETER_N_ISOS
+} lightmeter_iso_t;
+
+static const lightmeter_ev_t lightmeter_aps[] = {
+ {"1.4", 0},
+ {"1.8", -0.5},
+ {"2.0", -1},
+ {"2.4", -1.5},
+ {"2.8", -2},
+ {"3.3", -2.5},
+ {"4.0", -3},
+ {"4.8", -3.5},
+ {"5.6", -4},
+ {"6.7", -4.5},
+ {"8.0", -5},
+ {"9.5", -5.5},
+ {"11.", -6},
+ {"13.", -6.5},
+ {"16.", -7},
+ {"19.", -7.5},
+ {"22.", -8}};
+typedef enum {
+ LIGHTMETER_AP_1P4, LIGHTMETER_AP_1P8, LIGHTMETER_AP_2P0, LIGHTMETER_AP_2P4, LIGHTMETER_AP_2P8, LIGHTMETER_AP_3P3, LIGHTMETER_AP_4P0, LIGHTMETER_AP_4P8, LIGHTMETER_AP_5P6, LIGHTMETER_AP_6P7, LIGHTMETER_AP_8P0, LIGHTMETER_AP_9P5,
+ LIGHTMETER_AP_11, LIGHTMETER_AP_13, LIGHTMETER_AP_16, LIGHTMETER_AP_19, LIGHTMETER_AP_22,
+ LIGHTMETER_N_APS
+} lightmeter_ap_t;
+
+static const lightmeter_ev_t lightmeter_shs[] = {
+ {"LO-", 99},
+ {"HI ", -99},
+ {"30-", 5.0},
+ {"20-", 4.5},
+ {"15-", 4.0},
+ {"11-", 3.5},
+ {"8- ", 3.0},
+ {"6- ", 2.5},
+ {"4- ", 2.0},
+ {"3- ", 1.5},
+ {"2- ", 1.0},
+ {"1h-", 0.5},
+ {"1 ", 0.0},
+ {"1h ", -0.5},
+ {"2 ", -1.0},
+ {"3 ", -1.5},
+ {"4 ", -2.0},
+ {"6 ", -2.5},
+ {"8 ", -3.0},
+ {"12 ", -3.5},
+ {"15 ", -4.0},
+ {"20 ", -4.5},
+ {"30 ", -5.0},
+ {"45 ", -5.5},
+ {"60 ", -6.0},
+ {"90 ", -6.5},
+ {"125", -7.0},
+ {"180", -7.5},
+ {"250", -8.0},
+ {"350", -8.5},
+ {"500", -9.0},
+ {"750", -9.5},
+ {"1K ", -10.0},
+ {"1K5", -10.5},
+ {"2K ", -11.0},
+ {"3K ", -11.5},
+ {"4K ", -12.0},
+ {"6K ", -12.5},
+ {"8K ", -13.0}};
+typedef enum {
+ LIGHTMETER_SH_LOW, LIGHTMETER_SH_HIGH,
+ LIGHTMETER_SH_30S, LIGHTMETER_SH_20S, LIGHTMETER_SH_15S, LIGHTMETER_SH_11S, LIGHTMETER_SH_8S, LIGHTMETER_SH_6S, LIGHTMETER_SH_3S, LIGHTMETER_SH_4S, LIGHTMETER_SH_2S, LIGHTMETER_SH_1HS,
+ LIGHTMETER_SH_1, LIGHTMETER_SH_1H, LIGHTMETER_SH_2, LIGHTMETER_SH_3, LIGHTMETER_SH_4, LIGHTMETER_SH_6, LIGHTMETER_SH_8, LIGHTMETER_SH_12, LIGHTMETER_SH_15, LIGHTMETER_SH_20, LIGHTMETER_SH_30, LIGHTMETER_SH_45, LIGHTMETER_SH_60, LIGHTMETER_SH_90, LIGHTMETER_SH_125, LIGHTMETER_SH_180, LIGHTMETER_SH_250, LIGHTMETER_SH_350, LIGHTMETER_SH_500, LIGHTMETER_SH_750,
+ LIGHTMETER_SH_1K, LIGHTMETER_SH_1K5, LIGHTMETER_SH_2K, LIGHTMETER_SH_3K, LIGHTMETER_SH_4K, LIGHTMETER_SH_6K, LIGHTMETER_SH_8K,
+ LIGHTMETER_N_SHS
+} lightmeter_sh_t;
+
+typedef struct {
+ lightmeter_iso_t iso;
+ lightmeter_ap_t ap;
+ bool waiting_for_conversion;
+ float lux;
+ int mode;
+} lightmeter_state_t;
+
+static const opt3001_Config_t lightmeter_takeNewReading = {
+ .RangeNumber = 0B1100,
+ .ConversionTime = 0B1,
+ .Latch = 0B1,
+ .ModeOfConversionOperation = 0B01
+};
+
+static const opt3001_Config_t lightmeter_off = {
+ .ModeOfConversionOperation = 0B00
+};
+
+uint16_t lightmeter_mod(uint16_t m, uint16_t n);
+
+void lightmeter_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void lightmeter_face_activate(movement_settings_t *settings, void *context);
+void lightmeter_show_ev(lightmeter_state_t *state);
+bool lightmeter_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void lightmeter_face_resign(movement_settings_t *settings, void *context);
+
+static const uint8_t lightmeter_addr = 0x44;
+
+#define lightmeter_face ((const watch_face_t){ \
+ lightmeter_face_setup, \
+ lightmeter_face_activate, \
+ lightmeter_face_loop, \
+ lightmeter_face_resign, \
+ NULL, \
+})
+
+#endif // LIGHTMETER_FACE_H_
diff --git a/movement/watch_faces/settings/place_face.h b/movement/watch_faces/settings/place_face.h
new file mode 100644
index 00000000..a98c2642
--- /dev/null
+++ b/movement/watch_faces/settings/place_face.h
@@ -0,0 +1,231 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 Tobias Raayoni Last / @randogoth
+ *
+ * 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 place_FACE_H_
+#define place_FACE_H_
+
+#include "movement.h"
+
+/*
+ * PLACE FACE
+ * ==========
+ *
+ * Based on and expanded from the Sunrise/Sunset face. Outsourced the location setting functionality to
+ * its own face. Also serves as a converter between different coordinate notation formats.
+ *
+ * With the LIGHT button each place coordinate can be shown and edited in 4 different display modes:
+ *
+ * 1) Decimal Latitude and Longitude (WGS84) up to 5 decimal points
+ * 2) Latitude and Longitude (WGS84) in traditional DD°MM'SS" notation
+ * 3) Ten digit Open Location Code (aka. PlusCode) format
+ * 4) Ten digit Geohash format
+ *
+ * Using the ALARM button the user can flip through 2 pages of coordinate info to see the first and
+ * second sets of digits.
+ *
+ * (please also refer to the notes on precision below)
+ *
+ * Editing Mode
+ * ============
+ *
+ * A LONG PRESS of the LIGHT button toggles editing mode for each of the selected notations.
+ *
+ * In this mode LIGHT moves the cursor and ALARM changes the letter cycling through the available
+ * alphabet or numbers.
+ *
+ * When OLC or Geohash display are edited, Digit Info mode is activated. It serves as a workaround
+ * for the limitation of how ambiguously alphanumeric characters are displayed on the main seven segment
+ * digits of the watch face ( S or 5, I or 1, U or W?).
+ *
+ * The selected letter is also shown in the much easier to read alphanumeric 8 segment weekday digit above.
+ * In addition the '24H' indicator is active when the selected digit represents a number and the 'PM'
+ * indicator for a letter.
+ *
+ * A LONG PRESS of LIGHT saves the changes.
+ *
+ * Coordinates are read or stored to both the traditional internal location register and a file on
+ * the LFS file system ("place.loc"). By default the Watch Face loads the coordinates from file
+ * when activated. If no file is present, the coordinates are loaded from the register.
+ * (please also see the notes on precision below)
+ *
+ * Auxiliary Mode: Digit Info
+ * ==========================
+ *
+ * A LONG PRESS of the ALARM button toggles Digit Info mode when OLC or Geohash display is active.
+ * (LAP indicator is on) It is a means of being able to see the detailed Digit Info as described above
+ * but without the risk of accidentally editing any of digits.
+ *
+ * Both ALARM and LIGHT buttons can be used to flip through the letters.
+ *
+ * Notes on Coordinate Precision
+ * =============================
+ *
+ * The common WGS84 Latitude and Longitude degrees naturally do not represent meters in distance
+ * on the ground. 1° Longitude on the equatorial line equals a width of 111.32 kilometers, but
+ * at 40° latitude further North or South it is approximately 85 kilometers wide. The closer to
+ * the poles the narrower (and more precise) the latitude degrees get.
+ *
+ * The Sensor Watch's traditional 16bit location register only stores latitudes and longitudes
+ * with two decimal points. That equals a longitudal precision of 36 arc seconds, or ~1111 meters
+ * at the equator - precise enough for astronomical calculations, but not if you want to store the
+ * location of let's say a building.
+ *
+ * Hence we propose the <place.loc> file that serves the same purpose, but with a precision of
+ * five decimal digits. That equals 0.04 arc seconds or 1.11 meters at the equator.
+ *
+ * Please also note that the different notations of this watch face also have varying magnitudes
+ * of precision:
+ *
+ * | Format | Notation | Precision at Equator | Precision at 67° N/S |
+ * | ------------------ | ---------------------- | -------------------- | -------------------- |
+ * | 2d. Decimal LatLon | 29.98, 31.13 | 1111.320 m | 435.125 m |
+ * | 5d. Decimal LatLon | 29.97916, 31.13417 | 1.111 m | 0.435 m |
+ * | DMS LatLon | N 29°58′45″, E 31°8′3″ | 30.833 m | 12.083 m |
+ * | Open Location Code | 7GXHX4HM+MM | 13.875 m | 13.875 m |
+ * | Geohash | stq4s3x1qu | 1.189 m | 0.596 m |
+ *
+ * Since all notations are internally converted into degrees with 5 decimal points, expect some
+ * rounding errors when editing or loading the coordinates in other notation formats.
+ *
+ */
+
+static const char olc_alphabet[20] = "23456789CFGHJMPQRUWX";
+static const char geohash_alphabet[32] = "0123456789bCdEfGhjkmnpqrstuVwxyz";
+
+typedef struct {
+ uint8_t sign: 1; // 0-1
+ uint8_t hundreds: 1; // 0-1
+ uint8_t tens: 4; // 0-9
+ uint8_t ones: 4; // 0-9
+ uint8_t d01: 4; // 0-9
+ uint8_t d02: 4; // 0-9
+ uint8_t d03: 4; // 0-9
+ uint8_t d04: 4; // 0-9
+ uint8_t d05: 4; // 0-9
+} place_format_decimal_latlon_t;
+
+typedef struct {
+ uint8_t sign: 1; // 0-1
+ uint8_t hundreds: 1; // 0-1
+ uint8_t tens: 4; // 0-9
+ uint8_t ones: 4; // 0-9
+ uint8_t mins_tens: 3; // 0-5
+ uint8_t mins_ones: 4; // 0-9
+ uint8_t secs_tens: 3; // 0-5
+ uint8_t secs_ones: 4; // 0-9
+} place_format_dms_latlon_t;
+
+typedef struct {
+ uint8_t lat1: 5; // 2-X
+ uint8_t lon1: 5; // 2-X
+ uint8_t lat2: 5; // 2-X
+ uint8_t lon2: 5; // 2-X
+ uint8_t lat3: 5; // 2-X
+ uint8_t lon3: 5; // 2-X
+ uint8_t lat4: 5; // 2-X
+ uint8_t lon4: 5; // 2-X
+ uint8_t lat5: 5; // 2-X
+ uint8_t lon5: 5; // 2-X
+} place_format_olc_t;
+
+typedef struct {
+ int32_t latitude : 25;
+ int32_t longitude : 26;
+} coordinate_t;
+
+typedef struct {
+ place_format_decimal_latlon_t latitude;
+ place_format_decimal_latlon_t longitude;
+} place_coordinate_t;
+
+typedef struct {
+ uint8_t d01: 6; // 0-z
+ uint8_t d02: 6; // 0-z
+ uint8_t d03: 6; // 0-z
+ uint8_t d04: 6; // 0-z
+ uint8_t d05: 6; // 0-z
+ uint8_t d06: 6; // 0-z
+ uint8_t d07: 6; // 0-z
+ uint8_t d08: 6; // 0-z
+ uint8_t d09: 6; // 0-z
+ uint8_t d10: 6; // 0-z
+} place_format_geohash_t;
+
+typedef struct {
+ double max;
+ double min;
+} place_format_geohash_interval;
+
+typedef struct {
+ uint8_t min_digit : 1;
+ uint8_t max_digit : 3;
+} place_mode_schema_page_t;
+
+typedef struct {
+ uint8_t max_page : 3;
+ place_mode_schema_page_t page[4];
+} place_mode_schema_mode_t;
+
+enum place_modes_e {
+ MODE_DECIMAL = 0,
+ MODE_DMS,
+ MODE_OLC,
+ MODE_GEOHASH
+};
+
+typedef struct {
+ enum place_modes_e mode;
+ uint8_t page : 3;
+ int8_t active_digit: 4;
+ bool edit;
+ bool digit_info;
+ place_format_decimal_latlon_t working_latitude;
+ place_format_decimal_latlon_t working_longitude;
+ place_format_dms_latlon_t working_dms_latitude;
+ place_format_dms_latlon_t working_dms_longitude;
+ place_format_olc_t working_pluscode;
+ place_format_geohash_t working_geohash;
+ place_mode_schema_mode_t modes[4];
+} place_state_t;
+
+// PUBLIC WATCH FACE FUNCTIONS ////////////////////////////////////////////////
+
+void place_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);
+void place_face_activate(movement_settings_t *settings, void *context);
+bool place_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
+void place_face_resign(movement_settings_t *settings, void *context);
+
+void place_latlon_to_olc(char *pluscode, double latitude, double longitude);
+void place_latlon_to_geohash(char *geohash, double latitude, double longitude);
+
+#define place_face ((const watch_face_t){ \
+ place_face_setup, \
+ place_face_activate, \
+ place_face_loop, \
+ place_face_resign, \
+ NULL, \
+})
+
+#endif // place_FACE_H_
+