Skip to content

Commit

Permalink
Added flowtime watch face and instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
Ucodia committed Dec 17, 2023
1 parent c4b3f88 commit eb9bfdc
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
build/
build-sim/
build-sim/
.DS_Store
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
# flowtime-watch
# Flowtime Watch 🌀

The Flowtime Watch is an implementation of the [Flowtime](https://github.com/Ucodia/flowtime) on a classic Casio watch.

This project depends on the `movement` framework from the [Sensor Watch](https://github.com/joeycastillo/Sensor-Watch) project.

## Dependencies

To build this project, you need to install the `arm-none-eabi` variant of the Arm GNU Toolchain installed on your machine: [Arm GNU Toolchain Downloads](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads)

## Building

After cloning this repository, make sure you have pulled the submodules for `sensor-watch` and its submodules.

```
git submodule update --init
cd sensor-watch
git submodule update --init
```

Hack: These files need to be removed as make does not seem to respect priority of header directory as defined in the Makefile

```
rm sensor-watch/movement/movement_config.h
rm sensor-watch/movement/movement_faces.h
```

Find which Sensor Watch board you are building for (RED, GREEN or BLUE), then run the following command by specifying the proper color

```
cd make
make COLOR=RED
```

## Testing

To test in the emulator, run the following command

```
cd make
emmake make COLOR=RED
python3 -m http.server -d build-sim
```

The emulator should be available at http://localhost:8000/watch.html

## Flashing

To flash your Sensor Watch with the Flowtime watch firmware, copy the `movement/make/build/watch.uf2` firmware file to your board. Read more in the official [Sensor Watch documentation](https://www.sensorwatch.net/docs/firmware/flashing/).
3 changes: 3 additions & 0 deletions make/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ include $(TOP)/make.mk
# -I../drivers/ \
# -I../watch_faces/fitness/
INCLUDES += \
-I../src/ \
-I$(TOP)/movement/ \
-I$(TOP)/movement/watch_faces/ \
-I$(TOP)/movement/watch_faces/clock/ \
Expand Down Expand Up @@ -122,6 +123,8 @@ SRCS += \
$(TOP)/movement/watch_faces/clock/minute_repeater_decimal_face.c \
$(TOP)/movement/watch_faces/complication/tuning_tones_face.c \
$(TOP)/movement/watch_faces/complication/kitchen_conversions_face.c \
../src/flowtime.c \
../src/flowtime_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.
Expand Down
69 changes: 69 additions & 0 deletions src/flowtime.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "flowtime.h"
#include "watch.h"

double nextLcgValue(Lcg *lcg)
{
lcg->z = (lcg->multiplier * lcg->z + lcg->increment) % lcg->modulus;
return (double)lcg->z / lcg->modulus;
}

Lcg createLcg(long long seed, long long modulus, long long multiplier, long long increment)
{
Lcg lcg = {seed, modulus, multiplier, increment};
return lcg;
}

void getSeedsFromDate(watch_date_time date, long long *minuteSeed, long long *hourSeed)
{
char buffer[20];
sprintf(buffer, "%04d%02d%02d%02d", date.unit.year + 2020, date.unit.month, date.unit.day, date.unit.hour);
*minuteSeed = atoll(buffer);

sprintf(buffer, "%04d%02d%02d", date.unit.year + 2020, date.unit.month, date.unit.day);
*hourSeed = atoll(buffer);
}

void getSequenceFromLcg(Lcg lcg, int length, int *sequence)
{
double *values = (double *)malloc(length * sizeof(double));
for (int i = 0; i < length; i++)
{
values[i] = nextLcgValue(&lcg);
}

for (int i = 0; i < length; i++)
{
int count = 0;
for (int j = 0; j < length; j++)
{
if (values[j] < values[i] || (values[j] == values[i] && j < i))
{
count++;
}
}
sequence[i] = count;
}

free(values);
}

Time date_to_flowtime(watch_date_time date)
{
long long minuteSeed, hourSeed;
getSeedsFromDate(date, &minuteSeed, &hourSeed);

Lcg minuteLcg = createLcg(minuteSeed, 2147483647, 48271, 0);
Lcg hourLcg = createLcg(hourSeed, 4294967296, 1664525, 1013904223);

int minuteSequence[60];
int hourSequence[24];

getSequenceFromLcg(minuteLcg, 60, minuteSequence);
getSequenceFromLcg(hourLcg, 24, hourSequence);

Time result = {hourSequence[date.unit.hour], minuteSequence[date.unit.minute], date.unit.second};
return result;
}
22 changes: 22 additions & 0 deletions src/flowtime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "watch.h"

typedef struct Lcg
{
long long z;
long long modulus;
long long multiplier;
long long increment;
} Lcg;

typedef struct Time
{
int hour;
int minute;
int second;
} Time;

double nextLcgValue(Lcg *lcg);
Lcg createLcg(long long seed, long long modulus, long long multiplier, long long increment);
void getSeedsFromDate(watch_date_time date, long long *minuteSeed, long long *hourSeed);
void getSequenceFromLcg(Lcg lcg, int length, int *sequence);
Time date_to_flowtime(watch_date_time date);
110 changes: 110 additions & 0 deletions src/flowtime_face.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include <stdlib.h>
#include "flowtime_face.h"
#include "watch.h"
#include "watch_utility.h"
#include "watch_private_display.h"
#include "flowtime.h"

void flowtime_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(flowtime_state_t));
flowtime_state_t *state = (flowtime_state_t *)*context_ptr;
state->watch_face_index = watch_face_index;
state->reality_check = false;
}
}

void flowtime_face_activate(movement_settings_t *settings, void *context)
{
flowtime_state_t *state = (flowtime_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);

if (state->reality_check)
watch_set_indicator(WATCH_INDICATOR_BELL);
else
watch_clear_indicator(WATCH_INDICATOR_BELL);

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 flowtime_face_loop(movement_event_t event, movement_settings_t *settings, void *context)
{
flowtime_state_t *state = (flowtime_state_t *)context;
char buf[11];
uint8_t pos;

watch_date_time date_time;
Time flow_time;
bool alarm_button_down;
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();
flow_time = date_to_flowtime(date_time);
alarm_button_down = watch_get_pin_level(2);
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);

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, alarm_button_down ? date_time.unit.hour : flow_time.hour, alarm_button_down ? date_time.unit.minute : flow_time.minute);
}
else
{
sprintf(buf, "%s%2d%2d%02d%02d", watch_utility_get_weekday(date_time), date_time.unit.day, alarm_button_down ? date_time.unit.hour : flow_time.hour, alarm_button_down ? date_time.unit.minute : flow_time.minute, date_time.unit.second);
}

watch_display_string(buf, pos);
break;
default:
return movement_default_loop_handler(event, settings);
}

return true;
}

void flowtime_face_resign(movement_settings_t *settings, void *context)
{
(void)settings;
(void)context;
}

bool flowtime_face_wants_background_task(movement_settings_t *settings, void *context)
{
return false;
}
29 changes: 29 additions & 0 deletions src/flowtime_face.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef FLOWTIME_FACE_H_
#define FLOWTIME_FACE_H_

#include "movement.h"

typedef struct
{
uint32_t previous_date_time;
uint8_t last_battery_check;
uint8_t watch_face_index;
bool battery_low;
bool reality_check;
} flowtime_state_t;

void flowtime_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr);
void flowtime_face_activate(movement_settings_t *settings, void *context);
bool flowtime_face_loop(movement_event_t event, movement_settings_t *settings, void *context);
void flowtime_face_resign(movement_settings_t *settings, void *context);
bool flowtime_face_wants_background_task(movement_settings_t *settings, void *context);

#define flowtime_face ((const watch_face_t){ \
flowtime_face_setup, \
flowtime_face_activate, \
flowtime_face_loop, \
flowtime_face_resign, \
flowtime_face_wants_background_task, \
})

#endif // FLOWTIME_FACE_H_
27 changes: 27 additions & 0 deletions src/movement_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef MOVEMENT_CONFIG_H_
#define MOVEMENT_CONFIG_H_

#include "movement_faces.h"

const watch_face_t watch_faces[] = {
flowtime_face,
simple_clock_face,
stopwatch_face,
preferences_face,
set_time_face,
};

#define MOVEMENT_NUM_FACES (sizeof(watch_faces) / sizeof(watch_face_t))

/* Determines what face to go to from the first face on long press of the Mode button.
* Also excludes these faces from the normal rotation.
* In the default firmware, this lets you access temperature and battery voltage with a long press of Mode.
* Some folks also like to use this to hide the preferences and time set faces from the normal rotation.
* If you don't want any faces to be excluded, set this to 0 and a long Mode press will have no effect.
*/
#define MOVEMENT_SECONDARY_FACE_INDEX (MOVEMENT_NUM_FACES - 2) // or (0)

/* Custom hourly chime tune. Check movement_custom_signal_tunes.h for options */
#define SIGNAL_TUNE_DEFAULT

#endif // MOVEMENT_CONFIG_H_
Loading

0 comments on commit eb9bfdc

Please sign in to comment.