bring in Chirpy face
This commit is contained in:
331
watch-faces/io/chirpy_demo_face.c
Normal file
331
watch-faces/io/chirpy_demo_face.c
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
69
watch-faces/io/chirpy_demo_face.h
Normal file
69
watch-faces/io/chirpy_demo_face.h
Normal file
@@ -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_
|
||||
|
||||
Reference in New Issue
Block a user