]> git.earman.xyz Git - sensor-watch.git/commitdiff
bring in Chirpy face
authorjoeycastillo <joeycastillo@utexas.edu>
Tue, 22 Oct 2024 11:49:41 +0000 (07:49 -0400)
committerjoeycastillo <joeycastillo@utexas.edu>
Tue, 22 Oct 2024 11:49:41 +0000 (07:49 -0400)
Makefile
movement/watch_faces/demo/chirpy_demo_face.c [deleted file]
movement/watch_faces/demo/chirpy_demo_face.h [deleted file]
movement_faces.h
watch-faces.mk
watch-faces/io/chirpy_demo_face.c [new file with mode: 0644]
watch-faces/io/chirpy_demo_face.h [new file with mode: 0644]

index 80e42b5bafb3927ddb04736041833d2114de4da1..d37da38a13d172de446a964c04fd12b82aea9065 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -44,6 +44,7 @@ INCLUDES += \
   -I./filesystem \
   -I./shell \
   -I./movement/lib/sunriset \
+  -I./movement/lib/chirpy_tx \
   -I./watch-library/shared/watch \
   -I./watch-library/shared/driver \
   -I./watch-faces/clock \
@@ -51,6 +52,7 @@ INCLUDES += \
   -I./watch-faces/demo \
   -I./watch-faces/sensor \
   -I./watch-faces/settings \
+  -I./watch-faces/io \
 
 # Add your source files here.
 SRCS += \
@@ -62,6 +64,7 @@ SRCS += \
   ./shell/shell.c \
   ./shell/shell_cmd_list.c \
   ./movement/lib/sunriset/sunriset.c \
+  ./movement/lib/chirpy_tx/chirpy_tx.c \
   ./watch-library/shared/driver/thermistor_driver.c \
   ./watch-library/shared/watch/watch_common_buzzer.c \
   ./watch-library/shared/watch/watch_common_display.c \
diff --git a/movement/watch_faces/demo/chirpy_demo_face.c b/movement/watch_faces/demo/chirpy_demo_face.c
deleted file mode 100644 (file)
index 84e1670..0000000
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * 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(uint8_t watch_face_index, void **context_ptr) {
-    (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(void *context) {
-    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_and_duty_cycle(period, 25);
-    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_and_duty_cycle(period, 25);
-    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_and_duty_cycle(NotePeriods[BUZZER_NOTE_A5], 25);
-        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, void *context) {
-    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(void *context) {
-    (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
deleted file mode 100644 (file)
index a3ada8d..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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_
-
-/*
- * 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/
- */
-
-#include "movement.h"
-
-void chirpy_demo_face_setup(uint8_t watch_face_index, void ** context_ptr);
-void chirpy_demo_face_activate(void *context);
-bool chirpy_demo_face_loop(movement_event_t event, void *context);
-void chirpy_demo_face_resign(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_
-
index 2b72745f50330f8d08443c4f423112186d4a5c80..07896a15ef08cbba18fe746302cf5c823a294114 100644 (file)
@@ -44,4 +44,5 @@
 #include "light_sensor_face.h"
 #include "accelerometer_sleep_state_face.h"
 #include "irda_demo_face.h"
+#include "chirpy_demo_face.h"
 // New includes go above this line.
index 3c7f1b4fbb916dcd0473289c85d75fe60e6c0a0f..24930d073b07982aa789f8ffb30b843b7527bd79 100644 (file)
@@ -19,4 +19,5 @@ SRCS += \
   ./watch-faces/demo/light_sensor_face.c \
   ./watch-faces/demo/accelerometer_sleep_state_face.c \
   ./watch-faces/demo/irda_demo_face.c \
+  ./watch-faces/io/chirpy_demo_face.c \
 # New watch faces go above this line.
diff --git a/watch-faces/io/chirpy_demo_face.c b/watch-faces/io/chirpy_demo_face.c
new file mode 100644 (file)
index 0000000..6530512
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * 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 ACTIVITY_DATA_FILE_NAME "activity.dat"
+
+static uint8_t *activity_buffer = 0;
+static uint16_t activity_buffer_size = 0;
+
+void chirpy_demo_face_setup(uint8_t watch_face_index, void **context_ptr) {
+    (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(void *context) {
+    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 activity data? Load it.
+    int32_t sz = filesystem_get_file_size(ACTIVITY_DATA_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
+        activity_buffer_size = sz + 2;
+        activity_buffer = malloc(activity_buffer_size);
+        // First two bytes of prefix, so Chirpy RX can recognize this data type
+        activity_buffer[0] = 0xc0;
+        activity_buffer[1] = 0x00;
+        // Read file
+        filesystem_read_file(ACTIVITY_DATA_FILE_NAME, (char*)&activity_buffer[2], sz);
+    }
+}
+
+// To create / check test file in emulator:
+// echo TestData > activity.ini
+// cat activity.ini
+
+static void _cdf_update_lcd(chirpy_demo_state_t *state) {
+    watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "CH", "Chirp");
+    if (state->program == CDP_SCALE)
+        watch_display_text(WATCH_POSITION_BOTTOM, " SCALE");
+    else if (state->program == CDP_INFO_SHORT)
+        watch_display_text(WATCH_POSITION_BOTTOM, "SHORT ");
+    else if (state->program == CDP_INFO_LONG)
+        watch_display_text(WATCH_POSITION_BOTTOM, " LOng ");
+    else if (state->program == CDP_INFO_NANOSEC)
+        watch_display_text(WATCH_POSITION_BOTTOM, " ACtIV");
+    else
+        watch_display_text(WATCH_POSITION_BOTTOM, "----  ");
+}
+
+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_and_duty_cycle(period, 25);
+    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_and_duty_cycle(period, 25);
+    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 = activity_buffer;
+                curr_data_len = activity_buffer_size;
+            }
+        }
+        return;
+    }
+    // Sound or turn off buzzer
+    if ((tick_state->seq_pos % 8) == 0) {
+        watch_set_buzzer_period_and_duty_cycle(NotePeriods[BUZZER_NOTE_A5], 25);
+        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, void *context) {
+    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 (activity_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(void *context) {
+    (void)context;
+
+    if (activity_buffer != 0) {
+        free(activity_buffer);
+        activity_buffer = 0;
+        activity_buffer_size = 0;
+    }
+}
diff --git a/watch-faces/io/chirpy_demo_face.h b/watch-faces/io/chirpy_demo_face.h
new file mode 100644 (file)
index 0000000..a3ada8d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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_
+
+/*
+ * 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/
+ */
+
+#include "movement.h"
+
+void chirpy_demo_face_setup(uint8_t watch_face_index, void ** context_ptr);
+void chirpy_demo_face_activate(void *context);
+bool chirpy_demo_face_loop(movement_event_t event, void *context);
+void chirpy_demo_face_resign(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_
+